vole_document/limits.rs
1//! Centralized resource bounds.
2//!
3//! Every decode and encode path takes a [`Limits`]. Untrusted descriptors must
4//! be rejected *before* catastrophic work is performed, so all arithmetic on
5//! declared lengths and offsets is checked against these bounds and uses
6//! checked integer operations.
7
8/// Hard upper bounds applied while parsing and materializing a descriptor.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Limits {
11 /// Maximum accepted source/descriptor input size.
12 pub max_input_bytes: u64,
13 /// Maximum reconstructed output size for a single materialization.
14 pub max_output_bytes: u64,
15 /// Admission cap on the output of a single `DEFLATE_REPLAY`.
16 ///
17 /// This is a **VOLE replay-profile policy limit**, not an RFC 1951 maximum.
18 /// RFC 1951 permits arbitrarily many empty non-final stored blocks, so it
19 /// gives no finite `f(decompressed_size)` bound on `compressed_size`; a
20 /// bitstream that inflates to zero bytes may be arbitrarily large. VOLE
21 /// therefore declines to replay a descriptor whose declared output exceeds
22 /// this policy cap (see ADR-0016).
23 pub max_replay_bytes: u64,
24 /// Maximum length of a single record payload.
25 pub max_record_len: u32,
26 /// Maximum number of records in a container.
27 pub max_record_count: u32,
28 /// Maximum number of distinct byte objects (`OBJECT` records).
29 pub max_object_count: u32,
30 /// Maximum number of DRA instructions in a reconstruction graph.
31 pub max_graph_ops: u32,
32 /// Maximum repeat count for a single `REPEAT_LAST` instruction.
33 pub max_repeat_count: u64,
34 /// Maximum number of symbols in a single entropy channel.
35 pub max_channel_symbols: u64,
36 /// Maximum number of distinct entropy models (`MODEL` records).
37 pub max_model_count: u32,
38 /// Maximum number of entropy channels (`ENTROPY_CHANNEL` records).
39 pub max_channel_count: u32,
40 /// Maximum encoded size of a single entropy model payload.
41 pub max_entropy_model_bytes: u32,
42 /// Maximum number of lexical spans produced for a PDF input.
43 pub max_pdf_spans: u32,
44 /// Maximum number of selectors in a single `OBSERVATION_INDEX` record.
45 ///
46 /// Bounds the admissions of the optional partial-decode index (Phase 7.3)
47 /// before allocation: an index whose selector table would exceed this is
48 /// rejected at parse and declined by the index builder. It mirrors the
49 /// object/graph scale so the table cannot dwarf the document it describes.
50 pub max_index_selectors: u32,
51 /// Maximum accepted size of an optional `DIRECTORY` record payload.
52 ///
53 /// Bounds the seek directory (Phase 8) before allocation: a directory larger
54 /// than this is declined at decode rather than trusted. A directory is roughly
55 /// `13 * record_count` bytes, so this also caps the record count a directory
56 /// can describe.
57 pub max_directory_bytes: u32,
58 /// Maximum accepted size of an optional `CHECKPOINT` record payload.
59 ///
60 /// Bounds the byte-level partial-materialization checkpoint (Phase 13.4)
61 /// before allocation: a checkpoint larger than this is declined at decode
62 /// rather than trusted. A checkpoint is `20 + 16 * op_count` bytes, so this
63 /// also caps the op count a checkpoint can describe.
64 pub max_checkpoint_bytes: u32,
65 // The ZIP caps below mirror the threat model and DEFAULT/STRICT values frozen
66 // in `research/subagents/phase-12/I-security.md` §6 (threat ids Z1–Z15), as
67 // required by plan §DEC-3/§DEC-9.
68 /// Maximum number of ZIP members accepted in one archive (Phase 12, Z2).
69 pub max_zip_members: u32,
70 /// Maximum declared compressed size of a single ZIP member (Phase 12, Z1).
71 pub max_zip_member_compressed: u64,
72 /// Maximum declared uncompressed size of a single ZIP member (Phase 12, Z1).
73 pub max_zip_member_uncompressed: u64,
74 /// Maximum sum of declared uncompressed sizes across all members (Z2).
75 pub max_zip_aggregate_uncompressed: u64,
76 /// Maximum declared uncompressed/compressed ratio for one member (Z1).
77 pub max_zip_compression_ratio: u32,
78 /// Maximum raw name byte length of one member (Phase 12, Z13/Z14).
79 pub max_zip_name_bytes: u32,
80 /// Maximum raw extra-field byte length of one member (Z7/Z14).
81 pub max_zip_extra_bytes: u32,
82 /// Maximum per-entry comment byte length (Z14).
83 pub max_zip_entry_comment_bytes: u32,
84 /// Maximum archive comment byte length (Z14/Z15).
85 pub max_zip_archive_comment_bytes: u32,
86 /// Maximum central-directory byte length (Z14).
87 pub max_zip_central_dir_bytes: u64,
88 /// Maximum leading bytes before the first local header (Z14).
89 pub max_zip_prefix_bytes: u64,
90 /// Maximum trailing bytes after the EOCD record (Z14).
91 pub max_zip_trailing_bytes: u64,
92 // The XML/OPC caps below mirror the threat model and DEFAULT/STRICT values
93 // frozen in `research/subagents/phase-12/I-security.md` §6 (§2 XML, §3 OPC),
94 // as required by plan §DEC-4/§DEC-9. XML is derived (`Q_gen`) state only.
95 /// Maximum XML element nesting depth before a typed decline (Phase 12, §2).
96 pub max_xml_depth: u32,
97 /// Maximum decoded byte length of a single XML part (Phase 12, §2).
98 pub max_xml_part_bytes: u64,
99 /// Maximum number of XML pull events in a single part (Phase 12, §2).
100 pub max_xml_events: u64,
101 /// Maximum number of XML element nodes in a single part (Phase 12, §2).
102 pub max_xml_nodes: u64,
103 /// Maximum number of attributes on a single XML element (Phase 12, §2).
104 pub max_xml_attrs_per_element: u32,
105 /// Maximum total text bytes accepted across a single XML part (Phase 12, §2).
106 pub max_xml_text_bytes: u64,
107 /// Maximum relationships across all `.rels` parts (Phase 12, §3).
108 pub max_opc_rels: u32,
109 /// Maximum internal relationship traversal depth (Phase 12 cycles, §3).
110 pub max_opc_rel_depth: u32,
111 /// Maximum `Default`+`Override` entries in `[Content_Types].xml` (Phase 12, §3).
112 pub max_opc_content_types_overrides: u32,
113 /// Maximum byte length of an OPC part name (Phase 12, §3).
114 pub max_opc_part_name_bytes: u32,
115 // The EPUB/OCF caps below mirror the threat model and DEFAULT/STRICT values
116 // frozen in `research/subagents/phase-12/I-security.md` §6 (§4 EPUB), as
117 // required by plan §DEC-5/§DEC-9. EPUB semantics are derived (`Q_gen`) only.
118 /// Maximum `rootfile` entries accepted in `META-INF/container.xml` (Phase 12, §4).
119 pub max_epub_rootfiles: u32,
120 /// Maximum Package Document manifest items accepted (Phase 12, §4).
121 pub max_epub_manifest_items: u32,
122 /// Maximum Package Document spine `itemref`s accepted (Phase 12, §4).
123 pub max_epub_spine_items: u32,
124 /// Maximum navigation-document nesting depth accepted (Phase 12, §4).
125 pub max_epub_nav_depth: u32,
126 /// Maximum manifest `fallback` chain length followed (Phase 12, §4).
127 pub max_epub_fallback_chain: u32,
128 /// Maximum XHTML element nodes accepted in one content/nav document (Phase 12, §4).
129 pub max_xhtml_nodes: u32,
130 // The ODT/ODF caps below mirror the EPUB caps above (Phase 13.3 applies the same
131 // bounded-XML policy to the OpenDocument content model). ODT semantics are derived
132 // (`Q_gen`) only.
133 /// Maximum `file-entry` elements accepted in `META-INF/manifest.xml` (Phase 13.3).
134 pub max_odt_manifest_entries: u32,
135 /// Maximum block elements accepted in one OpenDocument content part (Phase 13.3).
136 pub max_odt_blocks: u32,
137 /// Maximum notes accepted in one OpenDocument content part (Phase 13.3).
138 pub max_odt_notes: u32,
139}
140
141impl Limits {
142 /// The default archival limits: generous, but always finite.
143 pub const DEFAULT: Limits = Limits {
144 max_input_bytes: 1 << 40, // 1 TiB
145 max_output_bytes: 1 << 40, // 1 TiB
146 max_replay_bytes: 1 << 34, // 16 GiB
147 max_record_len: 1 << 31, // 2 GiB
148 max_record_count: 1 << 20, // ~1M records
149 max_object_count: 1 << 20,
150 max_graph_ops: 1 << 20,
151 max_repeat_count: 1 << 32,
152 max_channel_symbols: 1 << 40,
153 max_model_count: 1 << 16,
154 max_channel_count: 1 << 16,
155 max_entropy_model_bytes: 4096,
156 max_pdf_spans: 1 << 26,
157 max_index_selectors: 1 << 20,
158 max_directory_bytes: 1 << 20,
159 max_checkpoint_bytes: 1 << 20,
160 max_zip_members: 1 << 20,
161 max_zip_member_compressed: 1 << 34,
162 max_zip_member_uncompressed: 1 << 34,
163 max_zip_aggregate_uncompressed: 1 << 36,
164 max_zip_compression_ratio: 1024,
165 max_zip_name_bytes: 1 << 16,
166 max_zip_extra_bytes: 1 << 16,
167 max_zip_entry_comment_bytes: 1 << 16,
168 max_zip_archive_comment_bytes: 1 << 16,
169 max_zip_central_dir_bytes: 1 << 28,
170 max_zip_prefix_bytes: 1 << 20,
171 max_zip_trailing_bytes: 1 << 20,
172 max_xml_depth: 256,
173 max_xml_part_bytes: 1 << 28,
174 max_xml_events: 1 << 24,
175 max_xml_nodes: 1 << 24,
176 max_xml_attrs_per_element: 4096,
177 max_xml_text_bytes: 1 << 28,
178 max_opc_rels: 1 << 20,
179 max_opc_rel_depth: 64,
180 max_opc_content_types_overrides: 1 << 20,
181 max_opc_part_name_bytes: 1 << 16,
182 max_epub_rootfiles: 16,
183 max_epub_manifest_items: 1 << 20,
184 max_epub_spine_items: 1 << 20,
185 max_epub_nav_depth: 64,
186 max_epub_fallback_chain: 32,
187 max_xhtml_nodes: 1 << 24,
188 max_odt_manifest_entries: 1 << 20,
189 max_odt_blocks: 1 << 20,
190 max_odt_notes: 1 << 20,
191 };
192
193 /// Tight limits for hostile-input testing and fuzzing.
194 pub const STRICT: Limits = Limits {
195 max_input_bytes: 1 << 26, // 64 MiB
196 max_output_bytes: 1 << 26, // 64 MiB
197 max_replay_bytes: 1 << 26, // 64 MiB
198 max_record_len: 1 << 24, // 16 MiB
199 max_record_count: 1 << 16, // 65536
200 max_object_count: 1 << 16,
201 max_graph_ops: 1 << 16,
202 max_repeat_count: 1 << 24,
203 max_channel_symbols: 1 << 26,
204 max_model_count: 1 << 12,
205 max_channel_count: 1 << 12,
206 max_entropy_model_bytes: 4096,
207 max_pdf_spans: 1 << 16,
208 max_index_selectors: 1 << 16,
209 max_directory_bytes: 1 << 18,
210 max_checkpoint_bytes: 1 << 18,
211 max_zip_members: 1 << 16,
212 max_zip_member_compressed: 1 << 26,
213 max_zip_member_uncompressed: 1 << 26,
214 max_zip_aggregate_uncompressed: 1 << 27,
215 max_zip_compression_ratio: 256,
216 max_zip_name_bytes: 4096,
217 max_zip_extra_bytes: 4096,
218 max_zip_entry_comment_bytes: 4096,
219 max_zip_archive_comment_bytes: 4096,
220 max_zip_central_dir_bytes: 1 << 20,
221 max_zip_prefix_bytes: 1 << 16,
222 max_zip_trailing_bytes: 1 << 16,
223 max_xml_depth: 64,
224 max_xml_part_bytes: 1 << 20,
225 max_xml_events: 1 << 16,
226 max_xml_nodes: 1 << 16,
227 max_xml_attrs_per_element: 256,
228 max_xml_text_bytes: 1 << 20,
229 max_opc_rels: 1 << 14,
230 max_opc_rel_depth: 16,
231 max_opc_content_types_overrides: 1 << 12,
232 max_opc_part_name_bytes: 4096,
233 max_epub_rootfiles: 4,
234 max_epub_manifest_items: 1 << 14,
235 max_epub_spine_items: 1 << 14,
236 max_epub_nav_depth: 16,
237 max_epub_fallback_chain: 8,
238 max_xhtml_nodes: 1 << 16,
239 max_odt_manifest_entries: 1 << 14,
240 max_odt_blocks: 1 << 14,
241 max_odt_notes: 1 << 12,
242 };
243}
244
245impl Default for Limits {
246 fn default() -> Self {
247 Limits::DEFAULT
248 }
249}