spec+plan: the arg-width validator — check a declared shape against the callee's machine code (#83)
* spec+plan: the arg-width validator — check a declared shape against the callee's machine code A hook descriptor names a shape (the callee's exact C signature) and nothing checks that name against the function it is about to detour. check-hook-shapes.sh proves core and the shim agree with EACH OTHER; on TerminateRound both were confidently wrong together, and the only thing that noticed was a SIGSEGV on a live server. The failure is asymmetric, and that asymmetry is the whole design: declaring a param NARROWER than the engine's truncates a pointer (a memory-safety bug that detonates far from the hook), while declaring it WIDER is harmless, because SysV leaves the upper half of a 32-bit arg undefined. So there is exactly one direction to police and it is mechanically detectable — a callee tells you how wide an argument is by how it stores it. `mov %rdx,-0xe0(%rbp)` is 64-bit; `mov %esi,%r15d` is not. Scoped deliberately narrow: only MOV r/m,r with a memory destination, only integer args, absence of evidence is a PASS. A validator that failed on "I could not tell" would refuse most functions and be switched off within a week. It is a smoke detector for the class that burned us, and the spec says so rather than letting a pass be over-read. Takes NO gamedata — the expectation is derived from the shape the descriptor already declares. The bug this prevents was a hand-written ABI claim drifting from reality; checking it with a SECOND hand-written ABI claim would be the same mistake with more steps. Not a place SourceMod did better: SM's detour signatures are C++ macros a human wrote while reading the disassembly. Making the shape data removed that human step, and this buys the safety back. * spec: record the three review corrections — terminators, the ABI table, and the OOB 10.1 the kVocabulary line contradicted 'takes no gamedata'; 10.2 the scan had to stop at the callee's own ret/jmp/call (490 of 3497 corpus refusals came from walking into the next function); 10.3 the widths had to come from the shape's ABI, not its addressable params, which made the kParamI64 branch dead code. Also records the OOB in the bounded-window copy, why refusal is kept over warn-and-log now that the false-fail classes are fixed, and the guards that remain untested.
G
Gabriel Hirakawa committed
1b442d86f0759ee257ff02c8f27e100c688c0717
Parent: abe9c83
Committed by GitHub <noreply@github.com>
on 8/3/2026, 10:51:41 PM