Conversation
… them The SRL decoder rejected streams that Windows servers send in ordinary RFX Progressive upgrade passes, and the resulting error ended the session on TCP and on the UDP tunnel alike. Three constructions tripped it: - The trailing zero byte is not always present. It is now stripped when present and not required, matching FreeRDP, whose `progressive_rfx_upgrade_state_finish` only skips it when one byte is left. - The encoder stops writing once every remaining entry of a component is zero. Bits past the end of the stream now read as zeros, as they do from the reference decoder's zero-filled bit accumulator, so the omitted entries decode as zeros, also across band boundaries. - A zero run may overshoot the entries that are left: at KP = 80 a single `0` bit adds 1024 zeros and is cheaper than an exact run. The decoder now caps the run at one component instead of rejecting it; anything past the cap could only be trailing zeros. The encoder is unchanged and still emits the terminator and exact runs. A failed upgrade pass still leaves the tile untouched; the test for that now uses a magnitude width SRL cannot represent. BREAKING CHANGE: `SrlError::MissingTerminator` and `SrlError::Truncated` are removed because the decoder no longer produces them.
|
Related: #1977 also changes the SRL decoder, in a different way. Both stop requiring the terminator, read past the end as zeros, and remove |
|
Another data point for this one: we hit the same failure independently, with Windows 11 over the graphics pipeline through the web client ( I haven't run this branch itself, but the cases it handles cover what we saw. We'll drop our local patch once this or #1977 lands, so we're not opening a competing PR. |
…#2007) Windows lists every dynamic channel it intends to move in its Soft-Sync request, including the ones the client declined with NO_LISTENER. Against a Windows 11 host the request lists channels 2, 6, 7, 8, 9, 10, 11 and 12 (CoreInput, MouseCursor, Graphics, Video, Geometry, ...), and only channel 7, the graphics pipeline, is open. `process_soft_sync_request` dropped a whole channel list as soon as one ID in it was not open. The tunnel was then never switched, and the channels the client had opened stayed on TCP while the server was already sending them on the tunnel (MS-RDPEDYC 3.2.5.3.1). Unopened channels are now skipped one by one, and the tunnel is switched for the rest. ## Testing - New `dvc::client::soft_sync_skips_channels_the_client_did_not_open` in `ironrdp-testsuite-core`. - Live, against a Windows 11 host over RDP-UDP version 2, with the viewer built from a branch that also carries the tunnel and client PRs of this series: the Soft-Sync request above now switches the tunnel, and the graphics pipeline moves onto it. ## Checks - `cargo fmt --all -- --check` - `cargo clippy --workspace --all-targets --features helper,__bench --locked -- -D warnings` - `cargo test --locked -p ironrdp-testsuite-core -p ironrdp-testsuite-extra`, plus the lib tests of the crates touched here - `cargo test --workspace --locked` on a branch that merges this PR with the other Windows interop PRs from this series - `typos` on the changed files ## Series These PRs port the Windows interop fixes and Linux backends from a downstream IronRDP fork, so the fork can be retired. Each one is based on `master` and can be reviewed and merged on its own. I also checked that all of them merge cleanly together in this order. - #2007 fix(dvc): Soft-Sync tunnel with declined channels - #2008 fix(session)!: channels and graphics on the tunnel - #2009 fix(rdpeudp): auto-detect on the tunnel - #2010 fix(graphics)!: SRL streams from Windows - #2011 fix(egfx): bitmap cache across ResetGraphics - #2012 feat(session): bandwidth measurements during the session - #2013 feat(client): graphics pipeline and RDP-UDP version options - #2014 fix(client): resize reconnects on the graphics pipeline - #2015 feat(client): transport event - #2016 feat(cliprdr): Linux clipboard backend - #2017 feat(rdpdr): printer on Linux and macOS Co-authored-by: AKolenda <testedemail2222@gmail.com>
There was a problem hiding this comment.
PR #2010 relaxes the RFX Progressive SRL decoder to accept three Windows constructions: an optional trailing zero byte, omitted trailing entries (bits past end read as zeros), and zero runs overshooting one component (capped at 4096 instead of rejected). The progressive.rs changes are test-only. The leniency is protocol-permissible and matches FreeRDP behavior, the interop motivation is credible with failing-session reports, and the tile-untouched-on-error property is preserved. All six candidates are valid: one skeptical finding about the trailing-byte strip is refined because its concrete example is arithmetically wrong, though a corrected construction confirms the underlying ambiguity; the remaining skeptical findings (silent truncation, stale doc) and all five code-compressor dead-plumbing findings are accepted as low-severity. No correctness defect in the main decode paths was found.
- [skeptical] Stripping a trailing 0x00 byte can drop a final positive max-magnitude value — medium 🟠 ❓ — crates/ironrdp-graphics/src/srl.rs
new() removes any trailing 0x00, so a terminator-less stream whose final data byte is all zeros is indistinguishable from one carrying the terminator. Bits in a stripped byte read identically to past-end zeros, so the divergence is confined to nonzero_pending = !is_exhausted() (line 107): when a zero-run codeword ends exactly at the stripped-payload boundary, a following positive max-magnitude value whose sign and unary zeros formed the stripped byte is decoded as 0 instead. Example: data [0x86, 0x00] decoded for 3 entries gives [3, 0, 0] stripped versus [3, 15, 0] with the byte kept; the doc comment's claim that a stripped zero data byte is harmless is therefore overstated. Whether Windows can emit this shape is unverifiable from the repository. - [skeptical] Arbitrary mid-stream truncation now decodes silently with no signal — low 🟡 — crates/ironrdp-graphics/src/srl.rs
The tolerance is justified for Windows omitting trailing entries, but is_exhausted() cannot distinguish an intentional early stop from corruption: truncation anywhere yields zeros, or a spurious positive maximum if a value was pending. This drops the malformed-stream detection half of#1696's guarantee while keeping only tile atomicity, so a transport bit error that ended the session now produces silent visual corruption with no log or counter. A trace/debug signal when the exhausted branch or the MAX_ZERO_RUN cap engages would partially restore observability at negligible cost. - [skeptical] decode_upgrade_pass doc still promises rejection of truncated streams — low 🟡 — crates/ironrdp-graphics/src/progressive.rs
The Errors section at line 168 says the function returns SrlError for a malformed or truncated SRL stream, but after this PR truncation decodes as zeros or positive maxima and succeeds, mutating the tile. A reader would wrongly assume truncation still fails the pass. One-line doc fix on a changed path whose central behavioral claim it contradicts. - [code-compressor] SrlDecoder::new can no longer fail; drop the Result — low 🟡 — crates/ironrdp-graphics/src/srl.rs
With MissingTerminator removed, the payload-stripping match in new() has no failing path, so it can return Self. This deletes the Ok wrapper, the propagation in decode_srl, the transpose at progressive.rs line 190, and test unwraps. The PR is already a breaking change (two error variants removed), so the signature change costs nothing extra. - [code-compressor] read_bit, read_bits, and decode_zero_run are now infallible; unwrap the Results — low 🟡 — crates/ironrdp-graphics/src/srl.rs
read_bit returns Ok(false) past the end with no error path, read_bits only forwards it, and decode_zero_run's only propagated errors came from those two, so all three can never fail. Their Result wrappers and propagation at call sites in decode and decode_nonzero are dead plumbing. The tail computation also simplifies to a plain cast: k = kp/8 <= 10, so the value fits usize without try_from/unwrap_or. All private, so no API impact. - [code-compressor] decode_nonzero's i16 conversion error branch is unreachable — low 🟡 — crates/ironrdp-graphics/src/srl.rs
max_magnitude bounds num_bits to 1..=15 so maximum is at most 32767, and the unary loop yields magnitude no greater than maximum, so magnitude always fits i16 and the try_from with MagnitudeOutOfRange cannot fail. Pre-existing code not added by this diff, so lowest priority, but it is unreachable error handling in a file whose change here removes dead error handling.
|
Verified on Windows Server 2022 (Standard, build 20348.5622) with the graphics pipeline on and no H.264 decoder. On Tested with AI assistance; results are from automated runs against the server. |
|
The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins. |
|
Pushed f47878a to address the latest review. The decoder no longer strips a final zero byte before reading coefficients, and it preserves a pending nonzero value when its sign or magnitude reaches EOF. Added the reviewed [0x86, 0x00] case, a pending value crossing a band boundary, and round trips with and without terminators. Removed the redundant exhaustion/cap logic and corrected the truncation documentation and PR description. All 245 graphics library tests, targeted Clippy with warnings denied, and formatting passed. These tests run in normal workspace CI. I did not add a corruption log: omitted trailing entries and truncation have the same zero-filled representation, so the decoder cannot reliably distinguish them. The documentation now states that limit. |
ignore this, my external review agent, idk why it activated on external PR |
|
Rechecked the failed notification against the latest commit: the normal build/test CI suite passes. The separate public API check fails before comparing changes because its fresh dependency resolution selects incompatible picky-krb 0.12.5 with sspi 0.21.3. The focused workflow repair is #2071, which builds both revisions with their committed lockfiles. It has passed a real IronRDP API build and unchanged/breaking/stale-lockfile fixtures locally. The workflow runs from the base branch, so this check needs that repair merged before a rerun can use it. |
Windows RFX Progressive upgrade streams may omit the final zero byte and trailing zero entries, or encode zero runs longer than the remaining coefficients. The decoder now accepts those streams without ending the session.
All bytes remain available until the requested coefficients are decoded. A final zero byte can contain sign or magnitude bits, so it is never stripped in advance. Zero-run events are consumed incrementally, and a pending nonzero coefficient is preserved even when its remaining bits come from the zero-filled reader at EOF. This matches FreeRDP's SRL reader; its optional trailing-byte skip happens after decoding.
The encoder is unchanged. Invalid magnitude widths still fail the upgrade pass without partially updating the tile. The decoder cannot distinguish omitted trailing entries from truncation, and the documentation now states that limitation.
Breaking changes
SrlError::MissingTerminatorandSrlError::Truncated.SrlDecoder::newreturnsSelfbecause constructing a decoder is infallible.Validation
ironrdp-graphicslibrary tests pass, including new zero-byte/EOF boundary and round-trip regressions with and without terminators.cargo clippy --locked -p ironrdp-graphics --all-targets -- -D warningsgit diff --checkpass.Part of the Windows interoperability series #2007–#2017.