Audit round 2 fixes: an unsafe default, four omissions, and two swallowed errors

The folder encryption probe returned "not encrypted" when it had sampled
nothing at all — a title shorter than one aligned unit skipped the loop
entirely. That verdict CLEARS the structural one an AACS directory
raised, so a genuinely encrypted folder would have been ripped as clear
and written ciphertext as video at exit 0. With no evidence it now keeps
the structural verdict, and its bounds arithmetic no longer trusts
disc-derived values not to wrap.

Reading an IFO header swallowed every I/O error and returned an empty
buffer, which sent each placement offset through unwrap_or(0) and
recorded no constraint at all — a permission error on one file produced a
silently misplaced VOB. The directory walk swallowed the same class while
claiming to skip only vanished files. Both now propagate; only NotFound
is skipped.

Four things the round-1 changes left inconsistent: two new error codes had
no doc comments, were absent from the io::Error mapping, printed no path
in Display, and were missing from the test that proves codes are distinct.
The demux sink dropped frames silently while the MKV muxer reported them.
And set_clips had been inserted INTO write_frame's doc comment, leaving
write_frame undocumented and its paragraphs describing the wrong function.

uid/gid used 0 as "not specified"; UDF's sentinel is 0xFFFFFFFF, and 0 is
root.
This commit is contained in:
Matthew Jackson
2026-08-05 17:16:03 -07:00
parent cbb3517afe
commit 60d9cc1bac
6 changed files with 88 additions and 36 deletions
+18 -2
View File
@@ -520,17 +520,33 @@ fn probe_folder_encryption(reader: &mut dyn SectorSource, disc: &Disc) -> Result
};
let base = extent.start_lba;
let mut unit = vec![0u8; UNIT_SECTORS as usize * SECTOR_BYTES];
let mut sampled = 0u32;
for i in 0..AACS_PROBE_UNITS as u32 {
let lba = base + i * UNIT_SECTORS;
if lba + UNIT_SECTORS > base + extent.sector_count {
// Saturating: `start_lba` and `sector_count` come off the medium, and a
// crafted or corrupt extent must not wrap this bound into a read past
// the end of the content.
let Some(lba) = base.checked_add(i.saturating_mul(UNIT_SECTORS)) else {
break;
};
let end = base.saturating_add(extent.sector_count);
if lba.saturating_add(UNIT_SECTORS) > end {
break;
}
debug_assert!(is_unit_aligned(lba, base));
reader.read_sectors(lba, UNIT_SECTORS as u16, &mut unit, false)?;
sampled += 1;
if aacs_unit_needs_decrypt(&unit, disc.content_format) {
return Ok(true);
}
}
// Nothing was actually sampled — the largest title is shorter than one
// aligned unit, so there is no evidence either way. "Not encrypted" is the
// dangerous default here: it would clear the structural verdict an `AACS`
// directory raised and rip ciphertext as though it were video, at exit 0.
// With no evidence, keep the structural verdict.
if sampled == 0 {
return Ok(true);
}
Ok(false)
}