PSD: the slices' version 6 descriptors share one limit of values

Each record's look-ahead descriptor read started with its own allowance
of 200,000 values, so a file with 10,000 records could make one Open read
about two billion. The descriptors of a slices resource now spend one
allowance between them, and a look-ahead that runs it out refuses the
slices with a warning instead of being read past.
This commit is contained in:
dotCooCoo
2026-11-14 21:51:22 -08:00
parent 492f2234c9
commit dd01ae03f7
2 changed files with 39 additions and 11 deletions
+21 -11
View File
@@ -29,9 +29,12 @@ const MAX_SLICES: usize = 10_000;
/// The deepest descriptor objects and lists nest.
const MAX_DEPTH: usize = 16;
/// The most values one descriptor holds, nested ones included.
/// The most values the descriptors of one slices resource hold together, nested ones included.
const MAX_VALUES: usize = 200_000;
/// The error of descriptors that hold more than [`MAX_VALUES`].
const TOO_MANY_VALUES: &str = "the slices' descriptor holds too many values";
/// One slice as the file stores it.
#[derive(Clone, Debug, PartialEq)]
struct PsdSlice {
@@ -60,13 +63,14 @@ fn read(bytes: &[u8]) -> Result<Option<Stored>, String> {
/// The slices of resource 1050's `data`.
fn decode(data: &[u8]) -> Result<Vec<PsdSlice>, String> {
let mut r = Reader::new(data);
// The values every descriptor of the resource may hold together.
let mut budget = MAX_VALUES;
match r.u32()? {
6 => version_6(&mut r),
6 => version_6(&mut r, &mut budget),
7 | 8 => {
if r.u32()? != 16 {
return Err("the slices' descriptor has a version other than 16".into());
}
let mut budget = MAX_VALUES;
let d = descriptor(&mut r, 0, &mut budget)?;
from_descriptor(&d)
}
@@ -75,7 +79,8 @@ fn decode(data: &[u8]) -> Result<Vec<PsdSlice>, String> {
}
/// Version 6: the bounds of all the slices, the group's name, the count, then each slice's record.
fn version_6(r: &mut Reader<'_>) -> Result<Vec<PsdSlice>, String> {
/// The descriptors that follow the records spend one `budget` of values between them.
fn version_6(r: &mut Reader<'_>, budget: &mut usize) -> Result<Vec<PsdSlice>, String> {
r.take(16)?;
unicode(r)?;
let count = r.u32()? as usize;
@@ -111,13 +116,18 @@ fn version_6(r: &mut Reader<'_>) -> Result<Vec<PsdSlice>, String> {
format!("#{red:02x}{green:02x}{blue:02x}")
};
// Photoshop 7 and later may follow a record with a descriptor (version 16); a record that
// doesn't start one is the next slice.
// doesn't start one is the next slice. A descriptor read that uses up the budget is an
// error.
if r.rest().starts_with(&16u32.to_be_bytes()) {
let mut ahead = Reader::new(r.rest());
let mut budget = MAX_VALUES;
if ahead.u32().is_ok() && descriptor(&mut ahead, 0, &mut budget).is_ok() {
let used = r.rest().len() - ahead.rest().len();
r.take(used)?;
ahead.u32()?;
match descriptor(&mut ahead, 0, budget) {
Ok(_) => {
let used = r.rest().len() - ahead.rest().len();
r.take(used)?;
}
Err(e) if e == TOO_MANY_VALUES => return Err(e),
Err(_) => {}
}
}
// Origin 0 is a slice generated automatically.
@@ -278,7 +288,7 @@ fn descriptor(r: &mut Reader<'_>, depth: usize, budget: &mut usize) -> Result<Ve
/// A value of type `kind`.
fn value(r: &mut Reader<'_>, kind: &[u8; 4], depth: usize, budget: &mut usize) -> Result<Value, String> {
*budget = budget.checked_sub(1).ok_or("the slices' descriptor holds too many values")?;
*budget = budget.checked_sub(1).ok_or(TOO_MANY_VALUES)?;
Ok(match kind {
b"long" => Value::Long(r.u32()? as i32),
b"doub" => Value::Double(f64::from_be_bytes(r.array()?)),
@@ -322,7 +332,7 @@ fn value(r: &mut Reader<'_>, kind: &[u8; 4], depth: usize, budget: &mut usize) -
b"obj " => {
let count = r.u32()? as usize;
for _ in 0..count {
*budget = budget.checked_sub(1).ok_or("the slices' descriptor holds too many values")?;
*budget = budget.checked_sub(1).ok_or(TOO_MANY_VALUES)?;
match &r.array::<4>()? {
b"prop" => {
class(r)?;
@@ -262,6 +262,24 @@ fn photoshop_7_descriptors_after_version_6_records_are_read_past() {
assert_eq!(names, ["a", "b"]);
}
#[test]
fn version_6_descriptors_share_one_budget_of_values() {
// Each record's descriptor holds 2,501 values, well under the 200,000 one descriptor may hold;
// 100 of them hold 250,100 together.
let list = D::List((0..2_500).map(D::Long).collect());
let trailing = [16u32.to_be_bytes().to_vec(), descriptor(&[("many", list)])].concat();
let records: Vec<S> = (0..100).map(|_| user("a", [0, 0, 5, 5])).collect();
// Alone, one such descriptor is read past.
let (s, r) = open(&with_resource(&psd(false), 1050, &v6(&records[..1], &trailing)), json!({}));
assert!(warnings(&r).is_empty(), "{r}");
assert_eq!(slices(&s).len(), 1);
// Together they are refused, and the file opens without slices.
let (s, r) = open(&with_resource(&psd(false), 1050, &v6(&records, &trailing)), json!({}));
let w = warnings(&r);
assert!(w.iter().any(|w| w.starts_with("the slices can't be read (") && w.contains("too many values")), "{w:?}");
assert!(slices(&s).is_empty());
}
#[test]
fn version_7_and_8_descriptors_open_as_user_slices() {
let slice = |origin: &'static str, kind: &'static str, l: i32, t: i32, r: i32, b: i32| {