Files
hakmem/SLAB_INDEX_FOR_INVESTIGATION.md

490 lines
14 KiB
Markdown
Raw Normal View History

CRITICAL FIX: TLS 未初期化による 4T SEGV を完全解消 **問題:** - Larson 4T で 100% SEGV (1T は 2.09M ops/s で完走) - System/mimalloc は 4T で 33.52M ops/s 正常動作 - SS OFF + Remote OFF でも 4T で SEGV **根本原因: (Task agent ultrathink 調査結果)** ``` CRASH: mov (%r15),%r13 R15 = 0x6261 ← ASCII "ba" (ゴミ値、未初期化TLS) ``` Worker スレッドの TLS 変数が未初期化: - `__thread void* g_tls_sll_head[TINY_NUM_CLASSES];` ← 初期化なし - pthread_create() で生成されたスレッドでゼロ初期化されない - NULL チェックが通過 (0x6261 != NULL) → dereference → SEGV **修正内容:** 全 TLS 配列に明示的初期化子 `= {0}` を追加: 1. **core/hakmem_tiny.c:** - `g_tls_sll_head[TINY_NUM_CLASSES] = {0}` - `g_tls_sll_count[TINY_NUM_CLASSES] = {0}` - `g_tls_live_ss[TINY_NUM_CLASSES] = {0}` - `g_tls_bcur[TINY_NUM_CLASSES] = {0}` - `g_tls_bend[TINY_NUM_CLASSES] = {0}` 2. **core/tiny_fastcache.c:** - `g_tiny_fast_cache[TINY_FAST_CLASS_COUNT] = {0}` - `g_tiny_fast_count[TINY_FAST_CLASS_COUNT] = {0}` - `g_tiny_fast_free_head[TINY_FAST_CLASS_COUNT] = {0}` - `g_tiny_fast_free_count[TINY_FAST_CLASS_COUNT] = {0}` 3. **core/hakmem_tiny_magazine.c:** - `g_tls_mags[TINY_NUM_CLASSES] = {0}` 4. **core/tiny_sticky.c:** - `g_tls_sticky_ss[TINY_NUM_CLASSES][TINY_STICKY_RING] = {0}` - `g_tls_sticky_idx[TINY_NUM_CLASSES][TINY_STICKY_RING] = {0}` - `g_tls_sticky_pos[TINY_NUM_CLASSES] = {0}` **効果:** ``` Before: 1T: 2.09M ✅ | 4T: SEGV 💀 After: 1T: 2.41M ✅ | 4T: 4.19M ✅ (+15% 1T, SEGV解消) ``` **テスト:** ```bash # 1 thread: 完走 ./larson_hakmem 2 8 128 1024 1 12345 1 → Throughput = 2,407,597 ops/s ✅ # 4 threads: 完走(以前は SEGV) ./larson_hakmem 2 8 128 1024 1 12345 4 → Throughput = 4,192,155 ops/s ✅ ``` **調査協力:** Task agent (ultrathink mode) による完璧な根本原因特定 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 01:27:04 +09:00
# 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 |