From 40d33d806b2412b47705ffb7b40e3266b7288374 Mon Sep 17 00:00:00 2001 From: Wenyong Huang Date: Wed, 15 Nov 2023 17:20:50 +0800 Subject: [PATCH] Add compilation flag to enable/disable heap corruption check (#2766) Heap corruption check in ems memory allocator is enabled by default to improve the security, but it may impact the performance a lot, this PR adds cmake variable and compiler flag to enable/disable it. --- core/config.h | 5 ++ core/shared/mem-alloc/ems/ems_alloc.c | 56 ++++++++++++++++++++- core/shared/mem-alloc/ems/ems_gc_internal.h | 2 + core/shared/mem-alloc/ems/ems_hmu.c | 2 + core/shared/mem-alloc/ems/ems_kfc.c | 19 ++++++- core/shared/mem-alloc/mem_alloc.cmake | 8 +++ 6 files changed, 88 insertions(+), 4 deletions(-) diff --git a/core/config.h b/core/config.h index 4bbb1023..14065536 100644 --- a/core/config.h +++ b/core/config.h @@ -315,6 +315,11 @@ #define BH_ENABLE_GC_VERIFY 0 #endif +/* Heap corruption check, enabled by default */ +#ifndef BH_ENABLE_GC_CORRUPTION_CHECK +#define BH_ENABLE_GC_CORRUPTION_CHECK 1 +#endif + /* Enable global heap pool if heap verification is enabled */ #if BH_ENABLE_GC_VERIFY != 0 #define WASM_ENABLE_GLOBAL_HEAP_POOL 1 diff --git a/core/shared/mem-alloc/ems/ems_alloc.c b/core/shared/mem-alloc/ems/ems_alloc.c index a29539dd..0e2c4da7 100644 --- a/core/shared/mem-alloc/ems/ems_alloc.c +++ b/core/shared/mem-alloc/ems/ems_alloc.c @@ -24,19 +24,23 @@ hmu_is_in_heap(void *hmu, gc_uint8 *heap_base_addr, gc_uint8 *heap_end_addr) static bool remove_tree_node(gc_heap_t *heap, hmu_tree_node_t *p) { - hmu_tree_node_t *q = NULL, **slot = NULL, *parent; - hmu_tree_node_t *root = heap->kfc_tree_root; + hmu_tree_node_t *q = NULL, **slot = NULL; +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 + hmu_tree_node_t *root = heap->kfc_tree_root, *parent; gc_uint8 *base_addr = heap->base_addr; gc_uint8 *end_addr = base_addr + heap->current_size; +#endif bh_assert(p); +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 parent = p->parent; if (!parent || p == root /* p can not be the ROOT node */ || !hmu_is_in_heap(p, base_addr, end_addr) || (parent != root && !hmu_is_in_heap(parent, base_addr, end_addr))) { goto fail; } +#endif /* get the slot which holds pointer to node p */ if (p == p->parent->right) { @@ -67,9 +71,11 @@ remove_tree_node(gc_heap_t *heap, hmu_tree_node_t *p) /* move right child up*/ *slot = p->right; if (p->right) { +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (!hmu_is_in_heap(p->right, base_addr, end_addr)) { goto fail; } +#endif p->right->parent = p->parent; } @@ -80,9 +86,11 @@ remove_tree_node(gc_heap_t *heap, hmu_tree_node_t *p) if (!p->right) { /* move left child up*/ *slot = p->left; +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (!hmu_is_in_heap(p->left, base_addr, end_addr)) { goto fail; } +#endif /* p->left can never be NULL unless it is corrupted. */ p->left->parent = p->parent; @@ -92,14 +100,18 @@ remove_tree_node(gc_heap_t *heap, hmu_tree_node_t *p) /* both left & right exist, find p's predecessor at first*/ q = p->left; +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (!hmu_is_in_heap(q, base_addr, end_addr)) { goto fail; } +#endif while (q->right) { q = q->right; +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (!hmu_is_in_heap(q, base_addr, end_addr)) { goto fail; } +#endif } /* remove from the tree*/ @@ -111,15 +123,19 @@ remove_tree_node(gc_heap_t *heap, hmu_tree_node_t *p) q->left = p->left; q->right = p->right; if (q->left) { +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (!hmu_is_in_heap(q->left, base_addr, end_addr)) { goto fail; } +#endif q->left->parent = q; } if (q->right) { +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (!hmu_is_in_heap(q->right, base_addr, end_addr)) { goto fail; } +#endif q->right->parent = q; } @@ -127,27 +143,35 @@ remove_tree_node(gc_heap_t *heap, hmu_tree_node_t *p) return true; fail: +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 heap->is_heap_corrupted = true; +#endif return false; } static bool unlink_hmu(gc_heap_t *heap, hmu_t *hmu) { +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 gc_uint8 *base_addr, *end_addr; +#endif gc_size_t size; bh_assert(gci_is_heap_valid(heap)); bh_assert(hmu && (gc_uint8 *)hmu >= heap->base_addr && (gc_uint8 *)hmu < heap->base_addr + heap->current_size); +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (hmu_get_ut(hmu) != HMU_FC) { heap->is_heap_corrupted = true; return false; } +#endif +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 base_addr = heap->base_addr; end_addr = base_addr + heap->current_size; +#endif size = hmu_get_size(hmu); if (HMU_IS_FC_NORMAL(size)) { @@ -156,10 +180,12 @@ unlink_hmu(gc_heap_t *heap, hmu_t *hmu) hmu_normal_node_t *node = heap->kfc_normal_list[node_idx].next; while (node) { +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (!hmu_is_in_heap(node, base_addr, end_addr)) { heap->is_heap_corrupted = true; return false; } +#endif node_next = get_hmu_normal_node_next(node); if ((hmu_t *)node == hmu) { if (!node_prev) /* list head */ @@ -205,7 +231,9 @@ hmu_set_free_size(hmu_t *hmu) bool gci_add_fc(gc_heap_t *heap, hmu_t *hmu, gc_size_t size) { +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 gc_uint8 *base_addr, *end_addr; +#endif hmu_normal_node_t *np = NULL; hmu_tree_node_t *root = NULL, *tp = NULL, *node = NULL; uint32 node_idx; @@ -219,8 +247,10 @@ gci_add_fc(gc_heap_t *heap, hmu_t *hmu, gc_size_t size) <= heap->base_addr + heap->current_size); bh_assert(!(size & 7)); +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 base_addr = heap->base_addr; end_addr = base_addr + heap->current_size; +#endif hmu_set_ut(hmu, HMU_FC); hmu_set_size(hmu, size); @@ -228,10 +258,12 @@ gci_add_fc(gc_heap_t *heap, hmu_t *hmu, gc_size_t size) if (HMU_IS_FC_NORMAL(size)) { np = (hmu_normal_node_t *)hmu; +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (!hmu_is_in_heap(np, base_addr, end_addr)) { heap->is_heap_corrupted = true; return false; } +#endif node_idx = size >> 3; set_hmu_normal_node_next(np, heap->kfc_normal_list[node_idx].next); @@ -265,10 +297,12 @@ gci_add_fc(gc_heap_t *heap, hmu_t *hmu, gc_size_t size) } tp = tp->left; } +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (!hmu_is_in_heap(tp, base_addr, end_addr)) { heap->is_heap_corrupted = true; return false; } +#endif } return true; } @@ -321,15 +355,19 @@ alloc_hmu(gc_heap_t *heap, gc_size_t size) bh_assert(node_idx >= init_node_idx); p = normal_head->next; +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (!hmu_is_in_heap(p, base_addr, end_addr)) { heap->is_heap_corrupted = true; return NULL; } +#endif normal_head->next = get_hmu_normal_node_next(p); +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (((gc_int32)(uintptr_t)hmu_to_obj(p) & 7) != 0) { heap->is_heap_corrupted = true; return NULL; } +#endif if ((gc_size_t)node_idx != (uint32)init_node_idx /* with bigger size*/ @@ -365,10 +403,12 @@ alloc_hmu(gc_heap_t *heap, gc_size_t size) bh_assert(root); tp = root->right; while (tp) { +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (!hmu_is_in_heap(tp, base_addr, end_addr)) { heap->is_heap_corrupted = true; return NULL; } +#endif if (tp->size < size) { tp = tp->right; @@ -462,10 +502,12 @@ gc_alloc_vo_internal(void *vheap, gc_size_t size, const char *file, int line) /* integer overflow */ return NULL; +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (heap->is_heap_corrupted) { os_printf("[GC_ERROR]Heap is corrupted, allocate memory failed.\n"); return NULL; } +#endif os_mutex_lock(&heap->lock); @@ -522,10 +564,12 @@ gc_realloc_vo_internal(void *vheap, void *ptr, gc_size_t size, const char *file, /* integer overflow */ return NULL; +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (heap->is_heap_corrupted) { os_printf("[GC_ERROR]Heap is corrupted, allocate memory failed.\n"); return NULL; } +#endif if (obj_old) { hmu_old = obj_to_hmu(obj_old); @@ -647,10 +691,12 @@ gc_free_vo_internal(void *vheap, gc_object_t obj, const char *file, int line) return GC_SUCCESS; } +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (heap->is_heap_corrupted) { os_printf("[GC_ERROR]Heap is corrupted, free memory failed.\n"); return GC_ERROR; } +#endif hmu = obj_to_hmu(obj); @@ -767,11 +813,13 @@ gci_dump(gc_heap_t *heap) else if (ut == HMU_FC) inuse = 'F'; +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (size == 0 || size > (uint32)((uint8 *)end - (uint8 *)cur)) { os_printf("[GC_ERROR]Heap is corrupted, heap dump failed.\n"); heap->is_heap_corrupted = true; return; } +#endif os_printf("#%d %08" PRIx32 " %" PRIx32 " %d %d" " %c %" PRId32 "\n", @@ -788,8 +836,12 @@ gci_dump(gc_heap_t *heap) i++; } +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (cur != end) { os_printf("[GC_ERROR]Heap is corrupted, heap dump failed.\n"); heap->is_heap_corrupted = true; } +#else + bh_assert(cur == end); +#endif } diff --git a/core/shared/mem-alloc/ems/ems_gc_internal.h b/core/shared/mem-alloc/ems/ems_gc_internal.h index 68b50545..6abe2b12 100644 --- a/core/shared/mem-alloc/ems/ems_gc_internal.h +++ b/core/shared/mem-alloc/ems/ems_gc_internal.h @@ -271,9 +271,11 @@ typedef struct gc_heap_struct { size[left] <= size[cur] < size[right] */ hmu_tree_node_t *kfc_tree_root; +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 /* whether heap is corrupted, e.g. the hmu nodes are modified by user */ bool is_heap_corrupted; +#endif gc_size_t init_size; gc_size_t highmark_size; diff --git a/core/shared/mem-alloc/ems/ems_hmu.c b/core/shared/mem-alloc/ems/ems_hmu.c index 41745e16..f9d7c0f4 100644 --- a/core/shared/mem-alloc/ems/ems_hmu.c +++ b/core/shared/mem-alloc/ems/ems_hmu.c @@ -83,7 +83,9 @@ hmu_verify(void *vheap, hmu_t *hmu) os_printf("Invalid padding for object created at %s:%d\n", (prefix->file_name ? prefix->file_name : ""), prefix->line_no); +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 heap->is_heap_corrupted = true; +#endif } } } diff --git a/core/shared/mem-alloc/ems/ems_kfc.c b/core/shared/mem-alloc/ems/ems_kfc.c index 80d20267..1dda3ae0 100644 --- a/core/shared/mem-alloc/ems/ems_kfc.c +++ b/core/shared/mem-alloc/ems/ems_kfc.c @@ -133,8 +133,11 @@ gc_destroy_with_pool(gc_handle_t handle) hmu_t *cur = (hmu_t *)heap->base_addr; hmu_t *end = (hmu_t *)((char *)heap->base_addr + heap->current_size); - if (!heap->is_heap_corrupted - && (hmu_t *)((char *)cur + hmu_get_size(cur)) != end) { + if ( +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 + !heap->is_heap_corrupted && +#endif + (hmu_t *)((char *)cur + hmu_get_size(cur)) != end) { os_printf("Memory leak detected:\n"); gci_dump(heap); ret = GC_ERROR; @@ -186,10 +189,12 @@ gc_migrate(gc_handle_t handle, char *pool_buf_new, gc_size_t pool_buf_size) if (offset == 0) return 0; +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (heap->is_heap_corrupted) { os_printf("[GC_ERROR]Heap is corrupted, heap migrate failed.\n"); return GC_ERROR; } +#endif heap->base_addr = (uint8 *)base_addr_new; @@ -211,11 +216,13 @@ gc_migrate(gc_handle_t handle, char *pool_buf_new, gc_size_t pool_buf_size) while (cur < end) { size = hmu_get_size(cur); +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (size <= 0 || size > (uint32)((uint8 *)end - (uint8 *)cur)) { os_printf("[GC_ERROR]Heap is corrupted, heap migrate failed.\n"); heap->is_heap_corrupted = true; return GC_ERROR; } +#endif if (hmu_get_ut(cur) == HMU_FC && !HMU_IS_FC_NORMAL(size)) { tree_node = (hmu_tree_node_t *)cur; @@ -238,11 +245,15 @@ gc_migrate(gc_handle_t handle, char *pool_buf_new, gc_size_t pool_buf_size) cur = (hmu_t *)((char *)cur + size); } +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 if (cur != end) { os_printf("[GC_ERROR]Heap is corrupted, heap migrate failed.\n"); heap->is_heap_corrupted = true; return GC_ERROR; } +#else + bh_assert(cur == end); +#endif return 0; } @@ -250,9 +261,13 @@ gc_migrate(gc_handle_t handle, char *pool_buf_new, gc_size_t pool_buf_size) bool gc_is_heap_corrupted(gc_handle_t handle) { +#if BH_ENABLE_GC_CORRUPTION_CHECK != 0 gc_heap_t *heap = (gc_heap_t *)handle; return heap->is_heap_corrupted ? true : false; +#else + return false; +#endif } #if BH_ENABLE_GC_VERIFY != 0 diff --git a/core/shared/mem-alloc/mem_alloc.cmake b/core/shared/mem-alloc/mem_alloc.cmake index c0b4157f..1754a1ac 100644 --- a/core/shared/mem-alloc/mem_alloc.cmake +++ b/core/shared/mem-alloc/mem_alloc.cmake @@ -10,6 +10,14 @@ if (WAMR_BUILD_GC_VERIFY EQUAL 1) add_definitions (-DBH_ENABLE_GC_VERIFY=1) endif () +if (NOT DEFINED WAMR_BUILD_GC_CORRUPTION_CHECK) + set (WAMR_BUILD_GC_CORRUPTION_CHECK 1) +endif () + +if (WAMR_BUILD_GC_CORRUPTION_CHECK EQUAL 0) + add_definitions (-DBH_ENABLE_GC_CORRUPTION_CHECK=0) +endif () + file (GLOB_RECURSE source_all ${MEM_ALLOC_DIR}/ems/*.c ${MEM_ALLOC_DIR}/tlsf/*.c