unlock/patch: preserve SCSI sense across the bridge; init aborts on dead bus; slow-retry first patch failure
Audit fixes (v1.1.0..HEAD regressions in the unlock migration + adaptive patch
speed):
- unlock_bridge ScsiAdapter: libfreemkv's transport returns Err on ANY non-zero
SCSI status (a normal CHECK CONDITION), not only transport faults. The adapter
was collapsing every such Err to { status: 0xFF, sense: None }, which discarded
the parsed sense and defeated the AACS handshake's ILLEGAL_REQUEST wedge guard
(so it kept hammering the drive — hard-rule #2) and inverted its
transport-vs-rejection diagnosis. Now reconstruct status + the 32-byte sense
buffer (sense_key@2, asc@12, ascq@13) and only emit 0xFF/None for a genuine
transport fault.
- Drive::init: a genuine transport fault during the drive-prep unlock means the
bus is dead — propagate it (the v1.1.0 invariant) instead of silently
swallowing it via `if let Ok`. Other errors (no matching unlocker) still fall
through to stock mode. SET CD SPEED max now runs only when the bus is alive.
- disc::patch: on the first read failure in a range, drop to slow recovery speed
and RE-ATTEMPT the same position at slow speed before marking it. A
single-sector range's first failing sector was being marked from a MAX-speed
read it never got to recover.
- docs: lib.rs architecture diagram (handshake → host_certs) and README (stale
pluggable-unlock-seam / register-unlocker / crates.io / docs.rs references).
This commit is contained in:
+31
-19
@@ -413,33 +413,45 @@ impl Drive {
|
||||
// the kind is Unknown — only an identity-keyed unlocker can match here.
|
||||
// The first matching unlocker runs; none matching leaves the drive in
|
||||
// stock mode so the host-cert AACS handshake (the OEM route) carries the
|
||||
// disc. An `Err` return means "nothing applied" — not a hard error; fall
|
||||
// through. (A transport fault during unlock is swallowed by the bridge
|
||||
// today, mirroring the old no-match fall-through.)
|
||||
// disc. A genuine transport fault means the bus is dead — abort init
|
||||
// (the v1.1.0 invariant; `if let Ok` was silently swallowing it). Every
|
||||
// other error (NotApplicable / no match) is "nothing applied" — fall
|
||||
// through to stock mode.
|
||||
self.init_ran = true;
|
||||
if let Ok(unlocked) = crate::unlock_bridge::run_unlockers(
|
||||
let r: Result<()> = match crate::unlock_bridge::run_unlockers(
|
||||
self.scsi.as_mut(),
|
||||
&self.drive_id,
|
||||
freemkv_unlock::DiscKind::Unknown,
|
||||
&[],
|
||||
) {
|
||||
self.unlocker_name =
|
||||
crate::unlock_bridge::unlocker_name(&self.drive_id).map(str::to_string);
|
||||
// Stash the OEM Volume ID the unlocker returned for the AACS handshake
|
||||
// phase (do_handshake reads it via `oem_vid()`). A drive-prep unlocker
|
||||
// always carries a VID; guard anyway.
|
||||
if let Some(vid) = unlocked.vid {
|
||||
self.oem_vid = Some(vid);
|
||||
Ok(unlocked) => {
|
||||
self.unlocker_name =
|
||||
crate::unlock_bridge::unlocker_name(&self.drive_id).map(str::to_string);
|
||||
// Stash the OEM Volume ID the unlocker returned for the AACS
|
||||
// handshake phase (do_handshake reads it via `oem_vid()`). A
|
||||
// drive-prep unlocker always carries a VID; guard anyway.
|
||||
if let Some(vid) = unlocked.vid {
|
||||
self.oem_vid = Some(vid);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Err(freemkv_unlock::UnlockError::Transport) => Err(Error::ScsiError {
|
||||
opcode: 0,
|
||||
status: crate::scsi::SCSI_STATUS_TRANSPORT_FAILURE,
|
||||
sense: None,
|
||||
}),
|
||||
Err(_) => Ok(()),
|
||||
};
|
||||
// Raise the drive to its maximum read speed with a generic SET CD SPEED —
|
||||
// UNCONDITIONALLY, whether or not an unlocker matched. A stock-mode BD/UHD
|
||||
// drive (no firmware unlocker) still wants max speed; gating this on an
|
||||
// unlocker match left such drives riplocked. (DVD returns earlier in
|
||||
// stock mode; its sweep sets DVD speed separately.) Best-effort: a
|
||||
// failure here must NOT fail the rip — a slow drive still rips.
|
||||
self.set_speed(crate::speed::DriveSpeed::Max.to_kbps());
|
||||
let r: Result<()> = Ok(());
|
||||
// UNCONDITIONALLY whenever the bus is alive (init didn't transport-fault),
|
||||
// whether or not an unlocker matched. A stock-mode BD/UHD drive (no
|
||||
// firmware unlocker) still wants max speed; gating this on an unlocker
|
||||
// match left such drives riplocked. (DVD returns earlier in stock mode;
|
||||
// its sweep sets DVD speed separately.) Best-effort: a failure here must
|
||||
// NOT fail the rip — a slow drive still rips.
|
||||
if r.is_ok() {
|
||||
self.set_speed(crate::speed::DriveSpeed::Max.to_kbps());
|
||||
}
|
||||
tracing::info!(
|
||||
target: "freemkv::drive",
|
||||
phase = "init",
|
||||
|
||||
Reference in New Issue
Block a user