fix: guard Tiny FG misclass and add fg_tiny_gate box

This commit is contained in:
Moe Charm (CI)
2025-12-01 16:05:55 +09:00
parent f32d996edb
commit 2bd8da9267
4 changed files with 83 additions and 38 deletions

View File

@ -0,0 +1,46 @@
// fg_tiny_gate_box.h - Tiny Front Gate guard box
// 役割: FG_DOMAIN_TINY 判定が出た時に Superslab 登録を確認し、誤分類なら MIDCAND に切替
// 境界: Tiny 経路へ入る前に 1 箇所でのみ実行する
#ifndef HAK_BOX_FG_TINY_GATE_H
#define HAK_BOX_FG_TINY_GATE_H
#include "../hakmem_super_registry.h" // hak_super_lookup / SUPERSLAB_MAGIC
#include "front_gate_v2.h"
#include <stdatomic.h>
#include <stdio.h>
typedef struct {
fg_classification_t fg;
int misclassified; // 1 when Tiny→MIDCAND に落とした
} fg_tiny_gate_result_t;
// 簡易ログ(初回のみ)
static inline void fg_tiny_gate_log(void* ptr, uint8_t hdr, uintptr_t off, int use_ss, int reg_init) {
static _Atomic int log_budget = 4;
int n = atomic_fetch_add_explicit(&log_budget, 1, memory_order_relaxed);
if (n >= 4) return;
fprintf(stderr,
"[FG_TINY_MISCLASS] ptr=%p hdr=0x%02x off=0x%lx g_use_ss=%d reg_init=%d → reroute to MIDCAND\n",
ptr, hdr, (unsigned long)off, use_ss, reg_init);
}
// Tiny判定のガード: Superslab登録が無ければ MIDCAND に変換
static inline fg_tiny_gate_result_t fg_tiny_gate(void* ptr, fg_classification_t fg) {
fg_tiny_gate_result_t r = {.fg = fg, .misclassified = 0};
if (fg.domain != FG_DOMAIN_TINY) return r;
SuperSlab* fg_ss = hak_super_lookup(ptr);
if (__builtin_expect(!(fg_ss && fg_ss->magic == SUPERSLAB_MAGIC), 0)) {
extern int g_use_superslab;
extern int g_super_reg_initialized;
uint8_t hdrb = 0;
if ((uintptr_t)ptr > 0) { hdrb = *((uint8_t*)ptr - 1); }
uintptr_t off = (uintptr_t)ptr & 0xFFFu;
fg_tiny_gate_log(ptr, hdrb, off, g_use_superslab, g_super_reg_initialized);
r.fg.domain = FG_DOMAIN_MIDCAND;
r.misclassified = 1;
}
return r;
}
#endif // HAK_BOX_FG_TINY_GATE_H

View File

