Skip to content

Panic in SampledFunction::apply when Type 0 /Domain bounds are inverted #288

Description

@MinghuaWang

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,
            })
            ...
        })
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions