From 1a052d611514e238e32fec405c749165b56ced7a Mon Sep 17 00:00:00 2001 From: Nigel Ihza Firdausy <106657905+Ni-zav@users.noreply.github.com> Date: Thu, 8 Oct 2026 04:12:39 +0700 Subject: [PATCH] Reject overflowing slice rectangles (#768) * fix(engine): reject overflowing slice rectangles (#715) * test(engine): cover slice rectangle overflow (#715) * refactor(engine): keep slice rect validation explicit (#715) * test(engine): exercise overflowing slice commands (#715) * fix(engine): reject invalid slice.set geometry before mutation --- crates/engine/src/slice_cmds.rs | 16 ++++++++++++---- crates/engine/src/slice_cmds/tests.rs | 13 +++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/slice_cmds.rs b/crates/engine/src/slice_cmds.rs index 68011b347b..c3a7f86bba 100644 --- a/crates/engine/src/slice_cmds.rs +++ b/crates/engine/src/slice_cmds.rs @@ -126,15 +126,20 @@ pub(crate) fn refresh(s: &mut Session) { pub(crate) fn rect_param(p: &Value) -> Option { let f = |v: &Value| v.as_f64().filter(|x| x.is_finite()).map(|x| x.round() as i32); + let rect = |x: i32, y: i32, w: i32, h: i32| { + if w <= 0 || h <= 0 { + return None; + } + Some(Rect::new(x, y, x.checked_add(w)?, y.checked_add(h)?)) + }; if let Some(a) = p.get("rect").and_then(Value::as_array).filter(|a| a.len() == 4) { let v: Vec = a.iter().filter_map(f).collect(); - if v.len() == 4 && v[2] > 0 && v[3] > 0 { - return Some(Rect::new(v[0], v[1], v[0] + v[2], v[1] + v[3])); + if v.len() == 4 { + return rect(v[0], v[1], v[2], v[3]); } return None; } - let (x, y, w, h) = (f(p.get("x")?)?, f(p.get("y")?)?, f(p.get("width")?)?, f(p.get("height")?)?); - (w > 0 && h > 0).then(|| Rect::new(x, y, x + w, y + h)) + rect(f(p.get("x")?)?, f(p.get("y")?)?, f(p.get("width")?)?, f(p.get("height")?)?) } fn hex_color(s: &str) -> Option<[u8; 4]> { @@ -289,6 +294,9 @@ fn from_guides(s: &mut Session) -> Result { fn set_slice(s: &mut Session, p: &Value) -> Result { let cmd = "slice.set"; + if ["rect", "x", "y", "width", "height"].iter().any(|key| p.get(key).is_some()) && rect_param(p).is_none() { + return Err(bad(cmd, "give a finite rectangle with positive dimensions and representable corners")); + } let id = target(s, p, cmd, true)?; let coalesce_into_promote = s.active().is_some_and(|d| d.history.undo_label() == Some("Promote to User Slice")) && p.get("number").is_some(); let label = "Slice Options"; diff --git a/crates/engine/src/slice_cmds/tests.rs b/crates/engine/src/slice_cmds/tests.rs index 9b6a1bd129..776e9344dd 100644 --- a/crates/engine/src/slice_cmds/tests.rs +++ b/crates/engine/src/slice_cmds/tests.rs @@ -50,6 +50,19 @@ fn user_slices_and_options() { } } +#[test] +fn rect_param_rejects_corner_overflow() { + assert_eq!(rect_param(&json!({"rect": [1, 2, 3, 4]})), Some(Rect::new(1, 2, 4, 6))); + assert_eq!(rect_param(&json!({"x": 1, "y": 2, "width": 3, "height": 4})), Some(Rect::new(1, 2, 4, 6))); + assert_eq!(rect_param(&json!({"rect": [i32::MAX, 0, 1, 1]})), None); + assert_eq!(rect_param(&json!({"x": 0, "y": i32::MAX, "width": 1, "height": 1})), None); + + let mut s = session(8); + assert!(s.execute("slice.new", json!({"rect": [i32::MAX, 0, 1, 1]})).is_err()); + let id = s.execute("slice.new", json!({"rect": [1, 1, 8, 8]})).unwrap()["slice"].as_u64().unwrap(); + assert!(s.execute("slice.set", json!({"slice": id, "rect": [0, i32::MAX, 1, 1]})).is_err()); +} + #[test] fn promote_auto_slice_and_divide() { let mut s = session(8);