@ -239,6 +239,8 @@ inline void* hak_alloc_at(size_t size, hak_callsite_t site) {
if (hdr->magic != HAKMEM_MAGIC) { fprintf(stderr, "[hakmem] ERROR: Invalid magic in allocated header!\n"); return ptr; }
hdr->alloc_site = site_id;
hdr->class_bytes = (size >= threshold) ? threshold : 0;
// Guard byte for FrontGate V2: force ptr[-1] away from 0xA?/0xB? to avoid Tiny misclass
((uint8_t*)hdr)[HEADER_SIZE - 1] = HAKMEM_FG_GUARD_BYTE;
#if HAKMEM_DEBUG_TIMING
HKM_TIME_END(HKM_CAT_HAK_ALLOC, t0);

View File

@ -9,6 +9,7 @@
#include "../ptr_trace.h" // Debug: pointer trace immediate dump on libc fallback
#include "front_gate_v2.h" // Phase 15: Box FG V2 - 1-byte header classification
#include "external_guard_box.h" // Phase 15: Box ExternalGuard - mincore (ENV controlled)
#include "fg_tiny_gate_box.h" // Tiny gate guard box (Superslab check)
#ifdef HAKMEM_POOL_TLS_PHASE1
#include "../pool_tls.h"
@ -82,6 +83,7 @@ void hak_free_at(void* ptr, size_t size, hak_callsite_t site) {
HKM_TIME_START(t0);
#endif
(void)site; (void)size;
int fg_misclass = 0; // Set when FG said Tiny but registry rejects
// Optional lightweight trace of early free calls (first few only)
#if !HAKMEM_BUILD_RELEASE
static int free_trace_en = -1; static _Atomic int free_trace_count = 0;
@ -129,6 +131,11 @@ void hak_free_at(void* ptr, size_t size, hak_callsite_t site) {
fg_classification_t fg = fg_classify_domain(ptr);
hak_free_route_log(fg_domain_name(fg.domain), ptr);
// Fail-Fast: Tiny判定は Superslab 登録が必須。無ければ MIDCAND に戻す(箱化)。
fg_tiny_gate_result_t fg_guard = fg_tiny_gate(ptr, fg);
fg = fg_guard.fg;
fg_misclass = fg_guard.misclassified;
switch (fg.domain) {
case FG_DOMAIN_TINY: {
// Fast path: Tiny (C0-C7) with 1-byte header (0xa0 | class_idx)
@ -199,18 +206,6 @@ void hak_free_at(void* ptr, size_t size, hak_callsite_t site) {
}
}
// ========== Box ExternalGuard: Last Resort ==========
// PHASE 15: Delegate to ExternalGuard (mincore + libc fallback)
// Expected: Called 0-10 times in bench (if >100 → box leak!)
{
if (external_guard_try_free(ptr)) {
goto done;
}
// ExternalGuard failed (unmapped) → skip free (leak)
hak_free_route_log("external_guard_skip", ptr);
goto done;
}
// Raw header dispatchmmap/malloc/BigCacheなど
{
void* raw = (char*)ptr - HEADER_SIZE;
@ -232,14 +227,7 @@ void hak_free_at(void* ptr, size_t size, hak_callsite_t site) {
if (!is_mapped) {
// Memory not accessible, ptr likely has no header
hak_free_route_log("unmapped_header_fallback", ptr);
// In direct-link mode, try tiny_free (handles headerless Tiny allocs)
if (!g_ldpreload_mode && g_invalid_free_mode) {
hak_tiny_free(ptr);
goto done;
}
// LD_PRELOAD mode: route to libc (might be libc allocation)
// Always punt to libc; never route unmapped/unknown pointers to Tiny
extern void __libc_free(void*);
ptr_trace_dump_now("free_api_libc_invalid_hdr");
__libc_free(ptr);
@ -259,31 +247,32 @@ void hak_free_at(void* ptr, size_t size, hak_callsite_t site) {
// One-shot request-trace to help diagnose SS registry lookups
hak_super_reg_reqtrace_dump(ptr);
// In direct-link mode, try routing to tiny free as best-effort recovery
// This handles case #1 where SuperSlab lookup failed but allocation is valid
if (!g_ldpreload_mode && g_invalid_free_mode) {
// Attempt tiny free (will validate internally and handle gracefully if invalid)
hak_free_route_log("invalid_magic_tiny_recovery", ptr);
hak_tiny_free(ptr);
goto done;
// Fail-fast diagnostics: never hand bad headers to Tiny or libc silently
SuperSlab* ss_diag = hak_super_lookup(ptr);
int slab_diag = ss_diag ? slab_index_for(ss_diag, ptr) : -1;
fprintf(stderr,
"[INVALID_MAGIC_FREE] ptr=%p magic=0x%X mode=%d ss=%p slab=%d\n",
ptr, hdr->magic, g_invalid_free_mode, (void*)ss_diag, slab_diag);
tiny_guard_on_invalid(ptr, hdr->magic);
// If this pointer was a misclassified Tiny header miss, punt to libc to avoid corrupting TLS
if (fg_misclass) {
fprintf(stderr, "[FREE_MISCLASS_SKIP] ptr=%p hdr=0x%x (ignored to avoid corruption)\n",
ptr, hdr->magic);
goto done; // leak-safe skip: not our allocation
}
// LD_PRELOAD mode or fallback mode: route to libc
// IMPORTANT: Use ptr (not raw), as NO header exists
// Never route invalid headers into Tiny; fail-fast by default
if (g_invalid_free_mode) {
// Skip mode: leak memory (original behavior, but logged)
static int leak_warn = 0;
if (!leak_warn) {
fprintf(stderr, "[hakmem] WARNING: Skipping free of invalid pointer %p (may leak memory)\n", ptr);
leak_warn = 1;
}
goto done;
abort();
} else {
// Fallback mode: route to libc
extern void __libc_free(void*);
ptr_trace_dump_now("free_api_libc_invalid_magic_fallback");
__libc_free(ptr); // Use ptr, not raw!
goto done;
ptr_trace_dump_now("free_api_invalid_magic_failfast");
abort();
}
}
// Phase 5-Step3: Use Mid/Large Config Box (compile-time constant in PGO mode)
@ -305,6 +294,8 @@ void hak_free_at(void* ptr, size_t size, hak_callsite_t site) {
hak_free_route_log("malloc_hdr", ptr);
extern void __libc_free(void*);
ptr_trace_dump_now("free_api_libc_malloc_hdr");
fprintf(stderr, "[FREE_LIBC_HDR] raw=%p user=%p size=%zu method=%d magic=0x%X\n",
raw, ptr, hdr->size, (int)hdr->method, hdr->magic);
__libc_free(raw);
break;
case ALLOC_METHOD_MMAP:

View File

@ -86,6 +86,7 @@ extern int g_ldpreload_mode;
#define HAKMEM_MAGIC 0x48414B4D // "HAKM" in ASCII (uint32_t)
#define HEADER_SIZE sizeof(AllocHeader)
#define HAKMEM_FG_GUARD_BYTE 0x5Au // Ensure front gate never misclassifies mid/large as Tiny
// THP thresholds (from config)
#define THP_THRESHOLD (2 * 1024 * 1024) // 2MB
@ -114,6 +115,9 @@ typedef struct {
uintptr_t owner_tid; // Owning thread (for Mid/Tiny per-thread fast path). 0 if unknown
} AllocHeader;
// Phase 10: Pointer Type Safety
#include "box/ptr_type_box.h"
typedef enum {
FREE_THERMAL_HOT, // すぐ再利用 → 何もしないKEEP
FREE_THERMAL_WARM, // 中間 → MADV_FREEmunmapしない
@ -392,7 +396,8 @@ static inline void hak_header_set_class(void* user_ptr, size_t class_bytes) {
// Args: raw - pointer to raw allocation (including header)
static inline void hak_free_malloc_impl(void* raw) {
if (!raw) return; // Safety check
free(raw);
extern void __libc_free(void*);
__libc_free(raw);
}
// Free mmap-allocated block
@ -403,7 +408,8 @@ static inline void hak_free_mmap_impl(void* raw, size_t size) {
#ifdef __linux__
munmap(raw, size);
#else
free(raw); // Fallback on non-Linux
extern void __libc_free(void*);
__libc_free(raw); // Fallback on non-Linux
#endif
}