mirror of
https://github.com/freemkv/freemkv-unlock.git
synced 2026-10-07 18:13:11 +00:00
freemkv: Identity knock must use the fixed 64-byte data-in allocation
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.
This commit is contained in:
Generated
+5
-5
@@ -142,7 +142,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "freemkv-unlock"
|
name = "freemkv-unlock"
|
||||||
version = "1.6.14"
|
version = "1.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -439,16 +439,16 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
|||||||
|
|
||||||
[[patch.unused]]
|
[[patch.unused]]
|
||||||
name = "freemkv-engine"
|
name = "freemkv-engine"
|
||||||
version = "1.6.14"
|
version = "1.7.0"
|
||||||
|
|
||||||
[[patch.unused]]
|
[[patch.unused]]
|
||||||
name = "freemkv-i18n"
|
name = "freemkv-i18n"
|
||||||
version = "1.6.14"
|
version = "1.7.0"
|
||||||
|
|
||||||
[[patch.unused]]
|
[[patch.unused]]
|
||||||
name = "freemkv-keysources"
|
name = "freemkv-keysources"
|
||||||
version = "1.6.14"
|
version = "1.7.0"
|
||||||
|
|
||||||
[[patch.unused]]
|
[[patch.unused]]
|
||||||
name = "libfreemkv"
|
name = "libfreemkv"
|
||||||
version = "1.6.14"
|
version = "1.7.0"
|
||||||
|
|||||||
+12
-13
@@ -66,10 +66,6 @@ mod subfn {
|
|||||||
/// (no bundled profile database — the firmware self-identifies).
|
/// (no bundled profile database — the firmware self-identifies).
|
||||||
const IDENTITY_MARKER: &[u8] = b"freemkv";
|
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
|
// 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
|
// 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.
|
// 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
|
// 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.
|
// Err(Transport) — this is the FIRST command, so a transport fault must abort.
|
||||||
fn identify(&self, scsi: &mut dyn ScsiTransport) -> std::result::Result<bool, UnlockError> {
|
fn identify(&self, scsi: &mut dyn ScsiTransport) -> std::result::Result<bool, UnlockError> {
|
||||||
let cdb = build_cdb(subfn::IDENTITY, STATE_OFF, IDENTITY_RESPONSE_LEN as u32);
|
// Identity MUST request the fixed KNOCK_RESP_LEN (64) allocation like
|
||||||
let mut buf = vec![0u8; IDENTITY_RESPONSE_LEN];
|
// 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) {
|
match scsi.execute(&cdb, DataDirection::FromDevice, &mut buf, 5_000) {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
let matched = r.status == 0
|
let matched = r.status == 0
|
||||||
@@ -389,7 +388,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn freemkv_identity_payload() -> Vec<u8> {
|
fn freemkv_identity_payload() -> Vec<u8> {
|
||||||
let mut p = vec![0u8; IDENTITY_RESPONSE_LEN];
|
let mut p = vec![0u8; KNOCK_RESP_LEN];
|
||||||
let s = b"freemkv 0.6.4";
|
let s = b"freemkv 0.6.4";
|
||||||
p[..s.len()].copy_from_slice(s);
|
p[..s.len()].copy_from_slice(s);
|
||||||
p
|
p
|
||||||
@@ -409,7 +408,7 @@ mod tests {
|
|||||||
/// The 10-byte knock CDB layout is a pinned wire-format contract.
|
/// The 10-byte knock CDB layout is a pinned wire-format contract.
|
||||||
#[test]
|
#[test]
|
||||||
fn build_cdb_encodes_the_knock_shape() {
|
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.len(), 10);
|
||||||
assert_eq!(cdb[0], 0x3C);
|
assert_eq!(cdb[0], 0x3C);
|
||||||
assert_eq!(cdb[1], 0x0E);
|
assert_eq!(cdb[1], 0x0E);
|
||||||
@@ -417,7 +416,7 @@ mod tests {
|
|||||||
assert_eq!(cdb[3], 0xDE);
|
assert_eq!(cdb[3], 0xDE);
|
||||||
assert_eq!(cdb[4], subfn::IDENTITY);
|
assert_eq!(cdb[4], subfn::IDENTITY);
|
||||||
assert_eq!(cdb[5], 0x00);
|
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);
|
assert_eq!(cdb[9], 0x00);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,8 +453,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn build_cdb_exact_bytes_per_subfn() {
|
fn build_cdb_exact_bytes_per_subfn() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
build_cdb(subfn::IDENTITY, STATE_OFF, IDENTITY_RESPONSE_LEN as u32),
|
build_cdb(subfn::IDENTITY, STATE_OFF, KNOCK_RESP_LEN as u32),
|
||||||
[0x3C, 0x0E, 0xC0, 0xDE, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00]
|
[0x3C, 0x0E, 0xC0, 0xDE, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00]
|
||||||
);
|
);
|
||||||
// Toggles carry the fixed KNOCK_RESP_LEN (64 = 0x40) data-in allocation in
|
// 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.
|
// 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!(FreemkvUnlocker::new().identify(&mut t).expect("no fault"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
t.cdbs[0],
|
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]
|
#[test]
|
||||||
fn identify_false_on_non_matching_payload() {
|
fn identify_false_on_non_matching_payload() {
|
||||||
let mut t = FakeTransport {
|
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"));
|
assert!(!FreemkvUnlocker::new().identify(&mut t).expect("no fault"));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user