mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-11 04:52:13 +00:00
craft-doc: validate explicitly zero ICC frame sizes
This commit is contained in:
1 parent
21a7644df4
commit
37ec49baca
2 files changed
+31
-1
No files matched your search
@@ -569,6 +569,9 @@ fn unzstd_bounded(frame: &[u8], size: DecodedSize, what: &str) -> Result<Vec<u8>
|
||||
DecodedSize::Exact(n) | DecodedSize::AtMost(n) => n,
|
||||
};
|
||||
let declared = dec.decoder.content_size();
|
||||
// After header validation, descriptor bits 7-6 (FCS flag) or 5 (single segment) mean
|
||||
// the size is present, even when zero. See RFC 8878, section 3.1.1.1.1.1.
|
||||
let has_content_size = frame.get(4).is_some_and(|descriptor| descriptor & 0xe0 != 0);
|
||||
if matches!(size, DecodedSize::AtMost(_)) && declared > len as u64 {
|
||||
return Err(Error::LimitExceeded(format!("{what}: declared payload exceeds {len} bytes")));
|
||||
}
|
||||
@@ -599,7 +602,7 @@ fn unzstd_bounded(frame: &[u8], size: DecodedSize, what: &str) -> Result<Vec<u8>
|
||||
if matches!(size, DecodedSize::Exact(_)) && out.len() < len {
|
||||
return Err(malformed(format!("{what} decodes to {} bytes, not {len}", out.len())));
|
||||
}
|
||||
if matches!(size, DecodedSize::AtMost(_)) && declared != 0 && declared != out.len() as u64 {
|
||||
if matches!(size, DecodedSize::AtMost(_)) && has_content_size && declared != out.len() as u64 {
|
||||
return Err(malformed(format!("{what}: declared payload size {declared} differs from {} decoded bytes", out.len())));
|
||||
}
|
||||
if !dec.decoder.is_finished() {
|
||||
|
||||
@@ -233,6 +233,33 @@ fn declared_nonzero_content_sizes_are_checked_before_and_after_decoding() {
|
||||
assert_eq!(pcraft_icc_profile(&zip(&[store("manifest.json", m.as_bytes()), store(&path, &z)]), &limits), Ok(Some(b"p".to_vec())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// A present zero size permits an empty payload, while an omitted size permits streamed data.
|
||||
fn explicit_zero_content_sizes_match_only_empty_payloads() {
|
||||
// A two-byte FCS has an offset of 256, so zero uses one, four or eight bytes.
|
||||
for (descriptor, width, single) in [(0x80, 4, false), (0xc0, 8, false), (0x20, 1, true), (0xa0, 4, true), (0xe0, 8, true)] {
|
||||
for payload in [b"p".as_slice(), b"".as_slice()] {
|
||||
let h = hash(payload);
|
||||
let path = craft_pcraft_format::blob_path(&h).unwrap();
|
||||
let m = named_manifest(&h);
|
||||
let mut z = vec![0x28, 0xb5, 0x2f, 0xfd, descriptor];
|
||||
if !single {
|
||||
z.push(0); // 1 KiB window
|
||||
}
|
||||
z.extend(std::iter::repeat_n(0, width));
|
||||
z.extend_from_slice(&[if payload.is_empty() { 1 } else { 9 }, 0, 0]);
|
||||
z.extend_from_slice(payload);
|
||||
let result = pcraft_icc_profile(&zip(&[store("manifest.json", m.as_bytes()), store(&path, &z)]), &Limits::default());
|
||||
if payload.is_empty() {
|
||||
assert_eq!(result, Ok(Some(Vec::new())), "descriptor {descriptor:#x}");
|
||||
} else {
|
||||
assert!(matches!(result, Err(Error::Malformed(_))), "descriptor {descriptor:#x}: {result:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(pcraft_icc_profile(&bundle(b"p"), &Limits::default()), Ok(Some(b"p".to_vec())), "unknown content size still streams");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Damaged envelopes and frames fail through the public accessor without escaping a panic.
|
||||
fn truncations_and_mutations_do_not_panic() {
|
||||
|
||||
Reference in new issue
Block a user