chromium/tools/crates/gnrt/lib/vet.rs

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use crate::config::BuildConfig;
use crate::group::Group;
use anyhow::Result;

fn group_vet_criteria(group: Group, shipped: Option<bool>) -> Vec<AuditCriteria> {
    use AuditCriteria::*;
    // No third-party crates from crates.io are allowed to implement crypto today.
    // We only accept crypto implementation from BoringSSL, which we bring in
    // through DEPS. If this should ever change we will need to add a
    // configuration option in gnrt_config.toml to allow a crate to
    // implement crypto.
    match (shipped, group) {
        // Safe crates can be used on adversarial inputs without a sandbox. They can not cause
        // security bugs in this case, and must satisfy the Rule of Two.
        //
        // "rule-of-two-safe-to-deploy" destructures into a combination of
        // * "safe-to-deploy", and
        // * "ub-risk-1".
        //
        // We currently consider ub-risk-2 as satisfying the Rule of Two, though there seems to be
        // some spot in between risk 1 and 2 that fits better and this could be improved.
        (Some(true), Group::Safe) | (None, Group::Safe) => {
            vec![CryptoSafe, SafeToDeploy, UbRisk2]
        }
        // Sandbox crates are used in a sandbox, so we have a weaker tolerance. There may be a bunch
        // of ASM code in there for example. Adversarial inputs may have a way to break things,
        // though we certainly try to avoid it.
        //
        // This type of crate is not well described in the UB risk guidelines for now, so we use
        // "ub-risk-3" for this category.
        (Some(true), Group::Sandbox) | (None, Group::Sandbox) => {
            vec![CryptoSafe, SafeToDeploy, UbRisk3]
        }
        // Code in tests is not run on user machines and does not interact with adversarial inputs.
        // Thus it does not need to be safe-to-deploy, but it needs to not be malicious against
        // developers and CI bots which is covered by "safe-to-run".
        (_, Group::Test) => vec![CryptoSafe, SafeToRun],
        // Crates that contribute to the shipped binary but are not themselves shipped (code
        // generators for example) do not get deployed themselves and do not interact with
        // adversarial inputs. Thus they need to be "safe-to-run" by developers and CI only.
        (Some(false), _) => vec![CryptoSafe, SafeToRun],
    }
}

#[derive(serde::Serialize)]
pub struct VetConfigToml {
    policies: Vec<Policy>,
}

#[derive(serde::Serialize)]
pub struct Policy {
    crate_name: String,
    criteria: Vec<AuditCriteria>,
}

/// Audit criteria used by Chromium for `cargo vet` audits.  This enum
/// represents and replicates the criteria that can be found in
/// https://github.com/google/rust-crate-audits/blob/main/audits.toml (e.g. `UbRisk2` corresponds
/// to the `[criteria.ub-risk-2]` entry in that `audits.toml` file.
/// Corresponding auditing standards are described in
/// https://github.com/google/rust-crate-audits/blob/main/auditing_standards.md
#[derive(serde::Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum AuditCriteria {
    CryptoSafe,
    SafeToDeploy,
    SafeToRun,
    #[serde(rename = "ub-risk-2")]
    UbRisk2,
    #[serde(rename = "ub-risk-3")]
    UbRisk3,
}

/// Generate the config.toml for `cargo vet` with policies that match the groups
/// specified for each crate through gnrt_config.toml.
pub fn create_vet_config<'a>(
    packages: impl IntoIterator<Item = &'a cargo_metadata::Package>,
    config: &BuildConfig,
    mut find_group: impl FnMut(&'a cargo_metadata::PackageId) -> Group,
    mut find_shipped: impl FnMut(&'a cargo_metadata::PackageId) -> Option<bool>,
) -> Result<VetConfigToml> {
    let mut vet_config_toml = VetConfigToml { policies: Vec::new() };
    for package in packages {
        let group = find_group(&package.id);
        let shipped = find_shipped(&package.id);

        let mut crate_name = package.name.clone();
        crate_name.push(':');
        crate_name.push_str(&package.version.to_string());

        let criteria = if config.resolve.remove_crates.contains(&package.name) {
            vec![]
        } else {
            group_vet_criteria(group, shipped)
        };

        vet_config_toml.policies.push(Policy { crate_name, criteria });
    }

    vet_config_toml.policies.sort_unstable_by(|a, b| a.crate_name.cmp(&b.crate_name));
    Ok(vet_config_toml)
}