From 04924757cbc2478ce76901ef0b73191175e7af1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20CORTIER?= Date: Wed, 10 May 2023 20:19:07 -0400 Subject: [PATCH] test: extract fuzzing oracles into another crate --- Cargo.lock | 11 ++- Cargo.toml | 1 + crates/ironrdp-fuzzing/Cargo.toml | 15 +++ crates/ironrdp-fuzzing/src/generators/mod.rs | 16 ++++ crates/ironrdp-fuzzing/src/lib.rs | 5 + crates/ironrdp-fuzzing/src/oracles/mod.rs | 98 ++++++++++++++++++++ crates/ironrdp-testsuite-core/Cargo.toml | 17 ++-- fuzz/Cargo.lock | 9 +- fuzz/Cargo.toml | 6 +- fuzz/fuzz_targets/bitmap_stream.rs | 19 +--- fuzz/fuzz_targets/pdu_decoding.rs | 60 +----------- fuzz/fuzz_targets/rle_decompression.rs | 16 +--- 12 files changed, 168 insertions(+), 105 deletions(-) create mode 100644 crates/ironrdp-fuzzing/Cargo.toml create mode 100644 crates/ironrdp-fuzzing/src/generators/mod.rs create mode 100644 crates/ironrdp-fuzzing/src/lib.rs create mode 100644 crates/ironrdp-fuzzing/src/oracles/mod.rs diff --git a/Cargo.lock b/Cargo.lock index c09f9c57..0deff951 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1560,6 +1560,15 @@ dependencies = [ "ironrdp-async", ] +[[package]] +name = "ironrdp-fuzzing" +version = "0.0.0" +dependencies = [ + "arbitrary", + "ironrdp-graphics", + "ironrdp-pdu", +] + [[package]] name = "ironrdp-graphics" version = "0.1.0" @@ -1649,10 +1658,10 @@ name = "ironrdp-testsuite-core" version = "0.0.0" dependencies = [ "anyhow", - "arbitrary", "array-concat", "hex", "ironrdp-connector", + "ironrdp-fuzzing", "ironrdp-graphics", "ironrdp-input", "ironrdp-pdu", diff --git a/Cargo.toml b/Cargo.toml index 1871b4e7..da40b00a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ expect-test = "1" ironrdp-async = { version = "0.1", path = "crates/ironrdp-async" } ironrdp-connector = { version = "0.1", path = "crates/ironrdp-connector" } ironrdp-futures = { version = "0.1", path = "crates/ironrdp-futures" } +ironrdp-fuzzing = { path = "crates/ironrdp-fuzzing" } ironrdp-graphics = { version = "0.1", path = "crates/ironrdp-graphics" } ironrdp-input = { version = "0.1", path = "crates/ironrdp-input" } ironrdp-pdu-generators = { path = "crates/ironrdp-pdu-generators" } diff --git a/crates/ironrdp-fuzzing/Cargo.toml b/crates/ironrdp-fuzzing/Cargo.toml new file mode 100644 index 00000000..39516d77 --- /dev/null +++ b/crates/ironrdp-fuzzing/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ironrdp-fuzzing" +version = "0.0.0" +edition = "2021" +description = "Provides test case generators and oracles for use with IronRDP fuzzing" +publish = false + +[lib] +doctest = false +test = false + +[dependencies] +arbitrary = { version = "1", features = ["derive"] } +ironrdp-graphics.workspace = true +ironrdp-pdu.workspace = true diff --git a/crates/ironrdp-fuzzing/src/generators/mod.rs b/crates/ironrdp-fuzzing/src/generators/mod.rs new file mode 100644 index 00000000..3d56942e --- /dev/null +++ b/crates/ironrdp-fuzzing/src/generators/mod.rs @@ -0,0 +1,16 @@ +//! Test case generators. +//! +//! Test case generators take raw, unstructured input from a fuzzer +//! (e.g. libFuzzer) and translate that into a structured test case (e.g. a +//! valid RDP PDU). +//! +//! These are generally implementations of the `Arbitrary` trait, or some +//! wrapper over an external tool, such that the wrapper implements the +//! `Arbitrary` trait for the wrapped external tool. + +#[derive(Arbitrary, Debug)] +pub struct BitmapInput<'a> { + pub src: &'a [u8], + pub width: u8, + pub height: u8, +} diff --git a/crates/ironrdp-fuzzing/src/lib.rs b/crates/ironrdp-fuzzing/src/lib.rs new file mode 100644 index 00000000..7ce9430d --- /dev/null +++ b/crates/ironrdp-fuzzing/src/lib.rs @@ -0,0 +1,5 @@ +#[macro_use] +extern crate arbitrary; + +pub mod generators; +pub mod oracles; diff --git a/crates/ironrdp-fuzzing/src/oracles/mod.rs b/crates/ironrdp-fuzzing/src/oracles/mod.rs new file mode 100644 index 00000000..49edc37c --- /dev/null +++ b/crates/ironrdp-fuzzing/src/oracles/mod.rs @@ -0,0 +1,98 @@ +//! Oracles. +//! +//! Oracles take a test case and determine whether we have a bug. For example, +//! one of the simplest oracles is to take a RDP PDU as our input test case, +//! encode and decode it, and (implicitly) check that no assertions +//! failed or segfaults happened. A more complicated oracle might compare the +//! result of two different implementations for the same thing, and +//! make sure that the two executions are observably identical (differential fuzzing). +//! +//! When an oracle finds a bug, it should report it to the fuzzing engine by +//! panicking. + +use crate::generators::BitmapInput; + +pub fn pdu_decode(data: &[u8]) { + use ironrdp_pdu::mcs::*; + use ironrdp_pdu::nego::*; + use ironrdp_pdu::rdp::*; + use ironrdp_pdu::*; + + let _ = decode::(data); + let _ = decode::(data); + let _ = decode::(data); + let _ = ConnectInitial::from_buffer(data); + let _ = ConnectResponse::from_buffer(data); + let _ = ClientInfoPdu::from_buffer(data); + let _ = capability_sets::CapabilitySet::from_buffer(data); + let _ = headers::ShareControlHeader::from_buffer(data); + let _ = decode::(data); + let _ = server_error_info::ServerSetErrorInfoPdu::from_buffer(data); + + let _ = gcc::ClientGccBlocks::from_buffer(data); + let _ = gcc::ServerGccBlocks::from_buffer(data); + let _ = gcc::ClientClusterData::from_buffer(data); + let _ = gcc::ConferenceCreateRequest::from_buffer(data); + let _ = gcc::ConferenceCreateResponse::from_buffer(data); + + let _ = server_license::ClientNewLicenseRequest::from_buffer(data); + let _ = server_license::ClientPlatformChallengeResponse::from_buffer(data); + let _ = server_license::InitialServerLicenseMessage::from_buffer(data); + let _ = server_license::ServerLicenseRequest::from_buffer(data); + let _ = server_license::InitialServerLicenseMessage::from_buffer(data); + let _ = server_license::ServerPlatformChallenge::from_buffer(data); + + let _ = vc::ChannelPduHeader::from_buffer(data); + + 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 _ = surface_commands::SurfaceCommand::from_buffer(data); + let _ = surface_commands::SurfaceBitsPdu::from_buffer(data); + let _ = surface_commands::FrameMarkerPdu::from_buffer(data); + let _ = surface_commands::ExtendedBitmapDataPdu::from_buffer(data); + let _ = surface_commands::BitmapDataHeader::from_buffer(data); + + let _ = codecs::rfx::Headers::from_buffer(data); + let _ = codecs::rfx::FrameAcknowledgePdu::from_buffer(data); + let _ = codecs::rfx::ContextPdu::from_buffer(data); + let _ = codecs::rfx::FrameBeginPdu::from_buffer(data); + let _ = codecs::rfx::FrameEndPdu::from_buffer(data); + let _ = codecs::rfx::RegionPdu::from_buffer(data); + let _ = codecs::rfx::TileSetPdu::from_buffer(data); + let _ = codecs::rfx::RfxRectangle::from_buffer(data); + let _ = codecs::rfx::Quant::from_buffer(data); + let _ = codecs::rfx::Tile::from_buffer(data); + let _ = codecs::rfx::SyncPdu::from_buffer(data); + let _ = codecs::rfx::CodecVersionsPdu::from_buffer(data); + let _ = codecs::rfx::ChannelsPdu::from_buffer(data); + let _ = codecs::rfx::Channel::from_buffer(data); + + let _ = input::InputEventPdu::from_buffer(data); + let _ = input::InputEvent::from_buffer(data); + + let _ = decode::(data); +} + +pub fn rle_decompress_bitmap(input: BitmapInput) { + let mut out = Vec::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); +} + +pub fn rdp6_decode_bitmap_stream_to_rgb24(input: BitmapInput) { + use ironrdp_graphics::rdp6::BitmapStreamDecoder; + + let mut out = Vec::new(); + + let _ = BitmapStreamDecoder::default().decode_bitmap_stream_to_rgb24( + input.src, + &mut out, + input.width as usize, + input.height as usize, + ); +} diff --git a/crates/ironrdp-testsuite-core/Cargo.toml b/crates/ironrdp-testsuite-core/Cargo.toml index 1c046488..222851cf 100644 --- a/crates/ironrdp-testsuite-core/Cargo.toml +++ b/crates/ironrdp-testsuite-core/Cargo.toml @@ -2,7 +2,7 @@ name = "ironrdp-testsuite-core" version = "0.0.0" edition = "2021" -description = "IronRDP test suite." +description = "IronRDP test suite" publish = false autotests = false @@ -16,22 +16,21 @@ path = "tests/main.rs" harness = true [dependencies] -ironrdp-pdu.workspace = true -ironrdp-graphics.workspace = true -arbitrary = { version = "1", features = ["derive"] } anyhow = "1" array-concat = "0.5.2" +ironrdp-pdu.workspace = true lazy_static = "1.4.0" paste = "1" [dev-dependencies] +hex = "0.4.3" ironrdp-connector.workspace = true -ironrdp-session.workspace = true +ironrdp-fuzzing.workspace = true +ironrdp-graphics.workspace = true ironrdp-input.workspace = true ironrdp-rdcleanpath.workspace = true -proptest.workspace = true -rstest.workspace = true -hex = "0.4.3" +ironrdp-session.workspace = true pretty_assertions = "1.3.0" +proptest.workspace = true rdp-rs = { git = "https://github.com/citronneur/rdp-rs", rev = "7ac880d7efb7f05efef3c84476f7c24f4053e0ea" } - +rstest.workspace = true diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 443fb686..b3fb42f5 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -234,11 +234,18 @@ dependencies = [ [[package]] name = "ironrdp-fuzz" version = "0.0.0" +dependencies = [ + "ironrdp-fuzzing", + "libfuzzer-sys", +] + +[[package]] +name = "ironrdp-fuzzing" +version = "0.0.0" dependencies = [ "arbitrary", "ironrdp-graphics", "ironrdp-pdu", - "libfuzzer-sys", ] [[package]] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index d01a1b44..adcbe334 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "ironrdp-fuzz" version = "0.0.0" -publish = false edition = "2021" +publish = false [package.metadata] cargo-fuzz = true @@ -14,10 +14,8 @@ members = ["."] debug = 1 [dependencies] -ironrdp-pdu = { path = "../crates/ironrdp-pdu" } -ironrdp-graphics = { path = "../crates/ironrdp-graphics" } +ironrdp-fuzzing = { path = "../crates/ironrdp-fuzzing" } libfuzzer-sys = "0.4" -arbitrary = { version = "1", features = ["derive"] } [[bin]] name = "pdu_decoding" diff --git a/fuzz/fuzz_targets/bitmap_stream.rs b/fuzz/fuzz_targets/bitmap_stream.rs index 80e5f758..475290d1 100644 --- a/fuzz/fuzz_targets/bitmap_stream.rs +++ b/fuzz/fuzz_targets/bitmap_stream.rs @@ -1,22 +1,7 @@ #![no_main] -use ironrdp_graphics::rdp6::BitmapStreamDecoder; 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 = Vec::new(); - - let _ = BitmapStreamDecoder::default().decode_bitmap_stream_to_rgb24( - input.src, - &mut out, - input.width as usize, - input.height as usize, - ); +fuzz_target!(|input: ironrdp_fuzzing::generators::BitmapInput<'_>| { + ironrdp_fuzzing::oracles::rdp6_decode_bitmap_stream_to_rgb24(input); }); diff --git a/fuzz/fuzz_targets/pdu_decoding.rs b/fuzz/fuzz_targets/pdu_decoding.rs index 04a3e24c..af53fa2d 100644 --- a/fuzz/fuzz_targets/pdu_decoding.rs +++ b/fuzz/fuzz_targets/pdu_decoding.rs @@ -1,65 +1,7 @@ #![no_main] -use ironrdp_pdu::mcs::*; -use ironrdp_pdu::nego::*; -use ironrdp_pdu::rdp::*; -use ironrdp_pdu::*; use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { - let _ = decode::(data); - let _ = decode::(data); - let _ = decode::(data); - let _ = ConnectInitial::from_buffer(data); - let _ = ConnectResponse::from_buffer(data); - let _ = ClientInfoPdu::from_buffer(data); - let _ = capability_sets::CapabilitySet::from_buffer(data); - let _ = headers::ShareControlHeader::from_buffer(data); - let _ = decode::(data); - let _ = server_error_info::ServerSetErrorInfoPdu::from_buffer(data); - - let _ = gcc::ClientGccBlocks::from_buffer(data); - let _ = gcc::ServerGccBlocks::from_buffer(data); - let _ = gcc::ClientClusterData::from_buffer(data); - let _ = gcc::ConferenceCreateRequest::from_buffer(data); - let _ = gcc::ConferenceCreateResponse::from_buffer(data); - - let _ = server_license::ClientNewLicenseRequest::from_buffer(data); - let _ = server_license::ClientPlatformChallengeResponse::from_buffer(data); - let _ = server_license::InitialServerLicenseMessage::from_buffer(data); - let _ = server_license::ServerLicenseRequest::from_buffer(data); - let _ = server_license::InitialServerLicenseMessage::from_buffer(data); - let _ = server_license::ServerPlatformChallenge::from_buffer(data); - - let _ = vc::ChannelPduHeader::from_buffer(data); - - 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 _ = surface_commands::SurfaceCommand::from_buffer(data); - let _ = surface_commands::SurfaceBitsPdu::from_buffer(data); - let _ = surface_commands::FrameMarkerPdu::from_buffer(data); - let _ = surface_commands::ExtendedBitmapDataPdu::from_buffer(data); - let _ = surface_commands::BitmapDataHeader::from_buffer(data); - - let _ = codecs::rfx::Headers::from_buffer(data); - let _ = codecs::rfx::FrameAcknowledgePdu::from_buffer(data); - let _ = codecs::rfx::ContextPdu::from_buffer(data); - let _ = codecs::rfx::FrameBeginPdu::from_buffer(data); - let _ = codecs::rfx::FrameEndPdu::from_buffer(data); - let _ = codecs::rfx::RegionPdu::from_buffer(data); - let _ = codecs::rfx::TileSetPdu::from_buffer(data); - let _ = codecs::rfx::RfxRectangle::from_buffer(data); - let _ = codecs::rfx::Quant::from_buffer(data); - let _ = codecs::rfx::Tile::from_buffer(data); - let _ = codecs::rfx::SyncPdu::from_buffer(data); - let _ = codecs::rfx::CodecVersionsPdu::from_buffer(data); - let _ = codecs::rfx::ChannelsPdu::from_buffer(data); - let _ = codecs::rfx::Channel::from_buffer(data); - - let _ = input::InputEventPdu::from_buffer(data); - let _ = input::InputEvent::from_buffer(data); - - let _ = decode::(data); + ironrdp_fuzzing::oracles::pdu_decode(data); }); diff --git a/fuzz/fuzz_targets/rle_decompression.rs b/fuzz/fuzz_targets/rle_decompression.rs index f3e372aa..3a9e2152 100644 --- a/fuzz/fuzz_targets/rle_decompression.rs +++ b/fuzz/fuzz_targets/rle_decompression.rs @@ -2,18 +2,6 @@ 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 = Vec::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); +fuzz_target!(|input: ironrdp_fuzzing::generators::BitmapInput<'_>| { + ironrdp_fuzzing::oracles::rle_decompress_bitmap(input); });