From 877f973be39d59a8483f28f24e0ca6bff18ca747 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 9 Sep 2025 14:45:59 +0800 Subject: [PATCH 01/12] Remove unused coalesced variable in selective_coalesce() The coalesced variable in selective_coalesce() was declared but never used. Remove it to clean up the code and reduce unnecessary clutter. --- lib/malloc.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/malloc.c b/lib/malloc.c index ac439ab..ef708fe 100644 --- a/lib/malloc.c +++ b/lib/malloc.c @@ -109,7 +109,6 @@ void free(void *ptr) static void selective_coalesce(void) { memblock_t *p = first_free; - uint32_t coalesced = 0; while (p && p->next) { /* Merge only when blocks are FREE *and* adjacent in memory */ @@ -117,7 +116,6 @@ static void selective_coalesce(void) if (!IS_USED(p) && !IS_USED(p->next) && pend == (uint8_t *) p->next) { p->size = GET_SIZE(p) + sizeof(memblock_t) + GET_SIZE(p->next); p->next = p->next->next; - coalesced++; free_blocks_count--; } else { p = p->next; From 84fd57caa63bd41789d6b49340f79cfd1d0087a3 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 9 Sep 2025 14:06:16 +0800 Subject: [PATCH 02/12] Fix missing alignment in calloc() and realloc() The calloc() and realloc() implementations did not align the requested size before passing it to the allocator. This could result in returning non-aligned memory blocks, which may trigger unaligned memory access exceptions on RISC-V. Align the total size in calloc() and the input size in realloc() to ensure the allocator always returns properly aligned memory. --- lib/malloc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/malloc.c b/lib/malloc.c index ef708fe..9f847ec 100644 --- a/lib/malloc.c +++ b/lib/malloc.c @@ -211,7 +211,7 @@ void *calloc(uint32_t nmemb, uint32_t size) if (unlikely(nmemb && size > MALLOC_MAX_SIZE / nmemb)) return NULL; - uint32_t total_size = nmemb * size; + uint32_t total_size = ALIGN4(nmemb * size); void *buf = malloc(total_size); if (buf) @@ -234,6 +234,8 @@ void *realloc(void *ptr, uint32_t size) return NULL; } + size = ALIGN4(size); + memblock_t *old_block = ((memblock_t *) ptr) - 1; /* Validate the existing block */ From a49b3459fecf7ba526e7ab7a0907b9e2c6831992 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 9 Sep 2025 01:14:16 +0800 Subject: [PATCH 03/12] Add panic error code for heap corruption Currently, the memory allocator does not trigger a kernel panic on detected corruption or invalid operations. However, once the heap is corrupted, the kernel can no longer guarantee safe or correct behavior. Introduce ERR_HEAP_CORRUPT with a corresponding error message to prepare for future panic-on-error handling in the memory allocator. --- include/private/error.h | 1 + kernel/error.c | 1 + 2 files changed, 2 insertions(+) diff --git a/include/private/error.h b/include/private/error.h index 1eb9711..27dcfb8 100644 --- a/include/private/error.h +++ b/include/private/error.h @@ -27,6 +27,7 @@ enum { /* Memory Protection Errors */ ERR_STACK_CHECK, /* Stack overflow or corruption detected */ + ERR_HEAP_CORRUPT, /* Heap corruption or invalid free detected */ /* IPC and Synchronization Errors */ ERR_PIPE_ALLOC, /* Pipe allocation failed */ diff --git a/kernel/error.c b/kernel/error.c index 1d6071e..4edb49d 100644 --- a/kernel/error.c +++ b/kernel/error.c @@ -22,6 +22,7 @@ static const struct error_code error_desc[] = { /* stack guard */ {ERR_STACK_CHECK, "stack corruption"}, + {ERR_HEAP_CORRUPT, "heap corruption or invalid free"}, /* IPC / sync */ {ERR_PIPE_ALLOC, "pipe allocation"}, From 9e55af466a2b3126a10f7e89991d71aab42f9ce1 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 9 Sep 2025 01:18:17 +0800 Subject: [PATCH 04/12] Panic on heap corruption detected in memory allocator The memory allocator previously attempted to continue execution even if validate_block() failed, which indicates unexpected heap corruption or invalid memory operations. Once the heap is corrupted, the kernel can no longer guarantee safe or correct behavior. Invoke panic(ERR_HEAP_CORRUPT) when validate_block() fails in malloc(), free(), or realloc(), ensuring the kernel halts immediately on fatal allocator errors. --- lib/malloc.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/malloc.c b/lib/malloc.c index 9f847ec..51b5bbf 100644 --- a/lib/malloc.c +++ b/lib/malloc.c @@ -4,6 +4,7 @@ #include #include +#include "private/error.h" #include "private/utils.h" /* Memory allocator using first-fit strategy with selective coalescing. @@ -71,6 +72,7 @@ void free(void *ptr) /* Validate the block being freed */ if (!validate_block(p) || !IS_USED(p)) { CRITICAL_LEAVE(); + panic(ERR_HEAP_CORRUPT); return; /* Invalid or double-free */ } @@ -146,6 +148,7 @@ void *malloc(uint32_t size) while (p) { if (!validate_block(p)) { CRITICAL_LEAVE(); + panic(ERR_HEAP_CORRUPT); return NULL; /* Heap corruption detected */ } @@ -239,8 +242,10 @@ void *realloc(void *ptr, uint32_t size) memblock_t *old_block = ((memblock_t *) ptr) - 1; /* Validate the existing block */ - if (!validate_block(old_block) || !IS_USED(old_block)) + if (!validate_block(old_block) || !IS_USED(old_block)) { + panic(ERR_HEAP_CORRUPT); return NULL; + } size_t old_size = GET_SIZE(old_block); From c74c04af30c49defd547343e2d280b32a6f94be7 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 9 Sep 2025 01:59:22 +0800 Subject: [PATCH 05/12] Add next block adjacency check to validate_block() The allocator previously only validated the size and bounds of a memory block. It did not check whether the block's successor was physically adjacent in memory. Extend validate_block() to verify that a block's next pointer matches the expected location based on its size. This ensures corruption in the linked list of blocks is detected early and consistently. --- lib/malloc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/malloc.c b/lib/malloc.c index 51b5bbf..3e2948f 100644 --- a/lib/malloc.c +++ b/lib/malloc.c @@ -56,6 +56,11 @@ static inline bool validate_block(memblock_t *block) if ((uint8_t *) block + sizeof(memblock_t) + size > (uint8_t *) heap_end) return false; + if (block->next && + (uint8_t *) block + sizeof(memblock_t) + GET_SIZE(block) != + (uint8_t *) block->next) + return false; + return true; } From 64eb9cbe6a1db0000415a369d4dacfd402894cf4 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 9 Sep 2025 03:58:12 +0800 Subject: [PATCH 06/12] Use validate_block() for adjacency checks and panic on failure Inline adjacency checks were open-coded in free() and selective_coalesce(). If these checks failed, the allocator would silently skip the block and continue, masking heap corruption. Replace the open-coded adjacency logic with calls to validate_block() to avoid duplication and ensure consistent validation. If a block fails validation, invoke panic(ERR_HEAP_CORRUPT) instead of silently ignoring the error, since heap corruption is fatal to kernel safety. --- lib/malloc.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/malloc.c b/lib/malloc.c index 3e2948f..62c9688 100644 --- a/lib/malloc.c +++ b/lib/malloc.c @@ -85,9 +85,7 @@ void free(void *ptr) free_blocks_count++; /* Forward merge if the next block is free and physically adjacent */ - if (p->next && !IS_USED(p->next) && - (uint8_t *) p + sizeof(memblock_t) + GET_SIZE(p) == - (uint8_t *) p->next) { + if (p->next && !IS_USED(p->next)) { p->size = GET_SIZE(p) + sizeof(memblock_t) + GET_SIZE(p->next); p->next = p->next->next; free_blocks_count--; @@ -101,9 +99,12 @@ void free(void *ptr) current = current->next; } - if (prev && !IS_USED(prev) && - (uint8_t *) prev + sizeof(memblock_t) + GET_SIZE(prev) == - (uint8_t *) p) { + if (prev && !IS_USED(prev)) { + if (!validate_block(prev)) { + CRITICAL_LEAVE(); + panic(ERR_HEAP_CORRUPT); + return; + } prev->size = GET_SIZE(prev) + sizeof(memblock_t) + GET_SIZE(p); prev->next = p->next; free_blocks_count--; @@ -119,8 +120,11 @@ static void selective_coalesce(void) while (p && p->next) { /* Merge only when blocks are FREE *and* adjacent in memory */ - uint8_t *pend = (uint8_t *) p + sizeof(memblock_t) + GET_SIZE(p); - if (!IS_USED(p) && !IS_USED(p->next) && pend == (uint8_t *) p->next) { + if (!validate_block(p)) { + panic(ERR_HEAP_CORRUPT); + return; + } + if (!IS_USED(p) && !IS_USED(p->next)) { p->size = GET_SIZE(p) + sizeof(memblock_t) + GET_SIZE(p->next); p->next = p->next->next; free_blocks_count--; From bc8ac1633b753951e0d93eb56fcf146dcf76931c Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 9 Sep 2025 14:48:58 +0800 Subject: [PATCH 07/12] Panic if free_blocks_count is invalid in malloc() In malloc(), free_blocks_count was decremented only if it was greater than zero. However, once a usable block has been found, free_blocks_count should never be zero. If this condition occurs, it indicates a fatal inconsistency in allocator state. Replace the conditional decrement with a check that panics with ERR_HEAP_CORRUPT when free_blocks_count <= 0, ensuring the kernel halts on heap accounting corruption. --- lib/malloc.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/malloc.c b/lib/malloc.c index 62c9688..567957b 100644 --- a/lib/malloc.c +++ b/lib/malloc.c @@ -177,8 +177,11 @@ void *malloc(uint32_t size) } MARK_USED(p); - if (free_blocks_count > 0) - free_blocks_count--; + if (unlikely(free_blocks_count <= 0)) { + panic(ERR_HEAP_CORRUPT); + return NULL; + } + free_blocks_count--; CRITICAL_LEAVE(); return (void *) (p + 1); From 48df0e2437058189a5cfb25232c2d92ebaa6c8d7 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 9 Sep 2025 13:55:00 +0800 Subject: [PATCH 08/12] Mark allocator panic paths as unlikely() Errors that trigger kernel panic in the memory allocator are expected to almost never occur during normal operation. Hint these branches with unlikely() so the compiler can better optimize the common fast path. This change wraps validation failures and other fatal checks with unlikely(), without altering runtime behavior. --- lib/malloc.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/malloc.c b/lib/malloc.c index 567957b..85460d3 100644 --- a/lib/malloc.c +++ b/lib/malloc.c @@ -45,20 +45,21 @@ static uint32_t free_blocks_count; /* track fragmentation */ /* Validate block integrity */ static inline bool validate_block(memblock_t *block) { - if (!IS_VALID_BLOCK(block)) + if (unlikely(!IS_VALID_BLOCK(block))) return false; size_t size = GET_SIZE(block); - if (!size || size > MALLOC_MAX_SIZE) + if (unlikely(!size || size > MALLOC_MAX_SIZE)) return false; /* Check if block extends beyond heap */ - if ((uint8_t *) block + sizeof(memblock_t) + size > (uint8_t *) heap_end) + if (unlikely((uint8_t *) block + sizeof(memblock_t) + size > + (uint8_t *) heap_end)) return false; - if (block->next && - (uint8_t *) block + sizeof(memblock_t) + GET_SIZE(block) != - (uint8_t *) block->next) + if (unlikely(block->next && + (uint8_t *) block + sizeof(memblock_t) + GET_SIZE(block) != + (uint8_t *) block->next)) return false; return true; @@ -75,7 +76,7 @@ void free(void *ptr) memblock_t *p = ((memblock_t *) ptr) - 1; /* Validate the block being freed */ - if (!validate_block(p) || !IS_USED(p)) { + if (unlikely(!validate_block(p) || !IS_USED(p))) { CRITICAL_LEAVE(); panic(ERR_HEAP_CORRUPT); return; /* Invalid or double-free */ @@ -100,7 +101,7 @@ void free(void *ptr) } if (prev && !IS_USED(prev)) { - if (!validate_block(prev)) { + if (unlikely(!validate_block(prev))) { CRITICAL_LEAVE(); panic(ERR_HEAP_CORRUPT); return; @@ -120,7 +121,7 @@ static void selective_coalesce(void) while (p && p->next) { /* Merge only when blocks are FREE *and* adjacent in memory */ - if (!validate_block(p)) { + if (unlikely(!validate_block(p))) { panic(ERR_HEAP_CORRUPT); return; } @@ -155,7 +156,7 @@ void *malloc(uint32_t size) memblock_t *p = first_free; while (p) { - if (!validate_block(p)) { + if (unlikely(!validate_block(p))) { CRITICAL_LEAVE(); panic(ERR_HEAP_CORRUPT); return NULL; /* Heap corruption detected */ @@ -254,7 +255,7 @@ void *realloc(void *ptr, uint32_t size) memblock_t *old_block = ((memblock_t *) ptr) - 1; /* Validate the existing block */ - if (!validate_block(old_block) || !IS_USED(old_block)) { + if (unlikely(!validate_block(old_block) || !IS_USED(old_block))) { panic(ERR_HEAP_CORRUPT); return NULL; } From 44eed33ed5b6e860216c9e335b2666ba0900f16f Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 9 Sep 2025 14:35:02 +0800 Subject: [PATCH 09/12] Add split_block() helper Splitting a large memory block into two smaller blocks is a common operation in the allocator. Introduce a split_block() helper to centralize this functionality and avoid repeating the same code. The helper also panics if the split size is invalid, ensuring stronger consistency checks in the allocator. --- lib/malloc.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/malloc.c b/lib/malloc.c index 85460d3..f009697 100644 --- a/lib/malloc.c +++ b/lib/malloc.c @@ -135,6 +135,28 @@ static void selective_coalesce(void) } } +static inline void split_block(memblock_t *block, size_t size) +{ + size_t remaining; + memblock_t *new_block; + + if (unlikely(size >= GET_SIZE(block))) { + panic(ERR_HEAP_CORRUPT); + return; + } + remaining = GET_SIZE(block) - size; + /* Split only when remaining memory is large enough */ + if (remaining < sizeof(memblock_t) + MALLOC_MIN_SIZE) + return; + new_block = (memblock_t *) ((size_t) block + sizeof(memblock_t) + size); + new_block->next = block->next; + new_block->size = remaining - sizeof(memblock_t); + MARK_FREE(new_block); + block->next = new_block; + block->size = size | IS_USED(block); + free_blocks_count++; /* New free block created */ +} + /* O(n) first-fit allocation with selective coalescing */ void *malloc(uint32_t size) { From b0fbaf76bebbb18eb7136acd94a22979e349d82d Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 9 Sep 2025 14:37:09 +0800 Subject: [PATCH 10/12] Use split_block() helper in malloc() malloc() previously contained open-coded logic for splitting a large block into two smaller ones. This is now replaced with a call to the split_block() helper introduced earlier. This removes duplicated code and ensures consistency by reusing the centralized split implementation, which also includes stronger validation and error handling. --- lib/malloc.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/malloc.c b/lib/malloc.c index f009697..a646160 100644 --- a/lib/malloc.c +++ b/lib/malloc.c @@ -185,19 +185,8 @@ void *malloc(uint32_t size) } if (!IS_USED(p) && GET_SIZE(p) >= size) { - size_t remaining = GET_SIZE(p) - size; - /* Split block only if remainder is large enough to be useful */ - if (remaining >= sizeof(memblock_t) + MALLOC_MIN_SIZE) { - memblock_t *new_block = - (memblock_t *) ((size_t) p + sizeof(memblock_t) + size); - new_block->next = p->next; - new_block->size = remaining - sizeof(memblock_t); - MARK_FREE(new_block); - p->next = new_block; - p->size = size; - free_blocks_count++; /* New free block created */ - } + split_block(p, size); MARK_USED(p); if (unlikely(free_blocks_count <= 0)) { From a73aa240c2e60e3efcbb5c0c5d35b1dde5bf6e29 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 9 Sep 2025 14:05:10 +0800 Subject: [PATCH 11/12] Add fast path for shrinking realloc() When realloc() is called with a smaller size than the current block, we can avoid allocating a new block and copying data. Instead, split the existing block into two smaller blocks using the split_block() helper. This fast path improves performance for shrinking realloc() calls and triggers selective coalescing only when fragmentation exceeds the threshold. --- lib/malloc.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/malloc.c b/lib/malloc.c index a646160..218e312 100644 --- a/lib/malloc.c +++ b/lib/malloc.c @@ -278,6 +278,16 @@ void *realloc(void *ptr, uint32_t size) old_size - size < sizeof(memblock_t) + MALLOC_MIN_SIZE) return ptr; + /* fast path for shrinking */ + if (size <= old_size) { + split_block(old_block, size); + /* Trigger coalescing only when fragmentation is high */ + if (free_blocks_count > COALESCE_THRESHOLD) + selective_coalesce(); + CRITICAL_LEAVE(); + return (void *) (old_block + 1); + } + void *new_buf = malloc(size); if (new_buf) { memcpy(new_buf, ptr, min(old_size, size)); From 2134290434ef935ec73eed373e424858e20df906 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Tue, 9 Sep 2025 14:43:53 +0800 Subject: [PATCH 12/12] Add fast path for growing realloc() When realloc() needs a larger size, we can check if the next block is free and adjacent. If the combined size of the current and next block is sufficient, merge them into a single block and split it to the desired size. This fast path avoids allocating a new block and copying data, and selective coalescing is triggered only when fragmentation exceeds the threshold, improving performance for growing realloc() calls. --- lib/malloc.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/malloc.c b/lib/malloc.c index 218e312..f893d3b 100644 --- a/lib/malloc.c +++ b/lib/malloc.c @@ -288,6 +288,23 @@ void *realloc(void *ptr, uint32_t size) return (void *) (old_block + 1); } + /* fast path for growing */ + if (old_block->next && !IS_USED(old_block->next) && + GET_SIZE(old_block) + sizeof(memblock_t) + GET_SIZE(old_block->next) >= + size) { + old_block->size = GET_SIZE(old_block) + sizeof(memblock_t) + + GET_SIZE(old_block->next); + old_block->next = old_block->next->next; + free_blocks_count--; + split_block(old_block, size); + /* Trigger coalescing only when fragmentation is high */ + if (free_blocks_count > COALESCE_THRESHOLD) + selective_coalesce(); + CRITICAL_LEAVE(); + return (void *) (old_block + 1); + } + + void *new_buf = malloc(size); if (new_buf) { memcpy(new_buf, ptr, min(old_size, size));