rc2: macOS cross-compile fix + security/recovery hardening
- build.rs: pass target -arch to cc so macos_shim cross-compiles (x86_64-apple-darwin) - AACS/CSS: unit-aligned decrypting sweep; per-VTS CSS title keys (hard-fail on wrong VTS); reject truncated Unit_Key_RO; AACS 2.0 sig-verify skip; CSS bus-auth random nonce - recovery: gap-filling mapfile load; sweep/copy resume reconciliation; stale-mapfile abort; patch wedge/damage-window range reset - mux: TS continuity + PSI CC desync guards; HEVC numTemporalLayers clamp; MPEG-2 pending byte-cap; PS parse_pts marker-bit validation; HdrFormat strict parse; Unknown-variant metadata - net/keydb: network:// SSRF parity (IPv4-mapped, CGNAT, 0.0.0.0/8, Class-E); bounded keydb header read + size cap + error context - io: durable mapfile fsync; NFS writeback degrade; sync_file_range error capture; Windows SCSI u32 transfer guard
This commit is contained in:
+21
-5
@@ -195,8 +195,14 @@ fn bus_auth(drive: &mut Drive) -> Result<(u8, [u8; 5])> {
|
||||
.map_err(|_| Error::CssAuthFailed)?;
|
||||
let agid = (buf[7] >> 6) & 0x03;
|
||||
|
||||
// Host sends challenge
|
||||
let host_challenge: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
// Host sends challenge. The spec wants a fresh per-session random nonce,
|
||||
// not a fixed constant — a predictable challenge weakens the bus-auth
|
||||
// handshake.
|
||||
let mut host_challenge = [0u8; 10];
|
||||
{
|
||||
use rand::RngCore;
|
||||
rand::thread_rng().fill_bytes(&mut host_challenge);
|
||||
}
|
||||
let mut hc_buf = [0u8; 16];
|
||||
hc_buf[0] = 0x00;
|
||||
hc_buf[1] = 0x0E;
|
||||
@@ -587,10 +593,20 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// Walk up from this file (src/css/auth.rs) to the crate `src` root.
|
||||
let src_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
|
||||
// Scan this crate's `src` plus the sibling workspace crates so the
|
||||
// key-material logging guard covers every crate that can reach the
|
||||
// CSS/AACS internals, not just libfreemkv. Missing sibling dirs (e.g.
|
||||
// when building the crate standalone) are simply skipped.
|
||||
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let workspace = manifest.parent().unwrap_or(manifest);
|
||||
let mut violations = Vec::new();
|
||||
scan_dir(&src_root, FORBIDDEN, &mut violations);
|
||||
scan_dir(&manifest.join("src"), FORBIDDEN, &mut violations);
|
||||
for sibling in ["autorip", "freemkv", "freemkv-keysources"] {
|
||||
let dir = workspace.join(sibling).join("src");
|
||||
if dir.is_dir() {
|
||||
scan_dir(&dir, FORBIDDEN, &mut violations);
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
violations.is_empty(),
|
||||
"key material logged in instrumentation:\n{}",
|
||||
|
||||
+20
-1
@@ -27,6 +27,14 @@ use crate::sector::SectorSource;
|
||||
pub struct CssState {
|
||||
/// 5-byte CSS title key (from SCSI auth or the crack fallback).
|
||||
pub title_key: [u8; 5],
|
||||
/// LBA half-open span `[start, end)` of the extent set this key was
|
||||
/// cracked from. CSS title keys are per-VTS: a key cracked from one
|
||||
/// VTS does NOT descramble a title living in a different VTS. The mux
|
||||
/// path checks whether the title being opened overlaps this span; if
|
||||
/// not, it re-cracks from that title's own extents. `None` for keys
|
||||
/// of unknown provenance (e.g. test fixtures) — treated as "applies
|
||||
/// everywhere" for backward compatibility.
|
||||
pub crack_span: Option<(u32, u32)>,
|
||||
}
|
||||
|
||||
/// Recover the CSS title key with no keys, by scanning scrambled sectors and
|
||||
@@ -70,6 +78,14 @@ pub fn crack_key_halt(
|
||||
// scans nothing. Callers pass `detect_max_batch_sectors(device_path)` for a
|
||||
// live drive, a file-safe value for an image, or 1 to force per-sector.
|
||||
let batch = (batch_sectors.max(1)) as u32;
|
||||
// Record the LBA span the key is being cracked from so the per-title mux
|
||||
// path can tell whether a later title lives in the same VTS (overlaps the
|
||||
// span → key applies) or a different one (→ re-crack). Half-open [min,max).
|
||||
let crack_span = extents
|
||||
.iter()
|
||||
.filter(|e| e.sector_count > 0)
|
||||
.map(|e| (e.start_lba, e.start_lba.saturating_add(e.sector_count)))
|
||||
.reduce(|(amin, amax), (bmin, bmax)| (amin.min(bmin), amax.max(bmax)));
|
||||
let mut tried = 0u32;
|
||||
let max_tries = 50_000u32;
|
||||
let mut buf = vec![0u8; batch as usize * 2048];
|
||||
@@ -107,7 +123,10 @@ pub fn crack_key_halt(
|
||||
let sect = &buf[s * 2048..(s + 1) * 2048];
|
||||
if is_scrambled(sect) {
|
||||
if let Some(key) = stevenson::crack_title_key(sect) {
|
||||
return Some(CssState { title_key: key });
|
||||
return Some(CssState {
|
||||
title_key: key,
|
||||
crack_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
if tried >= max_tries {
|
||||
|
||||
Reference in New Issue
Block a user