fix(graphics): harden RLE implementation (#86)

Mostly added bound checks in case an invalid RLE-compressed bitmap is
received. Implementation have been fuzzed for a few hours in order to
find blind spots.
This commit is contained in:
Benoît Cortier 2023-03-07 07:01:22 -05:00 committed by GitHub
parent 68ebc674a5
commit c949f5b46c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 376 additions and 95 deletions

View file

@ -34,6 +34,15 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Cache
uses: actions/cache@v3
with:
path: |
~/.cargo/registry/
~/.cargo/git/
./target/
key: ${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
- name: Check clippy
run: cargo clippy --workspace -- -D warnings
@ -45,6 +54,15 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Cache
uses: actions/cache@v3
with:
path: |
~/.cargo/registry/
~/.cargo/git/
./ffi/wasm/target/
key: ${{ runner.os }}-wasm-${{ hashFiles('Cargo.lock') }}
- name: Prepare runner
run: sudo apt install wabt
@ -69,5 +87,48 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Cache
uses: actions/cache@v3
with:
path: |
~/.cargo/registry/
~/.cargo/git/
./target/
key: ${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
- name: Test [${{ matrix.os }}]
run: cargo test --workspace
fuzz:
name: Fuzzing
runs-on: ubuntu-20.04
needs: formatting
steps:
- uses: actions/checkout@v3
- name: Fuzz build cache
uses: actions/cache@v3
with:
path: |
./fuzz/target/
./artifacts/
key: ${{ runner.os }}-fuzz-${{ hashFiles('./fuzz/Cargo.lock') }}
- name: Fuzz corpus cache
uses: actions/cache@v3
with:
path: |
./fuzz/corpus/
key: fuzz-corpus-cache
- name: Prepare runner
run: |
cd ./fuzz/
cargo install cargo-fuzz
rustup install nightly --profile=minimal
- name: Fuzz
run: |
rustup run nightly cargo fuzz run pdu_decoding -- -max_total_time=3s
rustup run nightly cargo fuzz run rle_decompression -- -max_total_time=3s

3
fuzz/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/target
/artifacts
/corpus

29
fuzz/Cargo.toml Normal file
View file

@ -0,0 +1,29 @@
[package]
name = "ironrdp-fuzz"
version = "0.0.0"
publish = false
edition = "2021"
[package.metadata]
cargo-fuzz = true
[workspace]
members = ["."]
[profile.release]
debug = 1
[dependencies]
ironrdp-core = { path = "../ironrdp-core" }
ironrdp-graphics = { path = "../ironrdp-graphics" }
libfuzzer-sys = "0.4"
arbitrary = { version = "1", features = ["derive"] }
bytes = "1.4.0"
[[bin]]
name = "pdu_decoding"
path = "fuzz_targets/pdu_decoding.rs"
[[bin]]
name = "rle_decompression"
path = "fuzz_targets/rle_decompression.rs"

View file

@ -1,10 +1,8 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;
extern crate ironrdp;
use ironrdp::rdp::*;
use ironrdp::*;
use ironrdp_core::rdp::*;
use ironrdp_core::*;
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let _ = Request::from_buffer(data);
@ -35,10 +33,7 @@ fuzz_target!(|data: &[u8]| {
let _ = fast_path::FastPathHeader::from_buffer(data);
let _ = fast_path::FastPathUpdatePdu::from_buffer(data);
let _ = fast_path::FastPathUpdate::from_buffer_with_code(
data,
fast_path::UpdateCode::SurfaceCommands,
);
let _ = fast_path::FastPathUpdate::from_buffer_with_code(data, fast_path::UpdateCode::SurfaceCommands);
let _ = surface_commands::SurfaceCommand::from_buffer(data);
let _ = surface_commands::SurfaceBitsPdu::from_buffer(data);

View file

@ -0,0 +1,20 @@
#![no_main]
use bytes::BytesMut;
use libfuzzer_sys::fuzz_target;
#[derive(arbitrary::Arbitrary, Debug)]
struct Input<'a> {
src: &'a [u8],
width: u8,
height: u8,
}
fuzz_target!(|input: Input<'_>| {
let mut out = BytesMut::new();
let _ = ironrdp_graphics::rle::decompress_24_bpp(input.src, &mut out, input.width, input.height);
let _ = ironrdp_graphics::rle::decompress_16_bpp(input.src, &mut out, input.width, input.height);
let _ = ironrdp_graphics::rle::decompress_15_bpp(input.src, &mut out, input.width, input.height);
let _ = ironrdp_graphics::rle::decompress_8_bpp(input.src, &mut out, input.width, input.height);
});

3
fuzz/rust-toolchain.toml Normal file
View file

@ -0,0 +1,3 @@
[toolchain]
channel = "nightly"
profile = "minimal"

View file

@ -1,4 +0,0 @@
target
corpus
artifacts

View file

@ -1,20 +0,0 @@
[package]
name = "ironrdp-fuzz"
version = "0.0.1"
authors = ["Automatically generated"]
publish = false
[[bin]]
name = "fuzz_pdu"
path = "fuzz_targets/fuzz_pdu.rs"
[package.metadata]
cargo-fuzz = true
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[dependencies]
ironrdp = { path = ".." }
libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" }

View file

@ -102,6 +102,8 @@ pub enum NegotiationError {
ResponseFailure(FailureCode),
#[error("Invalid tpkt header version")]
TpktVersionError,
#[error("Not enough bytes")]
NotEnoughBytes,
}
impl From<NegotiationError> for io::Error {
@ -138,6 +140,10 @@ impl PduParsing for Request {
read_and_check_class(&mut stream, 0)?;
if tpkt.length < TPDU_REQUEST_LENGTH {
return Err(NegotiationError::NotEnoughBytes);
}
let mut buffer = vec![0u8; tpkt.length - TPDU_REQUEST_LENGTH];
stream.read_exact(buffer.as_mut_slice())?;

View file

@ -108,6 +108,8 @@ pub enum RdpError {
ServerSetErrorInfoError(#[from] ServerSetErrorInfoError),
#[error("Input event PDU error")]
InputEventError(#[from] InputEventError),
#[error("Not enough bytes")]
NotEnoughBytes,
}
impl From<RdpError> for io::Error {

View file

@ -89,12 +89,18 @@ impl PduParsing for ShareControlHeader {
pdu_source,
share_id,
};
if pdu_type == ShareControlPduType::DataPdu {
// Some windows version have an issue where PDU
// there is some padding not part of the inner unit.
// Consume that data
let header_length = header.buffer_length();
if header_length != total_length {
if total_length < header_length {
return Err(RdpError::NotEnoughBytes);
}
let padding = total_length - header_length;
let mut data = vec![0u8; padding];
stream.read_exact(data.as_mut())?;
@ -103,6 +109,7 @@ impl PduParsing for ShareControlHeader {
Ok(header)
}
fn to_buffer(&self, mut stream: impl io::Write) -> Result<(), Self::Error> {
let pdu_type_with_version = PROTOCOL_VERSION | self.share_control_pdu.share_header_type().to_u16().unwrap();
@ -114,6 +121,7 @@ impl PduParsing for ShareControlHeader {
self.share_control_pdu.to_buffer(&mut stream)
}
fn buffer_length(&self) -> usize {
SHARE_CONTROL_HEADER_SIZE + self.share_control_pdu.buffer_length()
}

View file

@ -228,6 +228,8 @@ pub enum ServerLicenseError {
InvalidScopeCount(u32),
#[error("Received invalid sertificate length: {0}")]
InvalidCertificateLength(u32),
#[error("Blob too small")]
BlobTooSmall,
}
pub struct BlobHeader {

View file

@ -231,6 +231,9 @@ impl PduParsing for Scope {
fn from_buffer(mut stream: impl io::Read) -> Result<Self, Self::Error> {
let blob_header = BlobHeader::read_from_buffer(BlobType::Scope, &mut stream)?;
if blob_header.length < UTF8_NULL_TERMINATOR_SIZE {
return Err(ServerLicenseError::BlobTooSmall);
}
let mut blob_data = vec![0u8; blob_header.length];
stream.read_exact(&mut blob_data)?;
blob_data.resize(blob_data.len() - UTF8_NULL_TERMINATOR_SIZE, 0);

View file

@ -14,7 +14,13 @@ use bytes::BytesMut;
use core::fmt;
use std::ops::BitXor;
// TODO: error handling
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RlePixelFormat {
Rgb24,
Rgb16,
Rgb15,
Rgb8,
}
/// Decompress an RLE compressed bitmap.
///
@ -29,13 +35,13 @@ pub fn decompress(
width: impl Into<usize>,
height: impl Into<usize>,
bpp: impl Into<usize>,
) {
) -> Result<RlePixelFormat, RleError> {
match bpp.into() {
Mode24Bpp::BPP => decompress_24_bpp(src, dst, width, height),
Mode16Bpp::BPP => decompress_16_bpp(src, dst, width, height),
Mode15Bpp::BPP => decompress_15_bpp(src, dst, width, height),
Mode8Bpp::BPP => decompress_8_bpp(src, dst, width, height),
_ => todo!("return error"),
invalid => Err(RleError::InvalidBpp { bpp: invalid }),
}
}
@ -45,7 +51,12 @@ pub fn decompress(
/// `dst`: destination buffer
/// `width`: decompressed bitmap width
/// `height`: decompressed bitmap height
pub fn decompress_24_bpp(src: &[u8], dst: &mut BytesMut, width: impl Into<usize>, height: impl Into<usize>) {
pub fn decompress_24_bpp(
src: &[u8],
dst: &mut BytesMut,
width: impl Into<usize>,
height: impl Into<usize>,
) -> Result<RlePixelFormat, RleError> {
decompress_helper::<Mode24Bpp>(src, dst, width.into(), height.into())
}
@ -55,7 +66,12 @@ pub fn decompress_24_bpp(src: &[u8], dst: &mut BytesMut, width: impl Into<usize>
/// `dst`: destination buffer
/// `width`: decompressed bitmap width
/// `height`: decompressed bitmap height
pub fn decompress_16_bpp(src: &[u8], dst: &mut BytesMut, width: impl Into<usize>, height: impl Into<usize>) {
pub fn decompress_16_bpp(
src: &[u8],
dst: &mut BytesMut,
width: impl Into<usize>,
height: impl Into<usize>,
) -> Result<RlePixelFormat, RleError> {
decompress_helper::<Mode16Bpp>(src, dst, width.into(), height.into())
}
@ -65,7 +81,12 @@ pub fn decompress_16_bpp(src: &[u8], dst: &mut BytesMut, width: impl Into<usize>
/// `dst`: destination buffer
/// `width`: decompressed bitmap width
/// `height`: decompressed bitmap height
pub fn decompress_15_bpp(src: &[u8], dst: &mut BytesMut, width: impl Into<usize>, height: impl Into<usize>) {
pub fn decompress_15_bpp(
src: &[u8],
dst: &mut BytesMut,
width: impl Into<usize>,
height: impl Into<usize>,
) -> Result<RlePixelFormat, RleError> {
decompress_helper::<Mode15Bpp>(src, dst, width.into(), height.into())
}
@ -75,14 +96,91 @@ pub fn decompress_15_bpp(src: &[u8], dst: &mut BytesMut, width: impl Into<usize>
/// `dst`: destination buffer
/// `width`: decompressed bitmap width
/// `height`: decompressed bitmap height
pub fn decompress_8_bpp(src: &[u8], dst: &mut BytesMut, width: impl Into<usize>, height: impl Into<usize>) {
pub fn decompress_8_bpp(
src: &[u8],
dst: &mut BytesMut,
width: impl Into<usize>,
height: impl Into<usize>,
) -> Result<RlePixelFormat, RleError> {
decompress_helper::<Mode8Bpp>(src, dst, width.into(), height.into())
}
fn decompress_helper<Mode: DepthMode>(src: &[u8], dst: &mut BytesMut, width: usize, height: usize) {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RleError {
InvalidBpp {
bpp: usize,
},
BadOrderCode,
NotEnoughBytes {
expected: usize,
actual: usize,
},
InvalidImageSize {
maximum_additional: usize,
required_additional: usize,
},
EmptyImage,
UnexpectedZeroLength,
}
impl fmt::Display for RleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RleError::InvalidBpp { bpp } => write!(f, "invalid bytes per pixel: {bpp}"),
RleError::BadOrderCode => write!(f, "bad RLE order code"),
RleError::NotEnoughBytes { expected, actual } => {
write!(f, "not enough bytes: expected {expected} bytes, but got {actual}")
}
RleError::InvalidImageSize {
maximum_additional,
required_additional,
} => {
write!(
f,
"invalid image size advertised: output buffer can only receive at most {maximum_additional} additional bytes, but {required_additional} bytes are required"
)
}
RleError::EmptyImage => write!(f, "height or width is zero"),
RleError::UnexpectedZeroLength => write!(f, "unexpected zero-length"),
}
}
}
fn decompress_helper<Mode: DepthMode>(
src: &[u8],
dst: &mut BytesMut,
width: usize,
height: usize,
) -> Result<RlePixelFormat, RleError> {
if width == 0 || height == 0 {
return Err(RleError::EmptyImage);
}
let row_delta = Mode::COLOR_DEPTH * width;
dst.resize(row_delta * height, 0);
decompress_impl::<Mode>(src, dst, row_delta);
decompress_impl::<Mode>(src, dst, row_delta)?;
Ok(Mode::PIXEL_FORMAT)
}
macro_rules! ensure_size {
(from: $buf:ident, size: $expected:expr) => {{
let actual = $buf.remaining_len();
let expected = $expected;
if expected > actual {
return Err(RleError::NotEnoughBytes { expected, actual });
}
}};
(into: $buf:ident, size: $required_additional:expr) => {{
let maximum_additional = $buf.remaining_len();
let required_additional = $required_additional;
if required_additional > maximum_additional {
return Err(RleError::InvalidImageSize {
maximum_additional,
required_additional,
});
}
}};
}
/// RLE decompression implementation
@ -90,9 +188,7 @@ fn decompress_helper<Mode: DepthMode>(src: &[u8], dst: &mut BytesMut, width: usi
/// `src`: source buffer containing compressed bitmap
/// `dst`: destination buffer
/// `row_delta`: scanline length in bytes
fn decompress_impl<Mode: DepthMode>(src: &[u8], dst: &mut [u8], row_delta: usize) {
// FIXME: some bounds checks should be inserted
fn decompress_impl<Mode: DepthMode>(src: &[u8], dst: &mut [u8], row_delta: usize) -> Result<(), RleError> {
let mut src = Buf::new(src);
let mut dst = BufMut::new(dst);
@ -107,16 +203,20 @@ fn decompress_impl<Mode: DepthMode>(src: &[u8], dst: &mut [u8], row_delta: usize
insert_fg_pel = false;
}
ensure_size!(from: src, size: 1);
let header = src.read_u8();
// Extract the compression order code ID from the compression order header.
let code = Code::decode(header);
// Extract run length
let run_length = code.extract_run_length(header, &mut src);
let run_length = code.extract_run_length(header, &mut src)?;
// Handle Background Run Orders.
if code == Code::REGULAR_BG_RUN || code == Code::MEGA_MEGA_BG_RUN {
ensure_size!(into: dst, size: run_length * Mode::COLOR_DEPTH);
if is_first_line {
let num_iterations = if insert_fg_pel {
Mode::write_pixel(&mut dst, fg_pel);
@ -161,14 +261,20 @@ fn decompress_impl<Mode: DepthMode>(src: &[u8], dst: &mut [u8], row_delta: usize
{
// Handle Foreground Run Orders.
ensure_size!(from: src, size: Mode::COLOR_DEPTH);
if code == Code::LITE_SET_FG_FG_RUN || code == Code::MEGA_MEGA_SET_FG_RUN {
fg_pel = Mode::read_pixel(&mut src);
}
for _ in 0..run_length {
if is_first_line {
ensure_size!(into: dst, size: run_length * Mode::COLOR_DEPTH);
if is_first_line {
for _ in 0..run_length {
Mode::write_pixel(&mut dst, fg_pel);
} else {
}
} else {
for _ in 0..run_length {
let pixel_above = dst.read_pixel_above::<Mode>(row_delta);
let xored = pixel_above ^ fg_pel;
Mode::write_pixel(&mut dst, xored);
@ -177,9 +283,13 @@ fn decompress_impl<Mode: DepthMode>(src: &[u8], dst: &mut [u8], row_delta: usize
} else if code == Code::LITE_DITHERED_RUN || code == Code::MEGA_MEGA_DITHERED_RUN {
// Handle Dithered Run Orders.
ensure_size!(from: src, size: 2 * Mode::COLOR_DEPTH);
let pixel_a = Mode::read_pixel(&mut src);
let pixel_b = Mode::read_pixel(&mut src);
ensure_size!(into: dst, size: run_length * 2 * Mode::COLOR_DEPTH);
for _ in 0..run_length {
Mode::write_pixel(&mut dst, pixel_a);
Mode::write_pixel(&mut dst, pixel_b);
@ -187,8 +297,12 @@ fn decompress_impl<Mode: DepthMode>(src: &[u8], dst: &mut [u8], row_delta: usize
} else if code == Code::REGULAR_COLOR_RUN || code == Code::MEGA_MEGA_COLOR_RUN {
// Handle Color Run Orders.
ensure_size!(from: src, size: Mode::COLOR_DEPTH);
let pixel = Mode::read_pixel(&mut src);
ensure_size!(into: dst, size: run_length * Mode::COLOR_DEPTH);
for _ in 0..run_length {
Mode::write_pixel(&mut dst, pixel);
}
@ -200,6 +314,7 @@ fn decompress_impl<Mode: DepthMode>(src: &[u8], dst: &mut [u8], row_delta: usize
// Handle Foreground/Background Image Orders.
if code == Code::LITE_SET_FG_FGBG_IMAGE || code == Code::MEGA_MEGA_SET_FGBG_IMAGE {
ensure_size!(from: src, size: Mode::COLOR_DEPTH);
fg_pel = Mode::read_pixel(&mut src);
}
@ -208,12 +323,13 @@ fn decompress_impl<Mode: DepthMode>(src: &[u8], dst: &mut [u8], row_delta: usize
while number_to_read > 0 {
let c_bits = std::cmp::min(8, number_to_read);
ensure_size!(from: src, size: 1);
let bitmask = src.read_u8();
if is_first_line {
write_first_line_fg_bg_image::<Mode>(&mut dst, bitmask, fg_pel, c_bits);
write_first_line_fg_bg_image::<Mode>(&mut dst, bitmask, fg_pel, c_bits)?;
} else {
write_fg_bg_image::<Mode>(&mut dst, row_delta, bitmask, fg_pel, c_bits);
write_fg_bg_image::<Mode>(&mut dst, row_delta, bitmask, fg_pel, c_bits)?;
}
number_to_read -= c_bits;
@ -223,6 +339,9 @@ fn decompress_impl<Mode: DepthMode>(src: &[u8], dst: &mut [u8], row_delta: usize
let byte_count = run_length * Mode::COLOR_DEPTH;
ensure_size!(from: src, size: byte_count);
ensure_size!(into: dst, size: byte_count);
for _ in 0..byte_count {
dst.write_u8(src.read_u8());
}
@ -232,9 +351,9 @@ fn decompress_impl<Mode: DepthMode>(src: &[u8], dst: &mut [u8], row_delta: usize
const MASK_SPECIAL_FG_BG_1: u8 = 0x03;
if is_first_line {
write_first_line_fg_bg_image::<Mode>(&mut dst, MASK_SPECIAL_FG_BG_1, fg_pel, 8);
write_first_line_fg_bg_image::<Mode>(&mut dst, MASK_SPECIAL_FG_BG_1, fg_pel, 8)?;
} else {
write_fg_bg_image::<Mode>(&mut dst, row_delta, MASK_SPECIAL_FG_BG_1, fg_pel, 8);
write_fg_bg_image::<Mode>(&mut dst, row_delta, MASK_SPECIAL_FG_BG_1, fg_pel, 8)?;
}
} else if code == Code::SPECIAL_FGBG_2 {
// Handle Special Order 2.
@ -242,22 +361,28 @@ fn decompress_impl<Mode: DepthMode>(src: &[u8], dst: &mut [u8], row_delta: usize
const MASK_SPECIAL_FG_BG_2: u8 = 0x05;
if is_first_line {
write_first_line_fg_bg_image::<Mode>(&mut dst, MASK_SPECIAL_FG_BG_2, fg_pel, 8);
write_first_line_fg_bg_image::<Mode>(&mut dst, MASK_SPECIAL_FG_BG_2, fg_pel, 8)?;
} else {
write_fg_bg_image::<Mode>(&mut dst, row_delta, MASK_SPECIAL_FG_BG_2, fg_pel, 8);
write_fg_bg_image::<Mode>(&mut dst, row_delta, MASK_SPECIAL_FG_BG_2, fg_pel, 8)?;
}
} else if code == Code::SPECIAL_WHITE {
// Handle White Order.
ensure_size!(into: dst, size: Mode::COLOR_DEPTH);
Mode::write_pixel(&mut dst, Mode::WHITE_PIXEL);
} else if code == Code::SPECIAL_BLACK {
// Handle Black Order.
ensure_size!(into: dst, size: Mode::COLOR_DEPTH);
Mode::write_pixel(&mut dst, Mode::BLACK_PIXEL);
} else {
todo!("return unknown order error");
return Err(RleError::BadOrderCode);
}
}
Ok(())
}
#[derive(Clone, Copy, PartialEq, Eq)]
@ -338,11 +463,11 @@ impl Code {
}
/// Extract the run length of a compression order.
pub fn extract_run_length(self, header: u8, src: &mut Buf) -> usize {
pub fn extract_run_length(self, header: u8, src: &mut Buf) -> Result<usize, RleError> {
match self {
Self::REGULAR_FGBG_IMAGE => extract_run_length_regular_fg_bg(header, src),
Self::REGULAR_FGBG_IMAGE => extract_run_length_fg_bg(header, MASK_REGULAR_RUN_LENGTH, src),
Self::LITE_SET_FG_FGBG_IMAGE => extract_run_length_lite_fg_bg(header, src),
Self::LITE_SET_FG_FGBG_IMAGE => extract_run_length_fg_bg(header, MASK_LITE_RUN_LENGTH, src),
Self::REGULAR_BG_RUN | Self::REGULAR_FG_RUN | Self::REGULAR_COLOR_RUN | Self::REGULAR_COLOR_IMAGE => {
extract_run_length_regular(header, src)
@ -359,9 +484,9 @@ impl Code {
| Self::MEGA_MEGA_SET_FGBG_IMAGE
| Self::MEGA_MEGA_COLOR_IMAGE => extract_run_length_mega_mega(src),
Self::SPECIAL_FGBG_1 | Self::SPECIAL_FGBG_2 | Self::SPECIAL_WHITE | Self::SPECIAL_BLACK => 0,
Self::SPECIAL_FGBG_1 | Self::SPECIAL_FGBG_2 | Self::SPECIAL_WHITE | Self::SPECIAL_BLACK => Ok(0),
_ => 0,
_ => Ok(0),
}
}
}
@ -369,41 +494,50 @@ impl Code {
const MASK_REGULAR_RUN_LENGTH: u8 = 0x1F;
const MASK_LITE_RUN_LENGTH: u8 = 0x0F;
/// Extract the run length of a Regular-Form Foreground/Background Image Order
fn extract_run_length_regular_fg_bg(header: u8, src: &mut Buf) -> usize {
match header & MASK_REGULAR_RUN_LENGTH {
0 => usize::from(src.read_u8()) + 1,
run_length => usize::from(run_length) * 8,
}
}
/// Extract the run length of a Lite-Form Foreground/Background Image Order.
fn extract_run_length_lite_fg_bg(header: u8, src: &mut Buf) -> usize {
match header & MASK_LITE_RUN_LENGTH {
0 => usize::from(src.read_u8()) + 1,
run_length => usize::from(run_length) * 8,
/// Extract the run length of a Foreground/Background Image Order.
fn extract_run_length_fg_bg(header: u8, length_mask: u8, src: &mut Buf) -> Result<usize, RleError> {
match header & length_mask {
0 => {
ensure_size!(from: src, size: 1);
Ok(usize::from(src.read_u8()) + 1)
}
run_length => Ok(usize::from(run_length) * 8),
}
}
/// Extract the run length of a regular-form compression order.
fn extract_run_length_regular(header: u8, src: &mut Buf) -> usize {
fn extract_run_length_regular(header: u8, src: &mut Buf) -> Result<usize, RleError> {
match header & MASK_REGULAR_RUN_LENGTH {
// An extended (MEGA) run.
0 => usize::from(src.read_u8()) + 32,
run_length => usize::from(run_length),
0 => {
// An extended (MEGA) run.
ensure_size!(from: src, size: 1);
Ok(usize::from(src.read_u8()) + 32)
}
run_length => Ok(usize::from(run_length)),
}
}
fn extract_run_length_lite(header: u8, src: &mut Buf) -> usize {
fn extract_run_length_lite(header: u8, src: &mut Buf) -> Result<usize, RleError> {
match header & MASK_LITE_RUN_LENGTH {
// An extended (MEGA) run.
0 => usize::from(src.read_u8()) + 16,
run_length => usize::from(run_length),
0 => {
// An extended (MEGA) run.
ensure_size!(from: src, size: 1);
Ok(usize::from(src.read_u8()) + 16)
}
run_length => Ok(usize::from(run_length)),
}
}
fn extract_run_length_mega_mega(src: &mut Buf) -> usize {
usize::from(src.read_u16())
fn extract_run_length_mega_mega(src: &mut Buf) -> Result<usize, RleError> {
ensure_size!(from: src, size: 2);
let run_length = usize::from(src.read_u16());
if run_length == 0 {
Err(RleError::UnexpectedZeroLength)
} else {
Ok(run_length)
}
}
struct Buf<'a> {
@ -416,6 +550,10 @@ impl<'a> Buf<'a> {
Self { inner: bytes, pos: 0 }
}
fn remaining_len(&self) -> usize {
self.inner.len() - self.pos
}
fn read<const N: usize>(&mut self) -> [u8; N] {
let bytes = &self.inner[self.pos..self.pos + N];
self.pos += N;
@ -457,6 +595,10 @@ impl<'a> BufMut<'a> {
Self { inner: bytes, pos: 0 }
}
fn remaining_len(&self) -> usize {
self.inner.len() - self.pos
}
fn write(&mut self, bytes: &[u8]) {
self.inner[self.pos..self.pos + bytes.len()].copy_from_slice(bytes);
self.pos += bytes.len();
@ -493,6 +635,9 @@ trait DepthMode {
/// Bits per pixel
const BPP: usize;
/// Pixel format for this depth mode
const PIXEL_FORMAT: RlePixelFormat;
/// The black pixel value
const BLACK_PIXEL: Self::Pixel;
@ -515,6 +660,8 @@ impl DepthMode for Mode8Bpp {
const BPP: usize = 8;
const PIXEL_FORMAT: RlePixelFormat = RlePixelFormat::Rgb8;
const BLACK_PIXEL: Self::Pixel = 0x00;
const WHITE_PIXEL: Self::Pixel = 0xFF;
@ -537,6 +684,8 @@ impl DepthMode for Mode15Bpp {
const BPP: usize = 15;
const PIXEL_FORMAT: RlePixelFormat = RlePixelFormat::Rgb15;
const BLACK_PIXEL: Self::Pixel = 0x0000;
// 5 bits per RGB component:
@ -561,6 +710,8 @@ impl DepthMode for Mode16Bpp {
const BPP: usize = 16;
const PIXEL_FORMAT: RlePixelFormat = RlePixelFormat::Rgb16;
const BLACK_PIXEL: Self::Pixel = 0x0000;
// 5 bits for red, 6 bits for green, 5 bits for green:
@ -585,6 +736,8 @@ impl DepthMode for Mode24Bpp {
const BPP: usize = 24;
const PIXEL_FORMAT: RlePixelFormat = RlePixelFormat::Rgb24;
const BLACK_PIXEL: Self::Pixel = 0x000000;
// 8 bits per RGB component:
@ -607,7 +760,9 @@ fn write_fg_bg_image<Mode: DepthMode>(
bitmask: u8,
fg_pel: Mode::Pixel,
mut c_bits: usize,
) {
) -> Result<(), RleError> {
ensure_size!(into: dst, size: c_bits * Mode::COLOR_DEPTH);
let mut mask = 0x01;
repeat::<8>(|| {
@ -623,7 +778,9 @@ fn write_fg_bg_image<Mode: DepthMode>(
mask <<= 1;
c_bits == 0
})
});
Ok(())
}
/// Writes a foreground/background image to a destination buffer
@ -632,7 +789,9 @@ fn write_first_line_fg_bg_image<Mode: DepthMode>(
bitmask: u8,
fg_pel: Mode::Pixel,
mut c_bits: usize,
) {
) -> Result<(), RleError> {
ensure_size!(into: dst, size: c_bits * Mode::COLOR_DEPTH);
let mut mask = 0x01;
repeat::<8>(|| {
@ -646,7 +805,9 @@ fn write_first_line_fg_bg_image<Mode: DepthMode>(
mask <<= 1;
c_bits == 0
})
});
Ok(())
}
fn repeat<const N: usize>(mut op: impl FnMut() -> bool) {

View file

@ -37,7 +37,7 @@ fn decompress_bpp_16(#[case] src: &[u8]) {
// IronRDP
let mut ironrdp_out = bytes::BytesMut::new();
ironrdp_graphics::rle::decompress_16_bpp(src, &mut ironrdp_out, WIDTH, HEIGHT);
ironrdp_graphics::rle::decompress_16_bpp(src, &mut ironrdp_out, WIDTH, HEIGHT).expect("ironrdp decompress");
ironrdp_out.freeze()
};

View file

@ -8,6 +8,7 @@ use ironrdp_core::fast_path::{
use ironrdp_core::geometry::Rectangle;
use ironrdp_core::surface_commands::{FrameAction, FrameMarkerPdu, SurfaceCommand};
use ironrdp_core::{PduBufferParsing, ShareDataPdu};
use ironrdp_graphics::rle::RlePixelFormat;
use num_traits::FromPrimitive;
use super::codecs::rfx;
@ -85,16 +86,24 @@ impl Processor {
update.bits_per_pixel
);
ironrdp_graphics::rle::decompress(
match ironrdp_graphics::rle::decompress(
update.bitmap_data,
&mut buf,
update.width,
update.height,
update.bits_per_pixel,
);
) {
Ok(RlePixelFormat::Rgb16) => {
image.apply_rgb16_bitmap(&buf, &update.rectangle);
}
// TODO: support other pixel formats…
image.apply_rgb16_bitmap(&buf, &update.rectangle);
// TODO: support other pixel formats…
Ok(format @ (RlePixelFormat::Rgb8 | RlePixelFormat::Rgb15 | RlePixelFormat::Rgb24)) => {
warn!("Received RLE-compressed bitmap with unsupported color depth: {format:?}");
}
Err(e) => warn!("Invalid RLE-compressed bitmap: {e}"),
}
}
} else {
// Uncompressed bitmap data is formatted as a bottom-up, left-to-right series of
@ -102,8 +111,11 @@ impl Processor {
// four bytes (including up to three bytes of padding, as necessary).
trace!("Uncompressed raw bitmap");
// TODO: support other pixel formats…
image.apply_rgb16_bitmap(update.bitmap_data, &update.rectangle);
match update.bits_per_pixel {
16 => image.apply_rgb16_bitmap(update.bitmap_data, &update.rectangle),
// TODO: support other pixel formats…
unsupported => warn!("Invalid raw bitmap with {unsupported} bytes per pixels"),
}
}
match update_rectangle {