Merge pull request #385 from lilith/fix/colormap-shl-overflow
fix: prevent shift overflow panic in ColorMap size validation
This commit is contained in:
7 files changed
+183
-2
No files matched your search
@@ -0,0 +1,85 @@
|
||||
name: Fuzz
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 3 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
duration:
|
||||
description: 'Seconds per fuzz target'
|
||||
required: false
|
||||
default: '300'
|
||||
|
||||
concurrency:
|
||||
group: fuzz-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
fuzz:
|
||||
name: Fuzz (${{ matrix.target }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- decode_comprehensive
|
||||
- decode_image
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: fuzz-${{ matrix.target }}
|
||||
cache-directories: fuzz/target
|
||||
|
||||
- name: Install cargo-fuzz
|
||||
run: cargo install cargo-fuzz --locked
|
||||
|
||||
- name: Determine duration
|
||||
id: duration
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "schedule" ]; then
|
||||
echo "seconds=300" >> "$GITHUB_OUTPUT"
|
||||
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "seconds=${{ github.event.inputs.duration || '300' }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "seconds=60" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Seed corpus
|
||||
run: |
|
||||
mkdir -p fuzz/corpus/${{ matrix.target }}
|
||||
cp fuzz/regression/* fuzz/corpus/${{ matrix.target }}/ 2>/dev/null || true
|
||||
cp fuzz/seed_corpus/${{ matrix.target }}/* fuzz/corpus/${{ matrix.target }}/ 2>/dev/null || true
|
||||
|
||||
- name: Fuzz ${{ matrix.target }}
|
||||
run: |
|
||||
cargo +nightly fuzz run ${{ matrix.target }} \
|
||||
-- -max_total_time=${{ steps.duration.outputs.seconds }} \
|
||||
-dict=fuzz/tiff.dict \
|
||||
-rss_limit_mb=2048
|
||||
|
||||
- name: Upload crashes
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fuzz-crashes-${{ matrix.target }}
|
||||
path: fuzz/artifacts/
|
||||
retention-days: 30
|
||||
|
||||
regression:
|
||||
name: Fuzz regression
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Build
|
||||
run: cargo build
|
||||
- name: Regression seeds
|
||||
run: |
|
||||
if [ -d fuzz/regression ] && [ "$(ls fuzz/regression/ 2>/dev/null | wc -l)" -gt 0 ]; then
|
||||
cargo test --test fuzz_regression 2>/dev/null || echo "No regression test found — seeds exist but no test harness"
|
||||
fi
|
||||
@@ -1,3 +1,4 @@
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
coverage
|
||||
@@ -23,3 +23,9 @@ name = "decode_image"
|
||||
path = "fuzz_targets/decode_image.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "decode_comprehensive"
|
||||
path = "fuzz_targets/decode_comprehensive.rs"
|
||||
test = false
|
||||
doc = false
|
||||
@@ -0,0 +1,50 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let cursor = std::io::Cursor::new(data);
|
||||
|
||||
// Path 1: TiffHeader::parse (probe)
|
||||
{
|
||||
let _ = tiff::decoder::TiffHeader::parse(std::io::Cursor::new(data));
|
||||
}
|
||||
|
||||
// Path 2: Full decode with strict limits
|
||||
let mut decoder = match tiff::decoder::Decoder::open(cursor) {
|
||||
Ok(d) => d,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let mut limits = tiff::decoder::Limits::default();
|
||||
limits.decoding_buffer_size = 512 * 1024;
|
||||
limits.ifd_value_size = 64 * 1024;
|
||||
limits.intermediate_buffer_size = 512 * 1024;
|
||||
|
||||
decoder = decoder.with_limits(limits);
|
||||
|
||||
// Probe metadata — dimensions() triggers image validation,
|
||||
// must succeed before get_chunk_type/chunk_dimensions (they unwrap image)
|
||||
let dims_ok = decoder.dimensions().is_ok();
|
||||
let _ = decoder.colortype();
|
||||
if dims_ok {
|
||||
let _ = decoder.get_chunk_type();
|
||||
let _ = decoder.chunk_dimensions();
|
||||
}
|
||||
let _ = decoder.more_images();
|
||||
|
||||
// Read chunks individually if available
|
||||
let _ = decoder.read_chunk(0);
|
||||
|
||||
// Read full image
|
||||
let _ = decoder.read_image();
|
||||
|
||||
// Iterate through all IFDs
|
||||
while decoder.more_images() {
|
||||
if decoder.next_image().is_err() {
|
||||
break;
|
||||
}
|
||||
let _ = decoder.dimensions();
|
||||
let _ = decoder.colortype();
|
||||
let _ = decoder.read_image();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
# TIFF byte order and magic
|
||||
"\x49\x49\x2A\x00"
|
||||
"\x4D\x4D\x00\x2A"
|
||||
|
||||
# Common IFD tags as 2-byte LE pairs
|
||||
# 256 = ImageWidth
|
||||
"\x00\x01"
|
||||
# 257 = ImageLength
|
||||
"\x01\x01"
|
||||
# 258 = BitsPerSample
|
||||
"\x02\x01"
|
||||
# 259 = Compression
|
||||
"\x03\x01"
|
||||
# 262 = PhotometricInterpretation
|
||||
"\x06\x01"
|
||||
# 273 = StripOffsets
|
||||
"\x11\x01"
|
||||
# 277 = SamplesPerPixel
|
||||
"\x15\x01"
|
||||
# 278 = RowsPerStrip
|
||||
"\x16\x01"
|
||||
# 279 = StripByteCounts
|
||||
"\x17\x01"
|
||||
# 282 = XResolution
|
||||
"\x1A\x01"
|
||||
# 283 = YResolution
|
||||
"\x1B\x01"
|
||||
|
||||
# Common IFD tags as 2-byte BE pairs
|
||||
"\x01\x00"
|
||||
"\x01\x01"
|
||||
"\x01\x02"
|
||||
"\x01\x03"
|
||||
"\x01\x06"
|
||||
"\x01\x11"
|
||||
"\x01\x15"
|
||||
"\x01\x16"
|
||||
"\x01\x17"
|
||||
@@ -454,8 +454,9 @@ impl Image {
|
||||
match tag_reader.find_tag(Tag::ColorMap)? {
|
||||
Some(v) => {
|
||||
let map = v.into_u16_vec()?;
|
||||
let expected_len = 3usize
|
||||
.checked_mul(1usize << bits_per_sample)
|
||||
let expected_len = 1usize
|
||||
.checked_shl(bits_per_sample as u32)
|
||||
.and_then(|n| n.checked_mul(3))
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
if map.len() != expected_len {
|
||||
return Err(TiffError::FormatError(
|
||||
|
||||
Binary file not shown.
Reference in new issue
Block a user