os/mm: Add use-after-free detection to the heap allocator - #7475
Open
seokhun-eom24 wants to merge 1 commit into
Open
os/mm: Add use-after-free detection to the heap allocator#7475seokhun-eom24 wants to merge 1 commit into
seokhun-eom24 wants to merge 1 commit into
Conversation
seokhun-eom24
force-pushed
the
260806-uaf-detection
branch
from
August 8, 2026 05:26
20fb5f3 to
8d72fb1
Compare
Fill the data area of a chunk with the 0xA5A5A5A5 poison pattern when it enters the free list, and verify the pattern before the chunk is consumed again. A mismatch means the chunk was written while it was free, which is a use-after-free by whoever owned it previously. The pattern is written from mm_addfreechunk(), which every chunk passes through on its way into the free list. Verification happens wherever a free chunk is consumed: - mm_malloc() and mm_memalign(), before the chunk is split - mm_free(), mm_realloc() and mm_shrinkchunk(), before a neighbouring free chunk is merged or absorbed. Checking here reports the corruption much earlier than waiting for the chunk to be handed out again. On detection the report prints the ERROR line, the node information via mm_dump_node(), and a hex dump of the whole chunk so that the corrupted words stand out against the 0xA5A5A5A5 background. Only the data area following the free node bookkeeping is covered. The allocator reuses the first SIZEOF_MM_FREENODE bytes for flink/blink and the free debug info while a chunk is free, so a write there corrupts the free list and is caught by DEBUGASSERT_MM_FREE_NODE instead. The covered size is capped by CONFIG_DEBUG_MM_UAF_POISON_SIZE to bound the cost added to every free and every allocation. New configuration options: - CONFIG_DEBUG_MM_UAF: enable the detector - CONFIG_DEBUG_MM_UAF_POISON_SIZE: bytes to poison per chunk, default 64 - CONFIG_DEBUG_MM_UAF_PANIC: assert on detection instead of only reporting [Example Log] ```bash mm_uaf_verify: ERROR: Use after free detected. mm_dump_node: FREE NODE: addr = 0x63a94030, type = F, size = 112, preceding size = 112 mm_dump_node: FREE NODE owner pid = 29 (hello), allocated by code at addr = 0x0e26d4fb Dump heap: 0x63a94030 - 0x63a940a0 63a94030: 00000070 0e26d4fb 0000001d 00000070 00000000 63a81088 0e26d785 0707001d 63a94050: deadbeef cafebabe 12345678 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 63a94070: a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 63a94090: 07070707 07070707 07070707 07070707 80000070 0e26d503 0000001d 00000070 ``` [Example Log Interpretation] | Address/offset | Value | Meaning | |----------------|----------|---------| | +0x00 | 00000070 | Preceding chunk size = 112; allocation bit clear (free node) | | +0x04 | 0e26d4fb | Allocation caller address | | +0x08 | 0000001d | Low 16 bits: PID 29; high 16 bits: memory_state 0 | | +0x0c | 00000070 | Current chunk size = 112 | | +0x10 | 00000000 | Free-list flink | | +0x14 | 63a81088 | Free-list blink | | +0x18 | 0e26d785 | Free caller address | | +0x1c | 0707001d | Low 16 bits: free PID 29; high 16 bits: reserved 0x0707 | The poisoned data area begins at offset +0x20. With the default 64-byte poison size, the range from +0x20 through +0x5f is expected to contain 0xA5A5A5A5. In this example: ```text 0x63a94050 (+0x20): deadbeef <- overwritten 0x63a94054 (+0x24): cafebabe <- overwritten 0x63a94058 (+0x28): 12345678 <- overwritten 0x63a9405c (+0x2c): a5a5a5a5 <- intact poison ``` Note: The metadata interpretation above is best-effort. A write through a stale pointer can overwrite the free-node bookkeeping fields at +0x10 through +0x1f because they overlay the first 16 bytes of the former user area. An underrun or arbitrary write can also corrupt the header at +0x00 through +0x0f. The poison verifier excludes these metadata fields, so size, PID, caller addresses, and free-list links must not be considered trustworthy once metadata corruption is suspected. The fields in this example are internally consistent, but that does not prove they were untouched. Co-Authored-By: Claude Signed-off-by: seokhun-eom <seokhun.eom@samsung.com>
seokhun-eom24
force-pushed
the
260806-uaf-detection
branch
from
August 31, 2026 05:44
8d72fb1 to
19cf162
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fill the data area of a chunk with the 0xA5A5A5A5 poison pattern when it enters the free list, and verify the pattern before the chunk is consumed again.
A mismatch means the chunk was written while it was free, which is a use-after-free by whoever owned it previously.
The pattern is written from mm_addfreechunk(), which every chunk passes through on its way into the free list.
Verification happens wherever a free chunk is consumed:
chunk is merged or absorbed. Checking here reports the corruption much
earlier than waiting for the chunk to be handed out again.
On detection the report prints the ERROR line, the node information via mm_dump_node(), and a hex dump of the whole chunk so that the corrupted words stand out against the 0xA5A5A5A5 background.
Only the data area following the free node bookkeeping is covered.
The allocator reuses the first SIZEOF_MM_FREENODE bytes for flink/blink and the free debug info while a chunk is free, so a write there corrupts the free list and is caught by DEBUGASSERT_MM_FREE_NODE instead.
The covered size is capped by CONFIG_DEBUG_MM_UAF_POISON_SIZE to bound the cost added to every free and every allocation.
New configuration options:
[Example Log]
mm_uaf_verify: ERROR: Use after free detected. mm_dump_node: FREE NODE: addr = 0x63a94030, type = F, size = 112, preceding size = 112 mm_dump_node: FREE NODE owner pid = 29 (hello), allocated by code at addr = 0x0e26d4fb Dump heap: 0x63a94030 - 0x63a940a0 63a94030: 00000070 0e26d4fb 0000001d 00000070 00000000 63a81088 0e26d785 0707001d 63a94050: deadbeef cafebabe 12345678 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 63a94070: a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5 63a94090: 07070707 07070707 07070707 07070707 80000070 0e26d503 0000001d 00000070[Example Log Interpretation]
The poisoned data area begins at offset +0x20. With the default 64-byte poison size, the range from +0x20 through +0x5f is expected to contain 0xA5A5A5A5. In this example:
Note: The metadata interpretation above is best-effort. A write through a stale pointer can overwrite the free-node bookkeeping fields at +0x10 through +0x1f because they overlay the first 16 bytes of the former user area. An underrun or arbitrary write can also corrupt the header at +0x00 through +0x0f. The poison verifier excludes these metadata fields, so size, PID, caller addresses, and free-list links must not be considered trustworthy once metadata corruption is suspected. The fields in this example are internally consistent, but that does not prove they were untouched.