Fix H1: bound untrusted array counts; resolve L1 (review #006)

- Replace the three Vec::with_capacity(count) sites in materialize.rs
  with Vec::new() — validate_bytes on an adversarial count no longer
  OOM-aborts the process (AGENTS.md §3).
- New compile-time caps in schema.rs: MAX_ARRAY_ELEMENTS (2^16,
  enforced at BastArray::parse — the choke point every consumer
  inherits, bounds the walkers' per-element entry loops) and
  MAX_ARRAY_BYTES (2^26, enforced per walker against the mode-specific
  stride: compile_array, walk_array, compute_array_field).
- L1: fixed_composite_size/fixed_plan_size now return
  Result<Option<usize>>; unwrap_or_default() gone, overflow is a clean
  Schema error instead of silent stride-0.
- Zero-progress guard: stride-0 arrays whose elements consume 0 bytes
  (legal empty-struct elements) now error in plan_walk_variable_array_
  size and materialize_array_packed instead of looping count times.
- Tests: 8 new (parse/build/compile rejections, cap boundary,
  short-buffer clean error) + array_count_large_u64_parses_on_64bit
  rewritten to assert the new cap rejection. In-tree tests assert only
  the safe (compile-time) half per review #006's Methodology warning.
- Review #006 updated: H1/L1 resolution blocks, new finding N2
  (unbounded align annotations, found while re-deriving the cap
  arithmetic), resolution log, recommended order.

Verified: 482 tests green (405+17+34+14+12, 2 pre-existing ignored),
clippy -D warnings clean, wasm32-unknown-unknown build green.
This commit is contained in:
glm-5.3-flash committed 2026-09-02 16:46:46 +00:00
1 parent 27be01af93
commit 2d166f567b
8 files changed
+453 -49

No files matched your search

+14
View File
@@ -16,6 +16,20 @@ use std::fmt;
pub(crate) const U32_SIZE: usize = 4;
pub(crate) const DISCRIMINATOR_PATH: &str = "__discriminator";
/// Maximum declared array element count. Schemas are untrusted input
/// (AGENTS.md §3): every layout walker expands `count` into per-element
/// work (offset-map/layout entries, per-element reads), so an unbounded
/// count is a memory/CPU denial-of-service. The cap applies at parse
/// time, before any walker sees the array; 2^16 elements bounds the
/// per-array compile-time entry cost at ~10 MB.
pub(crate) const MAX_ARRAY_ELEMENTS: usize = 1 << 16;
/// Maximum computed byte size of a single array (`count × element
/// stride`). Bounds the arithmetic product independently of
/// [`MAX_ARRAY_ELEMENTS`] so multi-megabyte strides cannot combine with
/// a legal count into an unbounded layout request.
pub(crate) const MAX_ARRAY_BYTES: usize = 1 << 26;
/// The 18 BAST kinds recognized by the engine.
///
/// Each variant corresponds to a lowercase BAST kind string