0.31.0: hardening and correctness pass across mux, codec, AACS/CSS, UDF/MPLS/CLPI, recovery, drive/SCSI, labels, and I/O

Library-wide review-and-fix pass: tightened AACS keydb/handshake/variant
handling and trailing-partial-unit policy, corrected MPLS mark offset and
added UDF allocation bounds, hardened the mux/codec framing and M2TS paths,
guarded SCSI READ CAPACITY short transfers and unified error mapping, added
overflow guards on untrusted disc input, and made prefetch shutdown
deterministic. Release profile now builds with thin LTO + single codegen unit.
This commit is contained in:
Matthew Jackson
2026-06-07 17:37:38 -07:00
parent 5b6ea8f5c4
commit 061f68594a
128 changed files with 11838 additions and 3831 deletions
+28 -12
View File
@@ -1,13 +1,21 @@
//! CSS (Content Scramble System) — DVD disc encryption.
//!
//! CSS uses a weak 40-bit LFSR stream cipher (broken since 1999).
//! No keys needed — the title key is cracked from encrypted content
//! using a known-plaintext attack on MPEG-2 PES headers.
//!
//! The production entry point is [`resolve`]. Two title-key acquisition
//! paths exist behind it:
//! - The SCSI auth path drives bus authentication with the compiled-in CSS
//! player keys and reads the title key from the drive (the production DVD
//! path on a live drive).
//! - The crack fallback ([`crack_key`]) needs no keys — it attempts the
//! Stevenson known-plaintext attack on MPEG-2 PES headers. (Currently
//! non-functional; see the `crack` module docs.)
//!
//! Usage:
//! ```rust,ignore
//! let key = css::crack_key(reader, &extents)?;
//! css::descramble_sector(&key, &mut sector);
//! if let Some(state) = css::resolve(&mut ctx) {
//! css::descramble_sector(&state, &mut sector);
//! }
//! ```
pub mod auth;
@@ -22,7 +30,7 @@ use crate::sector::SectorSource;
/// CSS decryption state for a DVD title.
#[derive(Debug, Clone)]
pub struct CssState {
/// Cracked 5-byte title key
/// 5-byte CSS title key (from SCSI auth or the crack fallback).
pub title_key: [u8; 5],
}
@@ -37,7 +45,7 @@ pub struct CssState {
/// headers; works on disc images and on drives whose CSS auth path
/// is unavailable).
///
/// `live_drive` always wins when both modes are populated.
/// The `drive` (auth) path always wins when both modes are populated.
pub struct CssContext<'a> {
/// Live SCSI drive — when present, [`resolve`] tries the auth path.
pub drive: Option<&'a mut Drive>,
@@ -70,23 +78,32 @@ pub fn resolve(ctx: &mut CssContext<'_>) -> Option<CssState> {
None
}
/// Crack the CSS title key by reading encrypted sectors and applying
/// a known-plaintext attack on MPEG-2 headers.
///
/// Crack the CSS title key by scanning scrambled sectors across extents.
/// Crack the CSS title key by scanning scrambled sectors across extents and
/// applying a known-plaintext attack on MPEG-2 PES headers.
///
/// The Stevenson attack needs a sector where a PES header starts at byte
/// 0x80 (start of the encrypted region). This only happens when a new PES
/// packet begins at exactly sector offset 128. We scan up to 50000
/// scrambled sectors sequentially across all extents.
///
/// NOTE: the underlying recovery ([`crack::recover_title_key`]) is currently
/// non-functional against this crate's descrambler (see `crack` module
/// docs), so this scan returns `None`. The production DVD path uses the SCSI
/// auth path, not this crack fallback.
pub fn crack_key(reader: &mut dyn SectorSource, extents: &[Extent]) -> Option<CssState> {
let mut tried = 0u32;
let max_tries = 50_000;
// Reused across every scanned sector; read_sectors overwrites all 2048
// bytes on success, so no re-zeroing is needed between iterations.
let mut buf = vec![0u8; 2048];
for ext in extents {
let mut i = 0;
while i < ext.sector_count && tried < max_tries {
let mut buf = vec![0u8; 2048];
// Every scanned sector counts toward the cap, so a long run
// of unscrambled sectors can't read past the budget.
tried += 1;
if reader
.read_sectors(ext.start_lba + i, 1, &mut buf, true)
.is_ok()
@@ -95,7 +112,6 @@ pub fn crack_key(reader: &mut dyn SectorSource, extents: &[Extent]) -> Option<Cs
if let Some(key) = crack::crack_title_key(&buf) {
return Some(CssState { title_key: key });
}
tried += 1;
}
i += 1;
}