mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-02 22:41:47 -06:00
fix(render): a JBIG2 stream's decoding work is bounded in total (hayro-jbig2 patch 10)
Each JBIG2 bitmap is bounded, but nothing bounded how many a stream asks for, so a few hundred bytes could tie up a render worker for minutes, and CodeRabbit raised the same on #5461. Measured through pdfcraft-cli, before and after: - one halftone segment, a 795-byte PDF: 265 s, now refused at once; - 66 halftones over patterns 1 wide, a 3,894-byte PDF: about 330 s, now refused at once; - about 226,000 generic regions behind nested Flate, a 923-byte PDF: unfinished at 40 s, some 88 hours by extrapolation, now 4.8 s; - 8 dictionaries of symbols of no width, a 983-byte PDF: 81 s, now 0.7 s; - about 1,048,000 generic regions of no width, a 34 KB PDF: unfinished at 90 s, now 2.6 s; - an aggregated dictionary over 2^20 symbols, a 713-byte PDF: unfinished at 90 s, now 9.1 s; - 10,000 instances of an 8192 × 8192 symbol, an 18 KB PDF: 64 s, now 3.2 s; - 30 generic regions of no data: 48 s, now 4.7 s. A stream may now ask for 2^30 units of work: four of the largest pages, about 6 s on data the decoder predicts and about 17 s on data it can't, inside the viewer's 20 s watchdog. Decoding a bitmap costs a unit a pixel and 4 a row, since a row costs time at any width. Copying one costs a unit a word and 1 a row, so a tall thin bitmap costs its rows. Each item is charged before it is decoded, apart from what is known only as it goes: new symbols and text instances are charged in their loops. An aggregated symbol is charged for the symbol table it rebuilds. Past the bound the image isn't drawn, as with the other caps, and the rest of the page is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
85c1005148
commit
8243952569
@@ -3719,7 +3719,12 @@ fn jbig2_pattern_dictionary(height: u8, patterns: u32) -> Vec<u8> {
|
||||
/// 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<u8> {
|
||||
let mut halftone = jbig2_region_info(1, 1);
|
||||
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<u8> {
|
||||
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());
|
||||
@@ -3891,6 +3896,287 @@ fn jbig2_globals_that_are_jbig2_themselves_are_not_decoded() {
|
||||
assert!(jbig2_image_draws_with(&black_jbig2_page(), "/DeviceGray", "/DecodeParms << /JBIG2Globals 6 0 R >>", &objects));
|
||||
}
|
||||
|
||||
/// 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<u8> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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<u8>)>,
|
||||
spent: u64,
|
||||
kind: u8,
|
||||
refs: &'static [u8],
|
||||
data: Vec<u8>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// An image's page header can claim any size, and the Indexed path reserves its output from it
|
||||
/// before the page bitmap is sized: a 743-byte PDF asked for 137 GB, which aborts the process.
|
||||
/// Vendored hayro-jbig2 patch: a page past the bounds is refused when the stream is opened.
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+24
@@ -24,6 +24,30 @@ pub(crate) const MAX_PIXELS: u64 = 1 << 28;
|
||||
/// 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;
|
||||
|
||||
+24
-6
@@ -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<HalftoneRegionHeader<'a>> {
|
||||
let region_info = parse_region_segment_info(reader)?;
|
||||
|
||||
+12
-1
@@ -3,7 +3,7 @@
|
||||
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;
|
||||
@@ -218,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<PatternDictionaryHeader<'a>> {
|
||||
let flags_byte = reader.read_byte().ok_or(ParseError::UnexpectedEof)?;
|
||||
|
||||
+16
-4
@@ -4,7 +4,7 @@ use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::arithmetic_decoder::{ArithmeticDecoder, ArithmeticDecoderContext};
|
||||
use crate::bitmap::{BITMAP_WORDS, Bitmap, WORD_BITS, Word};
|
||||
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,
|
||||
@@ -46,6 +46,7 @@ pub(crate) fn decode(
|
||||
standard_tables: &StandardHuffmanTables,
|
||||
input_contexts: Option<&RetainedContexts>,
|
||||
room: u64,
|
||||
work_left: &mut u64,
|
||||
) -> Result<SymbolDictionary> {
|
||||
let num_new_symbols = header.num_new_symbols;
|
||||
|
||||
@@ -61,6 +62,7 @@ pub(crate) fn decode(
|
||||
pixels: 0,
|
||||
new_words: 0,
|
||||
room,
|
||||
work_left,
|
||||
header,
|
||||
standard_tables,
|
||||
};
|
||||
@@ -133,6 +135,9 @@ 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);
|
||||
@@ -359,6 +364,11 @@ fn decode_aggregation_bitmap(
|
||||
) -> Result<Bitmap> {
|
||||
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());
|
||||
@@ -444,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)
|
||||
}
|
||||
|
||||
@@ -491,6 +501,8 @@ struct SymbolDecodeContext<'a> {
|
||||
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,
|
||||
}
|
||||
@@ -914,8 +926,8 @@ mod tests {
|
||||
let input = vec![&symbol; 1000];
|
||||
let tables = StandardHuffmanTables::new();
|
||||
let words = symbol.words() as u64 * 1000;
|
||||
let decoded = decode(&header, &input, &[], &tables, None, words).unwrap();
|
||||
let decoded = decode(&header, &input, &[], &tables, None, words, &mut u64::MAX.clone()).unwrap();
|
||||
assert_eq!(decoded.exported_symbols.len(), 1000);
|
||||
assert!(decode(&header, &input, &[], &tables, None, words - 1).is_err());
|
||||
assert!(decode(&header, &input, &[], &tables, None, words - 1, &mut u64::MAX.clone()).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
+18
-3
@@ -11,7 +11,7 @@ use super::{
|
||||
use super::{RegionBitmap, generic_refinement};
|
||||
use crate::ScratchBuffers;
|
||||
use crate::arithmetic_decoder::{ArithmeticDecoder, ArithmeticDecoderContext};
|
||||
use crate::bitmap::Bitmap;
|
||||
use crate::bitmap::{Bitmap, copy_work, decode_work};
|
||||
use crate::error::{HuffmanError, OverflowError, ParseError, Result, SymbolError, bail};
|
||||
use crate::huffman_table::{HuffmanTable, StandardHuffmanTables, TableLine};
|
||||
use crate::integer_decoder::IntegerDecoder;
|
||||
@@ -25,6 +25,7 @@ pub(crate) fn decode(
|
||||
referred_tables: &[HuffmanTable],
|
||||
standard_tables: &StandardHuffmanTables,
|
||||
scratch: &mut ScratchBuffers,
|
||||
work_left: &mut u64,
|
||||
) -> Result<RegionBitmap> {
|
||||
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)
|
||||
|
||||
Vendored
+3
@@ -139,6 +139,8 @@ pub enum OverflowError {
|
||||
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 {
|
||||
@@ -168,6 +170,7 @@ impl fmt::Display for OverflowError {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+37
@@ -259,6 +259,18 @@ fn emit_bitmap<D: Decoder>(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<u32>,
|
||||
@@ -283,6 +295,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 {
|
||||
@@ -295,6 +308,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)?;
|
||||
@@ -312,11 +326,13 @@ 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)?;
|
||||
}
|
||||
SegmentType::PatternDictionary => {
|
||||
let header = pattern::parse(&mut reader)?;
|
||||
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)?;
|
||||
}
|
||||
@@ -325,6 +341,7 @@ fn decode_segments(
|
||||
// (6.5.5, step 1)
|
||||
// Collect references to avoid cloning; symbols are only cloned if re-exported.
|
||||
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<HuffmanTable> = seg
|
||||
@@ -351,12 +368,14 @@ fn decode_segments(
|
||||
&page_state.standard_tables,
|
||||
retained_contexts,
|
||||
page_state.retained_room(),
|
||||
&mut work_left,
|
||||
)?;
|
||||
page_state.store_symbol_dictionary(seg.header.segment_number, dictionary)?;
|
||||
}
|
||||
SegmentType::ImmediateTextRegion | SegmentType::ImmediateLosslessTextRegion => {
|
||||
// Collect symbols from referred symbol dictionaries (SBSYMS).
|
||||
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
|
||||
@@ -370,6 +389,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,
|
||||
@@ -383,6 +407,7 @@ fn decode_segments(
|
||||
&page_state.standard_tables,
|
||||
page_bitmap,
|
||||
scratch_buffers,
|
||||
&mut work_left,
|
||||
)?;
|
||||
} else {
|
||||
let region = text::decode(
|
||||
@@ -391,6 +416,7 @@ fn decode_segments(
|
||||
&referred_tables,
|
||||
&page_state.standard_tables,
|
||||
scratch_buffers,
|
||||
&mut work_left,
|
||||
)?;
|
||||
page_bitmap.combine(
|
||||
®ion.bitmap,
|
||||
@@ -404,6 +430,7 @@ fn decode_segments(
|
||||
SegmentType::IntermediateTextRegion => {
|
||||
// Collect symbols from referred symbol dictionaries (SBSYMS).
|
||||
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<HuffmanTable> = seg
|
||||
@@ -415,12 +442,18 @@ 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)?;
|
||||
}
|
||||
@@ -433,6 +466,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,
|
||||
@@ -460,6 +494,7 @@ 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)?;
|
||||
}
|
||||
@@ -473,6 +508,7 @@ 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)?;
|
||||
}
|
||||
@@ -490,6 +526,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)
|
||||
|
||||
Reference in New Issue
Block a user