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:
1 parent
27be01af93
commit
2d166f567b
8 files changed
+453
-49
No files matched your search
+21
-11
@@ -35,7 +35,9 @@
|
||||
//! is used for any offset/count cast (AGENTS.md §4).
|
||||
|
||||
use crate::error::AlkTypeError;
|
||||
use crate::schema::{AlkTypeKind, Endian, VariableEncoding};
|
||||
use crate::schema::{
|
||||
AlkTypeKind, Endian, VariableEncoding, MAX_ARRAY_ELEMENTS,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
const DEFS_KEY: &str = "$defs";
|
||||
@@ -851,6 +853,13 @@ impl BastArray {
|
||||
"bast: array at {path} has no `count` (variable-length arrays are not supported in v1, D-BAST-004)"
|
||||
))
|
||||
})?;
|
||||
if count > MAX_ARRAY_ELEMENTS {
|
||||
return Err(AlkTypeError::Schema(format!(
|
||||
"bast: array at {path} declares count {count}, which exceeds the \
|
||||
compile-time limit of {MAX_ARRAY_ELEMENTS} elements (untrusted schemas \
|
||||
must not be able to request unbounded per-element expansion)"
|
||||
)));
|
||||
}
|
||||
Ok(Self {
|
||||
element: Box::new(element),
|
||||
count,
|
||||
@@ -1750,7 +1759,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_count_large_u64_parses_on_64bit() {
|
||||
fn array_count_large_u64_rejected_by_compile_cap() {
|
||||
// Pre-0.3.1 this parsed u64::MAX verbatim (H1: the count fed
|
||||
// unbounded per-element expansion downstream). The cap now
|
||||
// rejects it at parse, before any walker sees the array.
|
||||
let root = json!({
|
||||
"$defs": {
|
||||
"S": { "kind": "struct", "fields": [
|
||||
@@ -1758,14 +1770,12 @@ mod tests {
|
||||
] }
|
||||
}
|
||||
});
|
||||
let d = BastDoc::new(&root, "S").expect("parses on usize>=u64 targets");
|
||||
let s = match d.root_def().kind() {
|
||||
BastDefKind::Struct(s) => s,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
match s.fields()[0].ty() {
|
||||
BastType::Array(a) => assert_eq!(a.count(), usize::try_from(u64::MAX).unwrap()),
|
||||
other => panic!("expected Array, got {other:?}"),
|
||||
let err = BastDoc::new(&root, "S").unwrap_err();
|
||||
match err {
|
||||
AlkTypeError::Schema(reason) => {
|
||||
assert!(reason.contains("compile-time limit"), "reason: {reason}");
|
||||
}
|
||||
other => panic!("expected Schema error, got {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user