lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

safety_qualification.rs (10873B)


      1 use std::collections::BTreeSet;
      2 use std::fs;
      3 use std::path::Path;
      4 use std::process::Command;
      5 
      6 use serde::Deserialize;
      7 
      8 const CONTRACT_PATH: &str = "contracts/releases/safety_matrix.toml";
      9 
     10 #[derive(Debug, Deserialize)]
     11 struct Contract {
     12     schema_version: u16,
     13     toolchain: String,
     14     miri_flags: Vec<String>,
     15     miri: Vec<MiriLane>,
     16     sanitizer: Vec<SanitizerLane>,
     17     exception: Vec<Exception>,
     18 }
     19 
     20 #[derive(Debug, Deserialize)]
     21 struct MiriLane {
     22     package: String,
     23     filter: String,
     24     authority: String,
     25 }
     26 
     27 #[derive(Debug, Deserialize)]
     28 struct SanitizerLane {
     29     kind: String,
     30     targets: Vec<String>,
     31     packages: Vec<String>,
     32     authority: String,
     33 }
     34 
     35 #[derive(Debug, Deserialize)]
     36 struct Exception {
     37     lane: String,
     38     targets: Vec<String>,
     39     owner: String,
     40     expires: String,
     41     reason: String,
     42 }
     43 
     44 #[derive(Debug, Deserialize)]
     45 struct Metadata {
     46     packages: Vec<MetadataPackage>,
     47 }
     48 
     49 #[derive(Debug, Deserialize)]
     50 struct MetadataPackage {
     51     name: String,
     52 }
     53 
     54 pub fn run(root: &Path) -> Result<(), String> {
     55     let contract = load(root)?;
     56     let packages = workspace_packages(root)?;
     57     validate(&contract, &packages)?;
     58     qualify_miri(root, &contract)?;
     59     qualify_sanitizers(root, &contract)?;
     60     Ok(())
     61 }
     62 
     63 fn load(root: &Path) -> Result<Contract, String> {
     64     let path = root.join(CONTRACT_PATH);
     65     let raw = fs::read_to_string(&path)
     66         .map_err(|error| format!("failed to read {}: {error}", path.display()))?;
     67     toml::from_str(&raw).map_err(|error| format!("failed to parse {}: {error}", path.display()))
     68 }
     69 
     70 fn workspace_packages(root: &Path) -> Result<BTreeSet<String>, String> {
     71     let output = Command::new("cargo")
     72         .args(["metadata", "--format-version", "1", "--no-deps", "--locked"])
     73         .current_dir(root)
     74         .output()
     75         .map_err(|error| format!("failed to start cargo metadata: {error}"))?;
     76     if !output.status.success() {
     77         return Err("cargo metadata failed while validating the safety matrix".to_owned());
     78     }
     79     let metadata: Metadata = serde_json::from_slice(&output.stdout)
     80         .map_err(|error| format!("failed to decode cargo metadata: {error}"))?;
     81     Ok(metadata
     82         .packages
     83         .into_iter()
     84         .map(|package| package.name)
     85         .collect())
     86 }
     87 
     88 fn validate(contract: &Contract, packages: &BTreeSet<String>) -> Result<(), String> {
     89     if contract.schema_version != 1
     90         || !contract.toolchain.starts_with("nightly-")
     91         || contract.miri_flags != ["-Zmiri-strict-provenance", "-Zmiri-disable-isolation"]
     92         || contract.miri.len() != 8
     93         || contract.sanitizer.len() != 1
     94     {
     95         return Err("invalid safety qualification contract".to_owned());
     96     }
     97 
     98     let miri = contract
     99         .miri
    100         .iter()
    101         .map(|lane| (&lane.package, &lane.filter))
    102         .collect::<BTreeSet<_>>();
    103     if miri.len() != contract.miri.len() {
    104         return Err("Miri package/filter pairs must be unique".to_owned());
    105     }
    106     for lane in &contract.miri {
    107         validate_identifier("Miri package", &lane.package)?;
    108         validate_test_filter(&lane.filter)?;
    109         validate_authority(&lane.authority)?;
    110         if !packages.contains(&lane.package) {
    111             return Err(format!(
    112                 "Miri package {} is not in the workspace",
    113                 lane.package
    114             ));
    115         }
    116     }
    117 
    118     for lane in &contract.sanitizer {
    119         if lane.kind != "address" || lane.targets.len() != 4 || lane.packages.len() != 3 {
    120             return Err("native sanitizer authority must cover address checks on four hosts and three boundaries".to_owned());
    121         }
    122         validate_authority(&lane.authority)?;
    123         let targets = lane.targets.iter().collect::<BTreeSet<_>>();
    124         let lane_packages = lane.packages.iter().collect::<BTreeSet<_>>();
    125         if targets.len() != lane.targets.len() || lane_packages.len() != lane.packages.len() {
    126             return Err("sanitizer targets and packages must be unique".to_owned());
    127         }
    128         for package in &lane.packages {
    129             validate_identifier("sanitizer package", package)?;
    130             if !packages.contains(package) {
    131                 return Err(format!(
    132                     "sanitizer package {package} is not in the workspace"
    133                 ));
    134             }
    135         }
    136     }
    137 
    138     if contract.exception.len() != 1 {
    139         return Err(
    140             "the unsupported sanitizer target authority must contain one bounded exception"
    141                 .to_owned(),
    142         );
    143     }
    144     let exception = &contract.exception[0];
    145     if exception.lane != "sanitizer"
    146         || exception.owner != "radroots-security"
    147         || exception.expires != "2026-10-01"
    148         || exception.targets.len() != 3
    149         || exception.reason.trim().is_empty()
    150     {
    151         return Err("invalid sanitizer target exception".to_owned());
    152     }
    153     Ok(())
    154 }
    155 
    156 fn validate_identifier(label: &str, value: &str) -> Result<(), String> {
    157     if value.is_empty()
    158         || !value
    159             .bytes()
    160             .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_')
    161     {
    162         return Err(format!("{label} must be a lowercase snake_case identifier"));
    163     }
    164     Ok(())
    165 }
    166 
    167 fn validate_test_filter(value: &str) -> Result<(), String> {
    168     let segments = value.split("::").collect::<Vec<_>>();
    169     if segments.len() < 3 {
    170         return Err("Miri filter must be a fully qualified test path".to_owned());
    171     }
    172     for segment in segments {
    173         validate_identifier("Miri test path segment", segment)?;
    174     }
    175     Ok(())
    176 }
    177 
    178 fn validate_authority(value: &str) -> Result<(), String> {
    179     if value.is_empty()
    180         || !value
    181             .bytes()
    182             .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-')
    183     {
    184         return Err("safety authority must be a lowercase kebab-case identifier".to_owned());
    185     }
    186     Ok(())
    187 }
    188 
    189 fn qualify_miri(root: &Path, contract: &Contract) -> Result<(), String> {
    190     let flags = contract.miri_flags.join(" ");
    191     for lane in &contract.miri {
    192         verify_test_exists(root, lane)?;
    193         let args = [
    194             format!("+{}", contract.toolchain),
    195             "miri".to_owned(),
    196             "test".to_owned(),
    197             "--locked".to_owned(),
    198             "-p".to_owned(),
    199             lane.package.clone(),
    200             "--lib".to_owned(),
    201             lane.filter.clone(),
    202             "--".to_owned(),
    203             "--exact".to_owned(),
    204         ];
    205         run_cargo(root, &args, &[("MIRIFLAGS", flags.as_str())], "Miri")?;
    206     }
    207     Ok(())
    208 }
    209 
    210 fn verify_test_exists(root: &Path, lane: &MiriLane) -> Result<(), String> {
    211     let args = [
    212         "test",
    213         "--locked",
    214         "-p",
    215         lane.package.as_str(),
    216         "--lib",
    217         lane.filter.as_str(),
    218         "--",
    219         "--exact",
    220         "--list",
    221     ];
    222     let output = Command::new("cargo")
    223         .args(args)
    224         .current_dir(root)
    225         .output()
    226         .map_err(|error| format!("failed to enumerate Miri test {}: {error}", lane.filter))?;
    227     if !output.status.success() {
    228         return Err(format!(
    229             "failed to enumerate Miri test {} in {}",
    230             lane.filter, lane.package
    231         ));
    232     }
    233     let stdout = String::from_utf8(output.stdout)
    234         .map_err(|error| format!("test enumeration emitted non-UTF-8 output: {error}"))?;
    235     let expected = format!("{}: test", lane.filter);
    236     if stdout.lines().any(|line| line == expected) {
    237         Ok(())
    238     } else {
    239         Err(format!(
    240             "Miri authority {} does not resolve to exactly one library test in {}",
    241             lane.filter, lane.package
    242         ))
    243     }
    244 }
    245 
    246 fn qualify_sanitizers(root: &Path, contract: &Contract) -> Result<(), String> {
    247     let host = rustc_host(root, &contract.toolchain)?;
    248     let mut matched = false;
    249     for lane in &contract.sanitizer {
    250         if !lane.targets.iter().any(|target| target == &host) {
    251             continue;
    252         }
    253         matched = true;
    254         let mut args = vec![
    255             format!("+{}", contract.toolchain),
    256             "test".to_owned(),
    257             "--locked".to_owned(),
    258             "--target".to_owned(),
    259             host.clone(),
    260         ];
    261         for package in &lane.packages {
    262             args.push("-p".to_owned());
    263             args.push(package.clone());
    264         }
    265         args.push("--lib".to_owned());
    266         let rustflags = format!("-Zsanitizer={}", lane.kind);
    267         run_cargo(
    268             root,
    269             &args,
    270             &[
    271                 ("RUSTFLAGS", rustflags.as_str()),
    272                 ("RUSTDOCFLAGS", rustflags.as_str()),
    273                 ("ASAN_OPTIONS", "detect_leaks=1:halt_on_error=1"),
    274             ],
    275             "sanitizer",
    276         )?;
    277     }
    278     if matched {
    279         return Ok(());
    280     }
    281     if contract
    282         .exception
    283         .iter()
    284         .any(|exception| exception.targets.iter().any(|target| target == &host))
    285     {
    286         eprintln!("sanitizer qualification excluded for governed target {host}");
    287         return Ok(());
    288     }
    289     Err(format!(
    290         "host {host} has neither a sanitizer lane nor a governed exception"
    291     ))
    292 }
    293 
    294 fn rustc_host(root: &Path, toolchain: &str) -> Result<String, String> {
    295     let output = Command::new("rustc")
    296         .args([format!("+{toolchain}"), "-vV".to_owned()])
    297         .current_dir(root)
    298         .output()
    299         .map_err(|error| format!("failed to start rustc: {error}"))?;
    300     if !output.status.success() {
    301         return Err(format!("rustc +{toolchain} -vV failed"));
    302     }
    303     let stdout = String::from_utf8(output.stdout)
    304         .map_err(|error| format!("rustc -vV emitted non-UTF-8 output: {error}"))?;
    305     stdout
    306         .lines()
    307         .find_map(|line| line.strip_prefix("host: ").map(str::to_owned))
    308         .ok_or_else(|| "rustc -vV did not report a host target".to_owned())
    309 }
    310 
    311 fn run_cargo(
    312     root: &Path,
    313     args: &[String],
    314     environment: &[(&str, &str)],
    315     label: &str,
    316 ) -> Result<(), String> {
    317     eprintln!("cargo {}", args.join(" "));
    318     let status = Command::new("cargo")
    319         .args(args)
    320         .envs(environment.iter().copied())
    321         .current_dir(root)
    322         .status()
    323         .map_err(|error| format!("failed to start {label} qualification: {error}"))?;
    324     if status.success() {
    325         Ok(())
    326     } else {
    327         Err(format!(
    328             "{label} qualification failed: cargo {}",
    329             args.join(" ")
    330         ))
    331     }
    332 }
    333 
    334 #[cfg(test)]
    335 mod tests {
    336     use super::{load, validate, workspace_packages};
    337 
    338     #[test]
    339     fn current_contract_covers_governed_safety_boundaries() {
    340         let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
    341             .parent()
    342             .and_then(std::path::Path::parent)
    343             .expect("workspace root");
    344         let packages = workspace_packages(root).expect("workspace packages");
    345         validate(&load(root).expect("contract"), &packages).expect("valid safety matrix");
    346     }
    347 }