[AUTOMATED] fix(p2): a Windows int 0x29 (__fastfail) ends the flow instead of unbalancing the stack - #417
Merged
Conversation
…an INT 0x29 stack unbalance, not C++ type recovery The need filed a 1,267-line MSVC checker with 165 anonymous variables, impossible conditions, raw synthetic stack writes and mangled BCrypt calls, and blamed C++ small-string type recovery. Measured on the witness, it is three independent defects and none of them is that. x86 SLEIGH lowers `INT imm8` to `intloc = swi(imm8); call [intloc]` — a call with no matching push — while x86-64-win.cspec's default __fastcall carries extrapop=8/stackshift=8, so every INT grows RSP by 8. The witness has eleven `INT 0x29` (__fastfail). Once two paths join 8 apart the frame stops being a constant offset from the spacebase, and the CALL return-address pushes survive as explicit stores. Proven twice without engine code: `--assert 'flow <addr> callreturn'` on the 11 sites gives 1267 -> 1171 lines, 63 -> 0 synthetic stores, 159 -> 144 declarations; byte-patching the 30 `MOV ECX,5 ; INT 0x29` sites to NOPs gives 1121 / 0. Across all 24 affected functions in the image: 2802 -> 2583 lines, 85 -> 0, 558 -> 493. The impossible condition does NOT fall out of that fix — it is faithful to the binary and its real home is 21 unrecovered REP STOSB loops. And the acceptance's `std::string` clause is not soundly satisfiable here: the function constructs no std::string, and std::string has no RTTI. Replacement clauses are proposed and verified in both directions. No engine code; proposal only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…0x29 (__fastfail) ends the flow x86 SLEIGH lifts `INT imm8` to `intloc = swi(imm8); call [intloc]` (ia.sinc:3671) — a `call` with no matching push, unlike every other x86 `CALL`, which lifts as `RSP = RSP - 8; push &next; call target`. Nothing downstream tells them apart, so x86-64-win.cspec's default __fastcall (extrapop="8" stackshift="8") hands eight bytes back after it and every interrupt raises the stack pointer by eight. Where two paths join carrying stack pointers eight apart, the frame stops being a constant offset from the spacebase: stack locals degenerate into offsets off a `char *` and each CALL's return-address push survives as an explicit store. On Windows `int 0x29` is __fastfail and never returns, so `option fastfailnoreturn` (default on, DIV-117) marks the call spec no-return at the P2 CALLIND seam and flow.rs plants the halt it already plants for a named no-return callee. Measured on crackmes.one 6a3822888a86e4c2c55254fe selam.exe 0x140002d50 (eleven int 0x29): 1267 -> 1147 lines, 63 -> 0 return-address stores, 160 -> 149 declarations, and BCryptGenerateSymmetricKey(*(unsigned long long *)&v66[0x50],..) becomes BCryptGenerateSymmetricKey(v52,&v55,v62,v54). Over the whole image 22 of 858 functions change, all shrinking, gotos 456 -> 390, no named call lost. Byte-identical on the 675-assertion datatest corpus and 20.5% faster on the witness. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mahaloz
force-pushed
the
feat/re-large-function-malformed-output
branch
from
September 5, 2026 21:29
8d7577c to
f3820e1
Compare
int 0x29 (__fastfail) ends the flow instead of unbalancing the stack
mahaloz
marked this pull request as ready for review
September 5, 2026 21:30
mahaloz
added a commit
that referenced
this pull request
Sep 5, 2026
The three needs that shipped in round 3 -- #417, #421, #422 -- had their acceptance probes flip from FAIL to PASS on a freshly built main at 80e965c, but the record on disk still said `status: open`. Anyone reading docs/re-needs/ (or any selector that reads it) would see three closed gaps as dispatchable work. This is the second time this edit has been applied: the first application was made in the main working tree and never committed, and it was lost when that tree was moved between branches. Committing it is the fix. Statuses, `closed_in_round` and `closing_pr` come from .kuna-repipe/rounds/3/acceptance-suite.json (sha 80e965c, 3 closed, 0 regressed) -- no verdict is re-derived here. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
A Windows
int 0x29raises the stack pointer by eight bytes, which wrecks theframe of every function containing one. Any MSVC-built PE reproduces it; here is
the smallest one, vendored in this PR:
v3is RSP tracked as a variable,&v3[-8] = 0x14000103dis thecall's ownreturn address stored through it, and the API call takes stack blobs where it
takes values. On a real 240 KB MSVC crackme (
selam.exe, function0x140002d50,eleven
int 0x29) that is 1,267 lines, 160 declarations and 63 of those stores.x86 SLEIGH lifts
INT imm8tointloc = swi(imm8); call [intloc]— acallwith no matching push, unlike every other x86
CALL, which lifts asRSP = RSP - 8; push &next; call target. Nothing downstream tells them apart, sox86-64-win.cspec's__fastcall(extrapop="8" stackshift="8") hands eightbytes back after it. Once two paths join carrying stack pointers eight apart, the
frame stops being a constant offset from the spacebase and everything above
follows.
The fix
option fastfailnoreturn(default on, DIV-119). On Windows,int 0x29is__fastfail— the MSVC/GSand STL_STL_VERIFYfailure path, whichterminates the process — so its call spec is marked no-return and
flow.rsplants the halt it already plants for a named no-return callee. The block ends
at the interrupt and the unbalanced stack pointer reaches no join.
windows/clangwindows), the1-byte constant vector
0x29, and the CALLIND having to read the storage theswiCALLOTHER wrote in the same instruction.int 0x80is a Linux syscall andint1/int3/intocarry areturnin their own semantics; none of them match.extrapopto acallwhoseinstruction did not push — is more general and needs no Windows gate, but it
changes the stack solver for every architecture and
int3genuinely doesreturn. That belongs in its own change.
"Subroutine does not return"warning: the divergence is definitional, andone function can hold a dozen sites.
The tests
tests/stages/kuna-fastfailnoreturn.xml(3 assertions, two-pass:offis thebug, default is the fix) and
tests/cli/large-function-malformed-output.jsonagainst the vendored PE32+ above; both fail without the change. On the witness
image 22 of 858 functions change, all of them shrinking, gotos 456 → 390, and no
named call is lost; across four further PEs the only non-improvement is
byte-identical output on the one with no
int 0x29.make test675/675 PARITYOK (baseline not re-pinned),
make test-stages631/631,make rust-testgreen,make check-specOK,make test-cli30/30,catalog OK. 20.5% faster on thewitness function (1,323.65 → 1,052.49 ms, 7 samples): cutting flow decodes less.
🤖 Generated with Claude Code