fix(udf): follow allocation-descriptor continuation past the AED header
read_icb_extents parsed a continuation block's allocation descriptors from offset 0, but a continuation block begins with a 24-byte Allocation Extent Descriptor (ECMA-167 4/14.5): 16-byte descriptor tag + prev-loc(4) + length_of_allocation_descriptors(4); the real ADs start at offset 24. Reading from 0 mis-parsed the AED tag as a descriptor -> one garbage extent, then an unknown-type break, silently truncating any file whose extents spill into a continuation block. Few-extent files store their ADs inline and never hit this path, so it stayed hidden; a heavily fragmented file (~1600 fragments) truncated to 113 extents + a bogus non-unit-aligned fragment that then aborted the mux (ExtentNotUnitAligned). Skip the 24-byte AED header, read l_ad bytes of descriptors from offset 24. Adds a regression test with a non-zero AED tag.
This commit is contained in:
+58
-5
@@ -593,10 +593,27 @@ impl UdfFs {
|
|||||||
match next_block {
|
match next_block {
|
||||||
Some(cont_lba) => {
|
Some(cont_lba) => {
|
||||||
read_sector(reader, self.meta_to_abs(cont_lba)?, &mut block)?;
|
read_sector(reader, self.meta_to_abs(cont_lba)?, &mut block)?;
|
||||||
// A continuation block is a list of Short ADs from byte 0,
|
// A continuation block does NOT begin with allocation
|
||||||
// spanning the whole sector.
|
// descriptors — it begins with a 24-byte Allocation Extent
|
||||||
ad_start = 0;
|
// Descriptor (ECMA-167 4/14.5, tag id 258): the 16-byte
|
||||||
ad_bytes = block.len();
|
// descriptor tag, then prev_allocation_extent_location
|
||||||
|
// (Uint32 @16) and length_of_allocation_descriptors
|
||||||
|
// (Uint32 @20). The real ADs (same Short/Long/Extended type
|
||||||
|
// as the file) start at offset 24, and their total byte
|
||||||
|
// length is that @20 field.
|
||||||
|
//
|
||||||
|
// Reading from offset 0 parses the AED's own tag header as
|
||||||
|
// allocation descriptors → one garbage extent, then an
|
||||||
|
// unknown extent_type break — silently truncating every
|
||||||
|
// file whose ADs spill into a continuation block (a heavily
|
||||||
|
// fragmented file, e.g. a Blu-ray 3D interleaved base-view
|
||||||
|
// .m2ts with ~1600 fragments). A normal few-extent .m2ts
|
||||||
|
// fits inline and never reaches here, which is why this
|
||||||
|
// stayed hidden.
|
||||||
|
let aed_l_ad =
|
||||||
|
u32::from_le_bytes([block[20], block[21], block[22], block[23]]) as usize;
|
||||||
|
ad_start = 24;
|
||||||
|
ad_bytes = aed_l_ad.min(block.len().saturating_sub(24));
|
||||||
}
|
}
|
||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
@@ -1535,8 +1552,15 @@ mod tests {
|
|||||||
|
|
||||||
/// A continuation block: a bare list of short ADs from byte 0.
|
/// A continuation block: a bare list of short ADs from byte 0.
|
||||||
fn build_cont_block(ads: &[(u32, u32, u32)]) -> [u8; 2048] {
|
fn build_cont_block(ads: &[(u32, u32, u32)]) -> [u8; 2048] {
|
||||||
|
// A continuation block is an Allocation Extent Descriptor (ECMA-167
|
||||||
|
// 4/14.5): a 16-byte descriptor tag, then prev_allocation_extent_location
|
||||||
|
// (Uint32 @16) and length_of_allocation_descriptors (Uint32 @20). The
|
||||||
|
// actual allocation descriptors begin at offset 24. The parser skips the
|
||||||
|
// 24-byte header and reads `l_ad` bytes of ADs from there.
|
||||||
let mut s = [0u8; 2048];
|
let mut s = [0u8; 2048];
|
||||||
let mut off = 0usize;
|
let l_ad = (ads.len() * 8) as u32;
|
||||||
|
s[20..24].copy_from_slice(&l_ad.to_le_bytes());
|
||||||
|
let mut off = 24usize;
|
||||||
for &(etype, dlen, dlba) in ads {
|
for &(etype, dlen, dlba) in ads {
|
||||||
let raw_len = (etype << 30) | (dlen & 0x3FFF_FFFF);
|
let raw_len = (etype << 30) | (dlen & 0x3FFF_FFFF);
|
||||||
s[off..off + 4].copy_from_slice(&raw_len.to_le_bytes());
|
s[off..off + 4].copy_from_slice(&raw_len.to_le_bytes());
|
||||||
@@ -1590,6 +1614,35 @@ mod tests {
|
|||||||
assert_eq!(extents, vec![(10, 4096), (20, 2048)]);
|
assert_eq!(extents, vec![(10, 4096), (20, 2048)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn icb_extents_continuation_skips_aed_header_not_read_as_extent() {
|
||||||
|
// Regression (Blu-ray 3D): a continuation block begins with a 24-byte
|
||||||
|
// Allocation Extent Descriptor whose 16-byte descriptor tag holds
|
||||||
|
// NON-zero bytes (tag id 258, CRC, location, ...). Reading allocation
|
||||||
|
// descriptors from offset 0 parses that tag header as an AD → a garbage
|
||||||
|
// extent, then an unknown extent_type break → every fragment past the
|
||||||
|
// first continuation is lost. On a 3D disc that truncated the 25.8 GB
|
||||||
|
// interleaved base-view feature to ~1.8 GB (113 of ~1600 fragments) and
|
||||||
|
// then choked the mux on the bogus, non-unit-aligned extent. The parser
|
||||||
|
// MUST skip the 24-byte AED header and read the real ADs from offset 24.
|
||||||
|
let icb = build_efe(6144, &[(0, 4096, 10), (3, 2048, 50)]);
|
||||||
|
let mut cont = build_cont_block(&[(0, 2048, 20)]);
|
||||||
|
// Stamp a realistic AED descriptor tag (id 258) into the header so a
|
||||||
|
// regression that reads from offset 0 mis-parses it as a bogus extent
|
||||||
|
// instead of finding the real (20, 2048) at offset 24.
|
||||||
|
cont[0..2].copy_from_slice(&258u16.to_le_bytes());
|
||||||
|
let mut reader = MapReader::new();
|
||||||
|
reader.put(5, icb);
|
||||||
|
reader.put(50, cont);
|
||||||
|
let fs = fs_with(0, 0, file_entry("3D", 5, 6144));
|
||||||
|
let extents = fs.read_icb_extents(&mut reader, 5).expect("extents");
|
||||||
|
assert_eq!(
|
||||||
|
extents,
|
||||||
|
vec![(10, 4096), (20, 2048)],
|
||||||
|
"continuation ADs must be read past the 24-byte AED header, not from offset 0"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn icb_extents_long_ad_returns_all_extents_not_just_first() {
|
fn icb_extents_long_ad_returns_all_extents_not_just_first() {
|
||||||
// Regression: BD-ROM large .m2ts files use Long ADs (16-byte
|
// Regression: BD-ROM large .m2ts files use Long ADs (16-byte
|
||||||
|
|||||||
Reference in New Issue
Block a user