Make the public per-sector descramble refuse a non-pack sector
descramble_region was fixed to require the MPEG-2 pack start code, but descramble_sector — the PUBLIC per-sector entry point, and the one the module-level example tells callers to use — still keyed on the byte 0x14 flag bits alone. The crate own documented guidance therefore led straight back into the defect this release exists to fix: a VIDEO_TS.IFO sector holding 0x15 at 0x14 while starting 00 26 00 00 loses 1912 of its 2048 bytes, and because that sector carries TT_SRPT the disc enumerates 38 titles while an image decrypted from it enumerates 10, at exit 0. It has no callers inside the crate, which is exactly why it survived three rounds: nothing exercised it. Putting the guard inside the function rather than in each caller is what keeps the safe path the easy one. The integration test that covers it built its sector from the flag byte alone, which no real scrambled sector looks like, so it stopped representing the path it names — the same fixture-realism gap already fixed in four other places this release.
This commit is contained in:
@@ -339,7 +339,25 @@ fn crack_key_scan(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Descramble a single CSS-encrypted sector in place.
|
/// Descramble a single CSS-encrypted sector in place.
|
||||||
|
///
|
||||||
|
/// A no-op unless the sector is a scrambled MPEG-2 PS PACK. The pack start code
|
||||||
|
/// is checked, not just the byte 0x14 flag bits, for the same reason
|
||||||
|
/// `descramble_region` checks it: 0x14 only means "scrambling control" inside a
|
||||||
|
/// pack, and in an IFO it is whatever that format stores there. A real
|
||||||
|
/// `VIDEO_TS.IFO` sector holds 0x15 there while starting `00 26 00 00`;
|
||||||
|
/// descrambling it destroyed 1912 of its 2048 bytes, and because that sector
|
||||||
|
/// carries TT_SRPT the disc enumerated 38 titles while an image decrypted from
|
||||||
|
/// it enumerated 10, silently, at exit 0.
|
||||||
|
///
|
||||||
|
/// This function has no callers inside the crate, but the module-level example
|
||||||
|
/// above prescribes it — so the crate's own documented guidance led straight
|
||||||
|
/// into that defect. Making the guard part of the function, rather than
|
||||||
|
/// something each caller must remember, is what keeps the safe path the easy
|
||||||
|
/// one.
|
||||||
pub fn descramble_sector(state: &CssState, sector: &mut [u8]) {
|
pub fn descramble_sector(state: &CssState, sector: &mut [u8]) {
|
||||||
|
if !is_scrambled_pack(sector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
lfsr::descramble_sector(&state.title_key, sector);
|
lfsr::descramble_sector(&state.title_key, sector);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -868,6 +886,34 @@ mod tests {
|
|||||||
/// that sector carries TT_SRPT the whole title table went with it — the
|
/// that sector carries TT_SRPT the whole title table went with it — the
|
||||||
/// disc enumerated 38 titles, an image decrypted from it enumerated 10, at
|
/// disc enumerated 38 titles, an image decrypted from it enumerated 10, at
|
||||||
/// exit 0 with no diagnostic. Guard on the pack start code, not the flag.
|
/// exit 0 with no diagnostic. Guard on the pack start code, not the flag.
|
||||||
|
/// The PUBLIC per-sector entry point must refuse a non-pack sector too.
|
||||||
|
///
|
||||||
|
/// Audit finding: `descramble_region` was fixed to require the pack start
|
||||||
|
/// code, but `descramble_sector` — which the module-level example tells
|
||||||
|
/// callers to use — still keyed on the 0x14 flag alone, so the crate's own
|
||||||
|
/// documented path reached the same defect.
|
||||||
|
#[test]
|
||||||
|
fn descramble_sector_refuses_a_non_pack_sector() {
|
||||||
|
let mut ifo_like = vec![0u8; 2048];
|
||||||
|
for (i, b) in ifo_like.iter_mut().enumerate() {
|
||||||
|
*b = (i as u8).wrapping_mul(37).wrapping_add(11);
|
||||||
|
}
|
||||||
|
ifo_like[0x00..0x04].copy_from_slice(&[0x00, 0x26, 0x00, 0x00]); // not a pack
|
||||||
|
ifo_like[0x14] = 0x15; // flag bits set — the trap
|
||||||
|
assert!(has_scramble_flag_bits(&ifo_like) && !is_scrambled_pack(&ifo_like));
|
||||||
|
|
||||||
|
let pristine = ifo_like.clone();
|
||||||
|
let state = CssState {
|
||||||
|
title_key: [0x42, 0x13, 0x37, 0xBE, 0xEF],
|
||||||
|
crack_span: None,
|
||||||
|
};
|
||||||
|
descramble_sector(&state, &mut ifo_like);
|
||||||
|
assert_eq!(
|
||||||
|
ifo_like, pristine,
|
||||||
|
"a non-pack sector must survive byte-identical through the public API"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn descramble_region_leaves_a_non_pack_sector_alone_even_with_the_flag_set() {
|
fn descramble_region_leaves_a_non_pack_sector_alone_even_with_the_flag_set() {
|
||||||
let mut ifo_like = vec![0u8; 2048];
|
let mut ifo_like = vec![0u8; 2048];
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ fn css_descramble_sector_roundtrip_via_public_api() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut sector = vec![0xAAu8; 2048];
|
let mut sector = vec![0xAAu8; 2048];
|
||||||
|
// Real scrambled DVD sectors are MPEG-2 PS packs, and the public API now
|
||||||
|
// requires the pack start code as well as the flag bits — byte 0x14 means
|
||||||
|
// something else entirely in an IFO or UDF sector.
|
||||||
|
sector[0x00..0x04].copy_from_slice(&[0x00, 0x00, 0x01, 0xBA]);
|
||||||
sector[0x14] = 0x30;
|
sector[0x14] = 0x30;
|
||||||
sector[0x54..0x59].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF, 0x42]);
|
sector[0x54..0x59].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF, 0x42]);
|
||||||
let original = sector.clone();
|
let original = sector.clone();
|
||||||
|
|||||||
Reference in New Issue
Block a user