mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-24 10:56:51 -06:00
* security: bound native DXF/SVG/PDF import allocation and quadratic work (CWE-400/407/789) Several native import paths let a crafted drawing cause quadratic CPU work or unbounded allocation: - DXF layer decoding scanned the whole layer list for every LAYER record (`d.layer_mut`), so many distinct layer names made import quadratic. Keep a case-normalised name→index map and update/append in O(1) (row 29). - ACAD_TABLE cells were materialised per table under a per-table cap, but many small tables could still add up. Add a document-wide table-cell budget in parse_entities; a table that would overflow it is reduced to empty (row 8). - DXF image name allocation restarted its `_2, _3…` suffix probe for every definition sharing a base name (quadratic). Keep a per-base next-suffix counter so probing resumes where it left off (row 13). - Image resolution cloned each definition's path/name into every referring image. Add an aggregate byte budget; once spent, string copies stop while geometry fields are still applied (row 42). - PDF link annotation scanned the whole selected-link vector for every primitive. Build a HashSet of linked handles once for O(1) membership (row 15). - SVG arc/curve flattening retained each flushed run with only a per-run cap, so repeated arcs amplified a compact path into unbounded geometry. Add a document-wide generated-vertex budget charged in `add` (row 31). New test `svg_arc_budget` covers the SVG budget. All `cadcraft-io` tests pass; clippy adds no new warnings. Addresses findings rows 8, 13, 15, 29, 31, 42. * fix: share DXF table budgets before allocation --------- Co-authored-by: skylartaylor <26588250+skylartaylor@users.noreply.github.com>
33 lines
1.5 KiB
Rust
33 lines
1.5 KiB
Rust
//! Reading an SVG whose paths flatten into a huge number of retained vertices must be rejected by
|
|
//! the document-wide generated-geometry budget, not amplified into gigabytes (CWE-400, row 31).
|
|
|
|
use cadcraft_io::read;
|
|
|
|
/// A path with many elliptical-arc subpaths under a non-uniform group transform; each subpath
|
|
/// flushes to its own retained polyline, so enough of them must exceed the document vertex budget.
|
|
fn arc_bomb(arcs: usize) -> Vec<u8> {
|
|
// `scale(1 2)` makes the transform non-uniform, forcing per-sample flattening rather than a
|
|
// single bulge arc. Each `M … a …` is a separate subpath that is flushed and retained.
|
|
let mut d = String::new();
|
|
for i in 0..arcs {
|
|
let y = i as i32;
|
|
d.push_str(&format!("M 0 {y} a 1000 500 0 1 1 1 1 "));
|
|
}
|
|
let svg = format!(r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><g transform="scale(1 2)"><path d="{d}"/></g></svg>"#);
|
|
svg.into_bytes()
|
|
}
|
|
|
|
#[test]
|
|
fn a_modest_arc_svg_still_reads() {
|
|
// A handful of arcs is well within budget and parses fine.
|
|
let d = read(&arc_bomb(4), "a.svg").expect("a small arc SVG should read");
|
|
assert!(d.model.iter().count() >= 1, "expected some geometry");
|
|
}
|
|
|
|
#[test]
|
|
fn an_arc_heavy_svg_is_rejected_by_the_vertex_budget() {
|
|
// ~1000 arcs x up to 10_000 chords each would far exceed the 8M vertex budget.
|
|
let err = read(&arc_bomb(30000), "a.svg");
|
|
assert!(err.unwrap_err().to_string().contains("SVG: too much generated geometry"));
|
|
}
|