diff --git a/vendor/README.craft-reader.md b/vendor/README.craft-reader.md index 2ce83e9a..f9f281ab 100644 --- a/vendor/README.craft-reader.md +++ b/vendor/README.craft-reader.md @@ -384,3 +384,20 @@ dictionary of revision 2–4 whose `/Length` is above 128 bits is refused with ` Those revisions take the file key from a 16-byte MD5 hash, so `/V 2 /R 3 /Length 256` sliced past the hash and panicked on open. Test: `crypto::pdfcraft_password_tests::legacy_key_length_above_128_bits_is_refused_not_a_panic`. + +JBIG2 decode bounds, ported from PdfCraft commits +[85c10051](https://github.com/storytold/pdfcraft/commit/85c100514) (hayro-jbig2 patches 5 to 9), +[82439525](https://github.com/storytold/pdfcraft/commit/824395256) (patch 10) and +[10107d94](https://github.com/storytold/pdfcraft/commit/10107d943) (patch 12), marked `PdfCraft patch` +in `hayro-jbig2`: a page's segments may hold at most 64 MiB (`MAX_RETAINED_WORDS`; regions, +dictionaries, Huffman tables, retained coding contexts), a segment may refer to at most 2^20 symbols +(`MAX_REFERRED_SYMBOLS`), a halftone grid has at most 2^25 cells, a custom Huffman table 2^16 lines, +a stream with its globals 2^20 segments and 2^22 references; a stream may ask for 2^30 units of +decoding work in total (`MAX_WORK`, charged item by item before each is decoded), and an image past a +bound is refused (not drawn) like the other caps; region locations, refinement offsets, symbol corners, +Huffman range lines and integers at the end of an `i32` no longer overflow. The `/JBIG2Globals` change +of 85c10051 for hayro-syntax was already carried here. PdfCraft's raster tests are adapted in +`hayro-jbig2/src/pdfcraft_tests.rs` (`jbig2_decoding_work_is_counted_item_by_item`, +`jbig2_placements_at_the_end_of_an_i32_draw_nothing`); the unit tests in `lib.rs`, `bitmap.rs` and +`decode/symbol.rs` come with the patches. PdfCraft's later JBIG2 follow-ups (b7ec3669, 72e763ad, +e3bc4e85, patch 13 and later) are not part of this port. diff --git a/vendor/hayro-jbig2/SOURCE.json b/vendor/hayro-jbig2/SOURCE.json index 6a972c12..5efee12a 100644 --- a/vendor/hayro-jbig2/SOURCE.json +++ b/vendor/hayro-jbig2/SOURCE.json @@ -62,7 +62,7 @@ "path": "src/bitmap.rs", "source_sha256": "59ed83d8bac6c39e40270a9856ca3faee628de7d9efae1b01f714e140e2b6b7e", "source_git_blob": "35ed97561597d30a70fe819cf8cfbd3780538f75", - "sha256": "99dddbef5f7b36c55d0e914d07664326598919eb6d54cdfa93f300076a8ae69d" + "sha256": "dd330bda095350b91eeb15b029bdae209e1ed3d5c711dff279b79abd7832f847" }, { "path": "src/decode/generic.rs", @@ -74,13 +74,13 @@ "path": "src/decode/generic_refinement.rs", "source_sha256": "625075671e510e8ff1d7c3cade857792413c091d57a35561ad0591d7f04c2ea5", "source_git_blob": "a566b8de58c078dec874d5d1dea36c0f557f1a16", - "sha256": "625075671e510e8ff1d7c3cade857792413c091d57a35561ad0591d7f04c2ea5" + "sha256": "93e2ce1a40c35fed24d4d611cfc1763518e00167b4e3c0a0671a18afa37c7064" }, { "path": "src/decode/halftone.rs", "source_sha256": "0e4a5ef92e194b4eba2367196e904786dd7d346cdf0ab291c72a77b5aee5873a", "source_git_blob": "eb5c1c21e8a7d1a99c497e83ea59547b7dd19a96", - "sha256": "0e4a5ef92e194b4eba2367196e904786dd7d346cdf0ab291c72a77b5aee5873a" + "sha256": "16454b87a0c23e42d09936ae88f85cc03b4ea0e9d00ee409e751b4c94906c9d9" }, { "path": "src/decode/mod.rs", @@ -92,43 +92,43 @@ "path": "src/decode/pattern.rs", "source_sha256": "abdd326c08dee0488678d74915e6bb4138c9b0c07d44f24c3a5794ff640799ba", "source_git_blob": "2c15d70b8f9925638e41cec0eb1d73b91c1533f2", - "sha256": "abdd326c08dee0488678d74915e6bb4138c9b0c07d44f24c3a5794ff640799ba" + "sha256": "d0a431725919158734f047101d004001f2e58fc21af8f5e92ef03ae06041b9b1" }, { "path": "src/decode/symbol.rs", "source_sha256": "aa57e88cdc6fd1647d7996875c253a643fedfc4438d5137e326e00d17d0d29ee", "source_git_blob": "f1aa8a6639e53dd350057d8d078198d363daa918", - "sha256": "aa57e88cdc6fd1647d7996875c253a643fedfc4438d5137e326e00d17d0d29ee" + "sha256": "627de1c83267597efef6d6c0d49aec004f43fd6e63490f117f744ed9210b47f0" }, { "path": "src/decode/text.rs", "source_sha256": "01863cdcd48ab68b5230948d12212a815d293c7c9d5382a140772088bfb1c405", "source_git_blob": "bbc2ac55c7d7d5b8aebffbecfe93fda8b88526e8", - "sha256": "01863cdcd48ab68b5230948d12212a815d293c7c9d5382a140772088bfb1c405" + "sha256": "32ae695fe181ba07181fa9915b6bce2ca333031c392fae66dd15ce955a8d7b95" }, { "path": "src/error.rs", "source_sha256": "39b6ec8fa058bc5c1722892290882e123d193d3844ed5846b30b742b94ba72b0", "source_git_blob": "c579368b44b444566178e68a2705cf9561fad71f", - "sha256": "39b6ec8fa058bc5c1722892290882e123d193d3844ed5846b30b742b94ba72b0" + "sha256": "326dd926cc52b191d0c0398b3b2bb482ca267919e369045f129930cfa485e7fb" }, { "path": "src/file.rs", "source_sha256": "10467a415ab2e8b92a8951a86f4c02c5c74f5a005629a11865a20bc23b26ca55", "source_git_blob": "1440a22efa89f0deec0029968c3a20bad8526d63", - "sha256": "10467a415ab2e8b92a8951a86f4c02c5c74f5a005629a11865a20bc23b26ca55" + "sha256": "c96741babbfacfd78bd2b63f0f16541aaa235f14bb4344ecd4707e3ca5942df1" }, { "path": "src/gray_scale.rs", "source_sha256": "46f12893f4573610789015ba7036f6afb1d30b1454c0c674fc7ccdd9337d7487", "source_git_blob": "2836ec77a7c8d3970b13dd46f576778c64bfe2d9", - "sha256": "46f12893f4573610789015ba7036f6afb1d30b1454c0c674fc7ccdd9337d7487" + "sha256": "4bb0963338bfc9e3773db822465d5acbc1267c3349f04b30bff8c52e254874bb" }, { "path": "src/huffman_table.rs", "source_sha256": "8c0dc0681351b9573fedd00afed26173be8bd4583210063bff75c4e95e9d8054", "source_git_blob": "2183c8eea065ff4f6bd708fa92fa4ba66844e739", - "sha256": "8c0dc0681351b9573fedd00afed26173be8bd4583210063bff75c4e95e9d8054" + "sha256": "943fc4fe1e20d62df3908593571d1fa752de3c22a7d90b62dbcb59a7d3b04f1d" }, { "path": "src/huffman_tables_generated.rs", @@ -140,7 +140,7 @@ "path": "src/integer_decoder.rs", "source_sha256": "6a5a89d8df1ab6c011f7a92223fbd384bbf274ccb1f15705c994d7bd55d1a680", "source_git_blob": "8b7656d2964058618687ce2409142dddcdeb068a", - "sha256": "6a5a89d8df1ab6c011f7a92223fbd384bbf274ccb1f15705c994d7bd55d1a680" + "sha256": "6c41270b6ae1ef85bdfd26c37085c6ae7782f82b15951b53a0758b01ee175920" }, { "path": "src/integration.rs", @@ -158,7 +158,7 @@ "path": "src/lib.rs", "source_sha256": "22463230cc82543ee93791b642566a308f0f4eb14d3089b24dc2e7f06d0f784c", "source_git_blob": "ad77a6dd17920b2db66f6bb5a0f2cb8efaafd41a", - "sha256": "22463230cc82543ee93791b642566a308f0f4eb14d3089b24dc2e7f06d0f784c" + "sha256": "d0431d1c57c552b250b7b48895f0d768bfc75496b30cd0146b77764b09759c0f" }, { "path": "src/page_info.rs", diff --git a/vendor/hayro-jbig2/src/bitmap.rs b/vendor/hayro-jbig2/src/bitmap.rs index 6d1e3c55..a240ca43 100644 --- a/vendor/hayro-jbig2/src/bitmap.rs +++ b/vendor/hayro-jbig2/src/bitmap.rs @@ -18,6 +18,35 @@ pub(crate) const MAX_DIMENSION: u32 = u16::MAX as u32; /// pixels, decoded one by one. pub(crate) const MAX_PIXELS: u64 = 1 << 28; +/// PdfCraft patch: the most cells a halftone grid may have, which still gives the largest page +/// (see [`MAX_PIXELS`]) cells of 3 × 3, unrotated. Each cell takes a 4-byte value while the grid decodes, so +/// the pixel bound alone let a 98-byte segment take 1 GiB; this one leaves 128 MiB. +pub(crate) const MAX_GRID_CELLS: u64 = 1 << 25; + +/// PdfCraft patch: the decoding work a stream may ask for, in pixels decoded from their code: four +/// of the largest pages, about 6 s on data the decoder predicts and about 17 s on data it can't, so +/// a render finishes inside its 20 s watchdog. Each bitmap is bounded on its own, but nothing +/// bounded how many there are: 1,140 bytes of generic regions took over 40 s, a 795-byte PDF's one +/// halftone 265 s, and a 923-byte PDF's regions would have taken days. +pub(crate) const MAX_WORK: u64 = 4 * MAX_PIXELS; + +/// PdfCraft patch: the work of a line of a Huffman table built for symbols, about 50 ns (see +/// [`MAX_WORK`]). +pub(crate) const HUFFMAN_LINE_WORK: u64 = 10; + +/// PdfCraft patch: the work of decoding a bitmap pixel by pixel (see [`MAX_WORK`]). A row costs up +/// to 21 ns however few pixels it has, about 4 pixels' worth, so one of no width isn't free: 16,384 +/// regions of 0 × 65535 took 9 s at a cost of nothing. +pub(crate) fn decode_work(width: u32, height: u32) -> u64 { + u64::from(height) * (u64::from(width) + 4) +} + +/// PdfCraft patch: the work of copying a bitmap (see [`MAX_WORK`]): a word at a time, and a row is +/// a copy of its own, so a tall thin bitmap costs its rows, not its pixels. +pub(crate) fn copy_work(width: u32, height: u32) -> u64 { + u64::from(height) * (u64::from(width.div_ceil(WORD_BITS)) + 1) +} + /// The underlying word type for packed pixel storage. /// Change this to `u32` to switch back to 32-bit words. pub(crate) type Word = u32; @@ -25,6 +54,23 @@ pub(crate) type Word = u32; pub(crate) const WORD_BITS: u32 = Word::BITS; pub(crate) const WORD_SHIFT: u32 = WORD_BITS - 1; +/// PdfCraft patch: the words a [`Bitmap`] takes before its pixels. A symbol of width 0 has no +/// pixels and still costs this: 5 KB of zero-width dictionaries held 781 MB. +pub(crate) const BITMAP_WORDS: usize = size_of::() / size_of::(); + +/// PdfCraft patch: refuse a page past the bounds on a bitmap (see `MAX_PIXELS`). A stream is held +/// to this when it is opened as well as when its page bitmap is sized: a caller sizes its own +/// output from the page header in between, and a 743-byte PDF's Indexed image asked for 137 GB. +pub(crate) fn check_page_size(width: u32, height: u32) -> Result<()> { + if width > MAX_DIMENSION || height > MAX_DIMENSION { + return Err(OverflowError::BitmapDimension.into()); + } + if u64::from(width) * u64::from(height) > MAX_PIXELS { + return Err(OverflowError::BitmapDimension.into()); + } + Ok(()) +} + /// A decoded bitmap with position information. /// /// "Pixels decoded by the MMR decoder having the value 'black' shall be treated @@ -58,6 +104,11 @@ fn fill_words(data: &mut Vec, len: usize, word: Word) -> Result<()> { } impl Bitmap { + /// PdfCraft patch: the words this bitmap holds, its header included. + pub(crate) fn words(&self) -> usize { + self.data.len().saturating_add(BITMAP_WORDS) + } + /// Create a new bitmap filled with white pixels (false). /// /// The bitmap is positioned at (0, 0). @@ -102,13 +153,7 @@ impl Bitmap { height: u32, default_pixel: bool, ) -> Result<()> { - if width > MAX_DIMENSION || height > MAX_DIMENSION { - return Err(OverflowError::BitmapDimension.into()); - } - // PdfCraft patch: see `MAX_PIXELS` (the page bitmap is sized here). - if u64::from(width) * u64::from(height) > MAX_PIXELS { - return Err(OverflowError::BitmapDimension.into()); - } + check_page_size(width, height)?; let stride = width.div_ceil(WORD_BITS); let default_word: Word = if default_pixel { !0 } else { 0 }; @@ -159,7 +204,8 @@ impl Bitmap { // TODO: Figure out how we can optimize this, especially since this is a // bottleneck for symbols/patterns. let dest_x_start = x.max(0); - let dest_x_end = (x + other.width as i32).min(self.width as i32); + // Saturating: a location can be anywhere an `i32` reaches, and past the end it is off the page. + let dest_x_end = x.saturating_add(other.width as i32).min(self.width as i32); if dest_x_start >= dest_x_end { return; @@ -170,7 +216,7 @@ impl Bitmap { let dest_x_end = dest_x_end as u32; for src_y in 0..other.height { - let dest_y = y + src_y as i32; + let dest_y = y.saturating_add(src_y as i32); if dest_y < 0 || dest_y >= self.height as i32 { continue; } diff --git a/vendor/hayro-jbig2/src/decode/generic_refinement.rs b/vendor/hayro-jbig2/src/decode/generic_refinement.rs index a566b8de..a6634439 100644 --- a/vendor/hayro-jbig2/src/decode/generic_refinement.rs +++ b/vendor/hayro-jbig2/src/decode/generic_refinement.rs @@ -179,10 +179,12 @@ impl<'a> RefinementContextGatherer<'a> { pub(crate) fn start_row(&mut self, region: &Bitmap, reference: &Bitmap, y: u32) { self.reg_y = y; - let ref_y = y as i32 - self.reference_dy; + // The offsets can be anywhere an `i32` reaches, so coordinates in the reference wrap here and + // below. Wrapped, they are off the reference, whose pixels there read as 0. + let ref_y = (y as i32).wrapping_sub(self.reference_dy); // This will on purpose wrap to a very large `u32` for negative values, such that // they resolve to 0 in `ContextGatherer::load_word`. - let ref_x0 = (-self.reference_dx) as u32; + let ref_x0 = self.reference_dx.wrapping_neg() as u32; self.reg_cur_x = 0; self.reg_m1 = if y >= 1 { @@ -193,9 +195,9 @@ impl<'a> RefinementContextGatherer<'a> { self.reg_cur = 0; self.ref_cur_x = 0; - self.ref_m1 = ContextGatherer::load_word(reference, (ref_y - 1) as u32, 0); + self.ref_m1 = ContextGatherer::load_word(reference, ref_y.wrapping_sub(1) as u32, 0); self.ref_cur = ContextGatherer::load_word(reference, ref_y as u32, 0); - self.ref_p1 = ContextGatherer::load_word(reference, (ref_y + 1) as u32, 0); + self.ref_p1 = ContextGatherer::load_word(reference, ref_y.wrapping_add(1) as u32, 0); self.ctx = match self.template { RefinementTemplate::Template1 => { @@ -257,7 +259,7 @@ impl<'a> RefinementContextGatherer<'a> { self.reg_cur = ContextGatherer::load_word(region, self.reg_y, new_start); } - let ref_x_signed = x as i32 - self.reference_dx; + let ref_x_signed = (x as i32).wrapping_sub(self.reference_dx); if ref_x_signed < 0 { self.ref_cur_x = 0; self.ref_m1 = 0; @@ -268,18 +270,20 @@ impl<'a> RefinementContextGatherer<'a> { if ref_x + 1 >= self.ref_cur_x + WORD_BITS || ref_x < self.ref_cur_x { let new_start = ref_x.saturating_sub(1); - let ref_y = self.reg_y as i32 - self.reference_dy; + let ref_y = (self.reg_y as i32).wrapping_sub(self.reference_dy); self.ref_cur_x = new_start; - self.ref_m1 = ContextGatherer::load_word(reference, (ref_y - 1) as u32, new_start); + self.ref_m1 = + ContextGatherer::load_word(reference, ref_y.wrapping_sub(1) as u32, new_start); self.ref_cur = ContextGatherer::load_word(reference, ref_y as u32, new_start); - self.ref_p1 = ContextGatherer::load_word(reference, (ref_y + 1) as u32, new_start); + self.ref_p1 = + ContextGatherer::load_word(reference, ref_y.wrapping_add(1) as u32, new_start); } } } #[inline(always)] pub(crate) fn tpgr_all_same(&self, x: u32) -> bool { - let ref_x = (x as i32 - self.reference_dx) as u32; + let ref_x = (x as i32).wrapping_sub(self.reference_dx) as u32; let rbx = ref_x.wrapping_sub(self.ref_cur_x); #[inline(always)] @@ -310,7 +314,7 @@ impl<'a> RefinementContextGatherer<'a> { #[inline(always)] pub(crate) fn ref_center_pixel(&self, x: u32) -> u8 { - let ref_x = (x as i32 - self.reference_dx) as u32; + let ref_x = (x as i32).wrapping_sub(self.reference_dx) as u32; let rbx = ref_x.wrapping_sub(self.ref_cur_x); ContextGatherer::get_buf_pixel(self.ref_cur, rbx) as u8 @@ -329,7 +333,7 @@ impl<'a> RefinementContextGatherer<'a> { #[inline(always)] fn gather_template0_default(&mut self, _region: &Bitmap, _reference: &Bitmap, x: u32) -> u16 { let bx = x - self.reg_cur_x; - let ref_x = (x as i32 - self.reference_dx) as u32; + let ref_x = (x as i32).wrapping_sub(self.reference_dx) as u32; let rbx = ref_x.wrapping_sub(self.ref_cur_x); let new_pixels = (ContextGatherer::get_buf_pixel(self.reg_m1, bx + 1) << 10) @@ -345,12 +349,12 @@ impl<'a> RefinementContextGatherer<'a> { #[inline(always)] fn gather_template0_custom(&mut self, region: &Bitmap, reference: &Bitmap, x: u32) -> u16 { let bx = x - self.reg_cur_x; - let ref_x_i = x as i32 - self.reference_dx; + let ref_x_i = (x as i32).wrapping_sub(self.reference_dx); let rbx = ref_x_i as u32 - self.ref_cur_x; let xi = x as i32; let yi = self.reg_y as i32; - let ref_y = yi - self.reference_dy; + let ref_y = yi.wrapping_sub(self.reference_dy); let at1 = self.at_pixels[0]; let at2 = self.at_pixels[1]; @@ -360,8 +364,8 @@ impl<'a> RefinementContextGatherer<'a> { | (ContextGatherer::get_buf_pixel(self.reg_m1, bx + 1) << 10) | (ContextGatherer::get_buf_pixel(self.reg_cur, bx.wrapping_sub(1)) << 9) | ((reference.get_pixel( - (ref_x_i + at2.x as i32) as u32, - (ref_y + at2.y as i32) as u32, + ref_x_i.wrapping_add(at2.x as i32) as u32, + ref_y.wrapping_add(at2.y as i32) as u32, ) as u16) << 8) | (ContextGatherer::get_buf_pixel(self.ref_m1, rbx.wrapping_add(1)) << 6) @@ -375,7 +379,7 @@ impl<'a> RefinementContextGatherer<'a> { #[inline(always)] fn gather_template1(&mut self, _region: &Bitmap, _reference: &Bitmap, x: u32) -> u16 { let bx = x - self.reg_cur_x; - let ref_x = (x as i32 - self.reference_dx) as u32; + let ref_x = (x as i32).wrapping_sub(self.reference_dx) as u32; let rbx = ref_x.wrapping_sub(self.ref_cur_x); let new_pixels = (ContextGatherer::get_buf_pixel(self.reg_m1, bx + 1) << 7) diff --git a/vendor/hayro-jbig2/src/decode/halftone.rs b/vendor/hayro-jbig2/src/decode/halftone.rs index eb5c1c21..10000f80 100644 --- a/vendor/hayro-jbig2/src/decode/halftone.rs +++ b/vendor/hayro-jbig2/src/decode/halftone.rs @@ -7,7 +7,7 @@ use super::RegionBitmap; use super::pattern::PatternDictionary; use super::{CombinationOperator, RegionSegmentInfo, Template, parse_region_segment_info}; use crate::ScratchBuffers; -use crate::bitmap::{Bitmap, MAX_DIMENSION, WORD_BITS}; +use crate::bitmap::{Bitmap, MAX_DIMENSION, WORD_BITS, copy_work, decode_work}; use crate::error::{OverflowError, ParseError, RegionError, Result, bail}; use crate::gray_scale::{GrayScaleParams, decode_gray_scale_image}; use crate::reader::Reader; @@ -48,11 +48,7 @@ pub(crate) fn decode_into( None }; - // "3) Set HBPP to ⌈log₂(HNUMPATS)⌉." (6.6.5) - let bits_per_pixel = (pattern_dict.patterns.len() as u32) - .saturating_sub(1) - .checked_ilog2() - .map_or(1, |n| n + 1); + let bits_per_pixel = bits_per_pixel(pattern_dict); // "4) Decode an image GI of size HGW by HGH with HBPP bits per pixel using // the gray-scale image decoding procedure as described in Annex C." (6.6.5) @@ -73,6 +69,28 @@ pub(crate) fn decode_into( Ok(()) } +/// PdfCraft patch: the work of a halftone region (see `bitmap::MAX_WORK`): a bit plane of the grid +/// for each bit of a pattern's index, a pattern copied for each cell, and the region itself. One +/// such segment in a 795-byte PDF took 265 s. +pub(crate) fn work(header: &HalftoneRegionHeader<'_>, patterns: &PatternDictionary) -> u64 { + let grid = &header.grid_position_and_size; + let cells = u64::from(grid.width) * u64::from(grid.height); + let planes = u64::from(bits_per_pixel(patterns)); + let copied = copy_work(patterns.pattern_width, patterns.pattern_height); + planes + .saturating_mul(decode_work(grid.width, grid.height)) + .saturating_add(cells.saturating_mul(copied)) + .saturating_add(copy_work(header.region_info.width, header.region_info.height)) +} + +/// "3) Set HBPP to ⌈log₂(HNUMPATS)⌉." (6.6.5) +fn bits_per_pixel(patterns: &PatternDictionary) -> u32 { + (patterns.patterns.len() as u32) + .saturating_sub(1) + .checked_ilog2() + .map_or(1, |n| n + 1) +} + /// Parse a halftone region segment header (7.4.5.1). pub(crate) fn parse<'a>(reader: &mut Reader<'a>) -> Result> { let region_info = parse_region_segment_info(reader)?; @@ -97,8 +115,8 @@ pub(crate) fn parse<'a>(reader: &mut Reader<'a>) -> Result MAX_DIMENSION || grid_height > MAX_DIMENSION { bail!(OverflowError::GridDimension); } - // PdfCraft patch: the product is bounded too (see `bitmap::MAX_PIXELS`). - if u64::from(grid_width) * u64::from(grid_height) > crate::bitmap::MAX_PIXELS { + // PdfCraft patch: the product is bounded too (see `bitmap::MAX_GRID_CELLS`). + if u64::from(grid_width) * u64::from(grid_height) > crate::bitmap::MAX_GRID_CELLS { bail!(OverflowError::GridDimension); } diff --git a/vendor/hayro-jbig2/src/decode/pattern.rs b/vendor/hayro-jbig2/src/decode/pattern.rs index 2c15d70b..03d4d8eb 100644 --- a/vendor/hayro-jbig2/src/decode/pattern.rs +++ b/vendor/hayro-jbig2/src/decode/pattern.rs @@ -3,15 +3,20 @@ use super::{AdaptiveTemplatePixel, Template, generic}; use crate::ScratchBuffers; use crate::arithmetic_decoder::{ArithmeticDecoder, ArithmeticDecoderContext}; -use crate::bitmap::{Bitmap, WORD_BITS, Word}; +use crate::bitmap::{Bitmap, WORD_BITS, Word, copy_work}; use crate::error::{OverflowError, ParseError, Result}; use crate::reader::Reader; use alloc::vec::Vec; +/// PdfCraft patch: the most words of shifted copies a dictionary keeps, 16 MiB, where 256 patterns +/// of 32 rows take 2 MiB. A 48-byte dictionary of 65,535 such patterns asked for 539 MB. +const MAX_SHIFTED_WORDS: u64 = 1 << 22; + /// Decode a pattern dictionary segment (7.4.4.2, 6.7). pub(crate) fn decode( header: &PatternDictionaryHeader<'_>, scratch: &mut ScratchBuffers, + room: u64, ) -> Result { let pattern_width = header.pattern_width as u32; let pattern_height = header.pattern_height as u32; @@ -133,7 +138,19 @@ pub(crate) fn decode( // for all possible 32-bit alignments, which later on allows us to // very easily blit it into the backdrop, significantly speeding up // the whole process. - let shifted_patterns = if pattern_width <= WORD_BITS && pattern_height <= WORD_BITS { + // + // PdfCraft patch: only when the table is small, and fits what the page may still hold + // (`room`), so that a speed-up never gets an image refused. It can be left out: + // `shifted_pattern` then returns `None` and the patterns are combined the slow way. + let shifted_words = (patterns.len() as u64) + .saturating_mul(32 * 2) + .saturating_mul(u64::from(pattern_height)); + let pattern_words = patterns.iter().map(|p| p.words() as u64).fold(0, u64::saturating_add); + let shifted_patterns = if pattern_width <= WORD_BITS + && pattern_height <= WORD_BITS + && shifted_words <= MAX_SHIFTED_WORDS + && shifted_words.saturating_add(pattern_words) <= room + { let h = pattern_height as usize; let mut data = Vec::with_capacity(patterns.len() * 32 * h * 2); for pattern in &patterns { @@ -180,6 +197,11 @@ impl PatternDictionary { let start = (pattern_idx * 32 + bit_offset as usize) * len; self.shifted_patterns.get(start..start + len) } + + /// PdfCraft patch: the words the dictionary holds, its patterns and their shifted copies. + pub(crate) fn words(&self) -> usize { + self.patterns.iter().map(Bitmap::words).fold(self.shifted_patterns.len(), usize::saturating_add) + } } /// Parsed pattern dictionary segment header (7.4.4.1). @@ -196,6 +218,17 @@ pub(crate) struct PatternDictionaryHeader<'a> { pub(crate) data: &'a [u8], } +impl PatternDictionaryHeader<'_> { + /// PdfCraft patch: the work of decoding the patterns, side by side in one bitmap, and of + /// taking each out of it (see `bitmap::MAX_WORK`). + pub(crate) fn work(&self) -> u64 { + let (width, height) = (u32::from(self.pattern_width), u32::from(self.pattern_height)); + let patterns = u64::from(self.num_patterns) + 1; + let side_by_side = u64::from(height) * (patterns.saturating_mul(u64::from(width)) + 4); + side_by_side.saturating_add(patterns.saturating_mul(copy_work(width, height))) + } +} + /// Parse a pattern dictionary segment header (7.4.4.1). pub(crate) fn parse<'a>(reader: &mut Reader<'a>) -> Result> { let flags_byte = reader.read_byte().ok_or(ParseError::UnexpectedEof)?; diff --git a/vendor/hayro-jbig2/src/decode/symbol.rs b/vendor/hayro-jbig2/src/decode/symbol.rs index f1aa8a66..94250a3d 100644 --- a/vendor/hayro-jbig2/src/decode/symbol.rs +++ b/vendor/hayro-jbig2/src/decode/symbol.rs @@ -4,7 +4,7 @@ use alloc::vec; use alloc::vec::Vec; use crate::arithmetic_decoder::{ArithmeticDecoder, ArithmeticDecoderContext}; -use crate::bitmap::{Bitmap, WORD_BITS}; +use crate::bitmap::{BITMAP_WORDS, Bitmap, HUFFMAN_LINE_WORK, WORD_BITS, Word, decode_work}; use crate::decode::generic::{decode_bitmap_mmr, parse_adaptive_template_pixels}; use crate::decode::text::{ DecodeContext, ReferenceCorner, TextRegionContexts, TextRegionFlags, TextRegionHeader, @@ -45,6 +45,8 @@ pub(crate) fn decode( referred_tables: &[HuffmanTable], standard_tables: &StandardHuffmanTables, input_contexts: Option<&RetainedContexts>, + room: u64, + work_left: &mut u64, ) -> Result { let num_new_symbols = header.num_new_symbols; @@ -58,6 +60,9 @@ pub(crate) fn decode( total_width: 0, height_class_height: 0, pixels: 0, + new_words: 0, + room, + work_left, header, standard_tables, }; @@ -130,6 +135,16 @@ pub(crate) fn decode( if ctx.pixels > MAX_DICTIONARY_PIXELS { bail!(OverflowError::BitmapDimension); } + // PdfCraft patch: see `bitmap::MAX_WORK`. + let work = decode_work(symbol_width, ctx.height_class_height); + *ctx.work_left = ctx.work_left.checked_sub(work).ok_or(OverflowError::DecodingWork)?; + // PdfCraft patch: see `room`. Rows take a word at least, so symbols of one column + // held 270 MiB within the pixel bound, however few the dictionary exported. + let rows = u64::from(symbol_width.div_ceil(WORD_BITS)) * u64::from(ctx.height_class_height); + ctx.new_words = ctx.new_words.saturating_add(rows).saturating_add(BITMAP_WORDS as u64); + if ctx.new_words > ctx.room { + bail!(OverflowError::RetainedBitmaps); + } match (ctx.header.flags.use_huffman, ctx.header.flags.use_refagg) { (false, false) => { @@ -202,6 +217,18 @@ pub(crate) struct SymbolDictionary { pub(crate) retained_contexts: Option, } +impl SymbolDictionary { + /// PdfCraft patch: the words the dictionary holds, its retained contexts included. Those are + /// a byte each, and 1.1 MB of dictionaries that retain them, 103 KB behind Flate, held 2.89 GB. + pub(crate) fn words(&self) -> usize { + let contexts = self.retained_contexts.as_ref().map_or(0, |contexts| { + let bytes = contexts.generic_region.len().saturating_add(contexts.refinement_region.len()); + bytes.saturating_mul(size_of::()) / size_of::() + }); + self.exported_symbols.iter().map(Bitmap::words).fold(contexts, usize::saturating_add) + } +} + /// Decode a symbol bitmap using refinement/aggregate coding (6.5.8.2). fn decode_refinement_aggregation_bitmap( ctx: &mut SymbolDecodeContext<'_>, @@ -337,6 +364,11 @@ fn decode_aggregation_bitmap( ) -> Result { let use_huffman = ctx.header.flags.use_huffman; + // PdfCraft patch: see `bitmap::MAX_WORK`. What follows is sized by all the symbols, for each + // aggregated symbol, at about 50 ns a symbol: a 713-byte PDF asked for an hour. + let work = u64::from(ctx.total_symbols()).saturating_mul(HUFFMAN_LINE_WORK); + *ctx.work_left = ctx.work_left.checked_sub(work).ok_or(OverflowError::DecodingWork)?; + // Concatenate input and new symbols. let mut all_symbols: Vec<&Bitmap> = Vec::with_capacity(ctx.symbols.input.len() + ctx.symbols.new.len()); @@ -422,7 +454,7 @@ fn decode_aggregation_bitmap( header.region_info.y_location, header.flags.default_pixel, )?; - decode_with(decode_ctx, &all_symbols, &header, &mut bitmap)?; + decode_with(decode_ctx, &all_symbols, &header, &mut bitmap, ctx.work_left)?; Ok(bitmap) } @@ -463,6 +495,14 @@ struct SymbolDecodeContext<'a> { symbols_decoded_count: u32, total_width: u32, height_class_height: u32, + /// PdfCraft patch: the words the page may still hold, which neither the new symbols nor the + /// exported ones may pass. They are counted as they are made: counted only once the + /// dictionary was stored, 8 MiB symbols exported again and again killed the process. + room: u64, + /// PdfCraft patch: the words of the new symbols so far (see `room`). + new_words: u64, + /// PdfCraft patch: the decoding work the stream may still ask for (see `bitmap::MAX_WORK`). + work_left: &'a mut u64, /// PdfCraft patch: symbol pixels decoded so far (see `MAX_DICTIONARY_PIXELS`). pixels: u64, } @@ -708,6 +748,7 @@ fn export_symbols(ctx: &mut SymbolDecodeContext<'_>) -> Result> { u32::try_from(value).map_err(|_| HuffmanError::InvalidCode.into()) }; let mut exported = Vec::with_capacity(ctx.header.num_exported_symbols as usize); + let mut exported_words: u64 = 0; let mut index: u32 = 0; let mut should_export = false; // PdfCraft patch: see `EXTRA_EMPTY_STEPS`. @@ -730,9 +771,13 @@ fn export_symbols(ctx: &mut SymbolDecodeContext<'_>) -> Result> { let symbol = ctx .symbols .get(symbol_idx as usize) - .ok_or(SymbolError::OutOfRange)? - .clone(); - exported.push(symbol); + .ok_or(SymbolError::OutOfRange)?; + // PdfCraft patch: see `room`. + exported_words = exported_words.saturating_add(symbol.words() as u64); + if exported_words > ctx.room { + bail!(OverflowError::RetainedBitmaps); + } + exported.push(symbol.clone()); } } @@ -864,3 +909,25 @@ pub(crate) fn parse<'a>(reader: &mut Reader<'a>) -> Result Result { let mut bitmap = Bitmap::new_with( header.region_info.width, @@ -41,6 +42,7 @@ pub(crate) fn decode( standard_tables, &mut bitmap, scratch, + work_left, )?; Ok(RegionBitmap { @@ -56,12 +58,13 @@ pub(crate) fn decode_into( standard_tables: &StandardHuffmanTables, bitmap: &mut Bitmap, scratch: &mut ScratchBuffers, + work_left: &mut u64, ) -> Result<()> { if header.flags.use_huffman { let mut reader = Reader::new(header.data); let ctx = DecodeContext::new_huffman(&mut reader, header, referred_tables, standard_tables)?; - decode_with(ctx, symbols, header, bitmap)?; + decode_with(ctx, symbols, header, bitmap, work_left)?; } else { let mut decoder = ArithmeticDecoder::new(header.data); @@ -76,7 +79,7 @@ pub(crate) fn decode_into( .resize(num_gr_contexts, ArithmeticDecoderContext::default()); let ctx = DecodeContext::new_arithmetic(&mut decoder, &mut contexts, &mut scratch.contexts); - decode_with(ctx, symbols, header, bitmap)?; + decode_with(ctx, symbols, header, bitmap, work_left)?; } Ok(()) @@ -88,6 +91,7 @@ pub(crate) fn decode_with( symbols: &[&Bitmap], header: &TextRegionHeader<'_>, region: &mut Bitmap, + work_left: &mut u64, ) -> Result<()> { let strip_size = header.strip_size(); @@ -156,6 +160,17 @@ pub(crate) fn decode_with( let symbol_width = symbol_bitmap_ref.width as i32; let symbol_height = symbol_bitmap_ref.height as i32; + // PdfCraft patch: see `bitmap::MAX_WORK`. An instance of a large symbol costs its copy, + // and 10,000 of one took 64 s. A refined one was decoded as well. Even one with no + // pixels goes round this loop. + let (width, height) = (symbol_bitmap_ref.width, symbol_bitmap_ref.height); + let copied = copy_work(width, height).saturating_add(1); + let work = match &symbol_bitmap { + SymbolBitmap::Reference(_) => copied, + SymbolBitmap::Owned(_) => copied.saturating_add(decode_work(width, height)), + }; + *work_left = work_left.checked_sub(work).ok_or(OverflowError::DecodingWork)?; + if !header.flags.transposed && (header.flags.reference_corner == ReferenceCorner::TopRight || header.flags.reference_corner == ReferenceCorner::BottomRight) @@ -173,23 +188,26 @@ pub(crate) fn decode_with( } let symbol_s = current_s; + // From the corner a symbol is placed by to its far side. The strip and the offset can + // be anywhere an `i32` reaches, and wrapped, the symbol is off the region. + let back = |corner: i32, size: i32| corner.wrapping_sub(size).wrapping_add(1); let (x, y) = if !header.flags.transposed { match header.flags.reference_corner { ReferenceCorner::TopLeft => (symbol_s, symbol_t), - ReferenceCorner::TopRight => (symbol_s - symbol_width + 1, symbol_t), - ReferenceCorner::BottomLeft => (symbol_s, symbol_t - symbol_height + 1), + ReferenceCorner::TopRight => (back(symbol_s, symbol_width), symbol_t), + ReferenceCorner::BottomLeft => (symbol_s, back(symbol_t, symbol_height)), ReferenceCorner::BottomRight => { - (symbol_s - symbol_width + 1, symbol_t - symbol_height + 1) + (back(symbol_s, symbol_width), back(symbol_t, symbol_height)) } } } else { match header.flags.reference_corner { ReferenceCorner::TopLeft => (symbol_t, symbol_s), - ReferenceCorner::TopRight => (symbol_t - symbol_width + 1, symbol_s), - ReferenceCorner::BottomLeft => (symbol_t, symbol_s - symbol_height + 1), + ReferenceCorner::TopRight => (back(symbol_t, symbol_width), symbol_s), + ReferenceCorner::BottomLeft => (symbol_t, back(symbol_s, symbol_height)), ReferenceCorner::BottomRight => { - (symbol_t - symbol_width + 1, symbol_s - symbol_height + 1) + (back(symbol_t, symbol_width), back(symbol_s, symbol_height)) } } }; diff --git a/vendor/hayro-jbig2/src/error.rs b/vendor/hayro-jbig2/src/error.rs index c579368b..c8bcf8b8 100644 --- a/vendor/hayro-jbig2/src/error.rs +++ b/vendor/hayro-jbig2/src/error.rs @@ -133,8 +133,14 @@ pub enum OverflowError { ReferenceOffset, /// Index computation overflowed. Index, - /// A symbol dictionary has too many symbols. + /// A symbol dictionary has too many symbols, or a segment refers to too many. SymbolCount, + /// PdfCraft patch: the segments retain more bitmaps than a page may hold. + RetainedBitmaps, + /// PdfCraft patch: a stream has more segments, or references, than it may. + SegmentCount, + /// PdfCraft patch: a stream asks for more decoding work than it may. + DecodingWork, } impl fmt::Display for DecodeError { @@ -161,7 +167,10 @@ impl fmt::Display for OverflowError { Self::ReferenceOffset => write!(f, "overflow in reference offset computation"), Self::Index => write!(f, "overflow in index computation"), Self::GridDimension => write!(f, "overflow in grid dimension computation"), - Self::SymbolCount => write!(f, "a symbol dictionary has too many symbols"), + Self::SymbolCount => write!(f, "too many symbols in a dictionary or referred to"), + Self::RetainedBitmaps => write!(f, "the segments retain too many bitmaps"), + Self::SegmentCount => write!(f, "too many segments or references in a stream"), + Self::DecodingWork => write!(f, "a stream asks for too much decoding work"), } } } diff --git a/vendor/hayro-jbig2/src/file.rs b/vendor/hayro-jbig2/src/file.rs index 1440a22e..5b8bbe98 100644 --- a/vendor/hayro-jbig2/src/file.rs +++ b/vendor/hayro-jbig2/src/file.rs @@ -5,12 +5,39 @@ use alloc::vec::Vec; -use crate::error::{FormatError, ParseError, Result, bail}; +use crate::error::{FormatError, OverflowError, ParseError, Result, bail}; use crate::reader::Reader; use crate::segment::{ Segment, SegmentType, parse_segment, parse_segment_data, parse_segment_header, }; +/// PdfCraft patch: the most segments a stream, with its globals, may have. They are all parsed and +/// sorted before the first is decoded, at about 64 bytes for 11 of input, so behind nested Flate a +/// 4,765-byte PDF reached 5 GB and failed only afterwards. +const MAX_SEGMENTS: usize = 1 << 20; + +/// PdfCraft patch: the most references those segments may make, 4 bytes each for 1 of input. +const MAX_REFERENCES: usize = 1 << 22; + +/// PdfCraft patch: the segments of a stream so far (see [`MAX_SEGMENTS`], [`MAX_REFERENCES`]). +#[derive(Default)] +pub(crate) struct SegmentCount { + segments: usize, + references: usize, +} + +impl SegmentCount { + /// Count one more segment, making `references` references. + fn add(&mut self, references: usize) -> Result<()> { + self.segments = self.segments.saturating_add(1); + self.references = self.references.saturating_add(references); + if self.segments > MAX_SEGMENTS || self.references > MAX_REFERENCES { + bail!(OverflowError::SegmentCount); + } + Ok(()) + } +} + /// "There are two standalone file organizations possible for a JBIG2 bitstream." /// (Annex D) #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -140,7 +167,9 @@ fn parse_segments<'a>( let mut segments = Vec::new(); match organization { - FileOrganization::Sequential => parse_segments_sequential(reader, &mut segments)?, + FileOrganization::Sequential => { + parse_segments_sequential(reader, &mut segments, &mut SegmentCount::default())?; + } FileOrganization::RandomAccess => parse_segments_random_access(reader, &mut segments)?, } @@ -155,6 +184,7 @@ fn parse_segments<'a>( pub(crate) fn parse_segments_sequential<'a>( reader: &mut Reader<'a>, segments: &mut Vec>, + count: &mut SegmentCount, ) -> Result<()> { loop { if reader.at_end() { @@ -162,6 +192,7 @@ pub(crate) fn parse_segments_sequential<'a>( } let segment = parse_segment(reader)?; + count.add(segment.header.referred_to_segments.len())?; // "If a file contains an end of file segment, it must be the last segment." // (7.4.11) @@ -187,6 +218,7 @@ fn parse_segments_random_access<'a>( segments: &mut Vec>, ) -> Result<()> { let mut headers = Vec::new(); + let mut count = SegmentCount::default(); loop { if reader.at_end() { @@ -194,6 +226,7 @@ fn parse_segments_random_access<'a>( } let header = parse_segment_header(reader)?; + count.add(header.referred_to_segments.len())?; // "If a file contains an end of file segment, it must be the last segment." // (7.4.11) diff --git a/vendor/hayro-jbig2/src/gray_scale.rs b/vendor/hayro-jbig2/src/gray_scale.rs index 2836ec77..4aa4229a 100644 --- a/vendor/hayro-jbig2/src/gray_scale.rs +++ b/vendor/hayro-jbig2/src/gray_scale.rs @@ -83,8 +83,8 @@ fn decode_arithmetic( if width > MAX_DIMENSION || height > MAX_DIMENSION { bail!(OverflowError::BitmapDimension); } - // PdfCraft patch: the product is bounded too (see `bitmap::MAX_PIXELS`). - if u64::from(width) * u64::from(height) > crate::bitmap::MAX_PIXELS { + // PdfCraft patch: the product is bounded too (see `bitmap::MAX_GRID_CELLS`). + if u64::from(width) * u64::from(height) > crate::bitmap::MAX_GRID_CELLS { bail!(OverflowError::BitmapDimension); } diff --git a/vendor/hayro-jbig2/src/huffman_table.rs b/vendor/hayro-jbig2/src/huffman_table.rs index 2183c8ee..1a20da52 100644 --- a/vendor/hayro-jbig2/src/huffman_table.rs +++ b/vendor/hayro-jbig2/src/huffman_table.rs @@ -14,11 +14,27 @@ include!("huffman_tables_generated.rs"); /// Maximum number of nodes in an inline Huffman table. const INLINE_TABLE_SIZE: usize = 43; +/// PdfCraft patch: the most lines a custom table may have. A line takes 2 bits of input at least +/// and 8 bytes here, so a table reading 250 MiB behind nested Flate, a 1,297-byte PDF, was killed +/// at 8 GiB before it could be counted. +const MAX_TABLE_LINES: usize = 1 << 16; + /// A queryable Huffman table. #[derive(Debug, Clone)] pub(crate) struct HuffmanTable(Rc); impl HuffmanTable { + /// PdfCraft patch: the words this table holds. Every table takes the room of the inline + /// variant, however few nodes it has, and a dynamic one its nodes besides. + pub(crate) fn words(&self) -> usize { + let nodes = match &*self.0 { + InnerHuffmanTable::Inline { .. } => 0, + InnerHuffmanTable::Dynamic { nodes } => nodes.len(), + }; + let bytes = nodes.saturating_mul(size_of::()); + bytes.saturating_add(size_of::()) / size_of::() + } + /// Create a new inline Huffman table from a fixed-size node array. fn from_inline(nodes: [HuffmanNode; INLINE_TABLE_SIZE]) -> Self { Self(Rc::new(InnerHuffmanTable::Inline { nodes })) @@ -219,6 +235,9 @@ impl HuffmanTable { // 5) "Decode each table line as follows:" // d) "If CURRANGELOW ≥ HTHIGH then proceed to step 6." while current_range_low < maximum_value { + if lines.len() >= MAX_TABLE_LINES { + bail!(HuffmanError::InvalidCode); + } // a) "Read HTPS bits. Set PREFLEN[NTEMP] to the value decoded." let prefix_length = reader .read_bits(prefix_length_bits) @@ -448,11 +467,11 @@ impl HuffmanNode { // 4) "Otherwise, if table line I is the lower range table line for this // table, then set: HTVAL = RANGELOW[I] − HTOFFSET" // 5) "Otherwise, set: HTVAL = RANGELOW[I] + HTOFFSET" - // `HTVAL` + // `HTVAL`. Wrapping: a range line's offset can take it past what an `i32` holds. let value = if leaf.is_lower { - leaf.range_low - range_offset + leaf.range_low.wrapping_sub(range_offset) } else { - leaf.range_low + range_offset + leaf.range_low.wrapping_add(range_offset) }; return Ok(Some(value)); diff --git a/vendor/hayro-jbig2/src/integer_decoder.rs b/vendor/hayro-jbig2/src/integer_decoder.rs index 8b7656d2..7f9f7831 100644 --- a/vendor/hayro-jbig2/src/integer_decoder.rs +++ b/vendor/hayro-jbig2/src/integer_decoder.rs @@ -70,7 +70,8 @@ impl IntegerDecoder { if s == 0 { Some(v as i32) } else if v > 0 { - Some(-(v as i32)) + // Wrapping: `V` can be 2^31, whose negative is `i32::MIN`, as the cast already reads it. + Some((v as i32).wrapping_neg()) } else { None } diff --git a/vendor/hayro-jbig2/src/lib.rs b/vendor/hayro-jbig2/src/lib.rs index ad77a6dd..078370ed 100644 --- a/vendor/hayro-jbig2/src/lib.rs +++ b/vendor/hayro-jbig2/src/lib.rs @@ -69,13 +69,16 @@ mod segment; mod simd; mod symbol_id_decoder; +#[cfg(test)] +mod pdfcraft_tests; + use error::bail; pub use error::{ DecodeError, FormatError, HuffmanError, OverflowError, ParseError, RegionError, Result, SegmentError, SymbolError, TemplateError, }; -use crate::file::parse_segments_sequential; +use crate::file::{SegmentCount, parse_segments_sequential}; use bitmap::Bitmap; use decode::CombinationOperator; use decode::generic; @@ -120,13 +123,14 @@ impl<'a> Image<'a> { /// Annex D.3. pub fn new_embedded(data: &'a [u8], globals: Option<&'a [u8]>) -> Result { let mut segments = Vec::new(); + let mut count = SegmentCount::default(); if let Some(globals_data) = globals { let mut reader = Reader::new(globals_data); - parse_segments_sequential(&mut reader, &mut segments)?; + parse_segments_sequential(&mut reader, &mut segments, &mut count)?; }; let mut reader = Reader::new(data); - parse_segments_sequential(&mut reader, &mut segments)?; + parse_segments_sequential(&mut reader, &mut segments, &mut count)?; segments.sort_by_key(|seg| seg.header.segment_number); @@ -162,6 +166,7 @@ impl<'a> Image<'a> { if page_info.width == 0 || height == 0 { bail!(FormatError::EmptyPage); } + bitmap::check_page_size(page_info.width, height)?; Ok(Self { segments, @@ -257,6 +262,18 @@ fn emit_bitmap(bitmap: &Bitmap, decoder: &mut D) { } } +/// PdfCraft patch: count `work` more decoding work, refusing past [`bitmap::MAX_WORK`] before what +/// asks for it is decoded. +fn charge_work(left: &mut u64, work: u64) -> Result<()> { + *left = left.checked_sub(work).ok_or(OverflowError::DecodingWork)?; + Ok(()) +} + +/// PdfCraft patch: the work of a region decoded pixel by pixel. +fn region_work(region: &decode::RegionSegmentInfo) -> u64 { + bitmap::decode_work(region.width, region.height) +} + fn decode_segments( segments: &[segment::Segment<'_>], height_from_stripes: Option, @@ -281,6 +298,7 @@ fn decode_segments( let page_bitmap = &mut decoder_ctx.page_bitmap; let page_state = &mut decoder_ctx.page_state; let scratch_buffers = &mut decoder_ctx.scratch_buffers; + let mut work_left = bitmap::MAX_WORK; // Process all segments. for seg in segments { @@ -293,6 +311,7 @@ fn decode_segments( SegmentType::ImmediateGenericRegion | SegmentType::ImmediateLosslessGenericRegion => { let had_unknown_length = seg.header.data_length.is_none(); let header = generic::parse(&mut reader, had_unknown_length)?; + charge_work(&mut work_left, region_work(&header.region_info))?; if page_state.can_decode_directly(page_bitmap, &header.region_info, false) { generic::decode_into(&header, page_bitmap, scratch_buffers)?; @@ -310,25 +329,22 @@ fn decode_segments( SegmentType::IntermediateGenericRegion => { // Intermediate segments cannot have unknown length. let header = generic::parse(&mut reader, false)?; + charge_work(&mut work_left, region_work(&header.region_info))?; let region = generic::decode(&header, scratch_buffers)?; - page_state.store_region(seg.header.segment_number, region.bitmap); + page_state.store_region(seg.header.segment_number, region.bitmap)?; } SegmentType::PatternDictionary => { let header = pattern::parse(&mut reader)?; - let dictionary = pattern::decode(&header, scratch_buffers)?; - page_state.store_pattern_dictionary(seg.header.segment_number, dictionary); + charge_work(&mut work_left, header.work())?; + let dictionary = pattern::decode(&header, scratch_buffers, page_state.retained_room())?; + page_state.store_pattern_dictionary(seg.header.segment_number, dictionary)?; } SegmentType::SymbolDictionary => { // "1) Concatenate all the input symbol dictionaries to form SDINSYMS." // (6.5.5, step 1) // Collect references to avoid cloning; symbols are only cloned if re-exported. - let input_symbols: Vec<&Bitmap> = seg - .header - .referred_to_segments - .iter() - .filter_map(|&num| page_state.get_symbol_dictionary(num)) - .flat_map(|dict| dict.exported_symbols.iter()) - .collect(); + let input_symbols = page_state.referred_symbols(&seg.header.referred_to_segments)?; + charge_work(&mut work_left, input_symbols.len() as u64)?; // Collect Huffman tables from referred table segments. let referred_tables: Vec = seg @@ -354,18 +370,15 @@ fn decode_segments( &referred_tables, &page_state.standard_tables, retained_contexts, + page_state.retained_room(), + &mut work_left, )?; - page_state.store_symbol_dictionary(seg.header.segment_number, dictionary); + page_state.store_symbol_dictionary(seg.header.segment_number, dictionary)?; } SegmentType::ImmediateTextRegion | SegmentType::ImmediateLosslessTextRegion => { // Collect symbols from referred symbol dictionaries (SBSYMS). - let symbols: Vec<&Bitmap> = seg - .header - .referred_to_segments - .iter() - .filter_map(|&num| page_state.get_symbol_dictionary(num)) - .flat_map(|dict| dict.exported_symbols.iter()) - .collect(); + let symbols = page_state.referred_symbols(&seg.header.referred_to_segments)?; + charge_work(&mut work_left, symbols.len() as u64)?; // Collect Huffman tables from referred table segments. // "These user-supplied Huffman decoding tables may be supplied either @@ -379,6 +392,11 @@ fn decode_segments( .collect(); let header = text::parse(&mut reader, symbols.len() as u32)?; + charge_work(&mut work_left, bitmap::copy_work(header.region_info.width, header.region_info.height))?; + if header.flags.use_huffman { + // Its symbol ID table has a line for each symbol. + charge_work(&mut work_left, (symbols.len() as u64).saturating_mul(bitmap::HUFFMAN_LINE_WORK))?; + } if page_state.can_decode_directly( page_bitmap, @@ -392,6 +410,7 @@ fn decode_segments( &page_state.standard_tables, page_bitmap, scratch_buffers, + &mut work_left, )?; } else { let region = text::decode( @@ -400,6 +419,7 @@ fn decode_segments( &referred_tables, &page_state.standard_tables, scratch_buffers, + &mut work_left, )?; page_bitmap.combine( ®ion.bitmap, @@ -412,13 +432,8 @@ fn decode_segments( } SegmentType::IntermediateTextRegion => { // Collect symbols from referred symbol dictionaries (SBSYMS). - let symbols: Vec<&Bitmap> = seg - .header - .referred_to_segments - .iter() - .filter_map(|&num| page_state.get_symbol_dictionary(num)) - .flat_map(|dict| dict.exported_symbols.iter()) - .collect(); + let symbols = page_state.referred_symbols(&seg.header.referred_to_segments)?; + charge_work(&mut work_left, symbols.len() as u64)?; // Collect Huffman tables from referred table segments. let referred_tables: Vec = seg @@ -430,14 +445,20 @@ fn decode_segments( .collect(); let header = text::parse(&mut reader, symbols.len() as u32)?; + charge_work(&mut work_left, bitmap::copy_work(header.region_info.width, header.region_info.height))?; + if header.flags.use_huffman { + // Its symbol ID table has a line for each symbol. + charge_work(&mut work_left, (symbols.len() as u64).saturating_mul(bitmap::HUFFMAN_LINE_WORK))?; + } let region = text::decode( &header, &symbols, &referred_tables, &page_state.standard_tables, scratch_buffers, + &mut work_left, )?; - page_state.store_region(seg.header.segment_number, region.bitmap); + page_state.store_region(seg.header.segment_number, region.bitmap)?; } SegmentType::ImmediateHalftoneRegion | SegmentType::ImmediateLosslessHalftoneRegion => { let pattern_dict = seg @@ -448,6 +469,7 @@ fn decode_segments( .ok_or(SegmentError::MissingPatternDictionary)?; let header = halftone::parse(&mut reader)?; + charge_work(&mut work_left, halftone::work(&header, pattern_dict))?; if page_state.can_decode_directly( page_bitmap, @@ -475,8 +497,9 @@ fn decode_segments( .ok_or(SegmentError::MissingPatternDictionary)?; let header = halftone::parse(&mut reader)?; + charge_work(&mut work_left, halftone::work(&header, pattern_dict))?; let region = halftone::decode(&header, pattern_dict, scratch_buffers)?; - page_state.store_region(seg.header.segment_number, region.bitmap); + page_state.store_region(seg.header.segment_number, region.bitmap)?; } SegmentType::IntermediateGenericRefinementRegion => { // Same logic as immediate refinement, but store result instead of combining. @@ -488,8 +511,9 @@ fn decode_segments( .unwrap_or(page_bitmap); let header = generic_refinement::parse(&mut reader)?; + charge_work(&mut work_left, region_work(&header.region_info))?; let region = generic_refinement::decode(&header, reference, scratch_buffers)?; - page_state.store_region(seg.header.segment_number, region.bitmap); + page_state.store_region(seg.header.segment_number, region.bitmap)?; } SegmentType::ImmediateGenericRefinementRegion | SegmentType::ImmediateLosslessGenericRefinementRegion => { @@ -505,6 +529,7 @@ fn decode_segments( .and_then(|&num| page_state.get_referred_segment(num)); let header = generic_refinement::parse(&mut reader)?; + charge_work(&mut work_left, region_work(&header.region_info))?; if let Some(referred_segment) = referred_segment && page_state.can_decode_directly(page_bitmap, &header.region_info, false) @@ -532,7 +557,7 @@ fn decode_segments( // "This segment contains data which defines one or more user-supplied // Huffman coding tables." (7.4.13) let table = HuffmanTable::read_custom(&mut reader)?; - page_state.store_huffman_table(seg.header.segment_number, table); + page_state.store_huffman_table(seg.header.segment_number, table)?; } SegmentType::EndOfPage | SegmentType::EndOfFile => { break; @@ -545,6 +570,23 @@ fn decode_segments( Ok(()) } +/// PdfCraft patch: the words a stored item costs in its list, at least, apart from what it holds. +const ENTRY_WORDS: u64 = 32; + +/// PdfCraft patch: the most words a page's segments may hold for later ones, 64 MiB: two square +/// bitmaps of 16,384 a side (`bitmap::MAX_PIXELS`). Each stored item is bounded on its own, but without a +/// bound on all of them a stream of a few dozen bytes a segment held 33 MB each (a 16,384-sample +/// square region of typical rows), and a few kilobytes could exhaust memory. A segment being +/// decoded takes more for a while, within its own bounds: the page bitmap isn't counted, nor is +/// what a region or a grid uses until it is stored or freed. +const MAX_RETAINED_WORDS: u64 = + 2 * (bitmap::MAX_PIXELS / bitmap::WORD_BITS as u64 + bitmap::BITMAP_WORDS as u64 + ENTRY_WORDS); + +/// PdfCraft patch: the most symbols one segment may refer to, 2^20, far beyond the 65,535 a +/// dictionary may export. References may repeat, so each names the same symbols again: without +/// this, 65,535 references to one dictionary in a 78 KB stream collected 17 GB of pointers. +const MAX_REFERRED_SYMBOLS: u64 = 1 << 20; + /// Page-level decoding state for a JBIG2 page. #[derive(Default)] pub(crate) struct PageState { @@ -561,6 +603,9 @@ pub(crate) struct PageState { /// Decoded Huffman tables from table segments, stored as (`segment_number`, table) pairs. /// "Tables – see 7.4.13." (type 53) pub(crate) huffman_tables: Vec<(u32, HuffmanTable)>, + /// PdfCraft patch: the words the regions, dictionaries and tables above hold, at most + /// [`MAX_RETAINED_WORDS`]. + pub(crate) retained_words: u64, /// Standard Huffman tables (`TABLE_A` through `TABLE_O`). pub(crate) standard_tables: StandardHuffmanTables, } @@ -573,6 +618,7 @@ impl PageState { self.pattern_dictionaries.clear(); self.symbol_dictionaries.clear(); self.huffman_tables.clear(); + self.retained_words = 0; // Standard tables are lazily built and reused across images. } @@ -610,9 +656,26 @@ impl PageState { } } + /// PdfCraft patch: the words the page may still hold for later segments. + fn retained_room(&self) -> u64 { + MAX_RETAINED_WORDS.saturating_sub(self.retained_words) + } + + /// PdfCraft patch: count a stored item holding `words` for later segments, refusing past + /// [`MAX_RETAINED_WORDS`]. + fn retain(&mut self, words: usize) -> Result<()> { + self.retained_words = self.retained_words.saturating_add(words as u64).saturating_add(ENTRY_WORDS); + if self.retained_words > MAX_RETAINED_WORDS { + bail!(OverflowError::RetainedBitmaps); + } + Ok(()) + } + /// Store a decoded region for later reference. - fn store_region(&mut self, segment_number: u32, region: Bitmap) { + fn store_region(&mut self, segment_number: u32, region: Bitmap) -> Result<()> { + self.retain(region.words())?; self.referred_segments.push((segment_number, region)); + Ok(()) } /// Look up a referred segment by number. @@ -624,8 +687,10 @@ impl PageState { } /// Store a decoded pattern dictionary for later reference. - fn store_pattern_dictionary(&mut self, segment_number: u32, dictionary: PatternDictionary) { + fn store_pattern_dictionary(&mut self, segment_number: u32, dictionary: PatternDictionary) -> Result<()> { + self.retain(dictionary.words())?; self.pattern_dictionaries.push((segment_number, dictionary)); + Ok(()) } /// Look up a pattern dictionary by segment number. @@ -637,8 +702,23 @@ impl PageState { } /// Store a decoded symbol dictionary for later reference. - fn store_symbol_dictionary(&mut self, segment_number: u32, dictionary: SymbolDictionary) { + fn store_symbol_dictionary(&mut self, segment_number: u32, dictionary: SymbolDictionary) -> Result<()> { + self.retain(dictionary.words())?; self.symbol_dictionaries.push((segment_number, dictionary)); + Ok(()) + } + + /// PdfCraft patch: the symbols of the dictionaries in `segments`, in order, refusing past + /// [`MAX_REFERRED_SYMBOLS`] before anything is collected. + fn referred_symbols(&self, segments: &[u32]) -> Result> { + let dictionaries = || segments.iter().filter_map(|&num| self.get_symbol_dictionary(num)); + let count = dictionaries() + .map(|dictionary| dictionary.exported_symbols.len() as u64) + .fold(0, u64::saturating_add); + if count > MAX_REFERRED_SYMBOLS { + bail!(OverflowError::SymbolCount); + } + Ok(dictionaries().flat_map(|dictionary| dictionary.exported_symbols.iter()).collect()) } /// Look up a symbol dictionary by segment number. @@ -650,8 +730,10 @@ impl PageState { } /// Store a decoded Huffman table for later reference. - fn store_huffman_table(&mut self, segment_number: u32, table: HuffmanTable) { + fn store_huffman_table(&mut self, segment_number: u32, table: HuffmanTable) -> Result<()> { + self.retain(table.words())?; self.huffman_tables.push((segment_number, table)); + Ok(()) } /// Look up a Huffman table by segment number. @@ -690,3 +772,51 @@ fn init_page( Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + /// A page past the bounds is refused when the stream is opened, since callers size their + /// output from it before decoding starts. 16,384 squared is exactly the bound on pixels. + #[test] + fn a_page_past_the_bounds_is_refused_when_opened() { + let stream = |width: u32, height: u32| { + let mut stream = vec![0, 0, 0, 0, 48, 0, 1, 0, 0, 0, 19]; + stream.extend_from_slice(&width.to_be_bytes()); + stream.extend_from_slice(&height.to_be_bytes()); + stream.extend_from_slice(&[0; 11]); // resolution, flags, striping + stream + }; + assert!(Image::new_embedded(&stream(16384, 16384), None).is_ok()); + assert!(Image::new_embedded(&stream(16385, 16384), None).is_err()); + assert!(Image::new_embedded(&stream(65536, 1), None).is_err(), "too wide, though few pixels"); + assert!(Image::new_embedded(&stream(1 << 20, 1 << 20), None).is_err()); + } + + /// A stream and its globals are one stream of segments, so they have one count. + #[test] + fn the_segments_of_a_stream_and_its_globals_count_together() { + let segment: &[u8] = &[0, 0, 0, 0, 62, 0, 1, 0, 0, 0, 0]; // an empty extension + let half = segment.repeat(1 << 19); + let more = segment.repeat((1 << 19) + 1); + let refused = |data: &[u8]| { + let error = Image::new_embedded(data, Some(&half)).err().map(|e| e.to_string()); + error.as_deref() == Some("too many segments or references in a stream") + }; + assert!(!refused(&half), "2^20 segments are allowed"); + assert!(refused(&more), "one more isn't"); + } + + /// A dictionary being decoded has what the page may still hold, not all of it: refused only + /// once it is stored, its copies would already have been made. + #[test] + fn the_room_left_shrinks_as_segments_are_stored() { + let mut page = PageState::default(); + assert_eq!(page.retained_room(), MAX_RETAINED_WORDS); + page.retain(1000).unwrap(); + assert_eq!(page.retained_room(), MAX_RETAINED_WORDS - 1000 - ENTRY_WORDS); + page.retained_words = MAX_RETAINED_WORDS; + assert_eq!(page.retained_room(), 0); + } +} diff --git a/vendor/hayro-jbig2/src/pdfcraft_tests.rs b/vendor/hayro-jbig2/src/pdfcraft_tests.rs new file mode 100644 index 00000000..e26d4e9c --- /dev/null +++ b/vendor/hayro-jbig2/src/pdfcraft_tests.rs @@ -0,0 +1,522 @@ +//! Hostile-input tests for the work, memory and overflow bounds: ported from PdfCraft's +//! raster tests, which draw the same streams through a PDF. Here a stream "draws" when it decodes. + +use crate::{Decoder, Image}; + +#[derive(Default)] +struct Count { + black: u64, +} + +impl Decoder for Count { + fn push_pixel(&mut self, black: bool) { + self.black += u64::from(black); + } + + fn push_pixel_chunk(&mut self, black: bool, chunk_count: u32) { + self.black += u64::from(black) * 8 * u64::from(chunk_count); + } + + fn next_line(&mut self) {} +} + +/// The black pixels of an embedded stream; none when it is refused. +fn black_pixels(stream: &[u8]) -> u64 { + let mut count = Count::default(); + match Image::new_embedded(stream, None).and_then(|image| image.decode(&mut count)) { + Ok(()) => count.black, + Err(_) => 0, + } +} + +/// Whether a 16 x 2 stream whose page defaults to black decodes: refused, nothing is drawn. +fn jbig2_image_draws(stream: &[u8]) -> bool { + black_pixels(stream) > 0 +} + +/// Bits written from the top of each byte, as JBIG2's Huffman-coded data is. +#[derive(Default)] +struct Bits { + bytes: Vec, + used: u32, +} + +impl Bits { + fn put(&mut self, value: u32, count: u32) { + for i in (0..count).rev() { + if self.used.is_multiple_of(8) { + self.bytes.push(0); + } + if (value >> i) & 1 != 0 { + *self.bytes.last_mut().unwrap() |= 0x80 >> (self.used % 8); + } + self.used += 1; + } + } + + fn align(&mut self) { + self.used = self.used.div_ceil(8) * 8; + } + + /// `value` in table B.1 (ISO 14492 Annex B). + fn table_b1(&mut self, value: u32) { + match value { + 0..=15 => { + self.put(0, 1); + self.put(value, 4); + } + 16..=271 => { + self.put(0b10, 2); + self.put(value - 16, 8); + } + 272..=65807 => { + self.put(0b110, 3); + self.put(value - 272, 16); + } + _ => { + self.put(0b111, 3); + self.put(value - 65808, 32); + } + } + } +} + +/// One embedded JBIG2 segment (ISO 14492 §7.2) of page 1. More than four references take the long +/// form, whose retention flags are left clear. +fn jbig2_segment(out: &mut Vec, number: u32, kind: u8, refs: &[u8], data: &[u8]) { + out.extend_from_slice(&number.to_be_bytes()); + out.push(kind); + if refs.len() <= 4 { + out.push(u8::try_from(refs.len()).unwrap() << 5); + } else { + out.extend_from_slice(&(0xE000_0000u32 | u32::try_from(refs.len()).unwrap()).to_be_bytes()); + out.extend(std::iter::repeat_n(0u8, (refs.len() + 1).div_ceil(8))); + } + out.extend_from_slice(refs); + out.push(1); + out.extend_from_slice(&u32::try_from(data.len()).unwrap().to_be_bytes()); + out.extend_from_slice(data); +} + +/// The page information of a 16 × 2 JBIG2 page whose default pixel is black, so that the image, if +/// it decodes, draws black whatever its other segments hold. +fn black_jbig2_page() -> Vec { + let mut page = Vec::new(); + page.extend_from_slice(&16u32.to_be_bytes()); + page.extend_from_slice(&2u32.to_be_bytes()); + page.extend_from_slice(&[0; 8]); // resolution + page.extend_from_slice(&[4, 0, 0]); // flags (default pixel black), striping + let mut stream = Vec::new(); + jbig2_segment(&mut stream, 0, 48, &[], &page); + stream +} + +/// A region's information field, at the page's corner. +fn jbig2_region_info(width: u32, height: u32) -> Vec { + let mut region = Vec::new(); + region.extend_from_slice(&width.to_be_bytes()); + region.extend_from_slice(&height.to_be_bytes()); + region.extend_from_slice(&[0; 9]); // x, y, combination operator + region +} + +/// An intermediate generic region, white, every row typical: it decodes in a few bytes however +/// large it is. +fn jbig2_white_region(width: u32, height: u32) -> Vec { + let mut region = jbig2_region_info(width, height); + region.push(0x08); // arithmetic coding, template 0, typical prediction + region.extend_from_slice(&[3, 0xff, 0xfd, 0xff, 2, 0xfe, 0xfe, 0xfe]); // AT pixels + // Every row typical, the first white: pdfcraft-filters' coding of a white 16,384 square. + region.extend_from_slice(&[0xb6, 0xc2, 0x1f, 0xff, 0x7f, 0xff, 0xac]); + region +} + +/// A pattern dictionary of `patterns` patterns of width 1 and `height` rows, with nothing coded. +fn jbig2_pattern_dictionary(height: u8, patterns: u32) -> Vec { + let mut dictionary = vec![0, 1, height]; + dictionary.extend_from_slice(&(patterns - 1).to_be_bytes()); + dictionary +} + +/// A 1 × 1 halftone region over a grid of `width` × `height` cells, all at the region's corner, +/// coded by MMR or arithmetically. +fn jbig2_halftone(width: u32, height: u32, mmr: bool) -> Vec { + jbig2_halftone_of(1, width, height, mmr) +} + +/// [`jbig2_halftone`] for a square region of `side` pixels. +fn jbig2_halftone_of(side: u32, width: u32, height: u32, mmr: bool) -> Vec { + let mut halftone = jbig2_region_info(side, side); + halftone.push(u8::from(mmr)); // template 0 + halftone.extend_from_slice(&width.to_be_bytes()); + halftone.extend_from_slice(&height.to_be_bytes()); + halftone.extend_from_slice(&[0; 12]); // grid offset and vector + halftone +} + +/// A Huffman-coded symbol dictionary of `symbols` symbols of width 0 and height 1, exported or not: +/// each holds no pixels, and costs a bitmap's header all the same. +fn zero_width_jbig2_dictionary(symbols: u32, exported: bool) -> Vec { + let mut dictionary = vec![0x00, 0x01]; // Huffman, tables B.4, B.2, B.1, B.1 + dictionary.extend_from_slice(&(if exported { symbols } else { 0 }).to_be_bytes()); + dictionary.extend_from_slice(&symbols.to_be_bytes()); // new + let mut bits = Bits::default(); + bits.put(0, 1); // height class delta 1 (B.4) + for _ in 0..symbols { + bits.put(0, 1); // width delta 0 (B.2) + } + bits.put(0b111111, 6); // end of the class (B.2) + bits.put(0, 5); // BMSIZE 0, uncompressed (B.1) + bits.align(); + if exported { + bits.table_b1(0); // the first export run isn't exported + } + bits.table_b1(symbols); + dictionary.extend_from_slice(&bits.bytes); + dictionary +} + +/// A Huffman-coded symbol dictionary of one white symbol, `side` from 76 up, exported or not. +fn square_jbig2_dictionary(side: u32, exported: bool) -> Vec { + let mut bits = Bits::default(); + bits.put(0b11111, 5); // height class delta, the upper line of table B.4 + bits.put(side - 76, 32); + bits.put(0b111110, 6); // width delta, the upper line of table B.2 + bits.put(side - 75, 32); + bits.put(0b111111, 6); // end of the class (B.2) + let bytes = side.div_ceil(8) + 1; // an MMR-coded white bitmap: one bit a row + bits.table_b1(bytes); // BMSIZE (B.1) + bits.align(); + for _ in 0..bytes { + bits.put(0xFF, 8); + } + if exported { + bits.table_b1(0); + } + bits.table_b1(1); + let mut dictionary = vec![0x00, 0x01]; // Huffman, tables B.4, B.2, B.1, B.1 + dictionary.extend_from_slice(&u32::from(exported).to_be_bytes()); + dictionary.extend_from_slice(&1u32.to_be_bytes()); // new + dictionary.extend_from_slice(&bits.bytes); + dictionary +} + +/// A text region of `width` × `height` placing symbol 0 of one, Huffman-coded, `instances` of 0 or 1. +fn jbig2_text_region(width: u32, height: u32, instances: u32) -> Vec { + let mut text = jbig2_region_info(width, height); + text.extend_from_slice(&[0x00, 0x11, 0, 0]); // Huffman, top left; tables B.6, B.8, B.11 + text.extend_from_slice(&instances.to_be_bytes()); + let mut bits = Bits::default(); + for code in 0..35 { + bits.put(u32::from(code == 1), 4); // only run code 1, one bit long + } + bits.put(0, 1); // symbol 0 is one bit long + bits.align(); + bits.put(0, 1); // the first strip's T (B.11) + if instances > 0 { + bits.put(0, 1); // delta T 1 (B.11) + bits.put(0, 9); // first S 0 (B.6) + bits.put(0, 1); // symbol 0 + bits.put(0b01, 2); // end of the strip (B.8) + } + text.extend_from_slice(&bits.bytes); + text +} + +/// A 1 × 1 text region placing one instance of symbol 0 of one, refined, Huffman-coded. +fn jbig2_refined_text_region() -> Vec { + let mut text = jbig2_region_info(1, 1); + text.extend_from_slice(&[0x80, 0x13, 0, 0]); // Huffman, refined by template 1, top left + text.extend_from_slice(&1u32.to_be_bytes()); + let mut bits = Bits::default(); + for code in 0..35 { + bits.put(u32::from(code == 1), 4); // only run code 1, one bit long + } + bits.put(0, 1); // symbol 0 is one bit long + bits.align(); + bits.put(0, 1); // the first strip's T (B.11) + bits.put(0, 1); // delta T 1 (B.11) + bits.put(0, 9); // first S 0 (B.6) + bits.put(0, 1); // symbol 0 + bits.put(1, 1); // refined + bits.put(0, 4); // no change of width, height or offset (B.14) + bits.table_b1(0); // no refinement data (B.1) + bits.align(); + bits.put(0b01, 2); // end of the strip (B.8) + text.extend_from_slice(&bits.bytes); + text +} + +/// A generic refinement region of 1 × 1, with whatever its data decodes to. +fn jbig2_refinement_region() -> Vec { + let mut region = jbig2_region_info(1, 1); + region.push(1); // template 1, no typical prediction + region.extend_from_slice(&[0; 4]); + region +} + +/// Each item a JBIG2 stream decodes costs its work, and the stream may ask for 2^30: four of the +/// largest pages, some 6 s to 17 s. Each bitmap is bounded on its own, but nothing bounded how many +/// there were, so 1,140 bytes of generic regions took over 40 s, and a 795-byte PDF's one halftone +/// 265 s. Vendored hayro-jbig2 patch: `MAX_WORK`. A row costs 4 pixels, even in a bitmap of no +/// width, and a copy goes by the word and the row, so a tall thin one costs its rows. Here regions +/// of typical rows, cheap to decode though charged like any, spend all but exactly what the item +/// costs, so it draws with that left and is refused with one less, for each item at its own cost. +#[test] +fn jbig2_decoding_work_is_counted_item_by_item() { + struct Item { + what: &'static str, + /// What comes first, and what it costs. + before: Vec<(u8, Vec)>, + spent: u64, + kind: u8, + refs: &'static [u8], + data: Vec, + cost: u64, + } + let stream = |item: &Item, left: u64| { + let mut stream = black_jbig2_page(); + let mut number = 0; + for (kind, data) in &item.before { + number += 1; + jbig2_segment(&mut stream, number, *kind, &[], data); + } + // A square costs 16,384 rows of 16,388, and a single row what it has and 4 more. + let work = (1u64 << 30) - item.spent - left; + let (row, square) = (16388u64, 16384 * 16388u64); + let mut squares = work / square; + let mut rest = work % square; + if rest > 0 && rest < 5 { + squares -= 1; + rest += square; + } + let mut rows = rest / row; + let mut last = rest % row; + if last > 0 && last < 5 { + rows -= 1; + last += row; + } + let regions = std::iter::repeat_n((16384, 16384), usize::try_from(squares).unwrap()) + .chain((rows > 0).then_some((16384, rows))) + .chain((last > 0).then_some((last - 4, 1))); + for (width, height) in regions { + number += 1; + let region = jbig2_white_region(u32::try_from(width).unwrap(), u32::try_from(height).unwrap()); + jbig2_segment(&mut stream, number, 38, &[], ®ion); // immediate generic region + } + jbig2_segment(&mut stream, number + 1, item.kind, item.refs, &item.data); + stream + }; + let symbol = || vec![(0, zero_width_jbig2_dictionary(1, true))]; + let patterns = || vec![(16, jbig2_pattern_dictionary(1, 2))]; + let exporting_none = { + let mut dictionary = vec![0x00, 0x01]; + dictionary.extend_from_slice(&[0; 8]); // exported, new + let mut bits = Bits::default(); + bits.table_b1(1); // the first export run, not exported, is the one it refers to + dictionary.extend_from_slice(&bits.bytes); + dictionary + }; + let zero = |height: u32| { + let mut bits = Bits::default(); + bits.put(0b11111, 5); // height class delta, the upper line of table B.4 + bits.put(height - 76, 32); + bits.put(0, 1); // width delta 0 (B.2) + bits.put(0b111111, 6); // end of the class (B.2) + bits.table_b1(0); // BMSIZE 0, stored (B.1) + bits.align(); + bits.table_b1(1); // the first export run, not exported, is the symbol + let mut dictionary = vec![0x00, 0x01]; + dictionary.extend_from_slice(&0u32.to_be_bytes()); // exported + dictionary.extend_from_slice(&1u32.to_be_bytes()); // new + dictionary.extend_from_slice(&bits.bytes); + dictionary + }; + // The cost of a pattern dictionary of two patterns: rows of them side by side, then each taken out. + let two_patterns = |width: u64, height: u64| height * (2 * width + 4) + 2 * height * (width.div_ceil(32) + 1); + let thin_region = |side: u32| { + let mut region = jbig2_white_region(1, 1); + region.splice(0..8, [0u32.to_be_bytes(), side.to_be_bytes()].concat()); + region + }; + let items = vec![ + Item { what: "immediate generic", before: vec![], spent: 0, kind: 38, refs: &[], data: jbig2_white_region(1, 1), cost: 5 }, + Item { what: "intermediate generic", before: vec![], spent: 0, kind: 36, refs: &[], data: jbig2_white_region(1, 1), cost: 5 }, + Item { what: "a region of no width", before: vec![], spent: 0, kind: 38, refs: &[], data: thin_region(1000), cost: 4000 }, + Item { what: "immediate refinement", before: vec![], spent: 0, kind: 42, refs: &[], data: jbig2_refinement_region(), cost: 5 }, + Item { what: "intermediate refinement", before: vec![], spent: 0, kind: 40, refs: &[], data: jbig2_refinement_region(), cost: 5 }, + Item { + what: "pattern dictionary", + before: vec![], + spent: 0, + kind: 16, + refs: &[], + data: jbig2_pattern_dictionary(1, 2), + cost: two_patterns(1, 1), + }, + Item { + what: "tall patterns", + before: vec![], + spent: 0, + kind: 16, + refs: &[], + data: jbig2_pattern_dictionary(32, 2), + cost: two_patterns(1, 32), + }, + // Rows cost in a symbol of no pixels too. + Item { what: "symbols without pixels", before: vec![], spent: 0, kind: 0, refs: &[], data: zero_width_jbig2_dictionary(3, false), cost: 12 }, + Item { what: "a tall symbol of no width", before: vec![], spent: 0, kind: 0, refs: &[], data: zero(1000), cost: 4000 }, + Item { what: "new symbols", before: vec![], spent: 0, kind: 0, refs: &[], data: square_jbig2_dictionary(80, false), cost: 6720 }, + Item { what: "referred symbols", before: symbol(), spent: 4, kind: 0, refs: &[1], data: exporting_none, cost: 1 }, + // Its symbol, and its one row and word; Huffman-coded, a table line for the symbol. + Item { what: "immediate text", before: symbol(), spent: 4, kind: 6, refs: &[1], data: jbig2_text_region(1, 1, 0), cost: 13 }, + Item { what: "intermediate text", before: symbol(), spent: 4, kind: 4, refs: &[1], data: jbig2_text_region(1, 1, 0), cost: 13 }, + Item { what: "a text region's pixels", before: symbol(), spent: 4, kind: 6, refs: &[1], data: jbig2_text_region(32, 32, 0), cost: 75 }, + Item { + what: "an intermediate text region's pixels", + before: symbol(), + spent: 4, + kind: 4, + refs: &[1], + data: jbig2_text_region(32, 32, 0), + cost: 75, + }, + // The region's 13, a copy of the symbol's row and the round of the loop. + Item { what: "an instance", before: symbol(), spent: 4, kind: 6, refs: &[1], data: jbig2_text_region(1, 1, 1), cost: 15 }, + // The region's 13, 80 rows of 4 words copied, the round of the loop. + Item { + what: "an instance of a large symbol", + before: vec![(0, square_jbig2_dictionary(80, true))], + spent: 6720, + kind: 6, + refs: &[1], + data: jbig2_text_region(1, 1, 1), + cost: 334, + }, + // And decoded as well as copied. + Item { + what: "a refined instance", + before: vec![(0, square_jbig2_dictionary(80, true))], + spent: 6720, + kind: 6, + refs: &[1], + data: jbig2_refined_text_region(), + cost: 7054, + }, + // A bit plane, a cell's copy and the region's. + Item { + what: "immediate halftone", + before: patterns(), + spent: two_patterns(1, 1), + kind: 22, + refs: &[1], + data: jbig2_halftone(1, 1, false), + cost: 9, + }, + Item { + what: "intermediate halftone", + before: patterns(), + spent: two_patterns(1, 1), + kind: 20, + refs: &[1], + data: jbig2_halftone(1, 1, false), + cost: 9, + }, + // A plane of 2 cells, each a copy of 32 rows of 2 words' cost, and the region's. + Item { + what: "a halftone's cells", + before: vec![(16, jbig2_pattern_dictionary(32, 2))], + spent: two_patterns(1, 32), + kind: 22, + refs: &[1], + data: jbig2_halftone(2, 1, false), + cost: 136, + }, + Item { + what: "a grid of no width", + before: patterns(), + spent: two_patterns(1, 1), + kind: 22, + refs: &[1], + data: jbig2_halftone(0, 1000, false), + cost: 4002, + }, + Item { + what: "a halftone's pixels", + before: patterns(), + spent: two_patterns(1, 1), + kind: 22, + refs: &[1], + data: jbig2_halftone_of(32, 1, 1, false), + cost: 71, + }, + ]; + for item in &items { + assert!(jbig2_image_draws(&stream(item, item.cost)), "{}: with its cost left it draws", item.what); + assert!(!jbig2_image_draws(&stream(item, item.cost - 1)), "{}: with one less it's refused", item.what); + } +} + +/// A JBIG2 stream can put a region, a reference or a symbol at the very end of what an `i32` holds, +/// where adding to it overflowed: a panic wherever overflow is checked, a build's own tests included. +/// A region or a symbol out there is off the page, so nothing draws. Vendored hayro-jbig2 patch: the arithmetic wraps, as +/// a release build's did, or in `Bitmap::combine` saturates, which comes to the same. Three of the text +/// streams are arithmetic-coded: one symbol on a strip of -2^31 + 1, with its bottom left as the corner +/// placed, one on a strip of -2^31, and one refined by an offset of `i32::MIN`, which reads nothing of +/// the old symbol and still draws. Two are Huffman-coded, each with its first S at an offset of +/// 0x7FFFFFFF on a table's 32-bit range line, past what an `i32` holds, and are refused further on. +#[test] +fn jbig2_placements_at_the_end_of_an_i32_draw_nothing() { + const FAR_CORNER: &str = "0000000030000100000013000000100000000c0000000000000000420000000000010000010000001e000003fffdff02fefefe00000001000000014fdc3ce0205df55c36bfffac00000002062001010000001f000000100000000c000000000000000004000000000001969747cbe88bffac"; + const STRIP: &str = "0000000030000100000013000000100000000c0000000000000000420000000000010000010000001e000003fffdff02fefefe00000001000000014fdda25e46956d3e0d3fffac00000002062001010000001f000000100000000c000000000000000004001000000001969747cbe5dbffac"; + const UPPER_LINE: &str = "0000000030000100000013000000100000000c0000000000000000420000000000010000010000001d000003fffdff02fefefe00000001000000014fdaea38c99641e0c8ffac000000020620010100000038000000100000000c00000000000000000400110000000000010100000000000000000000000000000000003f7fffffff0000000000000000"; + const LOWER_LINE: &str = "0000000030000100000013000000100000000c0000000000000000420000000000010000010000001d000003fffdff02fefefe00000001000000014fdaea38c99641e0c8ffac000000020620010100000038000000100000000c00000000000000000400110000000000010100000000000000000000000000000000003e7fffffff0000000000000000"; + const OFFSET: &str = "0000000030000100000013000000100000000c0000000000000000420000000000010000010000001e000003fffdff02fefefe00000001000000014fdda9172f614a037ddfffac0000000206200101000000260000000800000006000000000000000004801200000001a9695690b3e5f44a2f105e30d3ffac"; + let segment = |number: u32, kind: u8, data: &[u8]| { + let mut out = number.to_be_bytes().to_vec(); + out.extend_from_slice(&[kind, 0, 1]); + out.extend_from_slice(&u32::try_from(data.len()).unwrap().to_be_bytes()); + out.extend_from_slice(data); + out + }; + let side = 8u32.to_be_bytes(); + let page = |width: u32| { + let mut page = [width.to_be_bytes(), side].concat(); + page.extend_from_slice(&[0; 8]); // resolution + page.extend_from_slice(&[0, 0, 0]); // flags, striping + segment(0, 48, &page) + }; + let region = |width: u32, kind: u8, x: u32, y: u32, header: &[u8]| { + let mut region = [width.to_be_bytes(), side, x.to_be_bytes(), y.to_be_bytes()].concat(); + region.push(0); // combination operator + region.extend_from_slice(header); + region.extend_from_slice(&[0x5a; 64]); + [page(width), segment(1, kind, ®ion)].concat() + }; + let generic = |x, y| region(8, 38, x, y, &[0, 3, 0xff, 0xfd, 0xff, 2, 0xfe, 0xfe, 0xfe]); // template 0, AT pixels + // Immediate refinements of the page: template 0 with its usual AT pixels, with others, with typical + // prediction, and template 1. + let refined = |width, x, y, form: usize| { + region(width, 42, x, y, [&[0, 0xff, 0xff, 0xff, 0xff][..], &[0, 0xff, 0xff, 127, 127], &[2, 0xff, 0xff, 0xff, 0xff], &[1]][form]) + }; + let hex = |text: &str| (0..text.len() / 2).map(|i| u8::from_str_radix(&text[i * 2..i * 2 + 2], 16).unwrap()).collect::>(); + let ink = |jbig2: Vec, _width: u32, _height: u32| black_pixels(&jbig2); + assert!(ink(generic(2, 0), 8, 8) > 0, "a region on the page draws"); + for form in 0..4 { + assert!(ink(refined(8, 0, 0, form), 8, 8) > 0, "a refinement on the page draws: {form}"); + } + let end = 0x7FFF_FFFFu32; + for (x, y) in [(end, 0), (0, end), (end - 7, 0), (0, end - 7), (end - 129, 0), (0, end - 129)] { + assert_eq!(ink(generic(x, y), 8, 8), 0, "generic at {x:#x}, {y:#x}"); + for form in 0..4 { + assert_eq!(ink(refined(8, x, y, form), 8, 8), 0, "refinement {form} at {x:#x}, {y:#x}"); + } + } + // Past a 32-bit word of the row, where the reference is read again. + assert_eq!(ink(refined(64, 0, end, 0), 64, 8), 0, "a wide refinement"); + assert_eq!(ink(hex(FAR_CORNER), 16, 12), 0, "a symbol's far side past the end"); + assert_eq!(ink(hex(STRIP), 16, 12), 0, "a strip of -2^31"); + assert!(ink(hex(OFFSET), 16, 12) > 0, "a symbol refined by an offset of i32::MIN"); + assert_eq!(ink(hex(UPPER_LINE), 16, 12), 0, "an upper range line past an i32"); + assert_eq!(ink(hex(LOWER_LINE), 16, 12), 0, "a lower range line past an i32"); +}