Skip to content

Commit ac13b85

Browse files
ci: enable fuzzing the rust implementation under very specific edge cases (#55)
I found a set of edgecases under which the rust implementation does not crash.. They are very specific though.. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent bcdd8ee commit ac13b85

3 files changed

Lines changed: 72 additions & 20 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ jobs:
5757
components: rustfmt
5858
- run: just ci_mode=0 ci-test-msrv # Ignore warnings in MSRV
5959
fuzz:
60+
name: Fuzz
6061
runs-on: ubuntu-latest
6162

6263
env:
@@ -87,7 +88,6 @@ jobs:
8788
# --target x86_64-unknown-linux-gnu is necessary to not default to musl, which is not supported by cargo-fuzz.
8889
- run: cargo fuzz build --target x86_64-unknown-linux-gnu ${{ matrix.fuzz_target }}
8990
- run: cargo fuzz run --target x86_64-unknown-linux-gnu ${{ matrix.fuzz_target }} -- -max_total_time=${{ env.FUZZ_TIME }}
90-
if: ${{ matrix.fuzz_target != 'fastpfor_rust' }} # fastpfor_rust does not pass this
9191

9292
# Upload fuzzing artifacts on failure for post-mortem debugging.
9393
- uses: actions/upload-artifact@v4

fuzz/fuzz_targets/fastpfor_cpp.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,21 @@ fuzz_target!(|data: FuzzInput| {
3434
assert_eq!(dec_slice.len(), input.len());
3535
});
3636

37-
#[derive(arbitrary::Arbitrary, Debug)]
37+
#[derive(arbitrary::Arbitrary)]
3838
struct FuzzInput {
3939
data: Vec<u32>,
4040
codec: FuzzCodec,
4141
}
4242

43+
impl std::fmt::Debug for FuzzInput {
44+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45+
f.debug_struct("FuzzInput")
46+
.field("data_length", &self.data.len())
47+
.field("codec", &self.codec)
48+
.finish()
49+
}
50+
}
51+
4352
#[derive(Clone, Copy, Eq, PartialEq, arbitrary::Arbitrary, Debug)]
4453
enum FuzzCodec {
4554
BP32Codec,

fuzz/fuzz_targets/fastpfor_rust.rs

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,50 @@
22

33
use std::io::Cursor;
44

5-
use fastpfor::rust::{BLOCK_SIZE_256, DEFAULT_PAGE_SIZE, FastPFOR, Integer};
5+
use fastpfor::rust::{FastPFOR, Integer, BLOCK_SIZE_128, BLOCK_SIZE_256, DEFAULT_PAGE_SIZE};
66
use libfuzzer_sys::fuzz_target;
7+
use std::num::NonZeroU32;
78

8-
fuzz_target!(|input_data: Vec<u32>| {
9-
let mut codec = FastPFOR::new(DEFAULT_PAGE_SIZE, BLOCK_SIZE_256);
9+
fuzz_target!(|data: FuzzInput| {
10+
let input = data.data;
11+
let block_size = NonZeroU32::from(data.codec);
12+
let mut codec = FastPFOR::new(DEFAULT_PAGE_SIZE, block_size);
1013

11-
// Limit input size to avoid timeouts
12-
let input_data: Vec<u32> = input_data.into_iter().take(10_000).collect();
14+
// TODO: empty input is encoded as empty, which does not match the CPP version
15+
if input.is_empty() {
16+
return;
17+
}
18+
19+
// TODO: only multiples of block size seem to be exported from the compress - decompress cycle
20+
let bs = block_size.get() as usize;
21+
if input.len() < bs {
22+
return;
23+
}
24+
let last_block_size_multiple = input.len() / bs * bs;
25+
let input = input
26+
.into_iter()
27+
.take(last_block_size_multiple as usize)
28+
.collect::<Vec<_>>();
1329

1430
// Allocate output buffer with generous size
15-
let mut compressed = vec![0u32; input_data.len() * 2 + 1024];
31+
let mut compressed = vec![0u32; input.len() * 2 + 1024];
1632

1733
// Compress the data
1834
let mut output_offset = Cursor::new(0);
1935
codec
2036
.compress(
21-
&input_data,
22-
input_data.len() as u32,
37+
&input,
38+
input.len() as u32,
2339
&mut Cursor::new(0),
2440
&mut compressed,
2541
&mut output_offset,
2642
)
2743
.unwrap();
2844
let compressed_size = output_offset.position() as u32;
29-
if !input_data.is_empty() {
30-
assert!(compressed_size != 0, "compression should not be empty");
31-
}
45+
assert!(compressed_size != 0, "compression should not be empty");
3246

3347
// Now decompress
34-
let mut decompressed = vec![0u32; input_data.len()];
48+
let mut decompressed = vec![0u32; input.len()];
3549
let mut output_offset = Cursor::new(0);
3650

3751
codec
@@ -46,21 +60,50 @@ fuzz_target!(|input_data: Vec<u32>| {
4660
let decompressed_length = output_offset.position() as usize;
4761

4862
// Verify roundtrip
49-
if decompressed_length + input_data.len() < 200 {
63+
assert_eq!(decompressed_length, input.len());
64+
if decompressed_length + input.len() < 200 {
5065
assert_eq!(
51-
input_data,
66+
input,
5267
decompressed[..decompressed_length],
53-
"Decompressed length mismatch: expected {}, got {decompressed_length}",
54-
input_data.len()
68+
"Decompressed mismatch: expected {}, got {decompressed_length}",
69+
input.len()
5570
);
5671
} else {
57-
for (i, (&original, &decoded)) in input_data.iter().zip(decompressed.iter()).enumerate() {
72+
for (i, (&original, &decoded)) in input.iter().zip(decompressed.iter()).enumerate() {
5873
assert_eq!(
5974
original, decoded,
6075
"Mismatch at position {}: expected {}, got {}",
6176
i, original, decoded
6277
);
6378
}
6479
}
65-
assert_eq!(decompressed_length, input_data.len());
6680
});
81+
82+
#[derive(arbitrary::Arbitrary)]
83+
struct FuzzInput {
84+
data: Vec<u32>,
85+
codec: FuzzCodec,
86+
}
87+
88+
impl std::fmt::Debug for FuzzInput {
89+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90+
f.debug_struct("FuzzInput")
91+
.field("data_length", &self.data.len())
92+
.field("codec", &self.codec)
93+
.finish()
94+
}
95+
}
96+
97+
#[derive(arbitrary::Arbitrary, Debug, Clone, Copy, PartialEq, Eq)]
98+
enum FuzzCodec {
99+
FastPFOR256,
100+
FastPFOR128,
101+
}
102+
impl From<FuzzCodec> for NonZeroU32 {
103+
fn from(codec: FuzzCodec) -> Self {
104+
match codec {
105+
FuzzCodec::FastPFOR256 => BLOCK_SIZE_256,
106+
FuzzCodec::FastPFOR128 => BLOCK_SIZE_128,
107+
}
108+
}
109+
}

0 commit comments

Comments
 (0)