fuzz/fuzz_targets/container_header.rs

what this file is for · fuzz · 52 lines · the same file on GitHub

The file as it is in the tree, in the colours you chose. A line number is a link, and so is every box in this file’s diagram: it opens here with the function it names marked.


// SPDX-License-Identifier: GPL-3.0-or-later
//! The `.veil` container header, coverage-guided.
//!
//! This is the parser that reads a file somebody sent you. The properties are
//! the same three the deterministic campaign in
//! `crates/veilvoice-crypto/tests/parser_fuzz.rs` asserts -- never panic, never
//! hang, never claim success for something it did not fully understand -- but
//! explored by feedback rather than by construction.

#![no_main]

use libfuzzer_sys::fuzz_target;
use veilvoice_crypto::container;

fuzz_target!(|data: &[u8]| {
    let Ok((header, body)) = container::Header::parse(data) else {
        // A refusal is a correct outcome for almost every input. What must
        // never happen is a panic, and reaching this line at all proves there
        // was not one.
        return;
    };

    // An `Ok` is a claim about the buffer, so check the claim.
    assert!(
        body <= data.len(),
        "parse reported a ciphertext offset of {body} in a {} byte buffer",
        data.len()
    );

    // Re-serialising must reproduce exactly the bytes it came from. This is the
    // property that catches a field silently normalised on the way through --
    // which would mean the AEAD's associated data is not the header actually
    // stored, and the whole downgrade defence rests on those being the same
    // bytes.
    assert_eq!(
        header.to_bytes(),
        &data[..body],
        "header did not round-trip to the bytes it was parsed from"
    );

    // Opening with a wrong password must fail rather than panic, whatever the
    // header declares. This is the path that reaches Argon2 with
    // attacker-controlled cost parameters -- where F-2 and F-3 lived.
    let _ = container::open_with_password(b"not the password", data);

    // And the unattended ceiling must refuse rather than panic, too.
    let _ = container::open_with_password_within(
        b"not the password",
        data,
        veilvoice_crypto::kdf::KdfParams::UNATTENDED_MAX_M_COST,
    );
});