Skip to content

[AUTOMATED] fix(p2,cli): checker-exceeds-instruction-ceiling — a function past the instruction budget yields a truncated body, not nothing - #422

Merged
mahaloz merged 1 commit into
mainfrom
feat/re-checker-exceeds-instruction-ceiling
Sep 5, 2026
Merged

[AUTOMATED] fix(p2,cli): checker-exceeds-instruction-ceiling — a function past the instruction budget yields a truncated body, not nothing#422
mahaloz merged 1 commit into
mainfrom
feat/re-checker-exceeds-instruction-ceiling

Conversation

@mahaloz

@mahaloz mahaloz commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

The problem

A function whose flow exceeds kuna's 100000-instruction budget decompiled to
nothing at all, and the error named no way out. Any in-repo fixture reproduces it
once the budget is small enough to reach:

$ kuna decompile-all decompiler/crates/kuna-analysis/tests/fixtures/midstring_x86_64 \
      --functions main --option maxinstruction 5 --json
      "size": 48,
      "code": null,
      "error": "Flow exceeded maximum allowable instructions",

The budget has always had a second policy — truncate instead of throw — and it did
not truncate. It planted an artificial halt at the overrun address and then decoded
that instruction anyway, so the next address was queued and the walk carried on to
the end of the reachable body, now with one halt per instruction:

$ kuna decompile decompiler/crates/kuna-analysis/tests/fixtures/fauxware main \
      --option maxinstruction 5 --option errortoomanyinstructions off
void main(char *a0,char *a1)
{
  ...
    *(unsigned long *)&v2[-0x50] = 0x40073e;
    puts("Username: ",v2[-0x50]);
    return;
    return;

The budget value does not change that outcome, which is the tell. On the 7 MB MBA
crackme this need came from (crackmes.one 69d6affb110488a3205426e2), whose
sub_140001000 is one genuine 1,804,480-instruction function, the truncating arm
died at a failed 11.5 GB allocation after 38 s at maxinstruction 2000 and at
5,000,000 alike.

The fix

  • FlowInfo::process_instruction reports no fall-through at the budget after
    planting the halt, so the walk stops there. Every address still queued is halted
    the same way when it is popped; the decode ends at the budget instead of at the
    end of the body.
  • The halt is registered in visited as the instruction at that address, so a
    branch arriving there later resolves to it rather than raising "Could not find op
    at target address", and it starts a basic block — the lesson [AUTOMATED] fix(p2): funcboundflow's truncation must start a basic block #410 learned for the
    funcboundflow truncation.
  • With a truncation that truncates, the CLI's decompiling surfaces can afford it and
    take it (errortoomanyinstructions off, DIV-119). An inventory or query load never
    follows flow and does not; the engine, console and datatest defaults are unchanged,
    which is why neither parity corpus can move.
  • The warning header names both knobs, because an error that names no remedy is what
    sent the reporter through kuna catalog looking for an option that was never
    there: --option maxinstruction N raises the budget, --option errortoomanyinstructions on makes the overrun fatal again.

The witness now decompiles: 10,164 lines of C, error: null, 37.3 s and 3.76 GB for
the 100000 instructions the budget allows.

The tests

tests/cli/checker-exceeds-instruction-ceiling.json (the promoted acceptance,
restated on a 16 KB in-repo fixture because CI has no dataset) and a two-pass
kuna-cli integration test: --option errortoomanyinstructions on must still give
code: null and the old error. decompile_e2e.rs now asserts the bounded run emits
less C than the unbounded one — it did not before. decompile-all over all 156
analysis fixtures is byte-identical on both arms.

Gates: make test PARITY OK 675/675, make test-stages PARITY OK, make rust-test
green (5,596 passed / 0 failed), make check-spec OK, make test-cli 32/32, kuna catalog --check OK.

🤖 Generated with Claude Code

@mahaloz mahaloz added the full-ci Run the full cargo workspace suite on this PR before merge (internal PRs skip it by default) label Sep 5, 2026
…tion past the instruction budget yields a truncated body, not nothing

Overrunning the 100000-instruction flow budget returned `code: null` and an
error naming no remedy, so an agent decompiling a large obfuscated function got
nothing and no way to ask for more. The budget already had a truncating policy;
it did not truncate. `FlowInfo::process_instruction` planted a `badinstruction`
artificial halt at the overrun address and then decoded that instruction anyway
(faithful to upstream flow.cc:396), so the next address was queued and the walk
carried on to the end of the reachable body with one halt per instruction — an
out-of-memory abort on the 1.8M-instruction MBA checker this need came from, at
`maxinstruction` 2000 and 5,000,000 alike.

The branch now reports no fall-through after planting the halt, registers that
halt in `visited` as the instruction at the address so a later branch resolves
to it, and marks it a basic-block and instruction start. With a truncation that
truncates, the CLI's decompiling surfaces take it (DIV-118); the engine, console
and datatest defaults keep upstream's throw. The warning header names both knobs.

Closes the round-3 RE-friction need `checker-exceeds-instruction-ceiling`; the
acceptance probe is promoted to tests/cli/ against an in-repo fixture.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mahaloz
mahaloz force-pushed the feat/re-checker-exceeds-instruction-ceiling branch from 9953b9a to c5e0845 Compare September 5, 2026 22:11
@mahaloz
mahaloz merged commit 80e965c into main Sep 5, 2026
9 checks passed
@mahaloz
mahaloz deleted the feat/re-checker-exceeds-instruction-ceiling branch September 5, 2026 22:24
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-ci Run the full cargo workspace suite on this PR before merge (internal PRs skip it by default)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant