renesas: vendor CDBs from pioneer-optical

The identity read (3C 02 F1), the two gated memory reads (3C 02 B0 at 0x04
and 0x500000, 0xA4 bytes) and the read-unlock knock (3B 02 41 A5 AA AA) come
from the crate's constructors; the bytes on the wire are unchanged and the
knock test still pins them. The crate's flash::read_memory is not used: it
knocks before every read and stops on a refused knock, where this sequence
reads first and tries the second window even when the knock is refused.
This commit is contained in:
Matthew Jackson
2026-10-04 18:42:19 -07:00
parent 17e33f89cf
commit 800c78f86b
4 changed files with 29 additions and 14 deletions
+1
View File
@@ -4,6 +4,7 @@
### Changed ### Changed
- The Renesas/Pioneer unlocker builds its vendor CDBs (identity read, gated memory reads, read-unlock knock) with the `pioneer-optical` crate instead of hard-coded bytes; the commands sent are unchanged.
- AACS handshake diagnostics that a scan now reports itself log below WARN: unsupported or unverifiable drive certs, VID read failures and transport faults at DEBUG, `read_data_key_dropped` at INFO. - AACS handshake diagnostics that a scan now reports itself log below WARN: unsupported or unverifiable drive certs, VID read failures and transport faults at DEBUG, `read_data_key_dropped` at INFO.
## [1.7.7] — 2026-09-26 ## [1.7.7] — 2026-09-26
Generated
+7
View File
@@ -110,6 +110,7 @@ dependencies = [
"base64", "base64",
"num-bigint", "num-bigint",
"num-traits", "num-traits",
"pioneer-optical",
"rand", "rand",
"serde", "serde",
"serde_json", "serde_json",
@@ -207,6 +208,12 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pioneer-optical"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48a5bdb9e9a07422ceb5133d227fbe4ecce77060007e5a9bac0dda3d82d72911"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.107" version = "1.0.107"
+2
View File
@@ -30,6 +30,8 @@ rand = "0.10"
# material on drop so it doesn't linger in freed memory (defence in depth atop # material on drop so it doesn't linger in freed memory (defence in depth atop
# the redacting Debug impls). `derive` gives ZeroizeOnDrop for the key structs. # the redacting Debug impls). `derive` gives ZeroizeOnDrop for the key structs.
zeroize = { version = "1", features = ["derive"] } zeroize = { version = "1", features = ["derive"] }
# Pioneer/Renesas vendor CDB constructors (identity read, gated memory read, knock).
pioneer-optical = "0.7"
[lints.clippy] [lints.clippy]
uninlined_format_args = "allow" uninlined_format_args = "allow"
+19 -14
View File
@@ -9,9 +9,8 @@
use crate::scsi::{DataDirection, ScsiTransport, is_dead_bus}; use crate::scsi::{DataDirection, ScsiTransport, is_dead_bus};
use crate::{UnlockCtx, UnlockError, Unlocked, Unlocker}; use crate::{UnlockCtx, UnlockError, Unlocked, Unlocker};
/// READ_BUFFER mode 0x02, buffer 0xF1 — the Renesas vendor identity buffer. /// The Renesas vendor identity block length (READ_BUFFER 0x02/0xF1).
const RB_F1_CDB: [u8; 10] = [0x3C, 0x02, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00]; const RB_F1_LEN: usize = pioneer_optical::IDENTITY_LEN as usize;
const RB_F1_LEN: usize = 48;
/// The ASCII interface marker a Renesas controller returns at `[16..19]`. /// The ASCII interface marker a Renesas controller returns at `[16..19]`.
const RENESAS_MARKER: &[u8] = b"SAT"; const RENESAS_MARKER: &[u8] = b"SAT";
const RENESAS_MARKER_OFFSET: usize = 16; const RENESAS_MARKER_OFFSET: usize = 16;
@@ -27,7 +26,8 @@ const RENESAS_MARKER_OFFSET: usize = 16;
/// `Err(Transport)` on a dead bus. /// `Err(Transport)` on a dead bus.
pub fn is_renesas(scsi: &mut dyn ScsiTransport) -> std::result::Result<bool, UnlockError> { pub fn is_renesas(scsi: &mut dyn ScsiTransport) -> std::result::Result<bool, UnlockError> {
let mut buf = [0u8; RB_F1_LEN]; let mut buf = [0u8; RB_F1_LEN];
match scsi.execute(&RB_F1_CDB, DataDirection::FromDevice, &mut buf, 5_000) { let cdb = pioneer_optical::vendor_identity();
match scsi.execute(&cdb, DataDirection::FromDevice, &mut buf, 5_000) {
Ok(r) => { Ok(r) => {
let end = RENESAS_MARKER_OFFSET + RENESAS_MARKER.len(); let end = RENESAS_MARKER_OFFSET + RENESAS_MARKER.len();
Ok(r.status == 0 Ok(r.status == 0
@@ -72,27 +72,32 @@ impl Renesas {
// MakeMKV's vendor "open" sequence: primary read (A), and on refusal a // MakeMKV's vendor "open" sequence: primary read (A), and on refusal a
// knock + second read (B). // knock + second read (B).
fn vendor_open(scsi: &mut dyn ScsiTransport) -> std::result::Result<bool, UnlockError> { fn vendor_open(scsi: &mut dyn ScsiTransport) -> std::result::Result<bool, UnlockError> {
const RB_B0_04_CDB: [u8; 10] = [0x3C, 0x02, 0xB0, 0x00, 0x00, 0x04, 0x00, 0x00, 0xA4, 0x00];
const KNOCK_A5AAAA_CDB: [u8; 10] =
[0x3B, 0x02, 0x41, 0xA5, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00];
const RB_B0_500000_CDB: [u8; 10] =
[0x3C, 0x02, 0xB0, 0x50, 0x00, 0x00, 0x00, 0x00, 0xA4, 0x00];
// A: primary open read. // A: primary open read.
if read_is_good(scsi, &RB_B0_04_CDB)? { if read_is_good(scsi, &pioneer_optical::read_memory(0x04, OPEN_READ_LEN))? {
return Ok(true); return Ok(true);
} }
// B: MakeMKV's fallback — knock, then the second-window read. The knock is // B: MakeMKV's fallback — knock, then the second-window read. The knock is
// fire-and-forget (payload-less); its own status is not the signal. // fire-and-forget (payload-less); its own status is not the signal.
match scsi.execute(&KNOCK_A5AAAA_CDB, DataDirection::None, &mut [], 5_000) { match scsi.execute(
&pioneer_optical::knock(),
DataDirection::None,
&mut [],
5_000,
) {
Ok(_) => {} Ok(_) => {}
Err(e) if is_dead_bus(&e) => return Err(UnlockError::Transport), Err(e) if is_dead_bus(&e) => return Err(UnlockError::Transport),
Err(_) => {} // a drive that refuses the knock still gets the B read tried Err(_) => {} // a drive that refuses the knock still gets the B read tried
} }
read_is_good(scsi, &RB_B0_500000_CDB) read_is_good(
scsi,
&pioneer_optical::read_memory(0x50_0000, OPEN_READ_LEN),
)
} }
} }
/// The vendor "open" reads' length (0xA4 = 164 bytes).
const OPEN_READ_LEN: u32 = 0xA4;
/// Issue a 164-byte vendor READ_BUFFER; `Ok(true)` on GOOD status, `Ok(false)` /// Issue a 164-byte vendor READ_BUFFER; `Ok(true)` on GOOD status, `Ok(false)`
/// on CHECK CONDITION (drive refused), `Err(Transport)` on a senseless /// on CHECK CONDITION (drive refused), `Err(Transport)` on a senseless
/// transport failure (dead bus). /// transport failure (dead bus).
@@ -100,7 +105,7 @@ fn read_is_good(
scsi: &mut dyn ScsiTransport, scsi: &mut dyn ScsiTransport,
cdb: &[u8; 10], cdb: &[u8; 10],
) -> std::result::Result<bool, UnlockError> { ) -> std::result::Result<bool, UnlockError> {
let mut buf = [0u8; 164]; let mut buf = [0u8; OPEN_READ_LEN as usize];
match scsi.execute(cdb, DataDirection::FromDevice, &mut buf, 5_000) { match scsi.execute(cdb, DataDirection::FromDevice, &mut buf, 5_000) {
Ok(r) => Ok(r.status == 0), Ok(r) => Ok(r.status == 0),
Err(e) => { Err(e) => {