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

nordisk/holger

fuzz/src/lib.rs

//! **Holger's fuzz entries** (lane OSSF-NEXT, 2026-10-30): one function per untrusted
//! parser, the same function a libFuzzer target (`fuzz_targets/<name>.rs`), the seed test
//! (`tests/seeds.rs`) and a crash's regression test call.
//!
//! | target | what reads attacker bytes |
//! |---|---|
//! | `bundle_verify` | a `.holger.znippy` update bundle: the znippy archive index and members (`load_bundle`), its `core.json`, and the signed verify path under a pinned-signer policy (`load_bundle_verified`, `verify_member_map_with_time`) |
//! | `dsse_material` | provenance material: DSSE envelopes, Sigstore bundles, PEP 740 attestations, cosign attachments, npm signatures (`material::parse`), then `verify_artifact` against Sigstore's pinned public-good root |
//! | `intoto_statement` | an in-toto statement and its SLSA predicate (`statement::Statement::parse`) |
//! | `x509_cert` | a certificate as a Sigstore bundle carries it: DER, the identity and SCT extensions (`der`, `cert`) |
//! | `apk_index` | an `APKINDEX.tar.gz` and its signature segment (`apk::verify_index`) against Wolfi's key |
//! | `package_metadata` | the first byte picks the parser of a package's metadata: the `.deb` control stanza, the `.rpm` header, a Helm chart and its `Chart.yaml`, a wheel's `METADATA`, the licence a package declares, and the inspector over every format |
//!
//! A target only asks that the parser RETURNS: an `Err` is the right answer to most
//! inputs; a panic, a hang or an allocation past libFuzzer's limit is a finding.

pub mod seeds;

use std::path::PathBuf;
use std::sync::OnceLock;

/// Every target, in the order of its `[[bin]]` in `Cargo.toml`.
pub const TARGETS: &[&str] = &[
    "bundle_verify",
    "dsse_material",
    "intoto_statement",
    "x509_cert",
    "apk_index",
    "package_metadata",
];

/// Run `data` through `target`'s entry.
pub fn run(target: &str, data: &[u8]) {
    match target {
        "bundle_verify" => bundle_verify(data),
        "dsse_material" => dsse_material(data),
        "intoto_statement" => intoto_statement(data),
        "x509_cert" => x509_cert(data),
        "apk_index" => apk_index(data),
        "package_metadata" => package_metadata(data),
        other => panic!("no fuzz target {other}"),
    }
}

/// The bundle reader opens a path: each process writes its input to its own file.
fn scratch_file() -> &'static PathBuf {
    static F: OnceLock<PathBuf> = OnceLock::new();
    F.get_or_init(|| {
        let dir = std::env::temp_dir().join(format!("holger-fuzz-{}", std::process::id()));
        std::fs::create_dir_all(&dir).expect("a scratch dir");
        dir.join("input.holger.znippy")
    })
}

/// A pinned-signer policy no input can satisfy: the verify path runs to its refusal.
fn bundle_policy() -> &'static holger_update_bundle::TrustPolicy {
    use holger_update_bundle::policy::{ApprovedSigner, ExpiredPosture};
    static P: OnceLock<holger_update_bundle::TrustPolicy> = OnceLock::new();
    P.get_or_init(|| holger_update_bundle::TrustPolicy {
        schema_version: holger_update_bundle::schema::POLICY_SCHEMA.into(),
        trust_mode: Some(holger_update_bundle::TrustMode::PinnedSigners),
        trusted_roots: vec![],
        certificate_validity: holger_update_bundle::CertificateValidity::Enforce,
        approved_signers: vec![ApprovedSigner {
            fingerprint: "00".repeat(32),
            subject: None,
            roles: vec!["security-reviewer".into()],
        }],
        required_approval_roles: vec!["security-reviewer".into()],
        minimum_distinct_approvers: 1,
        trusted_release_signers: vec!["11".repeat(32)],
        release_signer_must_be_distinct: true,
        maximum_bundle_age_secs: None,
        require_expiry: true,
        on_expired_metadata: Some(ExpiredPosture::ReadOnly),
        require_sbom: true,
        require_vex: false,
        require_scan: false,
        allowed_products: vec![],
        allowed_models: vec![],
    })
}

pub fn bundle_verify(data: &[u8]) {
    let path = scratch_file();
    if std::fs::write(path, data).is_err() {
        return;
    }
    if let Ok(members) = holger_update_bundle::load_bundle(path) {
        if let Some(core) = members.get("core.json") {
            let _ = holger_update_bundle::BundleCore::from_json_bytes(core);
        }
        let _ = holger_update_bundle::verify::verify_member_map_with_time(
            &members,
            bundle_policy(),
            &holger_update_bundle::verify::TargetSpec::default(),
            None,
            &holger_update_bundle::HostRtc,
        );
    }
    let _ = holger_update_bundle::container::load_bundle_verified(path, bundle_policy());
}

