146 lines
4.5 KiB
Markdown
146 lines
4.5 KiB
Markdown
|
|
# False Positive Analysis Report: LIBC Pointer Misidentification
|
||
|
|
|
||
|
|
## Executive Summary
|
||
|
|
|
||
|
|
The `free(): invalid pointer` error is caused by **SS guessing logic** (lines 58-61 in `core/box/hak_free_api.inc.h`) which incorrectly identifies LIBC pointers as HAKMEM SuperSlab pointers, leading to wrong free path execution.
|
||
|
|
|
||
|
|
## Root Cause: SS Guessing Logic
|
||
|
|
|
||
|
|
### The Problematic Code
|
||
|
|
```c
|
||
|
|
// Lines 58-61 in core/box/hak_free_api.inc.h
|
||
|
|
for (int lg=21; lg>=20; lg--) {
|
||
|
|
uintptr_t mask=((uintptr_t)1<<lg)-1;
|
||
|
|
SuperSlab* guess=(SuperSlab*)((uintptr_t)ptr & ~mask);
|
||
|
|
if (guess && guess->magic==SUPERSLAB_MAGIC) {
|
||
|
|
int sidx=slab_index_for(guess,ptr);
|
||
|
|
int cap=ss_slabs_capacity(guess);
|
||
|
|
if (sidx>=0&&sidx<cap){
|
||
|
|
hak_free_route_log("ss_guess", ptr);
|
||
|
|
hak_tiny_free(ptr); // <-- WRONG! ptr might be from LIBC!
|
||
|
|
goto done;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Why This Is Dangerous
|
||
|
|
|
||
|
|
1. **Reads Arbitrary Memory**: The code aligns any pointer to 2MB/1MB boundary and reads from that address
|
||
|
|
2. **No Ownership Validation**: Even if magic matches, there's no proof the pointer belongs to that SuperSlab
|
||
|
|
3. **False Positive Risk**: If aligned address happens to contain `SUPERSLAB_MAGIC`, LIBC pointers get misrouted
|
||
|
|
|
||
|
|
## False Positive Scenarios
|
||
|
|
|
||
|
|
### Scenario 1: Memory Reuse
|
||
|
|
- HAKMEM previously allocated a SuperSlab at address X
|
||
|
|
- SuperSlab was freed but memory wasn't cleared
|
||
|
|
- LIBC malloc reuses memory near X
|
||
|
|
- SS guessing finds old SUPERSLAB_MAGIC at aligned address
|
||
|
|
- LIBC pointer wrongly sent to `hak_tiny_free()`
|
||
|
|
|
||
|
|
### Scenario 2: Random Collision
|
||
|
|
- LIBC allocates memory
|
||
|
|
- 2MB-aligned base happens to contain the magic value
|
||
|
|
- Bounds check accidentally passes
|
||
|
|
- LIBC pointer wrongly freed through HAKMEM
|
||
|
|
|
||
|
|
### Scenario 3: Race Condition
|
||
|
|
- Thread A: Checks magic, it matches
|
||
|
|
- Thread B: Frees the SuperSlab
|
||
|
|
- Thread A: Proceeds to use freed SuperSlab -> CRASH
|
||
|
|
|
||
|
|
## Test Results
|
||
|
|
|
||
|
|
Our test program demonstrates:
|
||
|
|
```
|
||
|
|
LIBC pointer: 0x65329b0e42b0
|
||
|
|
2MB-aligned base: 0x65329b000000 (reading from here is UNSAFE!)
|
||
|
|
```
|
||
|
|
|
||
|
|
The SS guessing reads from `0x65329b000000` which is:
|
||
|
|
- 2,093,072 bytes away from the actual pointer
|
||
|
|
- Arbitrary memory that might contain anything
|
||
|
|
- Not validated as belonging to HAKMEM
|
||
|
|
|
||
|
|
## Other Lookup Functions
|
||
|
|
|
||
|
|
### ✅ `hak_super_lookup()` - SAFE
|
||
|
|
- Uses proper registry with O(1) lookup
|
||
|
|
- Validates magic BEFORE returning pointer
|
||
|
|
- Thread-safe with acquire/release semantics
|
||
|
|
- Returns NULL for LIBC pointers
|
||
|
|
|
||
|
|
### ✅ `hak_pool_mid_lookup()` - SAFE
|
||
|
|
- Uses page descriptor hash table
|
||
|
|
- Only returns true for registered Mid pages
|
||
|
|
- Returns 0 for LIBC pointers
|
||
|
|
|
||
|
|
### ✅ `hak_l25_lookup()` - SAFE
|
||
|
|
- Uses page descriptor lookup
|
||
|
|
- Only returns true for registered L2.5 pages
|
||
|
|
- Returns 0 for LIBC pointers
|
||
|
|
|
||
|
|
### ❌ SS Guessing (lines 58-61) - UNSAFE
|
||
|
|
- Reads from arbitrary aligned addresses
|
||
|
|
- No proper validation
|
||
|
|
- High false positive risk
|
||
|
|
|
||
|
|
## Recommended Fix
|
||
|
|
|
||
|
|
### Option 1: Remove SS Guessing (RECOMMENDED)
|
||
|
|
```c
|
||
|
|
// DELETE lines 58-61 entirely
|
||
|
|
// The registered lookup already handles valid SuperSlabs
|
||
|
|
```
|
||
|
|
|
||
|
|
### Option 2: Add Proper Validation
|
||
|
|
```c
|
||
|
|
// Only use registered SuperSlabs, no guessing
|
||
|
|
SuperSlab* ss = hak_super_lookup(ptr);
|
||
|
|
if (ss && ss->magic == SUPERSLAB_MAGIC) {
|
||
|
|
int sidx = slab_index_for(ss, ptr);
|
||
|
|
int cap = ss_slabs_capacity(ss);
|
||
|
|
if (sidx >= 0 && sidx < cap) {
|
||
|
|
hak_tiny_free(ptr);
|
||
|
|
goto done;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// No guessing loop!
|
||
|
|
```
|
||
|
|
|
||
|
|
### Option 3: Check Header First
|
||
|
|
```c
|
||
|
|
// Check header magic BEFORE any SS operations
|
||
|
|
AllocHeader* hdr = (AllocHeader*)((char*)ptr - HEADER_SIZE);
|
||
|
|
if (hdr->magic == HAKMEM_MAGIC) {
|
||
|
|
// Only then try SS operations
|
||
|
|
} else {
|
||
|
|
// Definitely LIBC, use __libc_free()
|
||
|
|
__libc_free(ptr);
|
||
|
|
goto done;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Recommended Routing Order
|
||
|
|
|
||
|
|
The safest routing order for `hak_free_at()`:
|
||
|
|
|
||
|
|
1. **NULL check** - Return immediately if ptr is NULL
|
||
|
|
2. **Header check** - Check HAKMEM_MAGIC first (most reliable)
|
||
|
|
3. **Registered lookups only** - Use hak_super_lookup(), never guess
|
||
|
|
4. **Mid/L25 lookups** - These are safe with proper registry
|
||
|
|
5. **Fallback to LIBC** - If no match, assume LIBC and use __libc_free()
|
||
|
|
|
||
|
|
## Impact
|
||
|
|
|
||
|
|
- **Current**: LIBC pointers can be misidentified → crash
|
||
|
|
- **After fix**: Clean separation between HAKMEM and LIBC pointers
|
||
|
|
- **Performance**: Removing guessing loop actually improves performance
|
||
|
|
|
||
|
|
## Action Items
|
||
|
|
|
||
|
|
1. **IMMEDIATE**: Remove lines 58-61 (SS guessing loop)
|
||
|
|
2. **TEST**: Verify LIBC allocations work correctly
|
||
|
|
3. **AUDIT**: Check for similar guessing logic elsewhere
|
||
|
|
4. **DOCUMENT**: Add warnings about reading arbitrary aligned memory
|