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

nordisk/holger

update-bundle/src/tests/hostile_index.rs

//! A bundle whose znippy index is hostile Arrow IPC is a `Container` refusal
//! from `verify_bundle_file`, before any signature is looked at — never a panic
//! and never an abort of the process verifying it.
//!
//! Before znippy-common's IPC guard (lane arrowcrash) each of these died inside
//! `ZnippyArchive::open` -> `read_znippy_index` -> arrow-ipc's `StreamReader`:
//! the two seeds (from the Heimdall verify-bundle guest lane, heimdall 7403241)
//! panicked (`Buffer::slice_with_length`, `BooleanBuffer::new`), and the 1 TiB
//! `bodyLength` aborted the process on a failed allocation.

use std::fs;
use std::path::Path;

use super::testkit::*;
use crate::api::verify_bundle_file;
use crate::container::pack_bundle;
use crate::error::BundleError;
use crate::verify::TargetSpec;

fn refused_as_container(path: &Path) {
    let b = valid_bundle();
    match verify_bundle_file(path, &b.policy, &TargetSpec::default(), None) {
        Err(BundleError::Container(why)) => assert!(why.contains("open"), "{why}"),
        other => panic!(
            "{}: expected a Container refusal, got {other:?}",
            path.display()
        ),
    }
}

#[test]
fn hostile_index_seeds_are_container_refusals() {
    let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("testdata/hostile-ipc");
    for name in [
        "bundle-boolean-buffer.znippy",
        "bundle-buffer-overrun.znippy",
    ] {
        refused_as_container(&dir.join(name));
    }
}

#[test]
fn a_manifest_claiming_a_terabyte_body_is_a_container_refusal() {
    use znippy_common::arrow::ipc::{root_as_message, MessageHeader};
    let b = valid_bundle();
    let dir = tempfile::tempdir().unwrap();
    let out = dir.path().join("bundle.holger.znippy");
    pack_bundle(&b.members, &out).unwrap();
    let mut bytes = fs::read(&out).unwrap();
    let n = bytes.len();
    let start = u64::from_le_bytes(bytes[n - 8..].try_into().unwrap()) as usize;
    // Walk the manifest stream to its record batch and patch its bodyLength.
    let mut pos = start + 8; // continuation marker + schema length
    let schema_len = u32::from_le_bytes(bytes[start + 4..start + 8].try_into().unwrap()) as usize;
    pos += schema_len;
    let meta_len = u32::from_le_bytes(bytes[pos + 4..pos + 8].try_into().unwrap()) as usize;
    let meta_at = pos + 8;
    let msg = root_as_message(&bytes[meta_at..meta_at + meta_len]).unwrap();
    assert_eq!(msg.header_type(), MessageHeader::RecordBatch);
    let needle = msg.bodyLength().to_le_bytes();
    let at = bytes[meta_at..meta_at + meta_len]
        .windows(8)
        .position(|w| w == needle)
        .unwrap()
        + meta_at;
    bytes[at..at + 8].copy_from_slice(&(1i64 << 40).to_le_bytes());
    fs::write(&out, &bytes).unwrap();
    refused_as_container(&out);
}