Skip to content

Commit f516149

Browse files
committed
have decompress actually decompress arbitrary bytes
1 parent d5ad4df commit f516149

File tree

4 files changed

+119
-43
lines changed

4 files changed

+119
-43
lines changed

.github/workflows/checks.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,10 @@ jobs:
236236
include:
237237
- fuzz_target: decompress
238238
corpus: "bzip2-files/compressed"
239-
features: '--no-default-features --features="disable-checksum"'
240-
flags: fuzz-decompress
239+
features: '--no-default-features --features="disable-checksum,keep-invalid-in-corpus"'
241240
- fuzz_target: compress
242241
corpus: ""
243242
features: ''
244-
flags: fuzz-compress
245243
steps:
246244
- name: Checkout sources
247245
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
@@ -259,11 +257,13 @@ jobs:
259257
- name: Download custom decompression corpus
260258
if: ${{ contains(matrix.corpus, 'bzip2-files') }}
261259
run: |
262-
wget https://github.com/trifectatechfoundation/compression-corpus/releases/download/2025-04-14-151155/bzip2-files.zip
260+
wget https://github.com/trifectatechfoundation/compression-corpus/releases/download/2025-04-24-180855/bzip2-files.zip
263261
unzip bzip2-files.zip -d bzip2-files
264262
- name: Run `cargo fuzz`
265263
env:
266264
RUST_BACKTRACE: "1"
265+
# prevents `cargo fuzz coverage` from rebuilding everything
266+
RUSTFLAGS: "-C instrument-coverage"
267267
run: |
268268
cargo fuzz run ${{matrix.features}} ${{matrix.fuzz_target}} ${{matrix.corpus}} -- -max_total_time=20
269269
- name: Fuzz codecov
@@ -273,7 +273,7 @@ jobs:
273273
target/$(rustc --print host-tuple)/coverage/$(rustc --print host-tuple)/release/${{matrix.fuzz_target}} \
274274
-instr-profile=fuzz/coverage/${{matrix.fuzz_target}}/coverage.profdata \
275275
--format=lcov \
276-
-ignore-filename-regex="\.cargo|\.rustup|fuzz/src|test-libbz2" > lcov.info
276+
-ignore-filename-regex="\.cargo|\.rustup|fuzz_targets|test-libbz2" > lcov.info
277277
- name: List the corpus
278278
if: ${{ contains(matrix.corpus, 'bzip2-files') }}
279279
run: |
@@ -283,7 +283,7 @@ jobs:
283283
with:
284284
files: ./lcov.info
285285
fail_ci_if_error: false
286-
flags: ${{ matrix.flags }}
286+
flags: fuzz-${{ matrix.fuzz_target }}
287287
token: ${{ secrets.CODECOV_TOKEN }}
288288
name: fuzz
289289

fuzz/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ default = ["rust-allocator"]
1616
c-allocator = ["libbz2-rs-sys/c-allocator"]
1717
rust-allocator = ["libbz2-rs-sys/rust-allocator"]
1818
disable-checksum = ["libbz2-rs-sys/__internal-fuzz-disable-checksum"]
19+
keep-invalid-in-corpus = [] # For code coverage (on CI), we want to keep inputs that triggered the error branches
1920

2021
[dependencies.libfuzzer-sys]
2122
version = "0.4"
@@ -64,3 +65,9 @@ name = "compress"
6465
path = "fuzz_targets/compress.rs"
6566
test = false
6667
doc = false
68+
69+
[[bin]]
70+
name = "end_to_end"
71+
path = "fuzz_targets/end_to_end.rs"
72+
test = false
73+
doc = false

fuzz/fuzz_targets/decompress.rs

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,69 @@
11
#![no_main]
2-
use libbz2_rs_sys::BZ_OK;
3-
use libfuzzer_sys::fuzz_target;
42

