znippy-common/tests/openzl_internal_alloc.rs
//! **A verified read caps what OpenZL allocates for a chunk.**
//!
//! The heimdall fuzz find (lane zlwasm, `fixtures/openzl/regress-constant-stream-alloc.zl`)
//! is a 36-byte frame whose header declares 0 bytes and whose `constant` codec
//! asks OpenZL for a stream of ~2^64 bytes. Natively malloc refuses that, so it
//! is an error. With the element count set to 2^30 instead, the same 36 bytes
//! make OpenZL allocate and FILL 1 GiB before it fails (measured on oden,
//! 2026-10-31): the size comes from the frame's graph, not from the header that
//! `archive::frame_limit` bounds.
//!
//! `extract_file_verified` (the read Holger's bundle verifier uses) runs each
//! compressed chunk under an openzl-sys-rs allocation budget sized from the
//! chunk's declared size, so this frame behind a 16-byte index row is refused
//! without the process growing. It is its own test binary so the process peak
//! (VmHWM) it reads is its own.
#![cfg(feature = "openzl")]
use std::fs::File;
use std::os::unix::fs::FileExt;
use std::sync::Arc;
use znippy_common::index::{build_metadata_batch, lookup_schema};
use znippy_common::meta::{BlobMeta, ChunkMeta};
use znippy_common::meta_sink::{ArchiveMetaSink, ArrowIpcSink, GroupKey};
use znippy_common::ZnippyArchive;
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 mut frame = include_bytes!("fixtures/openzl/regress-constant-stream-alloc.zl").to_vec();
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 one-entry archive whose single compressed chunk is `blob`, declared `declared` bytes.
fn write_one_chunk(path: &std::path::Path, blob: &[u8], declared: u64) {
let file = Arc::new(File::create(path).unwrap());
file.write_all_at(blob, 0).unwrap();
let blobs = vec![BlobMeta {
blob_offset: 0,
blob_size: blob.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: blob.len() as u64,
},
}];
let batch = build_metadata_batch(&blobs, |_| "hostile.bin".to_string(), &[], &[]).unwrap();
let mut sink = ArrowIpcSink::new(file.clone(), blob.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_36_byte_frame_cannot_make_a_verified_read_allocate_a_gigabyte() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("h.znippy");
write_one_chunk(&path, &constant_frame_with_count(1 << 30), 16);
let archive = ZnippyArchive::open(&path).unwrap();
let before = peak_rss_mib();
let got = archive.extract_file_verified("hostile.bin");
let grew = peak_rss_mib().saturating_sub(before);
let err = got.expect_err("the frame is refused");
assert!(err.to_string().contains("budget"), "the refusal names the allocation budget: {err}");
assert!(grew < 64, "a 36-byte frame grew the process peak by {grew} MiB");
}