Audit round 1: playback order, silent title drops, image durability
Four fixes from the first audit round. Every finding was verified against a pinned tree and read directly before being accepted. resolve_vts_key sorted a VTS title-VOB extents largest-first. That is the 1.5.1 garbage bug, and it grew back in a new code path: the comment claimed it "matched the scan heuristic", but that heuristic WAS the bug and had already been fixed in decrypt_keys_for_title, which documents the rule (PLAYBACK ORDER, never largest-cell-first) and pins it with a regression test. A CSS DVDs biggest cell opens with a long clear run and crack_key shares one sector budget across the extent list, so starting there can exhaust it without ever MEETING scrambled data — and CSS recovers the key from scrambled data itself. The crack then returns None, the caller falls back to the disc-wide key, and every VOB in that VTS is descrambled wrongly: corrupt PES behind an intact header, written out as a complete extract at exit 0. parse_pgcit dropped titles silently in THREE places — an unparseable PGC, an out-of-range PGC index, and a truncated entry table. The finder caught one; the other two turned up on reading the function. parse_vmg already counts and warns per skipped title SET for exactly this reason, and this was the last place a disc could quietly report fewer titles than it has. write_image called flush() and returned Ok. flush() only pushes bytes into the page cache and promises nothing about durability, so a 6-90 GB image could be reported complete while still unwritten — a crash or an unmounted volume then leaves a truncated file the caller was told was finished. Now into_inner (so a buffered-write error surfaces instead of being dropped by BufWriter::drop) followed by sync_all. timeline used abs() on a saturating_sub result. Every other comparison in that module is saturating because the timestamps come off a disc and are not trusted; abs() panics on i64::MIN, which saturating_sub can produce.
This commit is contained in:
+49
-4
@@ -825,16 +825,36 @@ fn parse_pgcit(
|
||||
let entries_start = pgcit_offset + 8;
|
||||
|
||||
let mut titles = Vec::new();
|
||||
// Every `continue` below drops a title the user will never see. None of
|
||||
// them may be silent: `parse_vmg` already counts and warns per skipped
|
||||
// title SET, and this function was the remaining place where a disc could
|
||||
// quietly report fewer titles than it has.
|
||||
let mut skipped = 0usize;
|
||||
|
||||
for &(chapter_count, vts_title_num) in titles_info {
|
||||
// VTS title numbers are 1-based; map to PGC index (typically 1:1)
|
||||
let pgc_index = vts_title_num.saturating_sub(1) as usize;
|
||||
if pgc_index >= num_pgcs as usize {
|
||||
skipped += 1;
|
||||
tracing::warn!(
|
||||
target: "freemkv::scan",
|
||||
pgc_index,
|
||||
num_pgcs,
|
||||
"title points past the end of the PGC table; its title is omitted"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let entry_offset = entries_start + pgc_index * 8;
|
||||
if entry_offset + 8 > data.len() {
|
||||
skipped += 1;
|
||||
tracing::warn!(
|
||||
target: "freemkv::scan",
|
||||
pgc_index,
|
||||
entry_offset,
|
||||
len = data.len(),
|
||||
"PGC entry table is truncated; this title is omitted"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -846,13 +866,38 @@ fn parse_pgcit(
|
||||
|
||||
match parse_pgc(data, pgc_abs, chapter_count) {
|
||||
Ok(title) => titles.push(title),
|
||||
// By design: a single unparseable PGC (truncated/corrupt entry,
|
||||
// authoring-tool quirk) must not lose the whole title list.
|
||||
// Skip it and keep collecting the titles that do parse.
|
||||
Err(_) => continue,
|
||||
Err(e) => {
|
||||
// By design a single unparseable PGC (truncated/corrupt entry,
|
||||
// authoring-tool quirk) must not lose the whole title list. But
|
||||
// "not fatal" is not the same as "not worth saying": every PGC
|
||||
// skipped here is a title the user will never see, and this was
|
||||
// the only remaining silent one — `parse_vmg` above already
|
||||
// counts and warns per skipped title SET for exactly this
|
||||
// reason. A disc quietly reporting fewer titles than it has is
|
||||
// the failure this release exists to stop.
|
||||
skipped += 1;
|
||||
tracing::warn!(
|
||||
target: "freemkv::scan",
|
||||
pgc = pgc_index + 1,
|
||||
of = num_pgcs,
|
||||
error = %e,
|
||||
"PGC could not be parsed; its title is omitted"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if skipped > 0 {
|
||||
tracing::warn!(
|
||||
target: "freemkv::scan",
|
||||
skipped,
|
||||
kept = titles.len(),
|
||||
declared = titles_info.len(),
|
||||
"some titles were omitted from this title set"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(titles)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user