Skip to content

fix(graphics)!: decode RFX Progressive SRL streams as Windows encodes them - #2010

Open
AKolenda wants to merge 2 commits into
Devolutions:masterfrom
AKolenda:fix/srl-windows-streams
Open

AKolenda wants to merge 2 commits into
Devolutions:masterfrom
AKolenda:fix/srl-windows-streams

Conversation

@AKolenda

@AKolenda AKolenda commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

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

  • Remove SrlError::MissingTerminator and SrlError::Truncated.
  • SrlDecoder::new returns Self because constructing a decoder is infallible.

Validation

  • All 245 ironrdp-graphics library 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 warnings
  • Formatting and git diff --check pass.

Part of the Windows interoperability series #2007–#2017.

Copilot AI balanced review requested due to automatic review settings September 26, 2026 06:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@AKolenda

Copy link
Copy Markdown
Contributor Author

Related: #1977 also changes the SRL decoder, in a different way. Both stop requiring the terminator, read past the end as zeros, and remove MissingTerminator and Truncated. #1977 decodes the final byte as data and consumes zero runs one event at a time without a cap. This PR strips a trailing zero byte when present, as FreeRDP's progressive_rfx_upgrade_state_finish does, and caps a run at one component. They conflict in srl.rs and progressive.rs, so only one of them should land.

@meanaverage

Copy link
Copy Markdown
Contributor

Another data point for this one: we hit the same failure independently, with Windows 11 over the graphics pipeline through the web client (ironrdp-web). Upgrade passes failed first with the missing trailing zero byte and then, with that check removed, as truncated, and the session ended the first time Windows refined a picture. In our downstream build, not requiring the terminator and reading zero-run code words one at a time as values are needed (as FreeRDP does) fixed it.

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.

Marc-André Moreau (mamoreau-devolutions) pushed a commit that referenced this pull request Sep 28, 2026
…#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>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

  1. [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.
  2. [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.
  3. [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.
  4. [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.
  5. [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.
  6. [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.

Comment thread crates/ironrdp-graphics/src/srl.rs Outdated
Comment thread crates/ironrdp-graphics/src/srl.rs Outdated
@github-actions github-actions Bot added ai-reviewed/1 One automated review completed and removed needs-review A human reviewer is the current next actor labels Sep 29, 2026
@rajchauhan28

Copy link
Copy Markdown

Verified on Windows Server 2022 (Standard, build 20348.5622) with the graphics pipeline on and no H.264 decoder. On master (966a842), the session ends at the first progressive upgrade pass with Srl(MissingTerminator). With this PR (f10d8d6) merged, the first screen paints completely. Resizes after that still need the bitmap cache kept across ResetGraphics (#2011 or #1977). Details and numbers: #1977 (comment)

Tested with AI assistance; results are from automated runs against the server.

@chatgpt-codex-connector

Copy link
Copy Markdown

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.

@AKolenda
AKolenda deployed to llm-providers October 2, 2026 06:12 — with GitHub Actions Active
@AKolenda

AKolenda commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

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.

@github-actions github-actions Bot added automation-failed Exact-head automated classification or review failed or was unavailable risk/unknown Risk could not be determined automatically; needs maintainer-level scrutiny and removed risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny labels Oct 2, 2026
@AKolenda

AKolenda commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

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.

ignore this, my external review agent, idk why it activated on external PR

@AKolenda

AKolenda commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

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.

@CBenoit

Benoît Cortier (CBenoit) commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

PR automation is failing because of a picky-krb 0.12.5 incompatibility, fixed on master by #2074. Please rebase on master to fix it.

Update: no rebase needed anymore. picky-krb 0.12.5 was yanked from crates.io (re-released as 0.13.0), so the API check builds again without changes to this branch. PR automation has been re-run here and passes.

@github-actions github-actions Bot added risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny triage/overlap Possible overlap with another pull request; advisory only and removed risk/unknown Risk could not be determined automatically; needs maintainer-level scrutiny automation-failed Exact-head automated classification or review failed or was unavailable labels Oct 7, 2026
Marc-André Moreau (mamoreau-devolutions) pushed a commit that referenced this pull request Oct 9, 2026
## Problem

`OpenH264Decoder::decode` always reads its input as AVC format (4-byte
big-endian length-prefixed NAL units) and converts it to Annex B before
handing it to OpenH264.

MS-RDPEGFX defines the
[`RFX_AVC420_BITMAP_STREAM`](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpegfx/5f12c20e-2ea1-4ad1-a2a0-019ee3893731)
bitstream as "conforming to the byte stream format specified in
[ITU-H.264-201201] Annex B". Servers that follow the spec send start
codes, not lengths.

When the decoder gets Annex B, the first start code `00 00 00 01` is
read as a NAL length of 1. The bytes after it are then read as the next
length, which runs past the buffer, and the frame is dropped:

```
AVC NAL extends beyond buffer, discarding remaining data nal_len=805306368 offset=9 data_len=159657
```

GNOME Remote Desktop 50.2 and Windows Server 2022 both start every frame
with an access unit delimiter (`00 00 00 01 09 30 00 00 00 01 ...`), so
no AVC420 frame from either one decodes. `805306368` is `0x30000000`,
the bytes after the delimiter.

## Fix

A new public function, `pdu::is_avc_format`, decides the format.
`OpenH264Decoder::decode` uses it:

1. **Starts with a start code** (`00 00 01` or `00 00 00 01`): Annex B.
Passed to OpenH264 unchanged.
2. **Otherwise, the length prefixes chain exactly to the end of the
buffer**, and every length is nonzero: AVC format. Converted with
`avc_to_annex_b_into` as before.
3. **Anything else:** passed to OpenH264 unchanged as Annex B.

The start code is checked first because the chain check alone misreads
valid Annex B. `00 00 01 67` read as a length is 359, so a 363-byte
frame that starts with a 3-byte start code and an SPS also parses as a
one-unit AVC buffer. The same happens to a 325-byte P slice (`00 00 01
41`), and small single-slice P frames are what an idle screen produces.

Checking the start code first leaves one ambiguity: AVC senders whose
first NAL unit is 1 byte or 256–511 bytes long are read as Annex B.
Those senders don't follow the spec.

`is_avc_format` sits next to `avc_to_annex_b` in `pdu/avc.rs` so other
`H264Decoder` implementations can use it, and the `egfx_avc420_decode`
fuzz oracle runs it.

## Behavior changes to review

- **Malformed AVC input.** Previously, an AVC buffer with a truncated
last NAL unit or a zero-length NAL unit still decoded the complete units
before the bad one. Now it fails the chain check and goes to OpenH264 as
Annex B, which usually finds no picture. Conforming senders aren't
affected.
- **AVC input whose first NAL unit is 1 or 256–511 bytes** is now read
as Annex B (the ambiguity above).
- **`H264Decoder` trait docs** now say implementations may receive
either format. Third-party decoders written against the old docs may
only handle AVC input; they already fail against spec-conforming
servers.
- **New public API:** `ironrdp_egfx::pdu::is_avc_format`.
- **Cost.** Input that starts with a start code is not scanned. Other
input is scanned once by the chain check, and a second time by the
conversion if it is AVC.

## Docs

The docs that said the wire format is length-prefixed are corrected:
`decode.rs` module and trait docs, `avc_to_annex_b`, `annex_b_to_avc`,
`encode_avc420_bitmap_stream`, the encoder round-trip test comment, and
the two EGFX fuzz oracle comments. The spec link in `decode.rs` returned
404 and now points to the `RFX_AVC420_BITMAP_STREAM` page.

`encode.rs` already said the encoder produces Annex B, "the format
`RFX_AVC420_BITMAP_STREAM` carries on the wire", and the glutin renderer
(`crates/ironrdp-glutin-renderer/src/surface.rs`) already passes wire
bytes straight to OpenH264. This change makes the decoder agree with
both.

## Tests

Added to `crates/ironrdp-testsuite-core/tests/egfx/decode.rs`:

| Test | Case | Fails on |
| --- | --- | --- |
| `test_openh264_decode_annex_b` | Encoder's Annex B output decodes
as-is | `master` |
| `test_openh264_decode_annex_b_with_access_unit_delimiter` | Frame
starting with an AUD (GNOME Remote Desktop, Windows) | `master` |
| `test_openh264_decode_annex_b_three_byte_start_codes` | 3-byte start
codes | `master` |
| `test_openh264_decode_annex_b_that_also_parses_as_avc` | 363-byte `00
00 01 67` frame that also parses as AVC | `master`, and a chain-first
check |
| `test_is_avc_format` | Annex B, AVC, empty, and overrunning length |
(new function) |

The existing AVC tests pass unchanged. Three error-path test comments
were updated to describe the new path.

Run locally on 8fac24c:

- `cargo xtask check fmt`, `cargo xtask check lints`
- `typos` on the changed crates
- `cargo test -p ironrdp-testsuite-core --features openh264-bundled
egfx`: 83 passed
- `cargo test -p ironrdp-egfx --features openh264-bundled`: 55 passed

## Tested against servers

- **GNOME Remote Desktop 50.2** (VA-API encoding, Intel N100): a macOS
client ([rdp123](https://github.com/asd123ch/rdp123)) carrying the same
check on `ironrdp-egfx` 0.3.0 decodes AVC420 frames. Without it, none
decode.
- **Windows Server 2022**
([report](#1986 (comment))):
with this PR and #2010, 960 of 960 AVC420 frames decode. Without this
PR, the first AVC420 frame fails the same way and the session ends.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
AKolenda added 2 commits October 9, 2026 23:16
… 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.
@AKolenda
AKolenda force-pushed the fix/srl-windows-streams branch from f47878a to add736d Compare October 10, 2026 05:17
@AKolenda
AKolenda deployed to llm-providers October 10, 2026 05:18 — with GitHub Actions Active
@github-actions github-actions Bot removed the triage/overlap Possible overlap with another pull request; advisory only label Oct 10, 2026
@AKolenda
AKolenda force-pushed the fix/srl-windows-streams branch from add736d to 95d343b Compare October 10, 2026 05:40
@AKolenda

Copy link
Copy Markdown
Contributor Author

Rebased on master. On the overlap notice: this PR and #2085 are complementary. Neither duplicates the other's fix.

  • This PR changes only SRL parsing: srl.rs and the SrlDecoder construction and tests in decode_upgrade_pass.
  • fix(graphics): read the DWT variant from REGION flags #2085 changes only how ProgressiveDecoder::decode_bitmap picks the DWT variant: REGION flags instead of CONTEXT flags, and CONTEXT is no longer required. It does not touch decode_upgrade_pass or srl.rs.

The DWT variant applies after coefficients are reconstructed, so neither change affects the other's behaviour. They merge without conflicts in either order. On the merged result, the ironrdp-graphics, ironrdp-egfx and ironrdp-testsuite-core tests pass.

@AKolenda
AKolenda deployed to llm-providers October 10, 2026 05:41 — with GitHub Actions Active

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #2010 relaxes SRL decoding for RFX Progressive upgrade passes to match Windows-encoded streams: the trailing zero byte is no longer stripped or validated, bits past EOF read as zeros, and zero-run events are consumed incrementally without a decoder-side cap. I independently verified the event-per-iteration state machine is semantically equivalent to the previous accumulate loop (same KP updates), the decode loop terminates, the expect() in decode_nonzero is unreachable (num_bits <= 15 bounds magnitude at 32767), and the breaking API change (SrlDecoder::new now infallible; MissingTerminator/Truncated removed) has no remaining in-repo consumers. The three published findings are all low-severity: the by-design loss of truncation detection (documented, matches the reference decoder's zero-filled accumulator), a vestigial Option<SrlDecoder>/has_srl_values gate left from the old fallible constructor, and a leftover u16 return width in BitReader::read_bits forcing a conversion at its only…

  1. [code-compressor] Option<SrlDecoder> and has_srl_values gate are vestigial now that SrlDecoder::new is infallible — low 🟡 — crates/ironrdp-graphics/src/progressive.rs
    The PR made SrlDecoder::new infallible but kept the Option wrapper in decode_upgrade_pass: has_srl_values (an any-closure over bands) gates has_srl_values.then(|| SrlDecoder::new(srl_data)), and the loop matches on as_mut() with a None => Vec::new() fallback. The gate is redundant because when it is false every non-LL3 band with num_bits != 0 has zero_count == 0, and decode(0, num_bits) returns an empty Vec without consuming bits or validating num_bits. An unconditional SrlDecoder::new plus decode is behavior-preserving and deletes the closure, .then(...), and match (~11 lines, one branch); a short comment can carry the decode-only-when-values-exist intent. Null lines: only line 191 of this region is added by the PR.
  2. [code-compressor] read_bits returning u16 forces a usize::from conversion at its only call site — low 🟡 — crates/ironrdp-graphics/src/srl.rs
    read_bits was narrowed to u16 in this PR after the removed overflow-cap checking, but its sole decode call site (decode, line 97) immediately converts again with usize::from(self.reader.read_bits(k)). Since k = kp/8 is at most 10 (MAX_KP = 80), the value always fits; having read_bits return usize directly removes the conversion and matches the natural width for a run length, with no behavior change. Null lines: line 97 is added by the diff while the read_bits signature and body are mostly unchanged context.

Push a commit after addressing these findings. If no code change is needed, you may resolve inline threads and comment @github-actions review-ready to request human review.

Comment on lines +262 to +265
/// Reads one bit; bits past the end of the stream read as zero, like the reference
/// decoder's zero-filled bit accumulator, so a stream that omits its trailing zero
/// entries still decodes.
fn read_bit(&mut self) -> bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[skeptical] Removed truncation detection makes corrupt streams decode as maximum-magnitude coefficients — low 🟡 — read_bit now returns zeros past EOF with no error path, so a truncated or corrupt stream is silently accepted: a pending value whose sign/magnitude bits fall past the end decodes as the positive maximum (the PR's own tests assert coefficients[0] == 15 and SIGN_POSITIVE), and cut-off run tail bits read as zeros. Impact is limited to rendered pixel data: coefficients are clamped i16, num_values is bounded by band counts, and there is no memory-safety or non-termination exposure. The limitation is documented in SrlDecoder::new and decode_upgrade_pass and is inherent to the zero-filled-reader design that fixes the Windows interop failure; recorded so the loss of the malformed-stream signal is a conscious acceptance.

@github-actions github-actions Bot added ai-reviewed/2 Two automated reviews completed needs-author-action The pull request author is the current next actor and removed ai-reviewed/1 One automated review completed labels Oct 10, 2026

This branch was successfully deployed

1 active deployment
llm-providers — 95d343b0 Deployed Oct 10, 2026 by AKolenda via Classify pull request #2442
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed/2 Two automated reviews completed breaking-change Includes a breaking change, and requires special scrutiny at the boundaries kind/protocol Affects RDP or related protocol behavior needs-author-action The pull request author is the current next actor risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny scope/core Touches the core architectural tier size/S Size: up to 199 counted lines and 5 files; exceeds XS in either measure

Development

Successfully merging this pull request may close these issues.

5 participants