const TRUSTED_ROOT: &[u8] =
    include_bytes!("../../provenance/tests/fixtures/sigstore/trusted_root.json");

fn trust() -> &'static holger_provenance::trust::TrustRoot {
    static T: OnceLock<holger_provenance::trust::TrustRoot> = OnceLock::new();
    T.get_or_init(|| {
        holger_provenance::trust::TrustRoot::from_trusted_root_json(TRUSTED_ROOT)
            .expect("the pinned root reads")
    })
}

pub fn dsse_material(data: &[u8]) {
    use holger_provenance::policy::{effective, ProvenanceFloor, ProvenanceGate};
    let _ = holger_provenance::material::parse("fuzz.json", data);
    let policy = effective(&ProvenanceFloor::default(), &ProvenanceGate::Prefer);
    let materials = vec![("fuzz.sigstore.json".to_string(), data.to_vec())];
    let _ = holger_provenance::verify::verify_artifact(
        b"the artifact",
        &materials,
        &policy,
        Some(trust()),
    );
}

pub fn intoto_statement(data: &[u8]) {
    if let Ok(s) = holger_provenance::statement::Statement::parse(data) {
        let _ = s.describe_subjects();
        let _ = s.is_provenance();
        let _ = s.slsa();
        let _ = s.names(&holger_provenance::statement::ArtifactDigests::of(
            b"the artifact",
        ));
    }
}

pub fn x509_cert(data: &[u8]) {
    use holger_provenance::{cert, der};
    if let Ok((tlv, _rest)) = der::read(data) {
        let _ = der::children(tlv.content);
    }
    let _ = der::cert_parts(data);
    let _ = cert::identity(data);
    let _ = cert::scts(data);
    let _ = cert::validity(data);
    let _ = cert::allows_code_signing(data);
    let _ = cert::public_key(data);
    let _ = cert::issued_by(data, data);
}

const WOLFI_KEY: &[u8] =
    include_bytes!("../../provenance/tests/fixtures/wolfi/wolfi-signing.rsa.pub");

pub fn apk_index(data: &[u8]) {
    use holger_provenance::apk;
    static KEYS: OnceLock<Vec<apk::ApkKey>> = OnceLock::new();
    let keys =
        KEYS.get_or_init(|| {
            vec![apk::ApkKey::from_pem("wolfi-signing.rsa.pub", WOLFI_KEY)
                .expect("the wolfi key reads")]
        });
    let _ = apk::is_index("APKINDEX.tar.gz", data);
    let _ = apk::verify_index(data, keys);
}

/// The selector byte of `package_metadata`, in order.
pub const PACKAGE_PARSERS: &[&str] = &[
    "deb-control",
    "rpm-header",
    "helm-chart",
    "chart-yaml",
    "wheel-metadata",
    "metadata-text",
    "license",
    "inspect",
];

pub fn package_metadata(data: &[u8]) {
    let Some((&sel, rest)) = data.split_first() else {
        return;
    };
    match sel as usize % PACKAGE_PARSERS.len() {
        0 => {
            let _ = holger_znippy_deb_repository::control_from_deb(rest);
        }
        1 => {
            let _ = holger_znippy_rpm_repository::parse_rpm_header(
                rest, "hello", "2.10", "1", "x86_64",
            );
        }
        2 => {
            let _ = holger_znippy_helm_repository::chart_meta_from_tgz_capped(rest, 1 << 20);
        }
        3 => {
            let _ = holger_znippy_helm_repository::parse_chart_yaml(&String::from_utf8_lossy(rest));
        }
        4 => {
            let _ = holger_server_lib::scanner::wheel::read_metadata(rest);
        }
        5 => {
            let _ =
                holger_server_lib::scanner::wheel::parse_metadata(&String::from_utf8_lossy(rest));
        }
        6 => {
            let Some((&eco, body)) = rest.split_first() else {
                return;
            };
            let eco = [
                "pip", "npm", "cargo", "maven", "gem", "nuget", "deb", "rpm", "helm", "go",
            ][eco as usize % 10];
            let _ = holger_server_lib::scanner::license::detect(eco, body);
        }
        _ => {
            let Some((&f, body)) = rest.split_first() else {
                return;
            };
            let all = holger_inspect::Format::ALL;
            let format = all[f as usize % all.len()];
            static INSPECTOR: OnceLock<holger_inspect::Inspector> = OnceLock::new();
            let insp = INSPECTOR.get_or_init(|| {
                holger_inspect::Inspector::from_config(&holger_inspect::InspectConfig::default())
            });
            let _ = insp.inspect(body, format, "fuzz-1.0");
        }
    }
}