Skip to content

refactor(pdu): replace unreachable!() in capability_sets/ with exhaustive matching#1328

Open
Greg Lamberson (glamberson) wants to merge 1 commit into
Devolutions:masterfrom
lamco-admin:refactor/capability-sets-exhaustive-encoder
Open

refactor(pdu): replace unreachable!() in capability_sets/ with exhaustive matching#1328
Greg Lamberson (glamberson) wants to merge 1 commit into
Devolutions:masterfrom
lamco-admin:refactor/capability-sets-exhaustive-encoder

Conversation

@glamberson
Copy link
Copy Markdown
Contributor

Two _ => unreachable!() sites in crates/ironrdp-pdu/src/rdp/capability_sets/
become compile-time exhaustive matches as part of the audit pass on issue #1314.
Both sites are class (a) (provably unreachable in current code); the
hardening turns a future regression from a runtime panic into a compile error.

mod.rs:448 (inner match in CapabilitySet::Encode::encode's raw-buffer
fallback): the _ => unreachable!() catch-all is replaced with an explicit
enumeration of the 17 structured CapabilitySet variants that are already
handled by the outer match's specific arms. The new structured-variants arm
keeps unreachable!() as its body because it is unreachable under the
current outer-match structure. The change is that adding a new variant to
CapabilitySet is now a compile error here until the new variant is routed
in this Encode impl. PR #1313 (BitmapCacheV3 encoder unreachable!()
reached on decoder-accepted input) demonstrated why a runtime catch-all is
the wrong shape for this match.

bitmap_codecs/mod.rs:284 (nested match in CodecProperty decode for
GUID_REMOTEFX | GUID_IMAGE_REMOTEFX): the inner match guid { ... _ => unreachable!() } carried a dead arm because the outer match already
guaranteed guid was one of the two values. Replaced with an if guid == GUID_REMOTEFX { ... } else { ... }, eliminating the unreachable branch
entirely.

Neither site was a class (b) (reachable via fuzz) or class (c) (reachable
via attacker-controlled wire inputs) per the static analysis and empirical
fuzz evidence (pdu_round_trip from PR #1291 and message_decoding_invariants
from PR #1320 have exercised CapabilitySet and ShareControlHeader for
millions of iterations post-#1313 with no panics).

Refs #1314.

…tive matching

Two `_ => unreachable!()` sites in `crates/ironrdp-pdu/src/rdp/capability_sets/`
become compile-time exhaustive matches as part of the audit pass on issue Devolutions#1314.
Both sites are class (a) (provably unreachable in current code); the
hardening turns a future regression from a runtime panic into a compile error.

`mod.rs:448` (inner match in `CapabilitySet::Encode::encode`'s raw-buffer
fallback): the `_ => unreachable!()` catch-all is replaced with an explicit
enumeration of the 17 structured `CapabilitySet` variants that are already
handled by the outer match's specific arms. The new structured-variants arm
keeps `unreachable!()` as its body because it is unreachable under the
current outer-match structure. The change is that adding a new variant to
`CapabilitySet` is now a compile error here until the new variant is routed
in this `Encode` impl. PR Devolutions#1313 (BitmapCacheV3 encoder `unreachable!()`
reached on decoder-accepted input) demonstrated why a runtime catch-all is
the wrong shape for this match.

`bitmap_codecs/mod.rs:284` (nested match in `CodecProperty` decode for
`GUID_REMOTEFX | GUID_IMAGE_REMOTEFX`): the inner `match guid { ... _ =>
unreachable!() }` carried a dead arm because the outer match already
guaranteed `guid` was one of the two values. Replaced with an `if guid ==
GUID_REMOTEFX { ... } else { ... }`, eliminating the unreachable branch
entirely.

Neither site was a class (b) (reachable via fuzz) or class (c) (reachable
via attacker-controlled wire inputs) per the static analysis and empirical
fuzz evidence (`pdu_round_trip` from PR Devolutions#1291 and `message_decoding_invariants`
from PR Devolutions#1320 have exercised `CapabilitySet` and `ShareControlHeader` for
millions of iterations post-Devolutions#1313 with no panics).

Refs Devolutions#1314.
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

This PR is part of the ironrdp-pdu audit work (issue #1314) to remove runtime unreachable!() catch-alls in capability-set code paths and replace them with structures that either become compile-time exhaustive (for enum variants) or remove provably-dead branches (for GUID-discriminated decoding). The goal is to turn future regressions from runtime panics into compile errors or eliminate unreachable branches entirely.

Changes:

  • In CapabilitySet::encode, replaced an inner _ => unreachable!() with an explicit list of the structured CapabilitySet variants, so adding a new enum variant forces this site to be updated at compile time.
  • In Codec decoding for GUID_REMOTEFX | GUID_IMAGE_REMOTEFX, removed a dead nested match ... _ => unreachable!() and replaced it with an if/else based on the already-constrained guid.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
crates/ironrdp-pdu/src/rdp/capability_sets/mod.rs Makes the raw-buffer fallback’s inner match exhaustive w.r.t. CapabilitySet variants, preventing future missing-variant bugs from compiling.
crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs/mod.rs Removes an unreachable nested match arm in RemoteFX property decoding by leveraging the outer match’s constraint on guid.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines -281 to 288
match guid {
GUID_REMOTEFX => CodecProperty::RemoteFx(property),
GUID_IMAGE_REMOTEFX => CodecProperty::ImageRemoteFx(property),
_ => unreachable!(),
// `guid` is `GUID_REMOTEFX` or `GUID_IMAGE_REMOTEFX` by the
// outer match arm; the previous nested `match` plus
// `_ => unreachable!()` carried a dead arm.
if guid == GUID_REMOTEFX {
CodecProperty::RemoteFx(property)
} else {
CodecProperty::ImageRemoteFx(property)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thought: I would keep the unreachable!() here rather than fold it into an if/else. guid is already validated as GUID_REMOTEFX | GUID_IMAGE_REMOTEFX by the outer arm, so the _ branch is genuinely dead -- and it's because it should be dead that unreachable!() is the right tool: it's a redundant correctness check that fires loudly under tests/fuzzing if a future change (e.g. a third GUID added to the outer arm) breaks the invariant. The else turns that into a silent mislabel instead. Note this branch isn't reachable from the wire, only from a logic regression, so the usual "no panics in decoders" rule doesn't apply.

Optionally add a message to document the invariant inline: unreachable!("guid validated as RemoteFX or ImageRemoteFX by the outer match").

The other exhaustive-match changes in the PR look good to me.

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