CSS: fix the decrypted-HD-DVD false E7023 at the detection layer, not the public API
Commit 4cd9b7b ("key the DVD crack on the disc, not on the container") fixed a
real bug — a decrypted HD-DVD hit E7023 (CssKeyMissing) because the per-title
CSS crack keyed on the MPEG-PS container, which DVD and HD-DVD share — but did
it by adding a required `disc_format: DiscFormat` parameter to the PUBLIC
`DiscStream::new` and `build_iso_pipeline`, a `disc_format` field to
`MuxInput::Iso`/`Live`, and `DiscFormat::may_have_css`, threading the axis down
through mux/driver and mux/resolve. That changed the public API and broke every
downstream caller's compilation (freemkv-engine's integration test now needed 8
args, autorip's MuxInput arms a new field). 1.6.4 shipped and worked with these
exact signatures; a bug fix must not reshape them, and needing a whole
disc-format plumb for HD-DVD was a code smell.
Revert all of that plumbing (public signatures restored to their pre-4cd9b7b
form; no `disc_format` parameter or field, no `may_have_css`, anywhere), and fix
the ACTUAL bug where it lives: the scramble-detection heuristic.
Root cause: `is_scrambled_pack` counted a sector as CSS scramble evidence on
pack-start (00 00 01 BA) + bits 4-5 of byte 0x14. Offset 0x14 is only the PES
scrambling-control field when the sector is a genuine elementary-stream pack. An
HD-DVD `.evo` RDI navigation pack is private_stream_2 (stream_id 0xBF), an
MPEG-PS pack exactly like a DVD VOB, whose byte 0x14 is raw nav payload that
routinely has bits 4-5 set. On a decrypted HD-DVD (None keys, MPEG-PS, so it
reaches the crack) those nav packs flipped the scan's `saw_scrambled` flag; the
crack then found no key — there is no CSS on an HD-DVD — and the scan returned
ScrambledUncracked, hard-failing a good disc with E7023.
Fix: exclude the MPEG-PS structural stream_ids CSS never scrambles — system
header (0xBB), padding (0xBE), private_stream_2 (0xBF) — by the stream_id at
offset 0x11. This is the refinement the DVD design notes already called for
("matches CrackTitleKey"). It needs no format plumbing because byte 0x11 lives
in the CSS-clear header (0x00-0x7F, untouched by scrambling), so it is the true
stream_id even on ciphertext. A decrypted HD-DVD now scans to Unencrypted and
muxes cleanly.
The DVD CSS crack is preserved and proven: a genuinely CSS-scrambled DVD sector
is always video (0xE0-0xEF) or private_stream_1 (0xBD), never an excluded id, so
its scrambled packs still set saw_scrambled and still hard-fail an uncrackable
disc — the "ciphertext muxed as plaintext at rc=0" catastrophe cannot slip
through. Red-before-green both directions: dropping the 0x11 exclusion turns the
decrypted-HD-DVD case back into E7023; inverting it (only nav ids count) turns a
real uncrackable DVD into Unencrypted and strands a crackable one. Both mutations
are caught by tests.
Gate (cargo +1.97): fmt, clippy --all-targets -D warnings, 3539 tests green;
freemkv-engine and autorip both compile against this tree again; precommit.sh
libfreemkv clean.
This commit is contained in:
+5
-68
@@ -208,25 +208,12 @@ impl DiscStream {
|
||||
/// Works with physical drives and ISO files — both implement SectorSource.
|
||||
/// The caller opens the source, scans for titles/keys, and passes them in.
|
||||
/// The stream handles demuxing, decryption, and codec parsing internally.
|
||||
///
|
||||
/// `content_format` is the CONTAINER (TS vs PS demuxer). `disc_format` is
|
||||
/// the DISC FAMILY, and it exists as its own parameter because the two are
|
||||
/// not interchangeable: DVD and HD-DVD are both `ContentFormat::MpegPs`,
|
||||
/// yet only DVD can carry CSS. It gates the per-title CSS crack below. A
|
||||
/// caller that genuinely does not know the disc passes
|
||||
/// [`crate::disc::DiscFormat::Unknown`], which still attempts the crack —
|
||||
/// the safe direction (see [`crate::disc::DiscFormat::may_have_css`]).
|
||||
// Eight params is inherent to a constructor that takes the source, the
|
||||
// title, the keys, both format axes (container and disc family) and the
|
||||
// read-mode flags; grouping them would only relocate the same fields.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
mut reader: Box<dyn SectorSource>,
|
||||
title: DiscTitle,
|
||||
mut decrypt_keys: crate::decrypt::DecryptKeys,
|
||||
batch_sectors: u16,
|
||||
content_format: crate::disc::ContentFormat,
|
||||
disc_format: crate::disc::DiscFormat,
|
||||
raw: bool,
|
||||
halt: Option<Halt>,
|
||||
) -> std::io::Result<Self> {
|
||||
@@ -236,20 +223,17 @@ impl DiscStream {
|
||||
// Resolve this title's CSS key from the reader if the caller supplied
|
||||
// none — the SAME shared step the file-backed mux highway
|
||||
// (`build_iso_pipeline`) uses, so single-pass and multi-pass descramble a
|
||||
// DVD identically. No-op for a disc format that cannot carry CSS (HD-DVD
|
||||
// and the BD families — `disc_format`, NOT the MPEG-PS container, which
|
||||
// DVD and HD-DVD share), for AACS / already-keyed / genuinely-clear
|
||||
// input, and for `raw`; a scrambled-but-uncrackable DVD is a hard
|
||||
// `CssKeyMissing`. `halt` is passed here (not deferred to `with_halt`)
|
||||
// so a Stop during the crack scan is honored — the scan runs at
|
||||
// construction, before the caller can attach a token.
|
||||
// DVD identically. No-op for AACS / already-keyed / genuinely-clear input
|
||||
// or `raw`; a scrambled-but-uncrackable DVD is a hard `CssKeyMissing`.
|
||||
// `halt` is passed here (not deferred to `with_halt`) so a Stop during the
|
||||
// crack scan is honored — the scan runs at construction, before the caller
|
||||
// can attach a token.
|
||||
crate::css::resolve_dvd_title_key(
|
||||
&mut *reader,
|
||||
&extents,
|
||||
&mut decrypt_keys,
|
||||
batch_sectors,
|
||||
content_format,
|
||||
disc_format,
|
||||
raw,
|
||||
halt.as_ref(),
|
||||
)?;
|
||||
@@ -1218,7 +1202,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8, // request 8 sectors (16384 B); the source delivers 1 (2048 B)
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1309,7 +1292,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1346,7 +1328,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1459,7 +1440,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1503,7 +1483,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
crate::disc::ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1539,7 +1518,6 @@ mod tests {
|
||||
aacs,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1702,7 +1680,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1819,7 +1796,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -1883,7 +1859,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -2004,7 +1979,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -2064,7 +2038,6 @@ mod tests {
|
||||
keys,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -2164,7 +2137,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -2209,7 +2181,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
crate::disc::ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -2267,7 +2238,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
@@ -2277,36 +2247,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// The HD-DVD counterpart of the test above, pinned at the SAME boundary so
|
||||
/// the disc-format axis is proven to reach the shared CSS step through this
|
||||
/// constructor and not just inside `css::resolve_dvd_title_key`.
|
||||
///
|
||||
/// Byte-for-byte identical input to `disc_stream_new_dvd_none_scrambled_hard_fails`
|
||||
/// — same `LockedReader`, same MPEG-PS title, same `None` keys — with only
|
||||
/// the disc format changed. The DVD case must still be refused (E7023) and
|
||||
/// the HD-DVD case must construct: an HD-DVD is AACS and has no CSS, so
|
||||
/// there is no CSS key for it to be missing. Catches the mutation of
|
||||
/// dropping `disc_format` from `DiscStream::new`'s plumbing (or hardcoding
|
||||
/// a CSS-capable value there), which is exactly the shape of the shipped
|
||||
/// defect: E7023 on a perfectly good HD-DVD.
|
||||
#[test]
|
||||
fn disc_stream_new_hddvd_none_scrambled_does_not_hard_fail() {
|
||||
let res = DiscStream::new(
|
||||
Box::new(LockedReader),
|
||||
mpegps_title(8),
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::HdDvd,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
assert!(
|
||||
res.is_ok(),
|
||||
"an HD-DVD must never be refused for a missing CSS key — it carries no CSS"
|
||||
);
|
||||
}
|
||||
|
||||
/// `raw` must bypass the CSS crack at the DiscStream boundary too: the same
|
||||
/// scrambled-uncrackable input that hard-fails above must CONSTRUCT in raw
|
||||
/// mode (ciphertext passthrough), never hard-fail.
|
||||
@@ -2318,7 +2258,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
true, // raw
|
||||
None,
|
||||
);
|
||||
@@ -2532,7 +2471,6 @@ mod tests {
|
||||
crate::decrypt::DecryptKeys::None,
|
||||
8,
|
||||
ContentFormat::MpegPs,
|
||||
crate::disc::DiscFormat::Dvd,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
@@ -2654,7 +2592,6 @@ mod tests {
|
||||
},
|
||||
3,
|
||||
ContentFormat::BdTs,
|
||||
crate::disc::DiscFormat::BluRay,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user