fn arb_tensor_dims() -> impl Strategy<Value = (u32, Vec<u64>)> {
(0u32..=10).prop_flat_map(|n_dims| {
let dims = prop::collection::vec(
prop_oneof![
3 => 1u64..100, 1 => Just(0u64), 1 => Just(1u64), 1 => 100u64..10000, 1 => Just(1u64 << 30), ],
n_dims as usize,
);
dims.prop_map(move |d| (n_dims, d))
})
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(500))]
#[test]
fn fuzz_tensor_dimensions(
(n_dims, dims) in arb_tensor_dims(),
tensor_type in 0u32..20
) {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
let name = "fuzz_tensor";
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&n_dims.to_le_bytes());
for dim in &dims {
data.extend_from_slice(&dim.to_le_bytes());
}
data.extend_from_slice(&tensor_type.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
while data.len() % 32 != 0 {
data.push(0);
}
data.extend_from_slice(&[0u8; 64]);
let result = GGUFModel::from_bytes(&data);
let _ = result;
}
}
const GGUF_TYPES: [u32; 13] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ];
proptest! {
#![proptest_config(ProptestConfig::with_cases(200))]
#[test]
fn fuzz_metadata_types(
meta_type in prop::sample::select(&GGUF_TYPES),
value_len in 0usize..100
) {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes()); data.extend_from_slice(&1u64.to_le_bytes());
let key = "fuzz_key";
data.extend_from_slice(&(key.len() as u64).to_le_bytes());
data.extend_from_slice(key.as_bytes());
data.extend_from_slice(&meta_type.to_le_bytes());
match meta_type {
8 => { data.extend_from_slice(&(value_len as u64).to_le_bytes());
data.extend(std::iter::repeat_n(b'x', value_len));
}
9 => { data.extend_from_slice(&4u32.to_le_bytes()); data.extend_from_slice(&(value_len as u64).to_le_bytes()); for i in 0..value_len {
data.extend_from_slice(&(i as u32).to_le_bytes());
}
}
_ => {
data.extend_from_slice(&[0u8; 8]);
}
}
let result = GGUFModel::from_bytes(&data);
let _ = result;
}
#[test]
fn fuzz_invalid_metadata_type(
invalid_type in 13u32..256
) {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes());
let key = "invalid_type_key";
data.extend_from_slice(&(key.len() as u64).to_le_bytes());
data.extend_from_slice(key.as_bytes());
data.extend_from_slice(&invalid_type.to_le_bytes());
data.extend_from_slice(&[0u8; 8]);
let result = GGUFModel::from_bytes(&data);
prop_assert!(result.is_err());
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(300))]
#[test]
fn fuzz_tensor_names(
name_len in 0usize..1000,
name_byte in any::<u8>()
) {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data.extend_from_slice(&(name_len as u64).to_le_bytes());
data.extend(std::iter::repeat_n(name_byte, name_len));
data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&4u64.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
while data.len() % 32 != 0 {
data.push(0);
}
data.extend_from_slice(&[0u8; 16]);
let result = GGUFModel::from_bytes(&data);
let _ = result;
}
#[test]
fn fuzz_llama_tensor_patterns(
layer_num in 0u32..100,
component in prop::sample::select(&[
"attn_q", "attn_k", "attn_v", "attn_output",
"ffn_gate", "ffn_up", "ffn_down",
"attn_norm", "ffn_norm"
])
) {
let name = format!("blk.{}.{}.weight", layer_num, component);
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&2u32.to_le_bytes()); data.extend_from_slice(&64u64.to_le_bytes());
data.extend_from_slice(&64u64.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
while data.len() % 32 != 0 {
data.push(0);
}
data.extend_from_slice(&[0u8; 64]);
let result = GGUFModel::from_bytes(&data);
let _ = result;
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(300))]
#[test]
fn fuzz_tensor_offsets(
offset in prop_oneof![
Just(0u64),
Just(1u64),
Just(31u64), Just(32u64), Just(33u64), 0u64..1000, Just(u64::MAX), Just(u64::MAX - 1), ]
) {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
let name = "offset_test";
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&1u32.to_le_bytes());
data.extend_from_slice(&4u64.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes());
data.extend_from_slice(&offset.to_le_bytes());
while data.len() % 32 != 0 {
data.push(0);
}
if offset < 1000 {
while data.len() < (offset as usize) + 16 {
data.push(0);
}
}
let result = GGUFModel::from_bytes(&data);
let _ = result;
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn fuzz_multi_tensor(
num_tensors in 1usize..20,
dims in 1u64..100
) {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&(num_tensors as u64).to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
let mut offset = 0u64;
for i in 0..num_tensors {
let name = format!("tensor_{}", i);
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&1u32.to_le_bytes());
data.extend_from_slice(&dims.to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&offset.to_le_bytes());
offset += dims * 4;
}
while data.len() % 32 != 0 {
data.push(0);
}
for _ in 0..(offset / 4) {
data.extend_from_slice(&1.0f32.to_le_bytes());
}
let result = GGUFModel::from_bytes(&data);
if let Ok(model) = result {
prop_assert_eq!(model.tensors.len(), num_tensors);
}
}
}
#[test]
fn test_bounds_check_excessive_tensor_count() {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&100_001u64.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
let result = GGUFModel::from_bytes(&data);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("exceeds maximum") || err.contains("tensor_count"),
"Expected bounds check error, got: {}",
err
);
}
#[test]
fn test_bounds_check_excessive_metadata_count() {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&0u64.to_le_bytes());
data.extend_from_slice(&10_001u64.to_le_bytes());
let result = GGUFModel::from_bytes(&data);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("exceeds maximum") || err.contains("metadata_count"),
"Expected bounds check error, got: {}",
err
);
}
#[test]
fn test_bounds_check_excessive_n_dims() {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
let name = "bad_tensor";
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&100u32.to_le_bytes());
let result = GGUFModel::from_bytes(&data);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("dimensions") || err.contains("max allowed"),
"Expected n_dims bounds check error, got: {}",
err
);
}
#[test]
fn test_bounds_check_valid_counts() {
let mut data = Vec::new();
data.extend_from_slice(&GGUF_MAGIC.to_le_bytes());
data.extend_from_slice(&GGUF_VERSION_V3.to_le_bytes());
data.extend_from_slice(&1u64.to_le_bytes()); data.extend_from_slice(&1u64.to_le_bytes());
let key = "test.key";
data.extend_from_slice(&(key.len() as u64).to_le_bytes());
data.extend_from_slice(key.as_bytes());
data.extend_from_slice(&4u32.to_le_bytes()); data.extend_from_slice(&42u32.to_le_bytes());
let name = "test.weight";
data.extend_from_slice(&(name.len() as u64).to_le_bytes());
data.extend_from_slice(name.as_bytes());
data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&4u64.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u64.to_le_bytes());
while data.len() % 32 != 0 {
data.push(0);
}
data.extend_from_slice(&[0u8; 16]);
let result = GGUFModel::from_bytes(&data);
assert!(result.is_ok(), "Valid counts should succeed: {:?}", result);
}