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