Panic (multiply overflow) in `SampledFunction::apply`
### Describe the bug
`SampledFunction::apply` panics with `attempt to multiply with overflow` when evaluating a 1-D Type 0 (sampled) function whose `/Encode` array contains values that overflow `f32` to infinity.
The panic occurs at `src/object/function.rs:375`, where `let idx = i * n_out` overflows when `i = usize::MAX`. The overflow to `usize::MAX` happens via this chain:
1. PDF `/Encode` array contains a decimal number large enough to overflow `f32` (e.g., 38 nines: `99999999999999999999999999999999999999.`)
2. `str::parse::<f32>()` produces `f32::INFINITY`
3. `SampledFunctionInput::map` (line 260) computes `y = x.mul_add(Inf, Inf) = Inf`
4. `y.floor() as usize` (line 261) saturates to `usize::MAX` (Rust >= 1.45 saturating float-to-int cast)
5. `usize::MAX * n_out` (line 313) panics with integer multiplication overflow when `n_out >= 2`
.
Panic info:
Stack trace (relevant frames):
```
pdf::object::function::SampledFunctionInput::map
-> x.mul_add(Inf, Inf) // Inf from overflowing Encode values
-> Inf.floor() as usize // saturates to usize::MAX
pdf::object::function::SampledFunction::apply
-> let idx = i * n_out; // usize::MAX * 2 → PANIC (overflow)
```
Full stack trace:
```
thread 'main' panicked at pdf/src/object/function.rs:375:31:
attempt to multiply with overflow
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_const::panic_const_mul_overflow
at /rustc/ec7c02612527d185c379900b613311bc1dcbf7dc/library/core/src/panicking.rs:175:17
3: pdf::object::function::SampledFunction::apply
at ./pdf/src/object/function.rs:375:31
4: pdf::object::function::Function::apply
at ./pdf/src/object/function.rs:111:49
5: poc_nan_inf_panic::main
at ./pdf/examples/poc_nan_inf_panic.rs:20:18
```
### Root cause
In `src/object/function.rs`, the constructor stores `/Encode` values without validation:
```rust
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]),
encode_offset: e[0], // may be Inf/NaN
encode_scale: e[1], // may be Inf/NaN
size: s as usize,
})
//...
}
}
```
Then `SampledFunctionInput::map` uses these values in arithmetic that produces Inf, which is then cast to `usize`:
```rust
// lines 259-261:
fn map(&self, x: f32) -> (usize, usize, f32) {
let x = x.clamp(self.domain.0, self.domain.1);
let y = x.mul_add(self.encode_scale, self.encode_offset); // Inf if scale/offset overflow
(y.floor() as usize, self.size, y.fract()) // usize::MAX, _, NaN
}
```
Then in `apply`, the index is multiplied by the output dimension count:
```rust
// line 313:
let idx = i * n_out; // ← usize::MAX * n_out → overflow panic when n_out >= 2
```
### To reproduce
```rust
fn main() {
use pdf::file::FileOptions;
use pdf::object::ColorSpace;
let pdf_path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/examples/crash_nan_inf.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; 2];
func.apply(&[0.5], &mut out).unwrap();
}
_ => {}
}
}
```
Crash PDF: [crash_nan_inf.pdf](https://github.com/user-attachments/files/28861338/crash_nan_inf.pdf)
### Test environment
- Version: pdf master branch
- OS: Ubuntu 24.04, 64-bit
- Rustc version: rustc 1.91.0-nightly (ec7c02612 2025-08-05)
### Possible fix
Validate Encode values at construction:
```rust
// In Object::from_primitive for Function, Type 0 branch:
fn from_primitive(...) {
// ...
0 => {
// ...
.map(|(c, e, &s)| {
if !e[0].is_finite() || !e[1].is_finite() {
bail!("Invalid sampled function encode: [{}, {}]", e[0], e[1]);
}
Ok(SampledFunctionInput {
domain: (c[0], c[1]),
encode_offset: e[0],
encode_scale: e[1],
size: s as usize,
})
})
}
}
```
0 条评论