fix(session): Fix bitmap stride mismatch and out-of-bounds writes for xRDP compatibility#1252
fix(session): Fix bitmap stride mismatch and out-of-bounds writes for xRDP compatibility#1252Amartya Anshuman (amartyaa) wants to merge 3 commits into
Conversation
|
Thanks for the report and the fix Amartya Anshuman (@amartyaa). The stride insight is correct: MS-RDPBCGR §2.2.9.1.1.3.1.2.2 does distinguish A few suggestions a reviewer is likely to raise: 1. Horizontal over-paint when 2. Tests. A regression test for the stride case (construct a known-pattern bitmap whose data width differs from the destination width, call 3. Inconsistent bounds-check operators. Five sites use 4. Silent clipping vs Downstream context: this is relevant to Lamco's xRDP interop testing. Our test fleet uses xRDP 0.10.x on Ubuntu as a non-Windows comparison server, and IronRDP clients connecting there hit the diagonal-distortion symptom you describe. A version of this fix landing upstream would resolve a recurring noise item in our fleet runs. If a revised version lands, I can run it through our xRDP test fleet to confirm. |
|
Hello Greg Lamberson (@glamberson) !!
Happy to iterate further. Looking forward to Lamco's xRDP fleet results once this lands! |
There was a problem hiding this comment.
Pull request overview
Fixes diagonal bitmap shearing and out-of-bounds panics observed when ironrdp-session connects to xRDP servers, which send BitmapData whose pixel dimensions (update.width/update.height) don't match the destination rectangle. The fix derives a blit_rect whose size is the per-spec clip of bitmap-data vs. destination-rect dimensions and routes all apply_* calls through it, plus adds per-pixel/per-slice bounds guards in image.rs and enables unit tests for the crate.
Changes:
- In
fast_path.rs::process_bitmap_update, build ablit_rectfrommin(update.width, rect.width)/min(update.height, rect.height)and pass it to everyapply_*path instead ofupdate.rectangle. - In
image.rs, adddebug_assert!+if dst_idx + DST_COLOR_DEPTH <= self.data.len()per-pixel guards to all slow-path apply methods, amax_rowsclamp inapply_rgb24_iter, and a silent clamp indata_for_rect; also add atestsmodule covering rgb32 placement, rgb16 in-bounds writes, oversize rect rejection, anddata_for_rectclamping. - In
ironrdp-session/Cargo.toml, comment outtest = falseto enable the new unit tests.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
crates/ironrdp-session/src/fast_path.rs |
Introduces clipped blit_rect and routes all apply_* calls through it to fix stride/shearing. |
crates/ironrdp-session/src/image.rs |
Adds redundant-with-rect_fits per-pixel guards, clamps data_for_rect, and adds a unit-test module. |
crates/ironrdp-session/Cargo.toml |
Comments out test = false to allow the new tests to run. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if dst_idx + DST_COLOR_DEPTH <= self.data.len() { | ||
| let [r, g, b] = rdp_16bit_to_rgb(rgb16_value); | ||
| self.data[dst_idx + ri] = r; | ||
| self.data[dst_idx + gi] = g; | ||
| self.data[dst_idx + bi] = b; | ||
| self.data[dst_idx + ai] = 0xff; | ||
| } |
| @@ -844,7 +863,9 @@ impl DecodedImage { | |||
| .for_each(|(col_idx, src_pixel)| { | |||
| let dst_idx = ((top + row_idx) * image_width + left + col_idx) * DST_COLOR_DEPTH; | |||
|
|
|||
| let start = usize::from(rect.left) * self.bytes_per_pixel() + usize::from(rect.top) * self.stride(); | ||
| let end = | ||
| start + usize::from(rect.height() - 1) * self.stride() + usize::from(rect.width()) * self.bytes_per_pixel(); | ||
| let end = end.min(self.data.len()); | ||
| let start = start.min(end); |
| // Per MS-RDPBCGR §2.2.9.1.1.3.1.2.2: "If the size of the bitmap data | ||
| // exceeds the size of the rectangle, the additional rows and columns | ||
| // MUST be discarded by the client." Clip to the smaller of the two. | ||
| let clipped_width = update.width.min(update.rectangle.width()); | ||
| let clipped_height = update.height.min(update.rectangle.height()); | ||
| let blit_rect = InclusiveRectangle { | ||
| left: update.rectangle.left, | ||
| top: update.rectangle.top, | ||
| right: update.rectangle.left + clipped_width.saturating_sub(1), | ||
| bottom: update.rectangle.top + clipped_height.saturating_sub(1), | ||
| }; |
| #[test] | ||
| fn apply_rgb16_out_of_bounds_no_panic() { | ||
| let mut image = make_image(4, 4); | ||
|
|
||
| // Rectangle starting at (2, 2) with width=4, height=4 — extends to (5, 5), | ||
| // but image is only 4×4 (valid indices 0..3). The rect_fits check will | ||
| // skip this, so let's use a rect that fits per rect_fits but has pixels | ||
| // that compute dst_idx near the edge. | ||
| let rect = InclusiveRectangle { | ||
| left: 0, | ||
| top: 0, | ||
| right: 3, | ||
| bottom: 3, | ||
| }; | ||
|
|
||
| // 4×4 RGB16 bitmap: 2 bytes per pixel, 32 bytes total. | ||
| // Use a known 16-bit color value: 0xFFFF (white in RGB565). | ||
| let bitmap_data = vec![0xFF; 4 * 4 * 2]; | ||
|
|
||
| let result = image.apply_rgb16_bitmap(&bitmap_data, &rect); | ||
| assert!(result.is_ok()); | ||
|
|
||
| // Verify that pixel (0,0) was written (should be white-ish from RGB565 0xFFFF). | ||
| let px = 0; | ||
| assert_ne!(image.data[px], 0, "pixel (0,0) R should be non-zero"); | ||
| assert_eq!(image.data[px + 3], 0xFF, "pixel (0,0) A should be 0xFF"); | ||
| } |
| [lib] | ||
| doctest = false | ||
| test = false | ||
| # test = false |
Fixes #1251
Summary
This PR fixes two related issues in
ironrdp-sessionthat cause diagonal bitmap distortion and index-out-of-bounds panics when connecting to xRDP servers.Changes
crates/ironrdp-session/src/fast_path.rsxRDP sends bitmap updates where the data dimensions (
update.width×update.height) differ from the destination rectangle (update.rectangle). Per MS-RDPBCGR §2.2.9.1.1.3.1.2.2,bitmapWidth/bitmapHeightdefine the pixel data layout whiledestLeft/destTop/destRight/destBottomdefine the screen placement.The
apply_*methods use the rectangle's width as the row stride. Passingupdate.rectanglewhen it's wider/narrower than the actual data causes diagonal shearing.Fix: Construct a
blit_rectwhose dimensions match the actual bitmap data, positioned at the destination's top-left corner:crates/ironrdp-session/src/image.rsAdded bounds checks to all apply_* bitmap methods that were missing them:
Each pixel write is now guarded with
if dst_idx + bytes_per_pixel < self.data.len().This is consistent with how other parts of the codebase handle edge-of-framebuffer updates — pixels beyond the buffer are silently clipped rather than panicking.
Also added a
max_rowsclamp inapply_rgb24_iterto prevent iterating beyond the framebuffer height.Testing