Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/znippy

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");
}