Skip to content

Commit af11df1

Browse files
authored
feat(pdu): add arbitrary feature for structure-aware fuzzing (#1272)
1 parent 2e2699d commit af11df1

87 files changed

Lines changed: 469 additions & 5 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ironrdp-connector/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ test = false
1818

1919
[features]
2020
default = []
21-
arbitrary = ["dep:arbitrary"]
21+
arbitrary = ["dep:arbitrary", "ironrdp-pdu/arbitrary"]
2222
qoi = ["ironrdp-pdu/qoi"]
2323
qoiz = ["ironrdp-pdu/qoiz"]
2424

crates/ironrdp-connector/src/connection.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use crate::{
1818
};
1919

2020
#[derive(Debug)]
21-
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2221
pub struct ConnectionResult {
2322
pub io_channel_id: u16,
2423
pub user_channel_id: u16,
@@ -34,7 +33,6 @@ pub struct ConnectionResult {
3433

3534
#[derive(Default, Debug)]
3635
#[non_exhaustive]
37-
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
3836
pub enum ClientConnectorState {
3937
#[default]
4038
Consumed,
@@ -122,7 +120,6 @@ impl State for ClientConnectorState {
122120
}
123121

124122
#[derive(Debug)]
125-
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
126123
pub struct ClientConnector {
127124
pub config: Config,
128125
pub state: ClientConnectorState,

crates/ironrdp-connector/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub use crate::license_exchange::LicenseCache;
3434

3535
/// Provides user-friendly error messages for RDP negotiation failures
3636
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
3738
pub struct NegotiationFailure(ironrdp_pdu::nego::FailureCode);
3839

3940
impl NegotiationFailure {
@@ -97,6 +98,7 @@ pub struct BitmapConfig {
9798
}
9899

99100
#[derive(Debug, Clone)]
101+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
100102
pub struct SmartCardIdentity {
101103
/// DER-encoded X509 certificate
102104
pub certificate: Vec<u8>,
@@ -111,6 +113,7 @@ pub struct SmartCardIdentity {
111113
}
112114

113115
#[derive(Debug, Clone)]
116+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
114117
pub enum Credentials {
115118
UsernamePassword {
116119
username: String,
@@ -233,6 +236,11 @@ pub struct Config {
233236
pub enable_audio_playback: bool,
234237
pub performance_flags: PerformanceFlags,
235238

239+
// `Arc<dyn LicenseCache>` cannot be generated by `arbitrary`. Skipped via
240+
// `arbitrary(default)` which yields `None`. License-cache-dependent paths
241+
// are not exercised under fuzz; a fuzz-mode `LicenseCache` impl is a
242+
// possible follow-up.
243+
#[cfg_attr(feature = "arbitrary", arbitrary(default))]
236244
pub license_cache: Option<Arc<dyn LicenseCache>>,
237245

238246
// For Timezone Redirection to sync the server's timezone with the client's.
@@ -296,6 +304,7 @@ impl State for () {
296304
}
297305

298306
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
307+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
299308
pub enum Written {
300309
Nothing,
301310
Size(core::num::NonZeroUsize),
@@ -342,6 +351,8 @@ pub type ConnectorResult<T> = Result<T, ConnectorError>;
342351

343352
#[non_exhaustive]
344353
#[derive(Debug)]
354+
// ConnectorErrorKind carries foreign error types (sspi::Error, ironrdp_error::Error)
355+
// that do not implement Arbitrary, so this enum is intentionally not annotated.
345356
pub enum ConnectorErrorKind {
346357
Encode(ironrdp_core::EncodeError),
347358
Decode(ironrdp_core::DecodeError),

crates/ironrdp-connector/src/license_exchange.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ impl State for LicenseExchangeState {
5656
///
5757
/// [3.1.5.3.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpele/8f9b860a-3687-401d-b3bc-7e9f5d4f7528
5858
#[derive(Debug)]
59-
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
6059
pub struct LicenseExchangeSequence {
6160
pub state: LicenseExchangeState,
6261
pub io_channel_id: u16,
@@ -66,6 +65,29 @@ pub struct LicenseExchangeSequence {
6665
pub license_cache: Arc<dyn LicenseCache>,
6766
}
6867

68+
// `license_cache` is `Arc<dyn LicenseCache>` which cannot be derived. The hand-rolled impl
69+
// hardcodes `NoopLicenseCache`, so license-cache-dependent paths are not exercised under fuzz.
70+
// Reaching the license sequence is still useful coverage (MS-RDPELE is a historically
71+
// interesting target). A fuzz-mode `LicenseCache` impl is a possible follow-up.
72+
#[cfg(feature = "arbitrary")]
73+
impl<'a> arbitrary::Arbitrary<'a> for LicenseExchangeSequence {
74+
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
75+
Ok(Self {
76+
state: LicenseExchangeState::arbitrary(u)?,
77+
io_channel_id: u16::arbitrary(u)?,
78+
username: String::arbitrary(u)?,
79+
domain: Option::<String>::arbitrary(u)?,
80+
hardware_id: [
81+
u32::arbitrary(u)?,
82+
u32::arbitrary(u)?,
83+
u32::arbitrary(u)?,
84+
u32::arbitrary(u)?,
85+
],
86+
license_cache: Arc::new(NoopLicenseCache),
87+
})
88+
}
89+
}
90+
6991
// Use RefUnwindSafe so that types that embed LicenseCache remain UnwindSafe
7092
pub trait LicenseCache: Sync + Send + Debug + RefUnwindSafe {
7193
fn get_license(&self, license_info: LicenseInformation) -> ConnectorResult<Option<Vec<u8>>>;

crates/ironrdp-pdu/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ std = ["alloc", "ironrdp-error/std", "ironrdp-core/std"]
2222
alloc = ["ironrdp-core/alloc", "ironrdp-error/alloc"]
2323
qoi = []
2424
qoiz = ["qoi"]
25+
arbitrary = ["alloc", "dep:arbitrary", "bitflags/arbitrary"]
2526

2627
[dependencies]
2728
bitflags = "2.11"
2829
ironrdp-core = { path = "../ironrdp-core", version = "0.1", features = ["std"] } # public
2930
ironrdp-error = { path = "../ironrdp-error", version = "0.1" } # public
31+
arbitrary = { version = "1", features = ["derive"], optional = true }
3032
tap = "1"
3133

3234
# TODO: get rid of these dependencies (related code should probably go into another crate)

crates/ironrdp-pdu/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,15 @@ are considered to be known and defined. In such cases, `from_bits` also never fa
468468
precisely the same as `from_bits_retain`, except it’s less ergonomic because it returns a `Result`
469469
which must be needlessly handled.
470470

471+
## Feature Flags
472+
473+
- `std` (depends on `alloc`): enables `std` integration in `ironrdp-error` and `ironrdp-core`.
474+
- `alloc`: enables allocation-dependent code paths while keeping `no_std`-compatible.
475+
- `qoi`, `qoiz`: optional [QOI image format](https://qoiformat.org/) bitmap codec support.
476+
- `arbitrary` (depends on `alloc`): enables [`arbitrary::Arbitrary`](https://docs.rs/arbitrary)
477+
implementations on PDU types for use under structure-aware fuzzing. See
478+
[`fuzz/README.md`](../../fuzz/README.md) for the fuzz target organization.
479+
471480
This crate is part of the [IronRDP] project.
472481

473482
[IronRDP]: https://github.com/Devolutions/IronRDP

crates/ironrdp-pdu/src/basic_output/bitmap/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const FIRST_ROW_SIZE_VALUE: u16 = 0;
1717

1818
/// TS_UPDATE_BITMAP_DATA
1919
#[derive(Debug, Clone, PartialEq, Eq)]
20+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2021
pub struct BitmapUpdateData<'a> {
2122
pub rectangles: Vec<BitmapData<'a>>,
2223
}
@@ -85,6 +86,7 @@ impl<'de> Decode<'de> for BitmapUpdateData<'de> {
8586

8687
/// TS_BITMAP_DATA
8788
#[derive(Clone, PartialEq, Eq)]
89+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
8890
pub struct BitmapData<'a> {
8991
pub rectangle: InclusiveRectangle,
9092
pub width: u16,
@@ -197,6 +199,7 @@ impl Debug for BitmapData<'_> {
197199

198200
/// TS_CD_HEADER
199201
#[derive(Debug, Clone, PartialEq, Eq)]
202+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
200203
pub struct CompressedDataHeader {
201204
pub main_body_size: u16,
202205
pub scan_width: u16,
@@ -267,6 +270,7 @@ impl Encode for CompressedDataHeader {
267270

268271
bitflags! {
269272
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
273+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
270274
pub struct BitmapFlags: u16{
271275
const BITMAP_UPDATE_TYPE = 0x0001;
272276

@@ -276,6 +280,7 @@ bitflags! {
276280

277281
bitflags! {
278282
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
283+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
279284
pub struct Compression: u16 {
280285
const BITMAP_COMPRESSION = 0x0001;
281286
const NO_BITMAP_COMPRESSION_HDR = 0x0400;

crates/ironrdp-pdu/src/basic_output/bitmap/rdp6.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use ironrdp_core::{
66
const NON_RLE_PADDING_SIZE: usize = 1;
77

88
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
910
pub enum ColorPlaneDefinition {
1011
Argb,
1112
AYCoCg {
@@ -15,6 +16,7 @@ pub enum ColorPlaneDefinition {
1516
}
1617

1718
#[derive(Debug, Clone, PartialEq, Eq)]
19+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1820
pub struct BitmapStreamHeader {
1921
pub enable_rle_compression: bool,
2022
pub use_alpha: bool,
@@ -93,6 +95,7 @@ impl Encode for BitmapStreamHeader {
9395

9496
/// Represents `RDP6_BITMAP_STREAM` structure described in [MS-RDPEGDI] 2.2.2.5.1
9597
#[derive(Debug, Clone)]
98+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
9699
pub struct BitmapStream<'a> {
97100
pub header: BitmapStreamHeader,
98101
pub color_planes: &'a [u8],

crates/ironrdp-pdu/src/basic_output/fast_path/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use crate::rdp::headers::{CompressionFlags, SHARE_DATA_HEADER_COMPRESSION_MASK};
2424
reason = "this structure is used in the match expression in the integration tests"
2525
)]
2626
#[derive(Debug, Clone, PartialEq, Eq)]
27+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
2728
pub struct FastPathHeader {
2829
pub flags: EncryptionFlags,
2930
pub data_length: usize,
@@ -116,6 +117,7 @@ impl<'de> Decode<'de> for FastPathHeader {
116117

117118
/// TS_FP_UPDATE
118119
#[derive(Debug, Clone, PartialEq, Eq)]
120+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
119121
pub struct FastPathUpdatePdu<'a> {
120122
pub fragmentation: Fragmentation,
121123
pub update_code: UpdateCode,
@@ -217,6 +219,7 @@ impl<'de> Decode<'de> for FastPathUpdatePdu<'de> {
217219

218220
/// TS_FP_UPDATE data
219221
#[derive(Debug, Clone, PartialEq, Eq)]
222+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
220223
pub enum FastPathUpdate<'a> {
221224
SurfaceCommands(Vec<SurfaceCommand<'a>>),
222225
Bitmap(BitmapUpdateData<'a>),
@@ -329,6 +332,7 @@ impl Encode for FastPathUpdate<'_> {
329332

330333
#[repr(u8)]
331334
#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)]
335+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
332336
pub enum UpdateCode {
333337
Orders = 0x0,
334338
Bitmap = 0x1,
@@ -375,6 +379,7 @@ impl From<&FastPathUpdate<'_>> for UpdateCode {
375379

376380
#[repr(u8)]
377381
#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)]
382+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
378383
pub enum Fragmentation {
379384
Single = 0x0,
380385
Last = 0x1,
@@ -394,6 +399,7 @@ impl Fragmentation {
394399

395400
bitflags! {
396401
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
402+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
397403
pub struct EncryptionFlags: u8 {
398404
const SECURE_CHECKSUM = 0x1;
399405
const ENCRYPTED = 0x2;
@@ -404,6 +410,7 @@ bitflags! {
404410

405411
bitflags! {
406412
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
413+
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
407414
pub struct Compression: u8 {
408415
const COMPRESSION_USED = 0x2;
409416

0 commit comments

Comments
 (0)