From caf10cc5816d5f26bee02837650ce6952b69b878 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Sun, 25 Oct 2026 16:08:06 -0400 Subject: [PATCH] security(pathops): count the sweep budget on the snapped geometry --- crates/pathops/src/boolean.rs | 10 ++++++---- crates/pathops/src/planar.rs | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/pathops/src/boolean.rs b/crates/pathops/src/boolean.rs index fb824813c..654368177 100644 --- a/crates/pathops/src/boolean.rs +++ b/crates/pathops/src/boolean.rs @@ -281,10 +281,11 @@ impl Tidy { impl Arrangement { pub(crate) fn new(mut paths: Vec<(BezPath, FillRule)>) -> Result { let eps = eps_for(paths.iter().map(|p| &p.0))?; + snap_horizontals(&mut paths.iter_mut().map(|p| &mut p.0).collect::>(), eps); + // Counted on the snapped geometry, which is what the sweep sees: snapping can bring boxes into contact. if !crate::budget::sweep_fits(paths.iter().map(|p| &p.0)) { return Err(PathOpsError::TooComplex); } - snap_horizontals(&mut paths.iter_mut().map(|p| &mut p.0).collect::>(), eps); // The sweep can panic on rare near-degenerate input; that must never take the app down, // so it becomes an error (callers fall back to leaving the art unchanged). let sweep = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { @@ -641,11 +642,12 @@ pub fn try_normalize(path: &PathData, rule: FillRule) -> Result Result { let eps = eps_for([bp])?; - if !crate::budget::sweep_fits([bp]) { - return Err(PathOpsError::TooComplex); - } let mut bp = bp.clone(); snap_horizontals(&mut [&mut bp], eps); + // Counted on the snapped geometry, which is what the sweep sees: snapping can bring boxes into contact. + if !crate::budget::sweep_fits([&bp]) { + return Err(PathOpsError::TooComplex); + } let top = Topology::::from_path(&bp, eps).map_err(|_| PathOpsError::OpenPath)?; Ok(top.contours(|w| inside(rule, *w))) } diff --git a/crates/pathops/src/planar.rs b/crates/pathops/src/planar.rs index 9f375500b..799ef421e 100644 --- a/crates/pathops/src/planar.rs +++ b/crates/pathops/src/planar.rs @@ -1063,10 +1063,11 @@ pub(crate) fn map(shapes: &[Shape]) -> Option { return None; } let eps = eps_for(inputs.iter().map(|p| &p.0)).ok()?; + snap_horizontals(&mut inputs.iter_mut().map(|p| &mut p.0).collect::>(), eps); + // Counted on the snapped geometry, which is what the sweep sees: snapping can bring boxes into contact. if !crate::budget::sweep_fits(inputs.iter().map(|p| &p.0)) { return None; } - snap_horizontals(&mut inputs.iter_mut().map(|p| &mut p.0).collect::>(), eps); // The sweep can panic on rare near-degenerate input; that must never take the app down. let sweep = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| Topology::::from_paths(inputs.iter().map(|(p, i)| (p, *i)), eps)));