Skip to content

fix(decompiler): cap consecutive empty v1 frames in FrameInStreambuf#459

Open
CryptoJones wants to merge 1 commit into
masterfrom
fix/449-frame-empty-cap
Open

fix(decompiler): cap consecutive empty v1 frames in FrameInStreambuf#459
CryptoJones wants to merge 1 commit into
masterfrom
fix/449-frame-empty-cap

Conversation

@CryptoJones

Copy link
Copy Markdown
Owner

Closes #449.

FrameInStreambuf::underflow() skipped empty-payload frames in an unbounded for(;;) loop. A peer sending an endless run of zero-length v1 frames keeps the worker spinning in underflow() — never delivering a byte, never reaching EOF. That's a CPU-spin DoS against the native decompiler worker.

(Severity note from the original review: this is localhost IPC between Ghidra and its own decompiler child process, not a network-facing service — the threat model is a misbehaving/compromised sibling process, not a remote attacker. Still worth bounding.)

Change

  • New constant frame_v1::MAX_CONSECUTIVE_EMPTY_FRAMES = 16.
  • New Error::TOO_MANY_EMPTY_FRAMES.
  • underflow() counts consecutive empties per call and, when the run exceeds the cap, ends the stream (EOF) with lastError = TOO_MANY_EMPTY_FRAMES. The counter is correctly per-underflow(): a non-empty frame returns from the call, so all consecutive empties are always consumed within one invocation.

Tests

Two new unit tests in testframe_v1.cc:

  • frame_v1_tunnel_in_skips_max_empty_frames_then_delivers — exactly CAP empties still skip through to the real frame.
  • frame_v1_tunnel_in_caps_empty_frame_flood — CAP+1 empties → EOF + TOO_MANY_EMPTY_FRAMES, real frame never reached.

The existing frame_v1_tunnel_in_skips_empty_payload_frame (single empty) still holds.

Verification

The repo's decomp_test_dbg links -lbfd, which isn't available on this macOS host (the same reason CI's C++ tests are Ubuntu-only — see #447). So I compiled the actual edited frame_v1.cc + crc32.cc into a standalone driver (no BFD, no decompiler lib) exercising the real FrameInStreambuf:

MAX_CONSECUTIVE_EMPTY_FRAMES = 16
case1 (==CAP empties): delivered 'Z', OK  PASS
case2 (CAP+1 empties): EOF + TOO_MANY_EMPTY_FRAMES  PASS
case3 (1 empty): delivered 'Z', OK  PASS
ALL PASS

The TEST()-macro versions run under make decomp_test_dbg in CI on Ubuntu. No switch over frame_v1::Error exists, so the new enum value can't break -Wswitch.

🤖 Generated with Claude Code

…449)

FrameInStreambuf::underflow() skipped empty-payload frames in an
unbounded loop. A peer sending an endless run of zero-length frames
kept the worker spinning in underflow() — never delivering a byte,
never reaching EOF — a CPU-spin DoS.

Cap the run at MAX_CONSECUTIVE_EMPTY_FRAMES (16) per underflow() call;
exceeding it ends the stream with the new Error::TOO_MANY_EMPTY_FRAMES,
retrievable via getLastError(). The counter is per-underflow, which is
exactly the span over which empties are consecutive (a non-empty frame
returns from underflow and resets the count naturally).

Adds unit tests for the at-cap (still delivers) and over-cap (stops)
boundaries. Verified by compiling frame_v1.cc against a standalone
driver on macOS (no BFD): ==CAP delivers, CAP+1 -> TOO_MANY_EMPTY_FRAMES.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the lane:decomp-correctness Wrong-output decompiler fix; 3-business-day SLA. label Jun 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lane:decomp-correctness Wrong-output decompiler fix; 3-business-day SLA.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

frame_v1: FrameInStreambuf::underflow() loops forever on empty v1 frames

1 participant