5-
fn decompress_help(input: &[u8]) -> Vec<u8> {
6-
let mut dest_vec = vec![0u8; 1 << 16];
3+
use libbz2_rs_sys::{bz_stream, BZ2_bzDecompress, BZ2_bzDecompressEnd, BZ2_bzDecompressInit};
4+
use libfuzzer_sys::{fuzz_target, Corpus};
75

8-
let mut dest_len = dest_vec.len() as _;
9-
let dest = dest_vec.as_mut_ptr();
6+
fuzz_target!(|input: &[u8]| -> Corpus { decompress_help(input) });
107

11-
let source = input.as_ptr();
12-
let source_len = input.len() as _;
8+
fn decompress_help(source: &[u8]) -> Corpus {
9+
let mut strm: bz_stream = bz_stream::zeroed();
1310

14-
let err = unsafe { test_libbz2_rs_sys::decompress_rs(dest, &mut dest_len, source, source_len) };
11+
// Pick either small or fast based on a byte of input data.
12+
let small = source.get(source.len() / 2).map_or(false, |v| v % 2 == 0);
1513

16-
if err != BZ_OK {
17-
panic!("error {:?}", err);
18-
}
19-
20-
dest_vec.truncate(dest_len as usize);
14+
let ret = unsafe { BZ2_bzDecompressInit(&mut strm, 0, small as _) };
15+
assert_eq!(ret, libbz2_rs_sys::BZ_OK);
2116

22-
dest_vec
23-
}
17+
// Small enough to hit interesting cases, but large enough to hit the fast path
18+
let chunk_size = 16;
2419

25-
fuzz_target!(|data: Vec<u8>| {
26-
let mut length = 8 * 1024;
27-
let mut deflated = vec![0; length as usize];
28-
29-
let error = unsafe {
30-
test_libbz2_rs_sys::compress_c(
31-
deflated.as_mut_ptr().cast(),
32-
&mut length,
33-
data.as_ptr().cast(),
34-
data.len() as _,
35-
9,
36-
)
20+
// For code coverage (on CI), we want to keep inputs that triggered the error
21+
// branches, to get an accurate picture of what error paths we actually hit.
22+
//
23+
// It helps that on CI we start with a corpus of valid files: a mutation of such an
24+
// input is not a sequence of random bytes, but rather quite close to correct and
25+
// hence likely to hit interesting error conditions.
26+
let invalid_input = if cfg!(feature = "keep-invalid-in-corpus") {
27+
Corpus::Keep
28+
} else {
29+
Corpus::Reject
3730
};
3831

39-
assert_eq!(error, BZ_OK);
32+
let mut output = vec![0u8; source.len()];
33+
let output_len = output.len() as _;
4034

41-
deflated.truncate(length as _);
35+
strm.next_out = output.as_mut_ptr().cast::<core::ffi::c_char>();
36+
strm.avail_out = output_len;
4237

43-
let output = decompress_help(&deflated);
38+
for chunk in source.chunks(chunk_size) {
39+
strm.next_in = chunk.as_ptr() as *mut core::ffi::c_char;
40+
strm.avail_in = chunk.len() as _;
4441

45-
if output != data {
46-
let path = std::env::temp_dir().join("deflate.txt");
47-
std::fs::write(&path, &data).unwrap();
48-
eprintln!("saved input file to {path:?}");
42+
match unsafe { BZ2_bzDecompress(&mut strm) } {
43+
libbz2_rs_sys::BZ_STREAM_END => {
44+
break;
45+
}
46+
libbz2_rs_sys::BZ_OK => {
47+
continue;
48+
}
49+
libbz2_rs_sys::BZ_FINISH_OK | libbz2_rs_sys::BZ_OUTBUFF_FULL => {
50+
let add_space: u32 = Ord::max(1024, output.len().try_into().unwrap());
51+
output.resize(output.len() + add_space as usize, 0);
52+
53+
// If resize() reallocates, it may have moved in memory.
54+
strm.next_out = output.as_mut_ptr().cast::<core::ffi::c_char>();
55+
strm.avail_out += add_space;
56+
}
57+
_ => {
58+
unsafe { BZ2_bzDecompressEnd(&mut strm) };
59+
return invalid_input;
60+
}
61+
}
4962
}
5063

51-
assert_eq!(output, data);
52-
});
64+
let err = unsafe { BZ2_bzDecompressEnd(&mut strm) };
65+
match err {
66+
libbz2_rs_sys::BZ_OK => Corpus::Keep,
67+
_ => invalid_input,
68+
}
69+
}

fuzz/fuzz_targets/end_to_end.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#![no_main]
2+
use libbz2_rs_sys::BZ_OK;
3+
use libfuzzer_sys::fuzz_target;
4+
5+
fn decompress_help(input: &[u8]) -> Vec<u8> {
6+
let mut dest_vec = vec![0u8; 1 << 16];
7+
8+
let mut dest_len = dest_vec.len() as _;
9+
let dest = dest_vec.as_mut_ptr();
10+
11+
let source = input.as_ptr();
12+
let source_len = input.len() as _;
13+
14+
let err = unsafe { test_libbz2_rs_sys::decompress_rs(dest, &mut dest_len, source, source_len) };
15+
16+
if err != BZ_OK {
17+
panic!("error {:?}", err);
18+
}
19+
20+
dest_vec.truncate(dest_len as usize);
21+
22+
dest_vec
23+
}
24+
25+
fuzz_target!(|data: Vec<u8>| {
26+
let mut length = 8 * 1024;
27+
let mut deflated = vec![0; length as usize];
28+
29+
let error = unsafe {
30+
test_libbz2_rs_sys::compress_c(
31+
deflated.as_mut_ptr().cast(),
32+
&mut length,
33+
data.as_ptr().cast(),
34+
data.len() as _,
35+
9,
36+
)
37+
};
38+
39+
assert_eq!(error, BZ_OK);
40+
41+
deflated.truncate(length as _);
42+
43+
let output = decompress_help(&deflated);
44+
45+
if output != data {
46+
let path = std::env::temp_dir().join("deflate.txt");
47+
std::fs::write(&path, &data).unwrap();
48+
eprintln!("saved input file to {path:?}");
49+
}
50+
51+
assert_eq!(output, data);
52+
});

0 commit comments

Comments
 (0)