Fix the buffer pointer passed to cbfs_file_hash_mismatch(). Add a test case with LZ4 compression, which would catch the bug we are fixing. Change-Id: I36605e2dbc0423fa6743087512f2042b37c49d35 Signed-off-by: Yu-Ping Wu <yupingso@chromium.org> Reviewed-on: https://review.coreboot.org/c/coreboot/+/65149 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Paul Menzel <paulepanter@mailbox.org> Reviewed-by: Julius Werner <jwerner@chromium.org>
		
			
				
	
	
		
			272 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			272 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* SPDX-License-Identifier: GPL-2.0-only */
 | |
| 
 | |
| #include <cbfs.h>
 | |
| #include <cbfs_glue.h>
 | |
| #include <string.h>
 | |
| #include <mocks/cbfs_util.h>
 | |
| #include <tests/test.h>
 | |
| 
 | |
| #include "../libcbfs/cbfs.c"
 | |
| 
 | |
| /* Mocks */
 | |
| 
 | |
| unsigned long virtual_offset = 0;
 | |
| struct sysinfo_t lib_sysinfo;
 | |
| 
 | |
| size_t vb2_digest_size(enum vb2_hash_algorithm hash_alg)
 | |
| {
 | |
| 	if (hash_alg != VB2_HASH_SHA256) {
 | |
| 		fail_msg("Unsupported hash algorithm: %d\n", hash_alg);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	return VB2_SHA256_DIGEST_SIZE;
 | |
| }
 | |
| 
 | |
| vb2_error_t vb2_hash_verify(const void *buf, uint32_t size, const struct vb2_hash *hash)
 | |
| {
 | |
| 	check_expected_ptr(buf);
 | |
| 	check_expected(size);
 | |
| 
 | |
| 	assert_int_equal(hash->algo, VB2_HASH_SHA256);
 | |
| 
 | |
| 	if (!memcmp(hash->sha256, good_hash, sizeof(good_hash)))
 | |
| 		return VB2_SUCCESS;
 | |
| 
 | |
| 	if (!memcmp(hash->sha256, bad_hash, sizeof(bad_hash)))
 | |
| 		return VB2_ERROR_SHA_MISMATCH;
 | |
| 
 | |
| 	fail_msg("%s called with bad hash", __func__);
 | |
| 	return VB2_ERROR_SHA_MISMATCH;
 | |
| }
 | |
| 
 | |
| unsigned long ulzman(const unsigned char *src, unsigned long srcn, unsigned char *dst,
 | |
| 		     unsigned long dstn)
 | |
| {
 | |
| 	size_t copy_size = MIN(srcn, dstn);
 | |
| 	function_called();
 | |
| 	memcpy(dst, src, copy_size);
 | |
| 	return copy_size;
 | |
| }
 | |
| 
 | |
| size_t ulz4fn(const void *src, size_t srcn, void *dst, size_t dstn)
 | |
| {
 | |
| 	size_t copy_size = MIN(srcn, dstn);
 | |
| 	function_called();
 | |
| 	memcpy(dst, src, copy_size);
 | |
| 	return copy_size;
 | |
| }
 | |
| 
 | |
| enum cb_err cbfs_mcache_lookup(const void *mcache, size_t mcache_size, const char *name,
 | |
| 			       union cbfs_mdata *mdata_out, size_t *data_offset_out)
 | |
| {
 | |
| 	return CB_CBFS_CACHE_FULL;
 | |
| }
 | |
| 
 | |
| enum cb_err cbfs_lookup(cbfs_dev_t dev, const char *name, union cbfs_mdata *mdata_out,
 | |
| 			size_t *data_offset_out, struct vb2_hash *metadata_hash)
 | |
| {
 | |
| 	assert_non_null(dev);
 | |
| 	check_expected(name);
 | |
| 
 | |
| 	enum cb_err ret = mock_type(enum cb_err);
 | |
| 	if (ret != CB_SUCCESS)
 | |
| 		return ret;
 | |
| 
 | |
| 	memcpy(mdata_out, mock_ptr_type(const union cbfs_mdata *), sizeof(union cbfs_mdata));
 | |
| 	*data_offset_out = mock_type(size_t);
 | |
| 	return CB_SUCCESS;
 | |
| }
 | |
| 
 | |
| static void expect_cbfs_lookup(const char *name, enum cb_err err, const union cbfs_mdata *mdata,
 | |
| 			       size_t data_offset_out)
 | |
| {
 | |
| 	expect_string(cbfs_lookup, name, name);
 | |
| 	will_return(cbfs_lookup, err);
 | |
| 
 | |
| 	if (err == CB_SUCCESS) {
 | |
| 		will_return(cbfs_lookup, mdata);
 | |
| 		will_return(cbfs_lookup, data_offset_out);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| const void *cbfs_find_attr(const union cbfs_mdata *mdata, uint32_t attr_tag, size_t size_check)
 | |
| {
 | |
| 	return mock_ptr_type(void *);
 | |
| }
 | |
| 
 | |
| enum cb_err fmap_locate_area(const char *name, size_t *offset, size_t *size)
 | |
| {
 | |
| 	*offset = 0;
 | |
| 	*size = 0;
 | |
| 	return CB_SUCCESS;
 | |
| }
 | |
| 
 | |
| ssize_t boot_device_read(void *buf, size_t offset, size_t size)
 | |
| {
 | |
| 	/* Offset should be based on an address from lib_sysinfo.cbfs_offset */
 | |
| 	memcpy(buf, (void *)offset, size);
 | |
| 
 | |
| 	return size;
 | |
| }
 | |
| 
 | |
| const struct vb2_hash *cbfs_file_hash(const union cbfs_mdata *mdata)
 | |
| {
 | |
| 	return mock_ptr_type(const struct vb2_hash *);
 | |
| }
 | |
| 
 | |
| /* Utils */
 | |
| 
 | |
| static void clear_cbfs_boot_devices(void)
 | |
| {
 | |
| 	lib_sysinfo.cbfs_ro_mcache_offset = 0;
 | |
| 	lib_sysinfo.cbfs_ro_mcache_size = 0;
 | |
| 	lib_sysinfo.cbfs_offset = 0;
 | |
| 	lib_sysinfo.cbfs_size = 0;
 | |
| 	lib_sysinfo.cbfs_rw_mcache_offset = 0;
 | |
| 	lib_sysinfo.cbfs_rw_mcache_size = 0;
 | |
| 	memset((void *)cbfs_get_boot_device(true), 0, sizeof(struct cbfs_boot_device));
 | |
| 	memset((void *)cbfs_get_boot_device(false), 0, sizeof(struct cbfs_boot_device));
 | |
| }
 | |
| 
 | |
| void set_cbfs(uint64_t offset, size_t size)
 | |
| {
 | |
| 	clear_cbfs_boot_devices();
 | |
| 	lib_sysinfo.cbfs_offset = offset;
 | |
| 	lib_sysinfo.cbfs_size = size;
 | |
| }
 | |
| 
 | |
| /* Tests */
 | |
| 
 | |
| static int setup_test_cbfs(void **state)
 | |
| {
 | |
| 	clear_cbfs_boot_devices();
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void test_cbfs_map_no_hash(void **state)
 | |
| {
 | |
| 	void *mapping = NULL;
 | |
| 	size_t size = 0;
 | |
| 
 | |
| 	set_cbfs((uint64_t)&file_no_hash, sizeof(file_no_hash));
 | |
| 
 | |
| 	expect_cbfs_lookup(TEST_DATA_1_FILENAME, CB_SUCCESS,
 | |
| 			   (const union cbfs_mdata *)&file_no_hash,
 | |
| 			   be32toh(file_no_hash.header.offset));
 | |
| 	will_return(cbfs_find_attr, NULL);
 | |
| 
 | |
| 	if (CONFIG(LP_CBFS_VERIFICATION)) {
 | |
| 		/* File with no hash. No hash causes hash mismatch by default,
 | |
| 		   so mapping will not be completed successfully. */
 | |
| 		will_return(cbfs_file_hash, NULL);
 | |
| 		mapping = cbfs_map(TEST_DATA_1_FILENAME, NULL);
 | |
| 		assert_null(mapping);
 | |
| 	} else {
 | |
| 		mapping = cbfs_map(TEST_DATA_1_FILENAME, &size);
 | |
| 		assert_non_null(mapping);
 | |
| 		assert_int_equal(TEST_DATA_1_SIZE, size);
 | |
| 		assert_memory_equal(test_data_1, mapping, size);
 | |
| 		cbfs_unmap(mapping);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void test_cbfs_map_valid_hash_impl(void **state, bool lz4_compressed)
 | |
| {
 | |
| 	void *mapping = NULL;
 | |
| 	size_t size = 0;
 | |
| 	struct vb2_hash hash = {
 | |
| 		.algo = VB2_HASH_SHA256,
 | |
| 	};
 | |
| 	memcpy(&hash.sha256, good_hash, VB2_SHA256_DIGEST_SIZE);
 | |
| 
 | |
| 	set_cbfs((uint64_t)&file_valid_hash, sizeof(file_valid_hash));
 | |
| 
 | |
| 	expect_cbfs_lookup(TEST_DATA_1_FILENAME, CB_SUCCESS,
 | |
| 			   (const union cbfs_mdata *)&file_valid_hash,
 | |
| 			   be32toh(file_valid_hash.header.offset));
 | |
| 
 | |
| 	if (lz4_compressed) {
 | |
| 		struct cbfs_file_attr_compression cattr = {
 | |
| 			.compression = htobe32(CBFS_COMPRESS_LZ4),
 | |
| 			.decompressed_size = htobe32(TEST_DATA_1_SIZE),
 | |
| 		};
 | |
| 		will_return(cbfs_find_attr, &cattr);
 | |
| 		expect_function_call(ulz4fn);
 | |
| 	} else {
 | |
| 		will_return(cbfs_find_attr, NULL);
 | |
| 	}
 | |
| 
 | |
| 	if (CONFIG(LP_CBFS_VERIFICATION)) {
 | |
| 		will_return(cbfs_file_hash, &hash);
 | |
| 		expect_memory(vb2_hash_verify, buf,
 | |
| 			      &file_valid_hash.attrs_and_data[HASH_ATTR_SIZE], HASH_ATTR_SIZE);
 | |
| 		expect_value(vb2_hash_verify, size, TEST_DATA_1_SIZE);
 | |
| 		mapping = cbfs_map(TEST_DATA_1_FILENAME, &size);
 | |
| 		assert_non_null(mapping);
 | |
| 		assert_int_equal(TEST_DATA_1_SIZE, size);
 | |
| 		assert_memory_equal(mapping, &file_valid_hash.attrs_and_data[HASH_ATTR_SIZE],
 | |
| 				    size);
 | |
| 	} else {
 | |
| 		mapping = cbfs_map(TEST_DATA_1_FILENAME, &size);
 | |
| 		assert_non_null(mapping);
 | |
| 		assert_int_equal(TEST_DATA_1_SIZE, size);
 | |
| 		assert_memory_equal(test_data_1, mapping, size);
 | |
| 		cbfs_unmap(mapping);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void test_cbfs_map_valid_hash(void **state)
 | |
| {
 | |
| 	test_cbfs_map_valid_hash_impl(state, false);
 | |
| }
 | |
| 
 | |
| static void test_cbfs_map_valid_hash_with_lz4(void **state)
 | |
| {
 | |
| 	test_cbfs_map_valid_hash_impl(state, true);
 | |
| }
 | |
| 
 | |
| static void test_cbfs_map_invalid_hash(void **state)
 | |
| {
 | |
| 	void *mapping = NULL;
 | |
| 	size_t size = 0;
 | |
| 	struct vb2_hash hash = {
 | |
| 		.algo = VB2_HASH_SHA256,
 | |
| 	};
 | |
| 	memcpy(&hash.sha256, bad_hash, VB2_SHA256_DIGEST_SIZE);
 | |
| 
 | |
| 	set_cbfs((uint64_t)&file_broken_hash, sizeof(file_broken_hash));
 | |
| 
 | |
| 	expect_cbfs_lookup(TEST_DATA_1_FILENAME, CB_SUCCESS,
 | |
| 			   (const union cbfs_mdata *)&file_broken_hash,
 | |
| 			   be32toh(file_broken_hash.header.offset));
 | |
| 	will_return(cbfs_find_attr, NULL);
 | |
| 
 | |
| 	if (CONFIG(LP_CBFS_VERIFICATION)) {
 | |
| 		will_return(cbfs_file_hash, &hash);
 | |
| 		expect_memory(vb2_hash_verify, buf,
 | |
| 			      &file_broken_hash.attrs_and_data[HASH_ATTR_SIZE], HASH_ATTR_SIZE);
 | |
| 		expect_value(vb2_hash_verify, size, TEST_DATA_1_SIZE);
 | |
| 		mapping = cbfs_map(TEST_DATA_1_FILENAME, NULL);
 | |
| 		assert_null(mapping);
 | |
| 	} else {
 | |
| 		mapping = cbfs_map(TEST_DATA_1_FILENAME, &size);
 | |
| 		assert_non_null(mapping);
 | |
| 		assert_int_equal(TEST_DATA_1_SIZE, size);
 | |
| 		assert_memory_equal(test_data_1, mapping, size);
 | |
| 		cbfs_unmap(mapping);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int main(void)
 | |
| {
 | |
| 	const struct CMUnitTest tests[] = {
 | |
| 		cmocka_unit_test_setup(test_cbfs_map_no_hash, setup_test_cbfs),
 | |
| 		cmocka_unit_test_setup(test_cbfs_map_valid_hash, setup_test_cbfs),
 | |
| 		cmocka_unit_test_setup(test_cbfs_map_valid_hash_with_lz4, setup_test_cbfs),
 | |
| 		cmocka_unit_test_setup(test_cbfs_map_invalid_hash, setup_test_cbfs),
 | |
| 	};
 | |
| 
 | |
| 	return lp_run_group_tests(tests, NULL, NULL);
 | |
| }
 |