From 38e73060a7e84d7a10d7b13b62cf70e3bdb9f3f3 Mon Sep 17 00:00:00 2001 From: Martin Vlach Date: Tue, 13 Oct 2026 16:13:34 +0200 Subject: [PATCH] Live shapes: a polygon read from a file has 3 to 1000 sides Every command keeps a live polygon's side count in 3..=1000 (shape.polygon, object.setLiveShape, the Polygon tool), but reading a document took `sides` as any u32. Selecting such a polygon builds its Live Corners: LiveShape::polygon_outline makes one point per side and set_corners resizes the corner radii and kinds to the side count. A document a few bytes long ("sides": 4000000000) makes the app allocate tens of gigabytes and abort or stall, and the unsaved work in other documents is lost with it. Transforming such a polygon with cut corners regenerates the same outline. `sides` is now clamped to 3..=1000 when it is deserialized, so a polygon from any document that is read (opened, placed, packaged or taken from a library) keeps the limit the commands keep, and the code that uses the count needs no check of its own. The test reads polygons with 0, 5, 1000 and 4,000,000,000 sides, checks they come out with 3, 5, 1000 and 1000, and that the clamped one's outline has 1000 anchors. Reported-by: BS.ehlas.cz --- crates/doc/src/node.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/doc/src/node.rs b/crates/doc/src/node.rs index 1ff8a61df..1bfabf9c1 100644 --- a/crates/doc/src/node.rs +++ b/crates/doc/src/node.rs @@ -102,6 +102,8 @@ pub enum LiveShape { }, Polygon { radius: f64, + /// 3–1000, as [`LiveShape::set_sides`] gives it; a file's count is clamped when read. + #[serde(deserialize_with = "de_sides")] sides: u32, xf: Affine, /// Corner radii in document units, by vertex from the first one clockwise (none: sharp). @@ -326,6 +328,12 @@ fn all_round(kinds: &[CornerKind]) -> bool { kinds.iter().all(|k| *k == CornerKind::Round) } +/// A polygon's side count read from a file, clamped as the commands clamp it: the outline and its +/// corner lists hold one entry per side, so a huge count would allocate gigabytes. +fn de_sides<'de, D: serde::Deserializer<'de>>(d: D) -> Result { + u32::deserialize(d).map(|n| n.clamp(3, 1000)) +} + /// Whether `a` only moves (no rotation, scale, shear or reflection). fn moves_only(a: Affine) -> bool { let [a0, a1, a2, a3, _, _] = a.as_coeffs(); @@ -2190,6 +2198,24 @@ mod tests { assert_eq!(path.label(), "Path"); } + /// A polygon read from a file has 3 to 1000 sides, as the commands give it: selecting one + /// with four billion made its outline and corner lists allocate tens of gigabytes. + #[test] + fn a_polygon_from_a_file_has_3_to_1000_sides() { + let read = |sides: u32| { + let json = serde_json::json!({"shape": "polygon", "radius": 10.0, "sides": sides, "xf": [1, 0, 0, 1, 0, 0]}); + serde_json::from_value::(json).unwrap() + }; + let sides_of = |l: &LiveShape| match l { + LiveShape::Polygon { sides, .. } => *sides, + _ => panic!("not a polygon: {l:?}"), + }; + let huge = read(4_000_000_000); + assert_eq!([0, 5, 1000].map(|n| sides_of(&read(n))), [3, 5, 1000]); + assert_eq!(sides_of(&huge), 1000); + assert_eq!(huge.polygon_outline().anchor_count(), 1000); + } + #[test] fn a_polygon_reports_and_takes_its_radius_angle_and_equal_sides() { let close = |a: f64, b: f64| (a - b).abs() < 1e-9;