Skip to content

feat(fuzz): add egfx PDU decoders to pdu_decode oracle + fix two surfaced bugs#1271

Open
Greg Lamberson (glamberson) wants to merge 3 commits into
Devolutions:masterfrom
lamco-admin:feat/ironrdp-egfx-fuzz-target
Open

feat(fuzz): add egfx PDU decoders to pdu_decode oracle + fix two surfaced bugs#1271
Greg Lamberson (glamberson) wants to merge 3 commits into
Devolutions:masterfrom
lamco-admin:feat/ironrdp-egfx-fuzz-target

Conversation

@glamberson
Copy link
Copy Markdown
Contributor

@glamberson Greg Lamberson (glamberson) commented May 13, 2026

Summary

Extends the existing pdu_decode oracle in ironrdp-fuzzing::oracles with EGFX coverage, satisfying the Core Tier "must be fuzzed" invariant (per ARCHITECTURE.md) for the graphics-pipeline DVC parsing. Two pre-existing bugs in ironrdp-egfx's Avc444BitmapStream / Avc420BitmapStream decoders that the new coverage immediately surfaced are fixed in the same series.

What the oracle exercises

The EGFX decode list added to pdu_decode runs the input through:

  • GfxPdu (umbrella enum covering all 23 EGFX message types — primary entry point)
  • CacheToSurfacePdu (sole sub-PDU with an independent Decode impl)
  • EgfxCapabilitySet (aliased to disambiguate against capability_sets::CapabilitySet from ironrdp-pdu)
  • Avc420BitmapStream<'_> and Avc444BitmapStream<'_>
  • QuantQuality, Point, Color

Shape

Folded into the existing pdu_decode oracle in c5211e46 per your suggestion on the original filing ("I think it's fine to add this directly to pdu_decode, so we don't have an extra target for essentially the same logic"). No separate egfx_pdu_decoding.rs target / [[bin]] entry; the EGFX decoders sit alongside the existing PDU decoders in the same oracle and share the corpus.

Bug fixes surfaced by the new coverage

Both pre-existing in ironrdp-egfx; surfaced by the EGFX decode additions before this PR could merge cleanly.

  • f96ba08e fix(egfx): bound stream_len before split_at in Avc444BitmapStream: Avc444BitmapStream::decode read a 30-bit streamLen field and called src.split_at(stream_len as usize) without bounds-checking. ReadCursor::split_at panics on offset overflow. Fixed with ensure_size!(ctx: Self::NAME, in: src, size: stream_len) before the split, so a malformed streamLen returns NotEnoughBytes instead of panicking.
  • f741fb26 fix(egfx): cap Avc420BitmapStream pre-allocation against remaining bytes: Avc420BitmapStream::decode read a u32 num_regions and called Vec::with_capacity(num_regions as usize) twice without bounding. ASan tripped on a ~33 GB allocation for num_regions = 0xF7_00_AC_7E. Fixed by capping with_capacity at src.len() / (InclusiveRectangle::FIXED_PART_SIZE + QuantQuality::FIXED_PART_SIZE), matching the bounded-pre-allocation pattern in cliprdr/format_data/file_list.rs. The actual decode loop is unchanged.

Verification

  • cargo xtask check fmt
  • cargo xtask check lints
  • cargo xtask check locks
  • cargo xtask check typos
  • cargo xtask check tests
  • cargo check --manifest-path fuzz/Cargo.toml builds the pdu_decoding target cleanly
  • CI on f741fb26: all 9 jobs green including Fuzzing 5m21s (the same 5-minute fuzz budget that previously surfaced both bugs now passes clean)

Refs #1120.

Copy link
Copy Markdown
Member

@CBenoit Benoît Cortier (CBenoit) left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! That was a blind spot

Comment thread crates/ironrdp-fuzzing/src/oracles/mod.rs Outdated
Copy link
Copy Markdown
Member

@CBenoit Benoît Cortier (CBenoit) left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 5 changed files in this pull request and generated 1 comment.

Comment thread crates/ironrdp-fuzzing/src/oracles/mod.rs
@glamberson Greg Lamberson (glamberson) changed the title feat(fuzz): add egfx PDU decoder fuzz target feat(fuzz): add egfx PDU decoders to pdu_decode oracle + fix two surfaced bugs May 15, 2026
Adds eight egfx decode calls to the existing `pdu_decode` oracle:
`GfxPdu`, `CacheToSurfacePdu`, `CapabilitySet`, `Avc420BitmapStream`,
`Avc444BitmapStream`, `QuantQuality`, `Point`, `Color`.

Folds into `pdu_decode` rather than a separate target per maintainer
suggestion, since the byte-stream oracle pattern is identical.

`ironrdp-egfx` was the only Core-Tier-by-behaviour crate without
dedicated fuzz coverage of its PDU decoders.
The Avc444BitmapStream decoder reads a 30-bit stream length from the
header and then calls src.split_at(stream_len as usize). ReadCursor's
split_at panics when the requested offset exceeds the remaining buffer,
which crashes the decoder on malformed input where the declared
streamLen field is larger than the actual payload.

Adds an ensure_size! check before split_at so the same condition
returns a NotEnoughBytes error through DecodeResult instead of
panicking. Surfaced by the egfx fuzz coverage added in the previous
commit on this branch.
Avc420BitmapStream::decode reads a u32 num_regions and called
Vec::with_capacity(num_regions as usize) twice without bounding the
value. A fuzzer-controlled num_regions near u32::MAX caused the
allocator to attempt several gigabytes per call, which ASan trapped
as out-of-memory in the new pdu_decode egfx coverage.

Caps the with_capacity argument at src.len() / per-region size, where
per-region is the minimum decode footprint (InclusiveRectangle plus
QuantQuality). The actual read loop is unchanged and still returns
NotEnoughBytes via the inner Decode impls if num_regions does not
match the actual payload, so the only behavioural change is bounded
pre-allocation. Surfaced by the egfx fuzz coverage added earlier on
this branch.
@glamberson Greg Lamberson (glamberson) force-pushed the feat/ironrdp-egfx-fuzz-target branch from f741fb2 to 1d11c95 Compare May 15, 2026 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants