security(affinity): index live-corner radii by record

subpath() looked up each polyline anchor's corner radius by scanning the
entire radius list, O(anchors * radii). A file supplying large anchor and
radius arrays could cause quadratic CPU work before geometry limits
inspected the path.

Build a record-to-maximum-radius map once and look each anchor up in it,
preserving the previous fold(0.0, f64::max) behavior for duplicate record
indices.

Finding: 89f65d65-f637-44fc-9a81-a9039a413a4b (CWE-407)
This commit is contained in:
Dylan M. Taylor
2026-10-22 07:11:20 -04:00
parent 7c07280b66
commit 29e555c2f6
+10 -1
View File
@@ -210,7 +210,16 @@ fn anchors(records: &[Value]) -> Option<Vec<Anchor>> {
fn subpath(anchors: &[Anchor], closed: bool, radii: Option<&Vec<(usize, f64)>>) -> Option<SubPath> {
let first = anchors.first()?;
if let Some(radii) = radii.filter(|r| !r.is_empty()) {
let corner = |a: &Anchor| radii.iter().filter(|(rec, _)| *rec == a.record).map(|(_, r)| *r).fold(0.0, f64::max);
// Index each record's maximum radius once. Scanning the whole radius list per anchor
// was O(anchors * radii), which a file with large anchor and radius arrays could drive
// into quadratic work. Duplicate record indices keep the maximum, matching the prior
// fold(0.0, f64::max) behavior.
let mut radius_by_record: std::collections::HashMap<usize, f64> = std::collections::HashMap::with_capacity(radii.len());
for &(rec, r) in radii {
let slot = radius_by_record.entry(rec).or_insert(0.0);
*slot = slot.max(r);
}
let corner = |a: &Anchor| radius_by_record.get(&a.record).copied().unwrap_or(0.0);
let polyline = anchors.iter().all(|a| a.inh.is_none_or(|h| h == a.p) && a.out.is_none_or(|h| h == a.p));
if polyline {
let mut pts: Vec<(Point, f64)> = anchors.iter().map(|a| (a.p, corner(a))).collect();