From 0ecef703eff3b8eefb0f9512e5a592f79b03ba59 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sat, 24 Oct 2026 04:34:18 +0700 Subject: [PATCH] Security: cap the perspective grid's line-loop bounds at 121 a way --- crates/tools/src/distort/perspective.rs | 9 ++++++--- crates/tools/src/distort/perspective/view.rs | 10 ++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/crates/tools/src/distort/perspective.rs b/crates/tools/src/distort/perspective.rs index 3d3042d6d..f7bcacf43 100644 --- a/crates/tools/src/distort/perspective.rs +++ b/crates/tools/src/distort/perspective.rs @@ -562,14 +562,17 @@ impl PerspectiveGrid { s }; let (su, sv) = (step(w), step(h_len)); - // Counted, not summed: a count in whole lines (at most 121 a way). - for i in 0..=((w + 1e-9) / su).floor() as usize { + // Counted, not summed: a count in whole lines (at most 121 a way). The fixed 1e-9 + // tolerance can inflate the count when the whole domain is far smaller than it, so the + // bound is capped on its own, before any loop runs. + let count = |len: f64, s: f64| ((len + 1e-9) / s).floor().clamp(0.0, 120.0) as usize; + for i in 0..=count(w, su) { let u = i as f64 * su; if let (Some(a), Some(b)) = (h.apply(Point::new(u, 0.0)), h.apply(Point::new(u, h_len))) { out.push((a, b)); } } - for i in 0..=((h_len + 1e-9) / sv).floor() as usize { + for i in 0..=count(h_len, sv) { let v = i as f64 * sv; if let (Some(a), Some(b)) = (h.apply(Point::new(0.0, v)), h.apply(Point::new(w, v))) { out.push((a, b)); diff --git a/crates/tools/src/distort/perspective/view.rs b/crates/tools/src/distort/perspective/view.rs index 676736cad..62523b501 100644 --- a/crates/tools/src/distort/perspective/view.rs +++ b/crates/tools/src/distort/perspective/view.rs @@ -123,6 +123,16 @@ mod tests { assert_eq!(g.snap_offset(b, Vec2::new(18.0, 0.0)), Vec2::new(18.0, 0.0), "nothing within reach"); } + #[test] + fn a_tiny_stored_grid_draws_a_bounded_number_of_lines() { + // A stored grid can carry extreme values: the loop bound must not depend on a fixed + // tolerance swallowing the whole domain. + let g = PerspectiveGrid { cell: 1e-18, extent: 1e-18, height: 1e-18, ..PerspectiveGrid::normal(2, Rect::new(0.0, 0.0, 800.0, 600.0)) }; + for pl in Plane::ALL { + assert!(g.lines(pl).len() <= 121 + 121, "at most 121 lines a way on {pl:?}"); + } + } + #[test] fn colours_and_rulers_come_from_the_definition() { let mut g = grid();