Describe the bug
SampledFunction::apply panics with min > max, or either was NaN when evaluating a 1-D Type 0 (sampled) function whose /Domain array is inverted (e.g., [10 1] instead of [0 1]).
The panic occurs in SampledFunctionInput::map at src/object/function.rs:322, which calls x.clamp(self.domain.0, self.domain.1). Rust's f32::clamp panics when min > max or either bound is NaN. The constructor at line 191-192 stores domain: (c[0], c[1]) directly from the PDF stream dictionary without validating that c[0] <= c[1].
Stack trace (relevant frames):
pdf::object::function::SampledFunctionInput::map
-> x.clamp(10.0, 1.0) // domain = (10.0, 1.0), inverted!
-> PANIC: min > max
Full stack trace:
thread 'main' panicked at /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/num/f32.rs:1405:9:
min > max, or either was NaN. min = 10.0, max = 1.0
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::f32::<impl f32>::clamp::do_panic::runtime
at /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/panic.rs:218:21
3: core::f32::<impl f32>::clamp::do_panic
at /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/intrinsics/mod.rs:2367:9
4: core::f32::<impl f32>::clamp
at /home/tony/.rustup/toolchains/nightly-2025-08-06-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic.rs:223:9
5: pdf::object::function::SampledFunctionInput::map
at ./pdf/src/object/function.rs:322:19
6: pdf::object::function::SampledFunction::apply
at ./pdf/src/object/function.rs:374:51
7: pdf::object::function::Function::apply
at ./pdf/src/object/function.rs:111:49
8: poc_clamp_panic::main
at ./pdf/examples/poc_clamp_panic.rs:20:18
Root cause
In src/object/function.rs, the constructor stores /Domain values without validation:
impl Object for Function {
fn from_primitive(...) {
// ...
// lines 254-259 -- no validation that encode values are finite:
.map(|(c, e, &s)| SampledFunctionInput {
domain: (c[0], c[1]), // values from PDF, may have c[0] > c[1], or NaN
encode_offset: e[0],
encode_scale: e[1],
size: s as usize,
})
//...
}
}
Then SampledFunctionInput::map uses clamp which requires min <= max:
// line 322 -- panics when domain.0 > domain.1 or NaN:
fn map(&self, x: f32) -> (usize, usize, f32) {
let x = x.clamp(self.domain.0, self.domain.1); // PANIC if domain.0 > domain.1, or NaN
// ...
}
To reproduce
fn main() {
use pdf::file::FileOptions;
use pdf::object::ColorSpace;
let pdf_path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/examples/crash_clamp.pdf"
);
let file = FileOptions::cached()
.open(pdf_path)
.expect("failed to open PDF");
let page = file.pages().next().unwrap().expect("failed to get page");
let resources = page.resources().expect("failed to get resources");
let cs = resources.color_spaces.get("CS1").expect("CS1 not found");
match cs {
ColorSpace::Separation(_name, _alt, func) => {
let mut out = [0.0f32];
func.apply(&[5.0], &mut out).unwrap();
}
_ => {}
}
}
Crash pdf: crash_clamp.pdf
Test environment
- Version: pdf master
- OS: Ubuntu 24.04, 64-bit
- Rustc version: rustc 1.91.0-nightly (ec7c02612 2025-08-05)
Suggested fix
// In Object::from_primitive for Function, Type 0 branch:
fn from_primitive(...) {
// ...
0 => {
// ...
.map(|(c, e, &s)| {
let d0 = c[0];
let d1 = c[1];
if !d0.is_finite() || !d1.is_finite() || d0 > d1 {
bail!("Invalid sampled function domain: [{}, {}]", d0, d1);
}
Ok(SampledFunctionInput {
domain: (d0, d1),
encode_offset: e[0],
encode_scale: e[1],
size: s as usize,
})
...
})
}
}
Describe the bug
SampledFunction::applypanics withmin > max, or either was NaNwhen evaluating a 1-D Type 0 (sampled) function whose/Domainarray is inverted (e.g.,[10 1]instead of[0 1]).The panic occurs in
SampledFunctionInput::mapatsrc/object/function.rs:322, which callsx.clamp(self.domain.0, self.domain.1). Rust'sf32::clamppanics whenmin > maxor either bound is NaN. The constructor at line 191-192 storesdomain: (c[0], c[1])directly from the PDF stream dictionary without validating thatc[0] <= c[1].Stack trace (relevant frames):
Full stack trace:
Root cause
In
src/object/function.rs, the constructor stores/Domainvalues without validation:Then
SampledFunctionInput::mapusesclampwhich requiresmin <= max:To reproduce
Crash pdf: crash_clamp.pdf
Test environment
Suggested fix