mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-09 20:43:12 +00:00
Fuzzed inputs drove one check-one process to 2-6 GB (the 16 GB CI runner running two of them was killed every night). Three causes, each with a test: - lopdf (bootstrap inspector) sized PNG predictor rows from /Columns before reading data: /Columns 4294967295 allocated two 4 GiB rows. Vendored lopdf 0.45.0 refuses rows longer than the data (and uses saturating row widths); its load-time stream decoding also gets a 256 MiB limit (it had none). - hayro sized a tiling pattern's cell pixmap from /XStep x scale, bounded only by u16: a step far beyond /BBox allocated 2 GB. The vendored patch keeps the cell within 3000 px a side, lowering the resolution instead. - cos decoded cross-reference streams up to the general 1 GiB limit; they now stop at what 8,388,607 objects' rows can hold (~200 MB at most), and the section is reconstructed (logged) instead. On the nightly's seed (41) the worst captured input fell from 6.2 GB to 6 MB, and a 300 s fuzz run had no process above 300 MB (before: many at 2-5 GB). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
92 lines
3.2 KiB
Rust
92 lines
3.2 KiB
Rust
use crate::Object;
|
|
|
|
/// Type alias for the filter function used during PDF loading.
|
|
///
|
|
/// The function receives an object ID and a mutable reference to the object,
|
|
/// and returns `Some((id, object))` to keep it or `None` to discard it.
|
|
pub type FilterFunc = fn((u32, u16), &mut Object) -> Option<((u32, u16), Object)>;
|
|
|
|
/// Options for loading PDF documents.
|
|
///
|
|
/// Use this struct to configure password, object filtering, and strictness
|
|
/// when loading a PDF. The default is lenient parsing with no password or filter.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```no_run
|
|
/// use lopdf::{Document, LoadOptions};
|
|
///
|
|
/// // Load with a password
|
|
/// let doc = Document::load_with_options(
|
|
/// "encrypted.pdf",
|
|
/// LoadOptions::with_password("secret"),
|
|
/// );
|
|
///
|
|
/// // Load with strict parsing
|
|
/// let doc = Document::load_with_options(
|
|
/// "document.pdf",
|
|
/// LoadOptions { strict: true, ..Default::default() },
|
|
/// );
|
|
/// ```
|
|
#[derive(Clone, Default)]
|
|
pub struct LoadOptions {
|
|
/// Password for encrypted PDFs.
|
|
pub password: Option<String>,
|
|
/// Object filter applied during loading.
|
|
pub filter: Option<FilterFunc>,
|
|
/// When `true`, reject non-conforming PDFs instead of silently accepting them.
|
|
/// Defaults to `false` (lenient parsing).
|
|
pub strict: bool,
|
|
/// Maximum number of bytes any single stream may decompress to during
|
|
/// loading (object streams and cross-reference streams).
|
|
///
|
|
/// Compression filters can inflate a tiny input into an enormous output (a
|
|
/// "decompression bomb"). Because object and xref streams are decoded eagerly
|
|
/// while the document is loaded, an unbounded stream can exhaust memory
|
|
/// before any of your code runs. Set this to bound that per-stream cost when
|
|
/// loading untrusted PDFs; a stream that would exceed it fails with
|
|
/// [`crate::DecompressError::MemoryLimitExceeded`].
|
|
///
|
|
/// `None` (the default) applies no limit.
|
|
pub max_decompressed_size: Option<usize>,
|
|
}
|
|
|
|
impl std::fmt::Debug for LoadOptions {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("LoadOptions")
|
|
.field("password", &self.password.as_ref().map(|_| "***"))
|
|
.field("filter", &self.filter.map(|_| "fn(..)"))
|
|
.field("strict", &self.strict)
|
|
.field("max_decompressed_size", &self.max_decompressed_size)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl LoadOptions {
|
|
/// Create options with a password for encrypted PDFs.
|
|
pub fn with_password(password: &str) -> Self {
|
|
Self {
|
|
password: Some(password.to_string()),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Create options with an object filter.
|
|
pub fn with_filter(filter: FilterFunc) -> Self {
|
|
Self {
|
|
filter: Some(filter),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Create options that bound how large any single stream may decompress to
|
|
/// during loading, to defend against decompression bombs in untrusted PDFs.
|
|
/// See [`LoadOptions::max_decompressed_size`].
|
|
pub fn with_max_decompressed_size(max_decompressed_size: usize) -> Self {
|
|
Self {
|
|
max_decompressed_size: Some(max_decompressed_size),
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|