css: CSS bus-auth becomes a uniform registry Unlocker

Convert the CSS read-unlock into a first-class registry Unlocker (CssUnlocker)
dispatched through route_unlock like every other barrier removal, instead of
a direct call in scan. libfreemkv appends the built-in CSS unlocker (and, next,
the AACS cert handshake) exactly once via ensure_builtins(), AFTER any
client-registered firmware unlocker — so the registry order is firmware → cert
→ css, owned by the lib, not the client.

Defense in depth: the unlocker does NOT trust the caller-declared DiscKind.
matches() filters on the declared kind (Css), but unlock() self-verifies
against the drive's GET CONFIGURATION profile and refuses (UnlockError::
NotApplicable, a new shared "this unlocker doesn't apply" variant) WITHOUT
issuing a single CSS CDB if the drive reports a non-DVD profile — so a
mis-routed Blu-ray is never sent CSS bus-auth. Guard the firmware unlocker the
same structural way (it matches only the drive-prep phase, kind == Unknown).

Tests: CssUnlocker matches only DiscKind::Css; a BD-profile drive yields
NotApplicable with zero CSS CDBs issued.
This commit is contained in:
Matthew Jackson
2026-06-29 16:49:52 -07:00
parent cfcc524367
commit bac105a022
4 changed files with 194 additions and 10 deletions
+12 -8
View File
@@ -1471,14 +1471,18 @@ impl Disc {
tracing::info!(target: "freemkv::scan", extents = main_extents.len(), "phase: CSS — main feature located");
if let Some(unlock_lba) = main_extents.first().map(|e| e.start_lba) {
tracing::info!(target: "freemkv::scan", unlock_lba, "phase: CSS — bus-auth unlock");
// Unlock the drive's CSS read gating. A CSS-enforcing drive (the
// BU40N) refuses to return scrambled sectors until a CSS bus-auth
// handshake has run for the title; we run it here purely for that
// unlock and IGNORE the key it derives (the disc-key crack is
// unreliable). The real descramble key is recovered from the
// scrambled movie data itself via the known-plaintext attack — no
// player keys, no disc-key crack, no REPORT-KEY-derived title key.
if let Err(e) = crate::css::auth::unlock_css_reads(session.scsi_mut(), unlock_lba) {
// Unlock the drive's CSS read gating through the uniform
// unlocker registry: the in-tree CssUnlocker matches
// DiscKind::Css and runs the bus-auth handshake. A CSS-enforcing
// drive (the BU40N) refuses to return scrambled sectors until
// that handshake has run; we run it purely for that unlock and
// IGNORE any key (the descramble key is recovered keylessly from
// the scrambled movie data via the known-plaintext attack — no
// player keys, no disc-key crack, no REPORT-KEY title key).
let drive_id = session.drive_id.clone();
let css_ctx =
crate::unlock::UnlockCtx::new(&drive_id, crate::unlock::DiscKind::Css);
if let Err(e) = crate::unlock::route_unlock(session.scsi_mut(), &css_ctx) {
tracing::warn!(
target: "freemkv::scan",
error_code = e.code(),