Fix audit findings: SCSI constants, sg_io_hdr assert, handshake cap, sector overflow check

- Replace magic SCSI opcodes with named constants (S2)
- Add compile-time sg_io_hdr size assertion — 88 bytes on 64-bit (W2)
- Cap handshake cert attempts at 16 (W8)
- Validate IsoSectorReader/FileSectorReader against u32 overflow for >8TB (S8)
- encrypt.rs: limit host cert loop iterations
This commit is contained in:
MattJackson
2026-04-15 04:29:45 +00:00
parent d983985faa
commit 9ae7d6b38a
5 changed files with 45 additions and 15 deletions
+2 -1
View File
@@ -25,7 +25,8 @@ impl Disc {
let keydb_path = opts.resolve_keydb()?; let keydb_path = opts.resolve_keydb()?;
let keydb = KeyDb::load(&keydb_path).ok()?; let keydb = KeyDb::load(&keydb_path).ok()?;
for hc in &keydb.host_certs { const MAX_CERT_ATTEMPTS: usize = 16;
for hc in keydb.host_certs.iter().take(MAX_CERT_ATTEMPTS) {
match aacs::handshake::aacs_authenticate(session, &hc.private_key, &hc.certificate) { match aacs::handshake::aacs_authenticate(session, &hc.private_key, &hc.certificate) {
Ok(mut auth) => { Ok(mut auth) => {
let volume_id = match aacs::handshake::read_volume_id(session, &mut auth) { let volume_id = match aacs::handshake::read_volume_id(session, &mut auth) {
+20 -12
View File
@@ -39,6 +39,14 @@ pub enum DriveStatus {
Unknown, Unknown,
} }
// SCSI opcodes used in drive control
const SCSI_TEST_UNIT_READY: u8 = 0x00;
const SCSI_START_STOP_UNIT: u8 = 0x1B;
const SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL: u8 = 0x1E;
const SCSI_GET_EVENT_STATUS: u8 = 0x4A;
const SCSI_MODE_SENSE: u8 = 0x5A;
const SCSI_REPORT_KEY: u8 = 0xA4;
/// Recovery state after a read error — stay at min speed for N bytes. /// Recovery state after a read error — stay at min speed for N bytes.
const RECOVERY_WINDOW: u64 = 500 * 1024 * 1024; // 500 MB const RECOVERY_WINDOW: u64 = 500 * 1024 * 1024; // 500 MB
@@ -104,7 +112,7 @@ impl Drive {
} }
pub fn wait_ready(&mut self) -> Result<()> { pub fn wait_ready(&mut self) -> Result<()> {
let tur = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; let tur = [SCSI_TEST_UNIT_READY, 0x00, 0x00, 0x00, 0x00, 0x00];
let mut tried_reset = false; let mut tried_reset = false;
for _ in 0..60 { for _ in 0..60 {
@@ -142,7 +150,7 @@ impl Drive {
/// Uses GET EVENT STATUS NOTIFICATION which works regardless of firmware state. /// Uses GET EVENT STATUS NOTIFICATION which works regardless of firmware state.
pub fn drive_status(&mut self) -> DriveStatus { pub fn drive_status(&mut self) -> DriveStatus {
// GET EVENT STATUS NOTIFICATION: polled, media event class (0x10) // GET EVENT STATUS NOTIFICATION: polled, media event class (0x10)
let cdb = [0x4Au8, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00]; let cdb = [SCSI_GET_EVENT_STATUS, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00];
let mut buf = [0u8; 8]; let mut buf = [0u8; 8];
match self.scsi.as_mut().execute( match self.scsi.as_mut().execute(
&cdb, &cdb,
@@ -164,7 +172,7 @@ impl Drive {
} }
_ => { _ => {
// Fallback: try TUR // Fallback: try TUR
let tur = [0x00u8, 0x00, 0x00, 0x00, 0x00, 0x00]; let tur = [SCSI_TEST_UNIT_READY, 0x00, 0x00, 0x00, 0x00, 0x00];
let mut empty = [0u8; 0]; let mut empty = [0u8; 0];
match self.scsi.as_mut().execute( match self.scsi.as_mut().execute(
&tur, &tur,
@@ -193,17 +201,17 @@ impl Drive {
/// any step, even if the drive reports "tray open" (that's a valid state). /// any step, even if the drive reports "tray open" (that's a valid state).
pub fn reset(&mut self) -> Result<()> { pub fn reset(&mut self) -> Result<()> {
let mut buf = [0u8; 0]; let mut buf = [0u8; 0];
let tur = [0x00u8, 0x00, 0x00, 0x00, 0x00, 0x00]; let tur = [SCSI_TEST_UNIT_READY, 0x00, 0x00, 0x00, 0x00, 0x00];
// 1. Unlock + stop/start // 1. Unlock + stop/start
self.unlock_tray(); self.unlock_tray();
let stop = [0x1Bu8, 0x00, 0x00, 0x00, 0x00, 0x00]; let stop = [SCSI_START_STOP_UNIT, 0x00, 0x00, 0x00, 0x00, 0x00];
let _ = let _ =
self.scsi self.scsi
.as_mut() .as_mut()
.execute(&stop, crate::scsi::DataDirection::None, &mut buf, 5_000); .execute(&stop, crate::scsi::DataDirection::None, &mut buf, 5_000);
std::thread::sleep(std::time::Duration::from_millis(500)); std::thread::sleep(std::time::Duration::from_millis(500));
let start = [0x1Bu8, 0x00, 0x00, 0x00, 0x01, 0x00]; let start = [SCSI_START_STOP_UNIT, 0x00, 0x00, 0x00, 0x01, 0x00];
let _ = let _ =
self.scsi self.scsi
.as_mut() .as_mut()
@@ -223,7 +231,7 @@ impl Drive {
// After eject, TUR returning "Not Ready — tray open" (sense key 2) // After eject, TUR returning "Not Ready — tray open" (sense key 2)
// counts as success: the drive is functional, just needs disc reinserted. // counts as success: the drive is functional, just needs disc reinserted.
self.unlock_tray(); self.unlock_tray();
let eject = [0x1Bu8, 0x00, 0x00, 0x00, 0x02, 0x00]; let eject = [SCSI_START_STOP_UNIT, 0x00, 0x00, 0x00, 0x02, 0x00];
let _ = let _ =
self.scsi self.scsi
.as_mut() .as_mut()
@@ -335,7 +343,7 @@ impl Drive {
/// Read REPORT KEY RPC state (region playback control). /// Read REPORT KEY RPC state (region playback control).
pub fn report_key_rpc_state(&mut self) -> Option<Vec<u8>> { pub fn report_key_rpc_state(&mut self) -> Option<Vec<u8>> {
let cdb = [ let cdb = [
0xA4u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x00, SCSI_REPORT_KEY, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x00,
]; ];
let mut buf = vec![0u8; 8]; let mut buf = vec![0u8; 8];
let r = self let r = self
@@ -357,7 +365,7 @@ impl Drive {
/// Read MODE SENSE page data. /// Read MODE SENSE page data.
pub fn mode_sense_page(&mut self, page: u8) -> Option<Vec<u8>> { pub fn mode_sense_page(&mut self, page: u8) -> Option<Vec<u8>> {
let cdb = [0x5Au8, 0x00, page, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00]; let cdb = [SCSI_MODE_SENSE, 0x00, page, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00];
let mut buf = vec![0u8; 252]; let mut buf = vec![0u8; 252];
let r = self let r = self
.scsi .scsi
@@ -516,7 +524,7 @@ impl Drive {
/// Lock the tray so the disc cannot be ejected during a rip. /// Lock the tray so the disc cannot be ejected during a rip.
pub fn lock_tray(&mut self) { pub fn lock_tray(&mut self) {
let prevent = [0x1Eu8, 0x00, 0x00, 0x00, 0x01, 0x00]; let prevent = [SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0x00, 0x00, 0x00, 0x01, 0x00];
let mut buf = [0u8; 0]; let mut buf = [0u8; 0];
let _ = let _ =
self.scsi self.scsi
@@ -526,7 +534,7 @@ impl Drive {
/// Unlock the tray so the user can manually eject the disc. /// Unlock the tray so the user can manually eject the disc.
pub fn unlock_tray(&mut self) { pub fn unlock_tray(&mut self) {
let allow = [0x1Eu8, 0x00, 0x00, 0x00, 0x00, 0x00]; let allow = [SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0x00, 0x00, 0x00, 0x00, 0x00];
let mut buf = [0u8; 0]; let mut buf = [0u8; 0];
let _ = let _ =
self.scsi self.scsi
@@ -537,7 +545,7 @@ impl Drive {
/// Eject the disc tray. Unlocks first, then ejects. /// Eject the disc tray. Unlocks first, then ejects.
pub fn eject(&mut self) -> Result<()> { pub fn eject(&mut self) -> Result<()> {
self.unlock_tray(); self.unlock_tray();
let eject_cdb = [0x1Bu8, 0, 0, 0, 0x02, 0]; let eject_cdb = [SCSI_START_STOP_UNIT, 0, 0, 0, 0x02, 0];
let mut buf = [0u8; 0]; let mut buf = [0u8; 0];
self.scsi.as_mut().execute( self.scsi.as_mut().execute(
&eject_cdb, &eject_cdb,
+8 -1
View File
@@ -33,7 +33,14 @@ impl IsoSectorReader {
let file = File::open(Path::new(path)) let file = File::open(Path::new(path))
.map_err(|e| io::Error::new(e.kind(), format!("iso://{path}: {e}")))?; .map_err(|e| io::Error::new(e.kind(), format!("iso://{path}: {e}")))?;
let size = file.metadata()?.len(); let size = file.metadata()?.len();
let capacity = (size / SECTOR_SIZE) as u32; let sectors = size / SECTOR_SIZE;
if sectors > u32::MAX as u64 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("iso://{path}: image too large ({} TB, max ~8 TB)", size / (1024 * 1024 * 1024 * 1024)),
));
}
let capacity = sectors as u32;
Ok(Self { file, capacity }) Ok(Self { file, capacity })
} }
+7
View File
@@ -40,6 +40,13 @@ struct sg_io_hdr {
info: u32, info: u32,
} }
// Compile-time validation: sg_io_hdr must match the kernel's layout.
// 64 bytes on 64-bit, 44 bytes on 32-bit (pointer-size dependent).
#[cfg(target_pointer_width = "64")]
const _: () = assert!(std::mem::size_of::<sg_io_hdr>() == 88);
#[cfg(target_pointer_width = "32")]
const _: () = assert!(std::mem::size_of::<sg_io_hdr>() == 64);
pub struct SgIoTransport { pub struct SgIoTransport {
fd: i32, fd: i32,
} }
+8 -1
View File
@@ -27,7 +27,14 @@ impl FileSectorReader {
pub fn open(path: &str) -> std::io::Result<Self> { pub fn open(path: &str) -> std::io::Result<Self> {
let file = std::fs::File::open(path)?; let file = std::fs::File::open(path)?;
let len = file.metadata()?.len(); let len = file.metadata()?.len();
let capacity = (len / 2048) as u32; let sectors = len / 2048;
if sectors > u32::MAX as u64 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("{path}: image too large, max ~8 TB"),
));
}
let capacity = sectors as u32;
Ok(Self { Ok(Self {
file: std::io::BufReader::with_capacity(4 * 1024 * 1024, file), file: std::io::BufReader::with_capacity(4 * 1024 * 1024, file),
capacity, capacity,