From 26ed1a35bfebb9286817cf5a1e92a49e774e69f8 Mon Sep 17 00:00:00 2001 From: ".CooCoo" Date: Thu, 15 Oct 2026 01:09:25 -0700 Subject: [PATCH] fix(pdf): prevent overflow in EPS integer arithmetic Use checked division and remainder for EPS integer operators so i64::MIN with a divisor truncating to -1 cannot panic. Preserve the existing zero-divisor fallback and normal truncation behavior. Add public EPS parse regressions covering extreme, fractional and ordinary operands. PR #1223 --- crates/pdf/src/ps.rs | 18 ++++-------------- crates/pdf/src/tests_import.rs | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/crates/pdf/src/ps.rs b/crates/pdf/src/ps.rs index 65e352cb2..4401ace91 100644 --- a/crates/pdf/src/ps.rs +++ b/crates/pdf/src/ps.rs @@ -700,20 +700,10 @@ impl Interp { a / b } } - "idiv" => { - if b as i64 == 0 { - 0.0 - } else { - ((a as i64) / (b as i64)) as f64 - } - } - "mod" => { - if b as i64 == 0 { - 0.0 - } else { - ((a as i64) % (b as i64)) as f64 - } - } + // `checked_*` give 0 both for a zero divisor and for the one overflowing + // case, `i64::MIN / -1` (`/` and `%` panic on it in every build). + "idiv" => (a as i64).checked_div(b as i64).unwrap_or(0) as f64, + "mod" => (a as i64).checked_rem(b as i64).unwrap_or(0) as f64, "atan" => a.atan2(b).to_degrees().rem_euclid(360.0), "exp" => a.powf(b), "max" => a.max(b), diff --git a/crates/pdf/src/tests_import.rs b/crates/pdf/src/tests_import.rs index 532e32cb9..add447e3a 100644 --- a/crates/pdf/src/tests_import.rs +++ b/crates/pdf/src/tests_import.rs @@ -376,3 +376,25 @@ fn postscript_radix_numbers_outside_2_to_36_are_not_numbers() { let _ = parse(src.as_bytes()); } } + +#[test] +fn postscript_idiv_and_mod_of_i64_min_by_minus_one_give_zero() { + // `i64::MIN / -1` and `i64::MIN % -1` overflow, and `/` and `%` panic on them in debug and + // release builds. `as i64` turns any dividend at or below -2^63 into `i64::MIN` and turns + // a divisor of -1.5 into -1. + for (prog, want) in [ + ("-9223372036854775808 -1 idiv", 0), + ("-9223372036854775808 -1 mod", 0), + ("-99999999999999999999 -1.5 idiv", 0), + ("2 63 exp neg -1 mod", 0), + // The result is 0 for a divisor that truncates to 0. Other quotients are truncated + // toward zero, and a remainder has the sign of the dividend. + ("7 0 idiv", 0), + ("7 0.5 mod", 0), + ("-7 2 idiv", -3), + ("-7 2 mod", -1), + ] { + let doc = eps(format!("{prog} {want} eq {{ 0 0 10 10 rectfill }} if\n").as_bytes()); + assert_eq!(shape_names(&doc).len(), 1, "{prog}"); + } +}