From 860232bdafabbf136130405cdcaea67210b513ee Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Wed, 10 Aug 2022 16:19:04 +0200 Subject: [PATCH] heap: Add test to check that the corruption of free memory is detected This commit extends the heap test set by adding a test to check corruption detection in free memory block. For each byte of the free block memory, the test changes the value of the byte, call multi_heap_check(), make sure that the function returns 'corruption detected' only when comprehensive poisoning is set, restore the good value of the byte, calls multi_heap_check() again and make sure that it returns 'OK'. --- components/heap/test/test_corruption_check.c | 64 +++++++++++++++++++ components/heap/test_multi_heap_host/Makefile | 2 +- .../test_multi_heap_host/test_multi_heap.cpp | 64 +++++++++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 components/heap/test/test_corruption_check.c diff --git a/components/heap/test/test_corruption_check.c b/components/heap/test/test_corruption_check.c new file mode 100644 index 0000000000..25ca70977d --- /dev/null +++ b/components/heap/test/test_corruption_check.c @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "unity.h" +#include "stdio.h" + +#include "esp_heap_caps.h" + +/* executing multi_heap_internal_check_block_poisoning() + * takes longer on external RAM and therefore the timeout + * in the test of 30 seconds is exceeded. Execute the test + * on a smaller memory chunk + */ +#ifdef CONFIG_SPIRAM +const size_t MALLOC_SIZE = 16; +#else +const size_t MALLOC_SIZE = 64; +#endif +const uint8_t CORRUPTED_VALUE = 0xaa; + +/* This test will corrupt the memory of a free block in the heap and check + * that in the case of comprehensive poisoning the heap corruption is detected + * by heap_caps_check_integrity(). For light poisoning and no poisoning, the test will + * check that heap_caps_check_integrity() does not report the corruption. + */ +TEST_CASE("multi_heap poisoning detection", "[heap]") +{ + /* malloc some memory to get a pointer */ + uint8_t *ptr = heap_caps_malloc(MALLOC_SIZE, MALLOC_CAP_8BIT); + + /* free the memory to free the block but keep the pointer in mind */ + heap_caps_free(ptr); + + /* variable used in the test */ + uint8_t original_value = 0x00; + + for (size_t i = 0; i < MALLOC_SIZE; i++) + { + /* keep the good value in store in order to check that when we set the byte back + * to its original value, heap_caps_check_integrity() no longer returns the + * heap corruption. */ + original_value = ptr[i]; + + /* set corrupted value in the free memory*/ + ptr[i] = CORRUPTED_VALUE; + + bool is_heap_ok = heap_caps_check_integrity(MALLOC_CAP_8BIT, true); +#ifdef CONFIG_HEAP_POISONING_COMPREHENSIVE + /* check that heap_caps_check_integrity() detects the corruption */ + TEST_ASSERT_FALSE(is_heap_ok); +#else + /* the comprehensive corruption is not checked in the heap_caps_check_integrity() */ + TEST_ASSERT_TRUE(is_heap_ok); +#endif + /* fix the corruption by restoring the original value at ptr + i */ + ptr[i] = original_value; + + /* check that heap_caps_check_integrity() stops reporting the corruption */ + is_heap_ok = heap_caps_check_integrity(MALLOC_CAP_8BIT, true); + TEST_ASSERT_TRUE(is_heap_ok); + } +} diff --git a/components/heap/test_multi_heap_host/Makefile b/components/heap/test_multi_heap_host/Makefile index 6c10f8035a..7d97cbcc6f 100644 --- a/components/heap/test_multi_heap_host/Makefile +++ b/components/heap/test_multi_heap_host/Makefile @@ -17,7 +17,7 @@ INCLUDE_FLAGS = -I../include -I../../../tools/catch -I../tlsf GCOV ?= gcov -CPPFLAGS += $(INCLUDE_FLAGS) -D CONFIG_LOG_DEFAULT_LEVEL -g -fstack-protector-all -m32 -DCONFIG_HEAP_POISONING_COMPREHENSIVE +CPPFLAGS += $(INCLUDE_FLAGS) -D CONFIG_LOG_DEFAULT_LEVEL -g -fstack-protector-all -m32 CFLAGS += -Wall -Werror -fprofile-arcs -ftest-coverage CXXFLAGS += -std=c++11 -Wall -Werror -fprofile-arcs -ftest-coverage LDFLAGS += -lstdc++ -fprofile-arcs -ftest-coverage -m32 diff --git a/components/heap/test_multi_heap_host/test_multi_heap.cpp b/components/heap/test_multi_heap_host/test_multi_heap.cpp index e85cceddc6..c3cac1cad1 100644 --- a/components/heap/test_multi_heap_host/test_multi_heap.cpp +++ b/components/heap/test_multi_heap_host/test_multi_heap.cpp @@ -2,6 +2,8 @@ #include "multi_heap.h" #include "../multi_heap_config.h" +#include "../tlsf/tlsf_common.h" +#include "../tlsf/tlsf_block_functions.h" #include #include @@ -523,3 +525,65 @@ TEST_CASE("multi_heap allocation overhead", "[multi_heap]") multi_heap_free(heap, x); } + +/* This test will corrupt the memory of a free block in the heap and check + * that in the case of comprehensive poisoning the heap corruption is detected + * by multi_heap_check(). For light poisoning and no poisoning, the test will + * check that multi_heap_check() does not report the corruption. + */ +TEST_CASE("multi_heap poisoning detection", "[multi_heap]") +{ + const size_t HEAP_SIZE = 4 * 1024; + + /* define heap related data */ + uint8_t heap_mem[HEAP_SIZE]; + memset(heap_mem, 0x00, HEAP_SIZE); + + /* register the heap memory. One free block only will be available */ + multi_heap_handle_t heap = multi_heap_register(heap_mem, HEAP_SIZE); + + /* offset in memory at which to find the first free memory byte */ + const size_t free_memory_offset = sizeof(multi_heap_info_t) + sizeof(control_t) + block_header_overhead; + + /* block header of the free block under test in the heap () */ + const block_header_t* block = (block_header_t*)(heap_mem + free_memory_offset - sizeof(block_header_t)); + + /* actual number of bytes potentially filled with the free pattern in the free block under test */ + const size_t effective_free_size = block_size(block) - block_header_overhead - offsetof(block_header_t, next_free); + + /* variable used in the test */ + size_t affected_byte = 0x00; + uint8_t original_value = 0x00; + uint8_t corrupted_value = 0x00; + + /* repeat the corruption a few times to cover more of the free memory */ + for (size_t i = 0; i < effective_free_size; i++) + { + /* corrupt random bytes in the heap (it needs to be bytes from free memory in + * order to check that the comprehensive poisoning is doing its job) */ + affected_byte = free_memory_offset + i; + corrupted_value = (rand() % UINT8_MAX) | 1; + + /* keep the good value in store in order to check that when we set the byte back + * to its original value, multi_heap_check() no longer returns the heap corruption. */ + original_value = heap_mem[affected_byte]; + + /* make sure we are not replacing the original value with the same value */ + heap_mem[affected_byte] ^= corrupted_value; + + bool is_heap_ok = multi_heap_check(heap, true); +#ifdef CONFIG_HEAP_POISONING_COMPREHENSIVE + /* check that multi_heap_check() detects the corruption */ + REQUIRE(is_heap_ok == false); +#else + /* the comprehensive corruption is not checked in the multi_heap_check() */ + REQUIRE(is_heap_ok == true); +#endif + /* fix the corruption */ + heap_mem[affected_byte] = original_value; + + /* check that multi_heap_check() stops reporting the corruption */ + is_heap_ok = multi_heap_check(heap, true); + REQUIRE(is_heap_ok == true); + } +}