update-bundle/tests/openzl_alloc_budget.rs
//! **A 36-byte OpenZL frame cannot make the bundle verifier allocate a gigabyte.**
//!
//! The heimdall fuzz find (lane zlwasm, `fixtures/fuzz/regress-constant-stream-alloc.zl`):
//! a frame whose header declares 0 bytes and whose `constant` codec asks OpenZL for a
//! stream of ~2^64 bytes. With the element count set to 2^30, the same 36 bytes made
//! OpenZL allocate and fill 1 GiB natively before failing (lane gatwasm, 2026-10-31).
//! Here it is `core.json`, a control member, which is extracted BEFORE the release
//! signature is checked. znippy-common 0.10.4 decodes every chunk of a verified read
//! under an OpenZL allocation budget (openzl-sys-rs 0.4.2), so the bundle is refused
//! as a malformed container and the process does not grow.
//!
//! Its own test binary, so the process peak (VmHWM) it reads is its own.
use std::fs::File;
use std::os::unix::fs::FileExt;
use std::path::Path;
use std::sync::Arc;
use holger_update_bundle::container::load_bundle_verified;
use holger_update_bundle::policy::TrustPolicy;
use znippy_common::index::{build_metadata_batch, lookup_schema};
use znippy_common::meta::{BlobMeta, ChunkMeta};
use znippy_common::meta_sink::{ArchiveMetaSink, ArrowIpcSink, GroupKey};
fn peak_rss_mib() -> u64 {
let s = std::fs::read_to_string("/proc/self/status").unwrap_or_default();
s.lines()
.find(|l| l.starts_with("VmHWM:"))
.and_then(|l| l.split_whitespace().nth(1))
.and_then(|v| v.parse::<u64>().ok())
.map(|kib| kib / 1024)
.unwrap_or(0)
}
/// The fuzz frame with its 10-byte element-count varint (offset 0x11) set to `n`.
fn constant_frame_with_count(n: u64) -> Vec<u8> {
let p = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/fuzz/regress-constant-stream-alloc.zl");
let mut frame = std::fs::read(p).unwrap();
let mut v = n;
for i in 0..10 {
frame[0x11 + i] = (v & 0x7f) as u8 | if i < 9 { 0x80 } else { 0 };
v >>= 7;
}
frame
}
/// A bundle whose only member, `core.json`, is one compressed chunk holding
/// `frame`, declared `declared` bytes by the index.
fn write_bundle(path: &Path, frame: &[u8], declared: u64) {
let file = Arc::new(File::create(path).unwrap());
file.write_all_at(frame, 0).unwrap();
let blobs = vec![BlobMeta {
blob_offset: 0,
blob_size: frame.len() as u64,
chunk_meta: ChunkMeta {
fdata_offset: 0,
file_index: 0,
chunk_seq: 0,
checksum: [0u8; 32],
compressed: true,
uncompressed_size: declared,
compressed_size: frame.len() as u64,
},
}];
let batch = build_metadata_batch(&blobs, |_| "core.json".to_string(), &[], &[]).unwrap();
let mut sink = ArrowIpcSink::new(file.clone(), frame.len() as u64);
sink.push_subindex(
lookup_schema().as_ref(),
&[batch],
GroupKey {
pkg_type: 0,
repo: String::new(),
module_name: String::new(),
},
)
.unwrap();
Box::new(sink).finish().unwrap();
}
#[test]
fn a_gigabyte_constant_stream_in_core_json_is_refused_without_growing_memory() {
let dir = std::env::temp_dir().join(format!("holger-zlbudget-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("hostile.holger.znippy");
write_bundle(&path, &constant_frame_with_count(1 << 30), 16);
let policy = TrustPolicy::from_ron(
r#"(schema_version: "holger.bundle-trust-policy/v1", trust_mode: Some(PinnedSigners))"#,
)
.unwrap();
let before = peak_rss_mib();
let got = load_bundle_verified(&path, &policy);
let grew = peak_rss_mib().saturating_sub(before);
let _ = std::fs::remove_dir_all(&dir);
let e = got.expect_err("the hostile bundle is refused");
let msg = e.to_string();
assert!(
msg.contains("cannot extract bundle member"),
"a container refusal: {msg}"
);
assert!(
msg.contains("allocation budget"),
"refused by the OpenZL allocation budget: {msg}"
);
assert!(
grew < 64,
"a 36-byte frame grew the verifier's peak by {grew} MiB"
);
}