From 98000869b2ee9531739f4f0aabd39476f64da723 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:00:34 -0700 Subject: [PATCH] css: redact CssState Debug (test-guarded) CssState is reachable via the public Disc.css field; #[derive(Debug)] leaked the raw CSS title key on any {:?} of a Disc. Manual Debug prints crack_span only. --- src/css/mod.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/css/mod.rs b/src/css/mod.rs index 618fd03..44ab12d 100644 --- a/src/css/mod.rs +++ b/src/css/mod.rs @@ -29,7 +29,7 @@ use crate::sector::SectorSource; const CSS_LOCKED_BAIL: u32 = 64; /// CSS decryption state for a DVD title. -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct CssState { /// 5-byte CSS title key (from SCSI auth or the crack fallback). pub title_key: [u8; 5], @@ -43,6 +43,18 @@ pub struct CssState { pub crack_span: Option<(u32, u32)>, } +// Redacting `Debug`: `CssState` is reachable via the public `Disc.css` field, so +// a `{:?}` on a `Disc` would otherwise print the raw CSS title key. Print only +// the (non-secret) crack span. Guarded by `css_state_debug_is_redacted`. +impl std::fmt::Debug for CssState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CssState") + .field("title_key", &"") + .field("crack_span", &self.crack_span) + .finish() + } +} + /// Recover the CSS title key with no keys, by scanning scrambled sectors and /// running the Stevenson known-plaintext attack (see the [`stevenson`] module). /// @@ -364,6 +376,23 @@ mod tests { use super::*; use crate::error::{Error, Result}; + /// `CssState` is reachable via the public `Disc.css` field, so a `{:?}` on a + /// `Disc` must not print the raw CSS title key. Sentinel byte 213 (0xD5); + /// `crack_span` is non-secret and none of its values are 213. + #[test] + fn css_state_debug_is_redacted() { + let s = CssState { + title_key: [0xD5; 5], + crack_span: Some((10, 20)), + }; + let dbg = format!("{s:?}"); + assert!( + !dbg.contains("213"), + "CssState Debug leaked the title key: {dbg}" + ); + assert!(dbg.contains("redacted"), "CssState Debug missing marker: {dbg}"); + } + // ── is_scrambled ─────────────────────────────────────────────────────── /// is_scrambled returns false for any buffer shorter than one sector,