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:
Matthew Jackson
2026-06-29 22:16:28 -07:00
parent b36896564f
commit 8c38cb0918
5 changed files with 83 additions and 37 deletions
+25 -5
View File
@@ -44,11 +44,31 @@ impl fu::scsi::ScsiTransport for ScsiAdapter<'_> {
bytes_transferred: r.bytes_transferred,
sense: r.sense,
}),
// libfreemkv's transport returns Err only on a transport-layer fault.
Err(_) => Err(fu::scsi::ScsiError {
status: 0xFF,
sense: None,
}),
// libfreemkv's transport returns Err for ANY non-zero SCSI status —
// i.e. a normal drive CHECK CONDITION (ILLEGAL_REQUEST, etc.), NOT
// only a transport-layer fault. Preserve the real status AND the
// parsed sense across the seam: the AACS handshake's wedge guard
// bails on an ILLEGAL_REQUEST sense (so it stops hammering the drive),
// and its diagnosis distinguishes a cert rejection from a dead bus by
// the same status/sense. Collapsing everything to 0xFF/None defeated
// both. Reconstruct the 32-byte sense buffer at the offsets the
// unlock crate reads (sense_key@2 low-nibble, asc@12, ascq@13); a
// genuine transport fault (status 0xFF, no sense) maps through
// unchanged.
Err(e) => {
let (status, sense) = crate::drive::extract_scsi_context(&e);
let sense_buf = sense.map(|s| {
let mut b = [0u8; 32];
b[2] = s.sense_key & 0x0F;
b[12] = s.asc;
b[13] = s.ascq;
b
});
Err(fu::scsi::ScsiError {
status,
sense: sense_buf,
})
}
}
}
}