mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Security: Affinity import and brush expansion bounds (#3887)
* Audit: Affinity import (spot colour chain, zero-width records, corner radii) * Audit: Affinity layer names are cut at 1024 characters * Audit: brush expansion bounds (masks, library parsing, flattening), pressure profile lookup * Brush: reach a node's mask through its extras
This commit is contained in:
@@ -215,7 +215,13 @@ 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);
|
||||
// The largest radius of each record, once (not a scan of all of them for each anchor).
|
||||
let mut largest: std::collections::HashMap<usize, f64> = std::collections::HashMap::new();
|
||||
for (rec, r) in radii {
|
||||
let e = largest.entry(*rec).or_insert(0.0);
|
||||
*e = e.max(*r);
|
||||
}
|
||||
let corner = |a: &Anchor| largest.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();
|
||||
|
||||
@@ -517,6 +517,9 @@ struct ReadAt {
|
||||
|
||||
/// Read a document whose spreads are placed by `base` (for embedded documents, nested `depth`
|
||||
/// deep). What it uses is taken from `budget`, also when it fails part of the way.
|
||||
/// The most characters of a layer's name kept.
|
||||
const MAX_NAME_CHARS: usize = 1024;
|
||||
|
||||
fn read_at(bytes: &[u8], limits: Limits, budget: &mut Budget, base: Affine, depth: usize) -> ReadAt {
|
||||
let mut archive = match Archive::open(bytes, limits) {
|
||||
Ok(a) => a,
|
||||
@@ -692,7 +695,8 @@ impl<'s, 'a, 'b> Reader<'s, 'a, 'b> {
|
||||
let local = s.floats::<6>(id, b"Xfrm").map(Affine::from_xfrm).unwrap_or(Affine::IDENTITY);
|
||||
let world = local.then(parent);
|
||||
let class = s.class(id).unwrap_or(Tag(0));
|
||||
let name = s.str(id, b"Desc").unwrap_or_default().to_string();
|
||||
// A layer name is short; a long one is cut, as every visit of a shared object keeps a copy.
|
||||
let name: String = s.str(id, b"Desc").unwrap_or_default().chars().take(MAX_NAME_CHARS).collect();
|
||||
let visible = s.bool(id, b"Visi").unwrap_or(true);
|
||||
let locked = s.bool(id, b"Edtb").is_some_and(|e| !e);
|
||||
let opacity = s.f64(id, b"Opac").map_or(1.0, |o| o.clamp(0.0, 1.0));
|
||||
|
||||
@@ -139,6 +139,14 @@ fn floats(v: &Value) -> Option<Vec<f64>> {
|
||||
|
||||
/// A colour object (`RGBA`, `HSLA`, `CMYK`, `GRAY`, `LABA`, `Pant`).
|
||||
pub(crate) fn color(r: &mut Reader, id: ObjId) -> Option<Color> {
|
||||
color_within(r, id, 0)
|
||||
}
|
||||
|
||||
/// The most spot colours that stand for one another followed.
|
||||
const MAX_SPOT_CHAIN: u32 = 8;
|
||||
|
||||
/// [`color`], `depth` spot colours deep: one that stands for itself (or a ring of them) ends here.
|
||||
fn color_within(r: &mut Reader, id: ObjId, depth: u32) -> Option<Color> {
|
||||
let s = r.s;
|
||||
let class = s.class(id)?;
|
||||
let col = || -> Option<Vec<f64>> {
|
||||
@@ -170,7 +178,10 @@ pub(crate) fn color(r: &mut Reader, id: ObjId) -> Option<Color> {
|
||||
}
|
||||
b"Pant" => {
|
||||
r.warn("spot colours (imported as their RGB equivalent)");
|
||||
return s.obj(id, b"srgb").and_then(|c| color(r, c)).or_else(|| s.obj(id, b"base").and_then(|c| color(r, c)));
|
||||
if depth >= MAX_SPOT_CHAIN {
|
||||
return None;
|
||||
}
|
||||
return s.obj(id, b"srgb").and_then(|c| color_within(r, c, depth + 1)).or_else(|| s.obj(id, b"base").and_then(|c| color_within(r, c, depth + 1)));
|
||||
}
|
||||
b"RegC" => Color::Cmyk { c: 1.0, m: 1.0, y: 1.0, k: 1.0, a: 1.0 },
|
||||
_ => {
|
||||
|
||||
@@ -303,6 +303,10 @@ impl Parser<'_> {
|
||||
0x2c => {
|
||||
let n = usize::try_from(self.c.u32()?).map_err(|_| Error::Malformed("array length"))?;
|
||||
let size = usize::from(self.c.u16()?);
|
||||
// Records of no bytes take nothing from the data, whatever their number.
|
||||
if n > 0 && size == 0 {
|
||||
return Err(Error::Malformed("array of records without bytes"));
|
||||
}
|
||||
if n.checked_mul(size).is_none_or(|b| b > self.c.remaining()) {
|
||||
return Err(Error::Malformed("array longer than its data"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user