Describe the bug
color_no_convert panics with called Option::unwrap() on a None value when decoding a crafted jpeg. The panic occurs at the unchecked output_iter.next().unwrap() in src/decoder.rs:1481. The function iterates all bytes from every component's upsampled line buffer and writes them to the output line. When the internal line buffer is larger than the output length, it would cause the output iterator to be exhausted before all input bytes are consumed, leading to panic on unwrap().
Panic info:
thread 'main' panicked at src/decoder.rs:1481:34:
called `Option::unwrap()` on a `None` value
Stack trace (relevant frames):
jpeg_decoder::decoder::color_no_convert
→ output_iter.next().unwrap() // panic
jpeg_decoder::upsampler::Upsampler::upsample_and_interleave_row
jpeg_decoder::worker::compute_image_parallel
jpeg_decoder::decoder::compute_image
jpeg_decoder::decoder::Decoder<R>::decode_planes
jpeg_decoder::decoder::Decoder<R>::decode_internal
jpeg_decoder::decoder::Decoder<R>::decode
Full stack trace:
thread 'main' panicked at src/decoder.rs:1481:34:
called `Option::unwrap()` on a `None` value
stack backtrace:
0: __rustc::rust_begin_unwind
at /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/std/src/panicking.rs:697:5
1: core::panicking::panic_fmt
at /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/panicking.rs:75:14
2: core::panicking::panic
at /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/panicking.rs:145:5
3: core::option::unwrap_failed
at /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/option.rs:2130:5
4: core::option::Option<T>::unwrap
at /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:1009:21
5: jpeg_decoder::decoder::color_no_convert
at ./src/decoder.rs:1481:34
6: jpeg_decoder::upsampler::Upsampler::upsample_and_interleave_row
at ./src/upsampler.rs:62:9
7: jpeg_decoder::worker::compute_image_parallel
at ./src/worker/mod.rs:117:23
8: jpeg_decoder::decoder::compute_image
at ./src/decoder.rs:1334:9
9: jpeg_decoder::decoder::Decoder<R>::decode_planes
at ./src/decoder.rs:689:13
10: jpeg_decoder::decoder::Decoder<R>::decode_internal::{{closure}}
at ./src/decoder.rs:613:18
11: jpeg_decoder::worker::WorkerScope::get_or_init_worker
at ./src/worker/mod.rs:84:9
12: jpeg_decoder::decoder::Decoder<R>::decode_internal
at ./src/decoder.rs:612:22
13: jpeg_decoder::decoder::Decoder<R>::decode::{{closure}}
at ./src/decoder.rs:294:41
14: jpeg_decoder::worker::WorkerScope::with
at ./src/worker/mod.rs:61:9
15: jpeg_decoder::decoder::Decoder<R>::decode
at ./src/decoder.rs:294:9
16: poc_color_no_convert_panic::main
at ./examples/poc_color_no_convert_panic.rs:7:21
This was obtained by running with RUST_BACKTRACE=1 cargo run --example poc_color_no_convert_panic --no-default-features --features platform_independent
Root cause
In src/decoder.rs, color_no_convert iterates all bytes from every line buffer. When the line buffer is larger than output, it would cause the output iterator to be exhausted before all input bytes are consumed, which leads to the panic on unwrap().
// lines 1476-1484 — PANICS on subsampled images:
fn color_no_convert(data: &[Vec<u8>], output: &mut [u8]) {
let mut output_iter = output.iter_mut();
for pixel in data {
for d in pixel {
*(output_iter.next().unwrap()) = *d; // output exhausted, panic
}
}
}
To reproduce
// examples/poc_color_no_convert_panic.rs
use jpeg_decoder::{Decoder, ColorTransform};
fn main() {
let jpeg = std::fs::read("poc_1.jpeg").unwrap();
let mut decoder = Decoder::new(jpeg.as_slice());
decoder.set_color_transform(ColorTransform::None);
// This panics:
let _pixels = decoder.decode();
}
JPEG: poc_1.jpeg.zip (unzip it to get poc_1.jpeg)
Test environment
- Version: jpeg-decoder master (commit eb2d7c0)
- OS: Ubuntu 24.04 (x86_64)
- Rustc version: rustc 1.91.0-nightly (ec7c02612 2025-08-05)
Suggested fix
fn color_no_convert(data: &[Vec<u8>], output: &mut [u8]) {
let ncomp = data.len();
for (i, chunk) in output.chunks_exact_mut(ncomp).enumerate() {
for (j, component) in data.iter().enumerate() {
if let Some(&val) = component.get(i) {
chunk[j] = val;
}
}
}
}
Describe the bug
color_no_convertpanics withcalled Option::unwrap() on a None valuewhen decoding a crafted jpeg. The panic occurs at the uncheckedoutput_iter.next().unwrap()insrc/decoder.rs:1481. The function iterates all bytes from every component's upsampled line buffer and writes them to the output line. When the internal line buffer is larger than the output length, it would cause the output iterator to be exhausted before all input bytes are consumed, leading to panic onunwrap().Panic info:
Stack trace (relevant frames):
Full stack trace:
This was obtained by running with
RUST_BACKTRACE=1 cargo run --example poc_color_no_convert_panic --no-default-features --features platform_independentRoot cause
In
src/decoder.rs,color_no_convertiterates all bytes from every line buffer. When the line buffer is larger thanoutput, it would cause the output iterator to be exhausted before all input bytes are consumed, which leads to the panic onunwrap().To reproduce
JPEG: poc_1.jpeg.zip (unzip it to get poc_1.jpeg)
Test environment
Suggested fix