From 398910082014f9ab085af0a642fc3334b72bd188 Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Thu, 3 Sep 2026 07:41:43 -0700 Subject: [PATCH] freemkv: Identity knock must use the fixed 64-byte data-in allocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit identify() requested a 32-byte data-in allocation while the firmware commits KNOCK_RESP_LEN (64) bytes on every subfn. The short allocation desyncs the first command's data-in phase and wedges the drive, so the later bare 0xAD VID read aborts (CHECK CONDITION) — the "Raw Read GOOD but no VID" failure that left the keyserver with vid=none. Request KNOCK_RESP_LEN like every other knock; update the CDB-shape tests. Also sync Cargo.lock to the 1.7.0 manifest. --- Cargo.lock | 10 +++++----- src/freemkv/mod.rs | 25 ++++++++++++------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d20d09..21503db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,7 +142,7 @@ dependencies = [ [[package]] name = "freemkv-unlock" -version = "1.6.14" +version = "1.7.0" dependencies = [ "aes", "base64", @@ -439,16 +439,16 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[patch.unused]] name = "freemkv-engine" -version = "1.6.14" +version = "1.7.0" [[patch.unused]] name = "freemkv-i18n" -version = "1.6.14" +version = "1.7.0" [[patch.unused]] name = "freemkv-keysources" -version = "1.6.14" +version = "1.7.0" [[patch.unused]] name = "libfreemkv" -version = "1.6.14" +version = "1.7.0" diff --git a/src/freemkv/mod.rs b/src/freemkv/mod.rs index 08d4329..28a6cd8 100644 --- a/src/freemkv/mod.rs +++ b/src/freemkv/mod.rs @@ -66,10 +66,6 @@ mod subfn { /// (no bundled profile database — the firmware self-identifies). const IDENTITY_MARKER: &[u8] = b"freemkv"; -/// Response buffer size for the Identity probe — comfortably larger than the -/// current `"freemkv 0.6.4"` payload. -const IDENTITY_RESPONSE_LEN: usize = 32; - // Fixed response length for EVERY freemkv knock command; the drive commits this // many bytes on data-in regardless of subfn. A short/zero allocation desyncs the // transfer (ABORTED COMMAND, then a wedged FIFO) — see docs/freemkv-abi.md. @@ -138,8 +134,11 @@ impl FreemkvUnlocker { // Ok(false) if rejected/mismatched (not this firmware). A dead bus is // Err(Transport) — this is the FIRST command, so a transport fault must abort. fn identify(&self, scsi: &mut dyn ScsiTransport) -> std::result::Result { - let cdb = build_cdb(subfn::IDENTITY, STATE_OFF, IDENTITY_RESPONSE_LEN as u32); - let mut buf = vec![0u8; IDENTITY_RESPONSE_LEN]; + // Identity MUST request the fixed KNOCK_RESP_LEN (64) allocation like + // every other knock: the firmware commits 64 bytes on every subfn, so a + // short 32-byte read desyncs the data-in phase and wedges the VID read. + let cdb = build_cdb(subfn::IDENTITY, STATE_OFF, KNOCK_RESP_LEN as u32); + let mut buf = [0u8; KNOCK_RESP_LEN]; match scsi.execute(&cdb, DataDirection::FromDevice, &mut buf, 5_000) { Ok(r) => { let matched = r.status == 0 @@ -389,7 +388,7 @@ mod tests { } fn freemkv_identity_payload() -> Vec { - let mut p = vec![0u8; IDENTITY_RESPONSE_LEN]; + let mut p = vec![0u8; KNOCK_RESP_LEN]; let s = b"freemkv 0.6.4"; p[..s.len()].copy_from_slice(s); p @@ -409,7 +408,7 @@ mod tests { /// The 10-byte knock CDB layout is a pinned wire-format contract. #[test] fn build_cdb_encodes_the_knock_shape() { - let cdb = build_cdb(subfn::IDENTITY, STATE_OFF, IDENTITY_RESPONSE_LEN as u32); + let cdb = build_cdb(subfn::IDENTITY, STATE_OFF, KNOCK_RESP_LEN as u32); assert_eq!(cdb.len(), 10); assert_eq!(cdb[0], 0x3C); assert_eq!(cdb[1], 0x0E); @@ -417,7 +416,7 @@ mod tests { assert_eq!(cdb[3], 0xDE); assert_eq!(cdb[4], subfn::IDENTITY); assert_eq!(cdb[5], 0x00); - assert_eq!([cdb[6], cdb[7], cdb[8]], [0x00, 0x00, 0x20]); // 32, 24-bit BE + assert_eq!([cdb[6], cdb[7], cdb[8]], [0x00, 0x00, 0x40]); // 64 (KNOCK_RESP_LEN), 24-bit BE assert_eq!(cdb[9], 0x00); } @@ -454,8 +453,8 @@ mod tests { #[test] fn build_cdb_exact_bytes_per_subfn() { assert_eq!( - build_cdb(subfn::IDENTITY, STATE_OFF, IDENTITY_RESPONSE_LEN as u32), - [0x3C, 0x0E, 0xC0, 0xDE, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00] + build_cdb(subfn::IDENTITY, STATE_OFF, KNOCK_RESP_LEN as u32), + [0x3C, 0x0E, 0xC0, 0xDE, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00] ); // Toggles carry the fixed KNOCK_RESP_LEN (64 = 0x40) data-in allocation in // bytes 6..8 — the host must read the response or the drive wedges. @@ -481,14 +480,14 @@ mod tests { assert!(FreemkvUnlocker::new().identify(&mut t).expect("no fault")); assert_eq!( t.cdbs[0], - build_cdb(subfn::IDENTITY, STATE_OFF, IDENTITY_RESPONSE_LEN as u32) + build_cdb(subfn::IDENTITY, STATE_OFF, KNOCK_RESP_LEN as u32) ); } #[test] fn identify_false_on_non_matching_payload() { let mut t = FakeTransport { - payload: vec![0u8; IDENTITY_RESPONSE_LEN], + payload: vec![0u8; KNOCK_RESP_LEN], }; assert!(!FreemkvUnlocker::new().identify(&mut t).expect("no fault")); }