# slab_index_for/SS範囲チェック実装調査 - 詳細分析報告書 ## Executive Summary **CRITICAL BUG FOUND**: Buffer overflow vulnerability in multiple code paths when `slab_index_for()` returns -1 (invalid range). The `slab_index_for()` function correctly returns -1 when ptr is outside SuperSlab bounds, but **calling code does NOT check for -1 before using it as an array index**. This causes out-of-bounds memory access to SuperSlab's internal structure. --- ## 1. slab_index_for() 実装確認 ### Location: `core/hakmem_tiny_superslab.h` (Line 141-148) ```c static inline int slab_index_for(const SuperSlab* ss, const void* p) { uintptr_t base = (uintptr_t)ss; uintptr_t addr = (uintptr_t)p; uintptr_t off = addr - base; int idx = (int)(off >> 16); // 64KB per slab (2^16) int cap = ss_slabs_capacity(ss); return (idx >= 0 && idx < cap) ? idx : -1; // ^^^^^^^^^^ Returns -1 when: // 1. ptr < ss (negative offset) // 2. ptr >= ss + (cap * 64KB) (outside capacity) } ``` ### Implementation Analysis **正の部分:** - Offset calculation: `(addr - base)` は正確 - Capacity check: `ss_slabs_capacity(ss)` で 1MB/2MB どちらにも対応 - Return value: -1 で明示的に「無効」を示す **問題のある部分:** - Call site で -1 をチェック**していない**箇所が複数存在 ### ss_slabs_capacity() Implementation (Line 135-138) ```c static inline int ss_slabs_capacity(const SuperSlab* ss) { size_t ss_size = (size_t)1 << ss->lg_size; // 1MB (20) or 2MB (21) return (int)(ss_size / SLAB_SIZE); // 16 or 32 } ``` This correctly computes 16 slabs for 1MB or 32 slabs for 2MB. --- ## 2. 問題1: tiny_free_fast_ss() での範囲チェック欠落 ### Location: `core/tiny_free_fast.inc.h` (Line 91-92) ```c static inline int tiny_free_fast_ss(SuperSlab* ss, int slab_idx, void* ptr, uint32_t my_tid) { TinySlabMeta* meta = &ss->slabs[slab_idx]; // <-- CRITICAL BUG // If slab_idx == -1, this accesses ss->slabs[-1]! ``` ### Vulnerability Details **When slab_index_for() returns -1:** - slab_idx = -1 (from tiny_free_fast.inc.h:205) - `&ss->slabs[-1]` points to memory BEFORE the slabs array **Memory layout of SuperSlab:** ``` ss+0000: SuperSlab header (64B) - magic (8B) - size_class (1B) - active_slabs (1B) - lg_size (1B) - _pad0 (1B) - slab_bitmap (4B) - freelist_mask (4B) - nonempty_mask (4B) - total_active_blocks (4B) - refcount (4B) - listed (4B) - partial_epoch (4B) - publish_hint (1B) - _pad1 (3B) ss+0040: remote_heads[SLABS_PER_SUPERSLAB_MAX] (128B = 32*8B) ss+00C0: remote_counts[SLABS_PER_SUPERSLAB_MAX] (128B = 32*4B) ss+0140: slab_listed[SLABS_PER_SUPERSLAB_MAX] (128B = 32*4B) ss+01C0: partial_next (8B) ss+01C8: *** VULNERABILITY ZONE *** &ss->slabs[-1] points here (16B before valid slabs[0]) This overlaps with partial_next and padding! ss+01D0: ss->slabs[0] (first valid TinySlabMeta, 16B) - freelist (8B) - used (2B) - capacity (2B) - owner_tid (4B) ss+01E0: ss->slabs[1] ... ``` ### Impact When `slab_idx = -1`: 1. `meta = &ss->slabs[-1]` reads/writes 16 bytes at offset 0x1C8 2. This corrupts `partial_next` pointer (bytes 8-15 of the buffer) 3. Subsequent access to `meta->owner_tid` reads garbage or partially-valid data 4. `tiny_free_is_same_thread_ss()` performs ownership check on corrupted data ### Root Cause Path ``` tiny_free_fast() [tiny_free_fast.inc.h:209] ↓ slab_index_for(ss, ptr) [returns -1 if ptr out of range] ↓ tiny_free_fast_ss(ss, slab_idx=-1, ...) [NO bounds check] ↓ &ss->slabs[-1] [OUT-OF-BOUNDS ACCESS] ``` --- ## 3. 問題2: hak_tiny_free_with_slab() での範囲チェック ### Location: `core/hakmem_tiny_free.inc` (Line 96-101) ```c int slab_idx = slab_index_for(ss, ptr); int ss_cap = ss_slabs_capacity(ss); if (__builtin_expect(slab_idx < 0 || slab_idx >= ss_cap, 0)) { tiny_debug_ring_record(TINY_RING_EVENT_SUPERSLAB_ADOPT_FAIL, ...); return; } ``` **Status: CORRECT** - ✅ Bounds check present: `slab_idx < 0 || slab_idx >= ss_cap` - ✅ Early return prevents OOB access --- ## 4. 問題3: hak_tiny_free_superslab() での範囲チェック ### Location: `core/hakmem_tiny_free.inc` (Line 1164-1172) ```c int slab_idx = slab_index_for(ss, ptr); size_t ss_size = (size_t)1ULL << ss->lg_size; uintptr_t ss_base = (uintptr_t)ss; if (__builtin_expect(slab_idx < 0, 0)) { uintptr_t aux = tiny_remote_pack_diag(0xBAD1u, ss_base, ss_size, (uintptr_t)ptr); tiny_debug_ring_record(...); return; } ``` **Status: PARTIAL** - ✅ Checks `slab_idx < 0` - ⚠️ Missing check: `slab_idx >= ss_cap` - If slab_idx >= capacity, next line accesses out-of-bounds: ```c TinySlabMeta* meta = &ss->slabs[slab_idx]; // Can OOB if idx >= 32 ``` ### Vulnerability Scenario For 1MB SuperSlab (cap=16): - If ptr is at offset 1088KB (0x110000), off >> 16 = 0x11 = 17 - slab_index_for() returns -1 (not >= cap=16) - Line 1167 check passes: -1 < 0? YES → returns - OK (caught by < 0 check) For 2MB SuperSlab (cap=32): - If ptr is at offset 2112KB (0x210000), off >> 16 = 0x21 = 33 - slab_index_for() returns -1 (not >= cap=32) - Line 1167 check passes: -1 < 0? YES → returns - OK (caught by < 0 check) Actually, since slab_index_for() returns -1 when idx >= cap, the < 0 check is sufficient! --- ## 5. 問題4: Magazine spill 経路での範囲チェック ### Location: `core/hakmem_tiny_free.inc` (Line 305-316) ```c SuperSlab* owner_ss = hak_super_lookup(it.ptr); if (owner_ss && owner_ss->magic == SUPERSLAB_MAGIC) { int slab_idx = slab_index_for(owner_ss, it.ptr); TinySlabMeta* meta = &owner_ss->slabs[slab_idx]; // <-- NO CHECK! *(void**)it.ptr = meta->freelist; meta->freelist = it.ptr; meta->used--; ``` **Status: CRITICAL BUG** - ❌ No bounds check for slab_idx - ❌ slab_idx = -1 → &owner_ss->slabs[-1] out-of-bounds access ### Similar Issue at Line 464 ```c int slab_idx = slab_index_for(ss_owner, it.ptr); TinySlabMeta* meta = &ss_owner->slabs[slab_idx]; // <-- NO CHECK! ``` --- ## 6. 問題5: tiny_free_fast.inc.h:205 での範囲チェック ### Location: `core/tiny_free_fast.inc.h` (Line 205-209) ```c int slab_idx = slab_index_for(ss, ptr); uint32_t self_tid = tiny_self_u32(); // Box 6 Boundary: Try same-thread fast path if (tiny_free_fast_ss(ss, slab_idx, ptr, self_tid)) { // <-- PASSES slab_idx=-1 ``` **Status: CRITICAL BUG** - ❌ No bounds check before calling tiny_free_fast_ss() - ❌ tiny_free_fast_ss() immediately accesses ss->slabs[slab_idx] --- ## 7. SS範囲チェック全体サマリー | Code Path | File:Line | Check Status | Severity | |-----------|-----------|--------------|----------| | hak_tiny_free_with_slab() | hakmem_tiny_free.inc:96-101 | ✅ OK (both < and >=) | None | | hak_tiny_free_superslab() | hakmem_tiny_free.inc:1164-1172 | ✅ OK (checks < 0, -1 means invalid) | None | | magazine spill path 1 | hakmem_tiny_free.inc:305-316 | ❌ NO CHECK | CRITICAL | | magazine spill path 2 | hakmem_tiny_free.inc:464-468 | ❌ NO CHECK | CRITICAL | | tiny_free_fast_ss() | tiny_free_fast.inc.h:91-92 | ❌ NO CHECK on entry | CRITICAL | | tiny_free_fast() call site | tiny_free_fast.inc.h:205-209 | ❌ NO CHECK before call | CRITICAL | --- ## 8. 所有権/範囲ガード詳細 ### Box 3: Ownership Encapsulation (slab_handle.h) **slab_try_acquire()** (Line 32-78): ```c static inline SlabHandle slab_try_acquire(SuperSlab* ss, int idx, uint32_t tid) { if (!ss || ss->magic != SUPERSLAB_MAGIC) return {0}; int cap = ss_slabs_capacity(ss); if (idx < 0 || idx >= cap) { // <-- CORRECT: Range check return {0}; } TinySlabMeta* m = &ss->slabs[idx]; if (!ss_owner_try_acquire(m, tid)) { return {0}; } h.valid = 1; return h; } ``` **Status: CORRECT** - ✅ Range validation present before array access - ✅ owner_tid check done safely --- ## 9. TOCTOU 問題の可能性 ### Check-Then-Use Pattern Analysis **In tiny_free_fast_ss():** 1. Time T0: `slab_idx = slab_index_for(ss, ptr)` (no check) 2. Time T1: `meta = &ss->slabs[slab_idx]` (use) 3. Time T2: `tiny_free_is_same_thread_ss()` reads meta->owner_tid **TOCTOU Race Scenario:** - Thread A: slab_idx = slab_index_for(ss, ptr) → slab_idx = 0 (valid) - Thread B: [simultaneously] SuperSlab ss is unmapped and remapped elsewhere - Thread A: &ss->slabs[0] now points to wrong memory - Thread A: Reads/writes garbage data **Status: UNLIKELY but POSSIBLE** - Most likely attack: freeing to already-freed SuperSlab - Mitigated by: hak_super_lookup() validation (SUPERSLAB_MAGIC check) - But: If magic still valid, race exists --- ## 10. 発見したバグ一覧 ### Bug #1: tiny_free_fast_ss() - No bounds check on slab_idx **File:** core/tiny_free_fast.inc.h **Line:** 91-92 **Severity:** CRITICAL **Impact:** Buffer overflow when slab_index_for() returns -1 ```c static inline int tiny_free_fast_ss(SuperSlab* ss, int slab_idx, void* ptr, uint32_t my_tid) { TinySlabMeta* meta = &ss->slabs[slab_idx]; // BUG: No check if slab_idx < 0 or >= capacity ``` **Fix:** ```c if (slab_idx < 0 || slab_idx >= ss_slabs_capacity(ss)) return 0; TinySlabMeta* meta = &ss->slabs[slab_idx]; ``` ### Bug #2: Magazine spill path (first occurrence) - No bounds check **File:** core/hakmem_tiny_free.inc **Line:** 305-308 **Severity:** CRITICAL **Impact:** Buffer overflow in magazine recycling ```c int slab_idx = slab_index_for(owner_ss, it.ptr); TinySlabMeta* meta = &owner_ss->slabs[slab_idx]; // BUG: No bounds check *(void**)it.ptr = meta->freelist; ``` **Fix:** ```c int slab_idx = slab_index_for(owner_ss, it.ptr); if (slab_idx < 0 || slab_idx >= ss_slabs_capacity(owner_ss)) continue; TinySlabMeta* meta = &owner_ss->slabs[slab_idx]; ``` ### Bug #3: Magazine spill path (second occurrence) - No bounds check **File:** core/hakmem_tiny_free.inc **Line:** 464-467 **Severity:** CRITICAL **Impact:** Same as Bug #2 ```c int slab_idx = slab_index_for(ss_owner, it.ptr); TinySlabMeta* meta = &ss_owner->slabs[slab_idx]; // BUG: No bounds check ``` **Fix:** Same as Bug #2 ### Bug #4: tiny_free_fast() call site - No bounds check before tiny_free_fast_ss() **File:** core/tiny_free_fast.inc.h **Line:** 205-209 **Severity:** HIGH (depends on function implementation) **Impact:** Passes invalid slab_idx to tiny_free_fast_ss() ```c int slab_idx = slab_index_for(ss, ptr); uint32_t self_tid = tiny_self_u32(); // Box 6 Boundary: Try same-thread fast path if (tiny_free_fast_ss(ss, slab_idx, ptr, self_tid)) { // Passes slab_idx without checking ``` **Fix:** ```c int slab_idx = slab_index_for(ss, ptr); if (slab_idx < 0 || slab_idx >= ss_slabs_capacity(ss)) { hak_tiny_free(ptr); // Fallback to slow path return; } uint32_t self_tid = tiny_self_u32(); if (tiny_free_fast_ss(ss, slab_idx, ptr, self_tid)) { ``` --- ## 11. 修正提案 ### Priority 1: Fix tiny_free_fast_ss() entry point **File:** core/tiny_free_fast.inc.h (Line 91) ```c static inline int tiny_free_fast_ss(SuperSlab* ss, int slab_idx, void* ptr, uint32_t my_tid) { // ADD: Range validation if (__builtin_expect(slab_idx < 0 || slab_idx >= ss_slabs_capacity(ss), 0)) { return 0; // Invalid index → delegate to slow path } TinySlabMeta* meta = &ss->slabs[slab_idx]; // ... rest of function ``` **Rationale:** This is the fastest fix (5 bytes code addition) that prevents the OOB access. ### Priority 2: Fix magazine spill paths **File:** core/hakmem_tiny_free.inc (Line 305 and 464) At both locations, add bounds check: ```c int slab_idx = slab_index_for(owner_ss, it.ptr); if (slab_idx < 0 || slab_idx >= ss_slabs_capacity(owner_ss)) { continue; // Skip if invalid } TinySlabMeta* meta = &owner_ss->slabs[slab_idx]; ``` **Rationale:** Magazine spill is not a fast path, so small overhead acceptable. ### Priority 3: Add bounds check at tiny_free_fast() call site **File:** core/tiny_free_fast.inc.h (Line 205) Add validation before calling tiny_free_fast_ss(): ```c int slab_idx = slab_index_for(ss, ptr); if (__builtin_expect(slab_idx < 0 || slab_idx >= ss_slabs_capacity(ss), 0)) { hak_tiny_free(ptr); // Fallback return; } uint32_t self_tid = tiny_self_u32(); if (tiny_free_fast_ss(ss, slab_idx, ptr, self_tid)) { return; } ``` **Rationale:** Defense in depth - validate at call site AND in callee. --- ## 12. Test Case to Trigger Bugs ```c void test_slab_index_for_oob() { SuperSlab* ss = allocate_1mb_superslab(); // Case 1: Pointer before SuperSlab void* ptr_before = (void*)((uintptr_t)ss - 1024); int idx = slab_index_for(ss, ptr_before); assert(idx == -1); // Should return -1 // Case 2: Pointer at SS end (just beyond capacity) void* ptr_after = (void*)((uintptr_t)ss + (1024*1024)); idx = slab_index_for(ss, ptr_after); assert(idx == -1); // Should return -1 // Case 3: tiny_free_fast() with OOB pointer tiny_free_fast(ptr_after); // BUG: Calls tiny_free_fast_ss(ss, -1, ptr, tid) // Without fix: Accesses ss->slabs[-1] → buffer overflow } ``` --- ## Summary | Issue | Location | Severity | Status | |-------|----------|----------|--------| | slab_index_for() implementation | hakmem_tiny_superslab.h:141 | Info | Correct | | tiny_free_fast_ss() bounds check | tiny_free_fast.inc.h:91 | CRITICAL | Bug | | Magazine spill #1 bounds check | hakmem_tiny_free.inc:305 | CRITICAL | Bug | | Magazine spill #2 bounds check | hakmem_tiny_free.inc:464 | CRITICAL | Bug | | tiny_free_fast() call site | tiny_free_fast.inc.h:205 | HIGH | Bug | | slab_try_acquire() bounds check | slab_handle.h:32 | Info | Correct | | hak_tiny_free_superslab() bounds check | hakmem_tiny_free.inc:1164 | Info | Correct |