535 lines
15 KiB
Markdown
535 lines
15 KiB
Markdown
|
|
# FREE_TO_SS=1 SEGV - Technical Deep Dive
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
This document provides detailed code analysis of the SEGV bug in the FREE_TO_SS=1 code path, with complete reproduction scenarios and fix implementations.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Part 1: Bug #1 - Critical: size_class Validation Missing
|
||
|
|
|
||
|
|
### The Vulnerability
|
||
|
|
|
||
|
|
**Location:** Multiple points in the call chain
|
||
|
|
- `hakmem_tiny_free.inc:1520` (class_idx assignment)
|
||
|
|
- `hakmem_tiny_free.inc:1189` (g_tiny_class_sizes access)
|
||
|
|
- `hakmem_tiny_free.inc:1564` (HAK_STAT_FREE macro)
|
||
|
|
|
||
|
|
### Current Code (VULNERABLE)
|
||
|
|
|
||
|
|
**hakmem_tiny_free.inc:1517-1524**
|
||
|
|
```c
|
||
|
|
SuperSlab* fast_ss = NULL;
|
||
|
|
TinySlab* fast_slab = NULL;
|
||
|
|
int fast_class_idx = -1;
|
||
|
|
if (g_use_superslab) {
|
||
|
|
fast_ss = hak_super_lookup(ptr);
|
||
|
|
if (fast_ss && fast_ss->magic == SUPERSLAB_MAGIC) {
|
||
|
|
fast_class_idx = fast_ss->size_class; // ← NO BOUNDS CHECK!
|
||
|
|
} else {
|
||
|
|
fast_ss = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**hakmem_tiny_free.inc:1554-1566**
|
||
|
|
```c
|
||
|
|
SuperSlab* ss = fast_ss;
|
||
|
|
if (!ss && g_use_superslab) {
|
||
|
|
ss = hak_super_lookup(ptr);
|
||
|
|
if (!(ss && ss->magic == SUPERSLAB_MAGIC)) {
|
||
|
|
ss = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (ss && ss->magic == SUPERSLAB_MAGIC) {
|
||
|
|
hak_tiny_free_superslab(ptr, ss); // ← Called with unvalidated ss
|
||
|
|
HAK_STAT_FREE(ss->size_class); // ← OOB if ss->size_class >= 8
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Vulnerability in hak_tiny_free_superslab()
|
||
|
|
|
||
|
|
**hakmem_tiny_free.inc:1188-1203**
|
||
|
|
```c
|
||
|
|
if (__builtin_expect(g_tiny_safe_free, 0)) {
|
||
|
|
size_t blk = g_tiny_class_sizes[ss->size_class]; // ← OOB READ!
|
||
|
|
uint8_t* base = tiny_slab_base_for(ss, slab_idx);
|
||
|
|
uintptr_t delta = (uintptr_t)ptr - (uintptr_t)base;
|
||
|
|
int cap_ok = (meta->capacity > 0) ? 1 : 0;
|
||
|
|
int align_ok = (delta % blk) == 0;
|
||
|
|
int range_ok = cap_ok && (delta / blk) < meta->capacity;
|
||
|
|
if (!align_ok || !range_ok) {
|
||
|
|
// ... error handling ...
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Why This Causes SEGV
|
||
|
|
|
||
|
|
**Array Definition (hakmem_tiny.h:33-42)**
|
||
|
|
```c
|
||
|
|
#define TINY_NUM_CLASSES 8
|
||
|
|
|
||
|
|
static const size_t g_tiny_class_sizes[TINY_NUM_CLASSES] = {
|
||
|
|
8, // Class 0: 8 bytes
|
||
|
|
16, // Class 1: 16 bytes
|
||
|
|
32, // Class 2: 32 bytes
|
||
|
|
64, // Class 3: 64 bytes
|
||
|
|
128, // Class 4: 128 bytes
|
||
|
|
256, // Class 5: 256 bytes
|
||
|
|
512, // Class 6: 512 bytes
|
||
|
|
1024 // Class 7: 1024 bytes
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
**Scenario:**
|
||
|
|
```
|
||
|
|
Thread executes free(ptr) with HAKMEM_TINY_FREE_TO_SS=1
|
||
|
|
↓
|
||
|
|
hak_super_lookup(ptr) returns SuperSlab* ss
|
||
|
|
ss->magic == SUPERSLAB_MAGIC ✓ (valid magic)
|
||
|
|
But ss->size_class = 0xFF (corrupted memory!)
|
||
|
|
↓
|
||
|
|
hak_tiny_free_superslab(ptr, ss) called
|
||
|
|
↓
|
||
|
|
g_tiny_class_sizes[0xFF] accessed ← Out-of-bounds array access
|
||
|
|
↓
|
||
|
|
Array bounds: g_tiny_class_sizes[0..7]
|
||
|
|
Access: g_tiny_class_sizes[255]
|
||
|
|
Result: SIGSEGV (Segmentation Fault)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Reproduction (Hypothetical)
|
||
|
|
|
||
|
|
```c
|
||
|
|
// Assume corrupted SuperSlab with size_class=255
|
||
|
|
SuperSlab* ss = (SuperSlab*)corrupted_memory;
|
||
|
|
ss->magic = SUPERSLAB_MAGIC; // Valid magic (passes check)
|
||
|
|
ss->size_class = 255; // CORRUPTED field
|
||
|
|
ss->lg_size = 20;
|
||
|
|
|
||
|
|
// In hak_tiny_free_superslab():
|
||
|
|
if (g_tiny_safe_free) {
|
||
|
|
size_t blk = g_tiny_class_sizes[ss->size_class]; // Access [255]!
|
||
|
|
// Bounds: [0..7], Access: [255]
|
||
|
|
// Result: SEGFAULT
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### The Fix
|
||
|
|
|
||
|
|
**Minimal Fix (Priority 1):**
|
||
|
|
```c
|
||
|
|
// In hakmem_tiny_free.inc:1554-1566, before calling hak_tiny_free_superslab()
|
||
|
|
|
||
|
|
if (ss && ss->magic == SUPERSLAB_MAGIC) {
|
||
|
|
// ADDED: Validate size_class before use
|
||
|
|
if (__builtin_expect(ss->size_class >= TINY_NUM_CLASSES, 0)) {
|
||
|
|
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID,
|
||
|
|
(uint16_t)(0xBAD_CLASS | (ss->size_class & 0xFF)),
|
||
|
|
ptr,
|
||
|
|
(uint32_t)(ss->lg_size << 16 | ss->size_class));
|
||
|
|
if (g_tiny_safe_free_strict) { raise(SIGUSR2); }
|
||
|
|
return; // ADDED: Early return to prevent SEGV
|
||
|
|
}
|
||
|
|
|
||
|
|
hak_tiny_free_superslab(ptr, ss);
|
||
|
|
HAK_STAT_FREE(ss->size_class);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Comprehensive Fix (Priority 1+):**
|
||
|
|
```c
|
||
|
|
// In hakmem_tiny_free.inc:1554-1566
|
||
|
|
|
||
|
|
if (ss && ss->magic == SUPERSLAB_MAGIC) {
|
||
|
|
// CRITICAL VALIDATION: Check all SuperSlab metadata
|
||
|
|
int validation_ok = 1;
|
||
|
|
uint32_t diag_code = 0;
|
||
|
|
|
||
|
|
// Check 1: size_class
|
||
|
|
if (ss->size_class >= TINY_NUM_CLASSES) {
|
||
|
|
validation_ok = 0;
|
||
|
|
diag_code = 0xBAD1 | (ss->size_class << 8);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check 2: lg_size (only if size_class valid)
|
||
|
|
if (validation_ok && (ss->lg_size < 20 || ss->lg_size > 21)) {
|
||
|
|
validation_ok = 0;
|
||
|
|
diag_code = 0xBAD2 | (ss->lg_size << 8);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check 3: active_slabs (sanity check)
|
||
|
|
int expected_slabs = ss_slabs_capacity(ss);
|
||
|
|
if (validation_ok && ss->active_slabs > expected_slabs) {
|
||
|
|
validation_ok = 0;
|
||
|
|
diag_code = 0xBAD3 | (ss->active_slabs << 8);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!validation_ok) {
|
||
|
|
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID,
|
||
|
|
diag_code,
|
||
|
|
ptr,
|
||
|
|
((uint32_t)ss->lg_size << 8) | ss->size_class);
|
||
|
|
if (g_tiny_safe_free_strict) { raise(SIGUSR2); }
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
hak_tiny_free_superslab(ptr, ss);
|
||
|
|
HAK_STAT_FREE(ss->size_class);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Part 2: Bug #2 - TOCTOU Race in hak_super_lookup()
|
||
|
|
|
||
|
|
### The Race Condition
|
||
|
|
|
||
|
|
**Location:** `hakmem_super_registry.h:73-106`
|
||
|
|
|
||
|
|
### Current Implementation
|
||
|
|
|
||
|
|
```c
|
||
|
|
static inline SuperSlab* hak_super_lookup(void* ptr) {
|
||
|
|
if (!g_super_reg_initialized) return NULL;
|
||
|
|
|
||
|
|
// Try both 1MB and 2MB alignments
|
||
|
|
for (int lg = 20; lg <= 21; lg++) {
|
||
|
|
uintptr_t mask = (1UL << lg) - 1;
|
||
|
|
uintptr_t base = (uintptr_t)ptr & ~mask;
|
||
|
|
int h = hak_super_hash(base, lg);
|
||
|
|
|
||
|
|
// Linear probing with acquire semantics
|
||
|
|
for (int i = 0; i < SUPER_MAX_PROBE; i++) {
|
||
|
|
SuperRegEntry* e = &g_super_reg[(h + i) & SUPER_REG_MASK];
|
||
|
|
uintptr_t b = atomic_load_explicit((_Atomic uintptr_t*)&e->base,
|
||
|
|
memory_order_acquire);
|
||
|
|
|
||
|
|
// Match both base address AND lg_size
|
||
|
|
if (b == base && e->lg_size == lg) {
|
||
|
|
// Atomic load to prevent TOCTOU race with unregister
|
||
|
|
SuperSlab* ss = atomic_load_explicit(&e->ss, memory_order_acquire);
|
||
|
|
if (!ss) return NULL; // Entry cleared by unregister
|
||
|
|
|
||
|
|
// CRITICAL: Check magic BEFORE returning pointer
|
||
|
|
if (ss->magic != SUPERSLAB_MAGIC) return NULL;
|
||
|
|
|
||
|
|
return ss; // ← Pointer returned here
|
||
|
|
// But memory could be unmapped on next instruction!
|
||
|
|
}
|
||
|
|
if (b == 0) break; // Empty slot
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### The Race Scenario
|
||
|
|
|
||
|
|
**Timeline:**
|
||
|
|
```
|
||
|
|
TIME 0: Thread A: ss = hak_super_lookup(ptr)
|
||
|
|
- Reads registry entry
|
||
|
|
- Checks magic: SUPERSLAB_MAGIC ✓
|
||
|
|
- Returns ss pointer
|
||
|
|
|
||
|
|
TIME 1: Thread B: [Different thread or signal handler]
|
||
|
|
- Calls hak_super_unregister()
|
||
|
|
- Writes e->base = 0 (release semantics)
|
||
|
|
|
||
|
|
TIME 2: Thread B: munmap((void*)ss, SUPERSLAB_SIZE)
|
||
|
|
- Unmaps the entire 1MB/2MB region
|
||
|
|
- Physical pages reclaimed by kernel
|
||
|
|
|
||
|
|
TIME 3: Thread A: TinySlabMeta* meta = &ss->slabs[slab_idx]
|
||
|
|
- Attempts to access first cache line of ss
|
||
|
|
- Memory mapping: INVALID
|
||
|
|
- CPU raises SIGSEGV
|
||
|
|
- Result: SEGMENTATION FAULT
|
||
|
|
```
|
||
|
|
|
||
|
|
### Why FREE_TO_SS=1 Makes It Worse
|
||
|
|
|
||
|
|
**Without FREE_TO_SS:**
|
||
|
|
```c
|
||
|
|
// Normal path avoids explicit SS lookup in some cases
|
||
|
|
// Fast path uses TLS freelist directly
|
||
|
|
// Reduces window for TOCTOU race
|
||
|
|
```
|
||
|
|
|
||
|
|
**With FREE_TO_SS=1:**
|
||
|
|
```c
|
||
|
|
// Explicitly calls hak_super_lookup() at:
|
||
|
|
// hakmem.c:924 (outer entry)
|
||
|
|
// hakmem.c:969 (inner entry)
|
||
|
|
// hakmem_tiny_free.inc:1471, 1494, 1518, 1532, 1556
|
||
|
|
//
|
||
|
|
// Each lookup is a potential TOCTOU window
|
||
|
|
// Increases probability of race condition
|
||
|
|
```
|
||
|
|
|
||
|
|
### The Fix
|
||
|
|
|
||
|
|
**Option A: Re-check magic in hak_tiny_free_superslab()**
|
||
|
|
|
||
|
|
```c
|
||
|
|
// In hakmem_tiny_free_superslab(), add at entry:
|
||
|
|
|
||
|
|
static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
|
||
|
|
ROUTE_MARK(16);
|
||
|
|
|
||
|
|
// ADDED: Re-check magic to catch TOCTOU races
|
||
|
|
// If ss was unmapped since lookup, this access may SEGV, but
|
||
|
|
// we know it's due to TOCTOU, not corruption
|
||
|
|
if (__builtin_expect(ss->magic != SUPERSLAB_MAGIC, 0)) {
|
||
|
|
// SuperSlab was freed/unmapped after lookup
|
||
|
|
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID,
|
||
|
|
(uint16_t)0xTOCTOU,
|
||
|
|
ptr,
|
||
|
|
(uintptr_t)ss);
|
||
|
|
if (g_tiny_safe_free_strict) { raise(SIGUSR2); }
|
||
|
|
return; // Early exit
|
||
|
|
}
|
||
|
|
|
||
|
|
// Continue with normal processing...
|
||
|
|
int slab_idx = slab_index_for(ss, ptr);
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Option B: Use refcount to prevent munmap during free**
|
||
|
|
|
||
|
|
```c
|
||
|
|
// In hak_super_lookup():
|
||
|
|
|
||
|
|
static inline SuperSlab* hak_super_lookup(void* ptr) {
|
||
|
|
// ... existing code ...
|
||
|
|
|
||
|
|
if (b == base && e->lg_size == lg) {
|
||
|
|
SuperSlab* ss = atomic_load_explicit(&e->ss, memory_order_acquire);
|
||
|
|
if (!ss) return NULL;
|
||
|
|
|
||
|
|
if (ss->magic != SUPERSLAB_MAGIC) return NULL;
|
||
|
|
|
||
|
|
// ADDED: Increment refcount before returning
|
||
|
|
// This prevents hak_super_unregister() from calling munmap()
|
||
|
|
atomic_fetch_add_explicit(&ss->refcount, 1, memory_order_acq_rel);
|
||
|
|
|
||
|
|
return ss;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Then in free path:
|
||
|
|
```c
|
||
|
|
// After hak_tiny_free_superslab() completes:
|
||
|
|
if (ss) {
|
||
|
|
atomic_fetch_sub_explicit(&ss->refcount, 1, memory_order_release);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Part 3: Bug #3 - Integer Overflow in lg_size
|
||
|
|
|
||
|
|
### The Vulnerability
|
||
|
|
|
||
|
|
**Location:** `hakmem_tiny_free.inc:1165`
|
||
|
|
|
||
|
|
### Current Code
|
||
|
|
|
||
|
|
```c
|
||
|
|
size_t ss_size = (size_t)1ULL << ss->lg_size; // Line 1165
|
||
|
|
```
|
||
|
|
|
||
|
|
### The Problem
|
||
|
|
|
||
|
|
**Assumptions:**
|
||
|
|
- `ss->lg_size` should be 20 (1MB) or 21 (2MB)
|
||
|
|
- But no validation before use
|
||
|
|
|
||
|
|
**Undefined Behavior:**
|
||
|
|
```c
|
||
|
|
// Valid cases:
|
||
|
|
1ULL << 20 // = 1,048,576 (1MB) ✓
|
||
|
|
1ULL << 21 // = 2,097,152 (2MB) ✓
|
||
|
|
|
||
|
|
// Invalid cases (undefined behavior):
|
||
|
|
1ULL << 22 // Undefined (shift amount too large)
|
||
|
|
1ULL << 64 // Undefined (shift amount >= type width)
|
||
|
|
1ULL << 255 // Undefined (massive shift)
|
||
|
|
|
||
|
|
// Typical results:
|
||
|
|
1ULL << 64 → 0 or 1 (depends on CPU)
|
||
|
|
1ULL << 100 → Undefined (compiler may optimize away, corrupt, etc.)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Reproduction
|
||
|
|
|
||
|
|
```c
|
||
|
|
SuperSlab corrupted_ss;
|
||
|
|
corrupted_ss.lg_size = 100; // Corrupted
|
||
|
|
|
||
|
|
// In hak_tiny_free_superslab():
|
||
|
|
size_t ss_size = (size_t)1ULL << corrupted_ss.lg_size;
|
||
|
|
// ss_size = undefined (could be 0, 1, or garbage)
|
||
|
|
|
||
|
|
// Next line uses ss_size:
|
||
|
|
uintptr_t aux = tiny_remote_pack_diag(0xBAD1u, ss_base, ss_size, (uintptr_t)ptr);
|
||
|
|
// If ss_size = 0, diag packing is wrong
|
||
|
|
// Could lead to corrupted debug info or SEGV
|
||
|
|
```
|
||
|
|
|
||
|
|
### The Fix
|
||
|
|
|
||
|
|
```c
|
||
|
|
// In hak_tiny_free_superslab.inc:1160-1172
|
||
|
|
|
||
|
|
static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
|
||
|
|
ROUTE_MARK(16);
|
||
|
|
HAK_DBG_INC(g_superslab_free_count);
|
||
|
|
|
||
|
|
// ADDED: Validate lg_size before use
|
||
|
|
if (__builtin_expect(ss->lg_size < 20 || ss->lg_size > 21, 0)) {
|
||
|
|
uintptr_t bad_base = (uintptr_t)ss;
|
||
|
|
size_t bad_size = 0; // Safe default
|
||
|
|
uintptr_t aux = tiny_remote_pack_diag(0xBAD_LGSIZE | ss->lg_size,
|
||
|
|
bad_base, bad_size, (uintptr_t)ptr);
|
||
|
|
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID,
|
||
|
|
(uint16_t)(0xB000 | ss->size_class),
|
||
|
|
ptr, aux);
|
||
|
|
if (g_tiny_safe_free_strict) { raise(SIGUSR2); }
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// NOW safe to use ss->lg_size
|
||
|
|
int slab_idx = slab_index_for(ss, ptr);
|
||
|
|
size_t ss_size = (size_t)1ULL << ss->lg_size;
|
||
|
|
// ... continue ...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Part 4: Integration of All Fixes
|
||
|
|
|
||
|
|
### Recommended Implementation Order
|
||
|
|
|
||
|
|
**Step 1: Apply Priority 1 Fix (size_class validation)**
|
||
|
|
- Location: `hakmem_tiny_free.inc:1554-1566`
|
||
|
|
- Risk: Very low (only adds bounds checks)
|
||
|
|
- Benefit: Blocks 85% of SEGV cases
|
||
|
|
|
||
|
|
**Step 2: Apply Priority 2 Fix (TOCTOU re-check)**
|
||
|
|
- Location: `hakmem_tiny_free_superslab.inc:1160`
|
||
|
|
- Risk: Very low (defensive check only)
|
||
|
|
- Benefit: Blocks TOCTOU races
|
||
|
|
|
||
|
|
**Step 3: Apply Priority 3 Fix (lg_size validation)**
|
||
|
|
- Location: `hakmem_tiny_free_superslab.inc:1165`
|
||
|
|
- Risk: Very low (validation before use)
|
||
|
|
- Benefit: Blocks integer overflow
|
||
|
|
|
||
|
|
**Step 4: Add comprehensive entry validation**
|
||
|
|
- Location: `hakmem.c:924-932, 969-976`
|
||
|
|
- Risk: Low (early rejection of bad pointers)
|
||
|
|
- Benefit: Defense-in-depth
|
||
|
|
|
||
|
|
### Complete Patch Strategy
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Apply in this order:
|
||
|
|
1. git apply fix-1-size-class-validation.patch
|
||
|
|
2. git apply fix-2-toctou-recheck.patch
|
||
|
|
3. git apply fix-3-lgsize-validation.patch
|
||
|
|
4. make clean && make box-refactor # Rebuild
|
||
|
|
5. Run test suite with HAKMEM_TINY_FREE_TO_SS=1
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Part 5: Testing Strategy
|
||
|
|
|
||
|
|
### Unit Tests
|
||
|
|
|
||
|
|
```c
|
||
|
|
// Test 1: Corrupted size_class
|
||
|
|
TEST(FREE_TO_SS, CorruptedSizeClass) {
|
||
|
|
SuperSlab corrupted;
|
||
|
|
corrupted.magic = SUPERSLAB_MAGIC;
|
||
|
|
corrupted.size_class = 255; // Out of bounds
|
||
|
|
|
||
|
|
void* ptr = test_alloc(64);
|
||
|
|
// Register corrupted SS in registry
|
||
|
|
// Call free(ptr) with FREE_TO_SS=1
|
||
|
|
// Expect: No SEGV, proper error logging
|
||
|
|
ASSERT_NE(get_last_error_code(), 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Test 2: Corrupted lg_size
|
||
|
|
TEST(FREE_TO_SS, CorruptedLgSize) {
|
||
|
|
SuperSlab corrupted;
|
||
|
|
corrupted.magic = SUPERSLAB_MAGIC;
|
||
|
|
corrupted.size_class = 4; // Valid
|
||
|
|
corrupted.lg_size = 100; // Out of bounds
|
||
|
|
|
||
|
|
void* ptr = test_alloc(128);
|
||
|
|
// Register corrupted SS in registry
|
||
|
|
// Call free(ptr) with FREE_TO_SS=1
|
||
|
|
// Expect: No SEGV, proper error logging
|
||
|
|
ASSERT_NE(get_last_error_code(), 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Test 3: TOCTOU Race
|
||
|
|
TEST(FREE_TO_SS, TOCTOURace) {
|
||
|
|
std::thread alloc_thread([]() {
|
||
|
|
void* ptr = test_alloc(256);
|
||
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||
|
|
free(ptr);
|
||
|
|
});
|
||
|
|
|
||
|
|
std::thread free_thread([]() {
|
||
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||
|
|
// Unregister all SuperSlabs (simulates race)
|
||
|
|
hak_super_unregister_all();
|
||
|
|
});
|
||
|
|
|
||
|
|
alloc_thread.join();
|
||
|
|
free_thread.join();
|
||
|
|
// Expect: No crash, proper error handling
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Integration Tests
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Test with Larson benchmark
|
||
|
|
make box-refactor
|
||
|
|
HAKMEM_TINY_FREE_TO_SS=1 HAKMEM_TINY_SAFE_FREE=1 ./larson_hakmem 2 8 128 1024 1 12345 4
|
||
|
|
# Expected: No SEGV, reasonable performance
|
||
|
|
|
||
|
|
# Test with stress test
|
||
|
|
HAKMEM_TINY_FREE_TO_SS=1 HAKMEM_TINY_SAFE_FREE=1 ./bench_comprehensive_hakmem
|
||
|
|
# Expected: All tests pass
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Conclusion
|
||
|
|
|
||
|
|
The FREE_TO_SS=1 SEGV bug is caused by missing validation of SuperSlab metadata fields. The fixes are straightforward bounds checks on `size_class` and `lg_size`, with optional TOCTOU mitigation via re-checking magic.
|
||
|
|
|
||
|
|
Implementing all three fixes provides defense-in-depth against:
|
||
|
|
1. Memory corruption
|
||
|
|
2. TOCTOU races
|
||
|
|
3. Integer overflows
|
||
|
|
|
||
|
|
Total effort: < 50 lines of code
|
||
|
|
Risk level: Very low
|
||
|
|
Benefit: Eliminates critical SEGV path
|