mirror of
https://github.com/storytold/craft-libs.git
synced 2026-10-31 07:39:23 +00:00
hayro-jbig2: cap long-form referred-to segment count (#176)
* hayro-jbig2: cap long-form referred-to-segment count A crafted JBIG2 segment header can claim a 29-bit referred-to-segment count; Vec::with_capacity reserved ~2 GiB from that field before a single referred-to number was read, and the resulting allocation failure aborts the process in a way catch_unwind cannot contain (fatal on wasm32, where memory is capped well below 2 GiB). A ~100 KB Flate-compressed PDF was enough to reach it inside the 256 MiB stream budget. Real files refer to a handful of segments, so the count is now capped at u16::MAX; larger claims fail as InvalidReferredCount before any retention-flag bytes are consumed. Regression test included. Updates SOURCE.json's local hash for src/segment.rs and records the patch in vendor/README.craft-reader.md, following the vendored tree's provenance practice. Co-Authored-By: Claude Code <noreply@anthropic.com> * hayro-jbig2: record git-blob sha256 for patched segment.rs The previous entry hashed the Windows working-tree checkout (CRLF) of src/segment.rs instead of the LF content git stores, so every other manifest entry's convention (git-blob content hash) was broken by this one file. Re-hash against `git show HEAD:vendor/hayro-jbig2/src/segment.rs` as the vendored manifests' other 30 entries do. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
ffe7f8fce8
commit
f20f364d55
3 files changed
+44
-1
No files matched your search
Vendored
+6
@@ -36,6 +36,12 @@ Byte transfer-function samples use rounded 8-bit values; failed or non-finite ev
|
||||
leave the sample unchanged. Historical source hashes remain in each `SOURCE.json`,
|
||||
alongside local hashes and the compatibility reference.
|
||||
|
||||
The JBIG2 segment parser caps the long-form referred-to-segment count at `u16::MAX`
|
||||
and rejects a larger claim as an invalid count before any retention-flag or
|
||||
referred-to-number bytes are consumed, so a crafted header cannot turn
|
||||
`Vec::with_capacity` into a multi-gigabyte reservation that aborts on allocation
|
||||
failure.
|
||||
|
||||
This remains the patched 0.7 backend. It retains the existing Bradford Lab conversion,
|
||||
floating component precision and codec versions, rather than claiming full 0.8
|
||||
equivalence. The independent color oracles, backend tests and facade contracts cover
|
||||
|
||||
Vendored
+1
-1
@@ -176,7 +176,7 @@
|
||||
"path": "src/segment.rs",
|
||||
"source_sha256": "58d9fd05a15358b4ca945ffa3b8c1872f4ea57f8bbe50e8db3c6fce5bcac2914",
|
||||
"source_git_blob": "35c17c38af62b1fece4e280cda923cdcb23a8661",
|
||||
"sha256": "58d9fd05a15358b4ca945ffa3b8c1872f4ea57f8bbe50e8db3c6fce5bcac2914"
|
||||
"sha256": "c64e2845dbdd26e33b5461acde399ba4820f9942e9090a32782f44ff0fd5e61f"
|
||||
},
|
||||
{
|
||||
"path": "src/simd.rs",
|
||||
|
||||
Vendored
+37
@@ -8,6 +8,14 @@ use alloc::vec::Vec;
|
||||
use crate::error::{ParseError, Result, SegmentError, bail, err};
|
||||
use crate::reader::Reader;
|
||||
|
||||
/// PdfCraft patch: the most segments one segment may refer to. A crafted long-form
|
||||
/// header can claim a 29-bit count, which `Vec::with_capacity` below would turn into
|
||||
/// a multi-gigabyte reservation before a single referred-to number is read — an
|
||||
/// allocation failure then aborts the process in a way `catch_unwind` cannot contain.
|
||||
/// Real files refer to a handful of segments, so `u16::MAX` is far beyond any
|
||||
/// legitimate need.
|
||||
pub(crate) const MAX_REFERRED_TO_SEGMENTS: u32 = u16::MAX as u32;
|
||||
|
||||
/// "The segment type is a number between 0 and 63, inclusive. Not all values
|
||||
/// are allowed." (7.3)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -183,6 +191,12 @@ pub(crate) fn parse_segment_header(reader: &mut Reader<'_>) -> Result<SegmentHea
|
||||
u32::from_be_bytes([count_and_retention & 0x1F, rest[0], rest[1], rest[2]])
|
||||
};
|
||||
|
||||
// Reject an absurd count before reserving memory for it, and before
|
||||
// skipping the retention-flag bytes it claims to own.
|
||||
if referred_to_count > MAX_REFERRED_TO_SEGMENTS {
|
||||
bail!(SegmentError::InvalidReferredCount);
|
||||
}
|
||||
|
||||
// Skip retention flag bytes in long form.
|
||||
// "The first one-byte field following the initial four-byte field is formatted
|
||||
// as follows: Bit 0: Retain bit for this segment. Bit 1-7: Retain bits for
|
||||
@@ -416,4 +430,27 @@ mod tests {
|
||||
|
||||
assert_eq!(header.data_length, Some(32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_referred_to_count_is_capped() {
|
||||
use crate::error::DecodeError;
|
||||
|
||||
// A long-form header claiming the 29-bit maximum of 536,870,911
|
||||
// referred-to segments would otherwise make `Vec::with_capacity`
|
||||
// reserve ~2 GiB before a single referred-to number is read.
|
||||
// The first byte is 0xFF: long form, count bits 0x1F; the count is
|
||||
// 0x1FFFFFFF.
|
||||
let data = [
|
||||
0x00, 0x01, 0x00, 0x00, // Segment number = 65536
|
||||
0x86, // Flags: type 6, page assoc 1 byte, deferred non-retain
|
||||
0xFF, 0xFF, 0xFF, 0xFF, // Long form, count = 536,870,911
|
||||
];
|
||||
|
||||
let mut reader = Reader::new(&data);
|
||||
let error = parse_segment_header(&mut reader).unwrap_err();
|
||||
assert!(matches!(
|
||||
error,
|
||||
DecodeError::Segment(SegmentError::InvalidReferredCount)
|
||||
));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user