1use crate::error::FileVaultError;
18use crate::read::{le_u16, le_u32, le_u64};
19use crate::volume_header::VolumeHeader;
20use crate::xts;
21
22const BLOCK_TYPE_ENCRYPTED_METADATA_POINTER: u16 = 0x0011;
25const METADATA_UNIT_SIZE: usize = 8192;
27pub const BLOCK_HEADER_SIZE: usize = 64;
30const BLOCK_HEADER_TYPE_OFFSET: usize = 10;
32const PAYLOAD_METADATA_SIZE_OFFSET: usize = 0;
34const PAYLOAD_VG_DESCRIPTOR_OFFSET: usize = 156;
36const VG_COUNT_OFFSET: usize = 8;
38const VG_PRIMARY_BLOCK_OFFSET: usize = 32;
41const BLOCK_NUMBER_MASK: u64 = 0x0000_ffff_ffff_ffff;
43const MAX_METADATA_BLOCKS: u64 = 1 << 20;
45const MAX_ENCRYPTED_METADATA_BLOCKS: u64 = 1 << 20;
47
48#[derive(Debug, Clone)]
50pub struct EncryptedMetadataLocation {
51 pub primary_offset: u64,
53 pub block_count: u64,
55 pub length: u64,
57}
58
59#[must_use]
61pub fn plaintext_metadata_region(header: &VolumeHeader) -> (u64, u64) {
62 let block = header.metadata_block_numbers.first().copied().unwrap_or(0);
63 let offset = block.saturating_mul(u64::from(header.block_size));
64 (offset, 0)
65}
66
67pub fn plaintext_metadata_size(
74 header: &VolumeHeader,
75 first_block: &[u8],
76) -> Result<u64, FileVaultError> {
77 let block_type = le_u16(first_block, BLOCK_HEADER_TYPE_OFFSET);
78 if block_type != BLOCK_TYPE_ENCRYPTED_METADATA_POINTER {
79 return Err(FileVaultError::MetadataStructureMissing {
80 what: "0x0011 encrypted-metadata pointer block",
81 });
82 }
83 let size = u64::from(le_u32(
84 first_block,
85 BLOCK_HEADER_SIZE + PAYLOAD_METADATA_SIZE_OFFSET,
86 ));
87 let block_size = u64::from(header.block_size);
88 if block_size == 0 {
89 return Err(FileVaultError::OutOfRange {
90 what: "block size is zero",
91 });
92 }
93 if size == 0 || size / block_size > MAX_METADATA_BLOCKS {
94 return Err(FileVaultError::OutOfRange {
95 what: "plaintext metadata region size",
96 });
97 }
98 Ok(size)
99}
100
101pub fn locate_encrypted_metadata(
111 header: &VolumeHeader,
112 region: &[u8],
113) -> Result<EncryptedMetadataLocation, FileVaultError> {
114 let block_type = le_u16(region, BLOCK_HEADER_TYPE_OFFSET);
115 if block_type != BLOCK_TYPE_ENCRYPTED_METADATA_POINTER {
116 return Err(FileVaultError::MetadataStructureMissing {
117 what: "0x0011 encrypted-metadata pointer block",
118 });
119 }
120
121 let descriptor_offset =
122 le_u32(region, BLOCK_HEADER_SIZE + PAYLOAD_VG_DESCRIPTOR_OFFSET) as usize;
123
124 if descriptor_offset
126 .checked_add(VG_PRIMARY_BLOCK_OFFSET + 8)
127 .map_or(true, |end| end > region.len())
128 {
129 return Err(FileVaultError::MetadataStructureMissing {
130 what: "volume-groups descriptor (offset out of region)",
131 });
132 }
133
134 let block_count = le_u64(region, descriptor_offset + VG_COUNT_OFFSET);
135 let primary_block =
136 le_u64(region, descriptor_offset + VG_PRIMARY_BLOCK_OFFSET) & BLOCK_NUMBER_MASK;
137
138 if block_count == 0 || block_count > MAX_ENCRYPTED_METADATA_BLOCKS {
139 return Err(FileVaultError::OutOfRange {
140 what: "encrypted-metadata block count",
141 });
142 }
143
144 let block_size = u64::from(header.block_size);
145 let primary_offset =
146 primary_block
147 .checked_mul(block_size)
148 .ok_or(FileVaultError::OutOfRange {
149 what: "encrypted-metadata primary offset",
150 })?;
151 let length = block_count
152 .checked_mul(block_size)
153 .ok_or(FileVaultError::OutOfRange {
154 what: "encrypted-metadata length",
155 })?;
156
157 Ok(EncryptedMetadataLocation {
158 primary_offset,
159 block_count,
160 length,
161 })
162}
163
164pub fn decrypt_metadata(header: &VolumeHeader, ciphertext: &mut [u8]) {
168 xts::decrypt_units(
169 ciphertext,
170 &header.key_data,
171 &header.physical_volume_identifier,
172 METADATA_UNIT_SIZE,
173 0,
174 );
175}
176
177const BLOCK_TYPE_SEGMENT_DESCRIPTOR: u16 = 0x0305;
179const SEG_FIRST_ENTRY_PAYLOAD_OFFSET: usize = 8;
181const SEG_ENTRY_STRIDE: usize = 40;
183const SEG_LOGICAL_BLOCK_OFFSET: usize = 8;
185const SEG_NUM_BLOCKS_OFFSET: usize = 16;
187const SEG_PHYSICAL_BLOCK_OFFSET: usize = 32;
189const MAX_SEGMENTS: u32 = 1 << 16;
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194pub struct SegmentEntry {
195 pub logical_block: u64,
197 pub physical_block: u64,
199 pub number_of_blocks: u32,
201}
202
203#[must_use]
210pub fn parse_segments(metadata: &[u8], block_size: usize) -> Vec<SegmentEntry> {
211 let Some(block_offset) =
212 find_block_of_type(metadata, block_size, BLOCK_TYPE_SEGMENT_DESCRIPTOR)
213 else {
214 return Vec::new();
215 };
216 let payload = block_offset + BLOCK_HEADER_SIZE;
217 let count = le_u32(metadata, payload).min(MAX_SEGMENTS);
218
219 let mut out = Vec::new();
220 for i in 0..count as usize {
221 let entry = payload + SEG_FIRST_ENTRY_PAYLOAD_OFFSET + i * SEG_ENTRY_STRIDE;
222 if entry + SEG_ENTRY_STRIDE > block_offset + block_size {
225 break;
226 }
227 let physical_block =
228 le_u64(metadata, entry + SEG_PHYSICAL_BLOCK_OFFSET) & BLOCK_NUMBER_MASK;
229 out.push(SegmentEntry {
230 logical_block: le_u64(metadata, entry + SEG_LOGICAL_BLOCK_OFFSET),
231 physical_block,
232 number_of_blocks: le_u32(metadata, entry + SEG_NUM_BLOCKS_OFFSET),
233 });
234 }
235 out
236}
237
238#[must_use]
240fn find_block_of_type(metadata: &[u8], block_size: usize, block_type: u16) -> Option<usize> {
241 if block_size == 0 {
242 return None; }
244 let mut offset = 0;
245 while offset + block_size <= metadata.len() {
246 if le_u16(metadata, offset + BLOCK_HEADER_TYPE_OFFSET) == block_type {
247 return Some(offset);
248 }
249 offset += block_size;
250 }
251 None
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 fn header(block_size: u32, mbn0: u64) -> VolumeHeader {
259 VolumeHeader {
260 block_size,
261 bytes_per_sector: 512,
262 physical_volume_size: 0,
263 metadata_block_numbers: [mbn0, 0, 0, 0],
264 key_data: [0u8; 16],
265 physical_volume_identifier: [0u8; 16],
266 }
267 }
268
269 fn build_region(block_size: usize, count: u64, primary: u64) -> Vec<u8> {
273 let mut region = vec![0u8; block_size * 4];
274 region[10..12].copy_from_slice(&BLOCK_TYPE_ENCRYPTED_METADATA_POINTER.to_le_bytes());
276 let payload = BLOCK_HEADER_SIZE;
278 region[payload..payload + 4].copy_from_slice(&((block_size as u32) * 4).to_le_bytes());
280 let desc = 2 * block_size;
282 region[payload + PAYLOAD_VG_DESCRIPTOR_OFFSET..payload + PAYLOAD_VG_DESCRIPTOR_OFFSET + 4]
283 .copy_from_slice(&(desc as u32).to_le_bytes());
284 region[desc + VG_COUNT_OFFSET..desc + VG_COUNT_OFFSET + 8]
285 .copy_from_slice(&count.to_le_bytes());
286 let primary_field = primary | (0x1234u64 << 48);
288 region[desc + VG_PRIMARY_BLOCK_OFFSET..desc + VG_PRIMARY_BLOCK_OFFSET + 8]
289 .copy_from_slice(&primary_field.to_le_bytes());
290 region
291 }
292
293 #[test]
294 fn locates_encrypted_metadata_masking_volume_index() {
295 let h = header(4096, 1);
296 let region = build_region(4096, 6144, 2049);
297 let loc = locate_encrypted_metadata(&h, ®ion).unwrap();
298 assert_eq!(loc.block_count, 6144);
299 assert_eq!(loc.primary_offset, 2049 * 4096);
300 assert_eq!(loc.length, 6144 * 4096);
301 }
302
303 #[test]
304 fn rejects_wrong_first_block_type() {
305 let h = header(4096, 1);
306 let mut region = build_region(4096, 6144, 2049);
307 region[10..12].copy_from_slice(&0x0010u16.to_le_bytes());
308 assert!(matches!(
309 locate_encrypted_metadata(&h, ®ion),
310 Err(FileVaultError::MetadataStructureMissing { .. })
311 ));
312 }
313
314 #[test]
315 fn rejects_absurd_block_count() {
316 let h = header(4096, 1);
317 let region = build_region(4096, u64::from(u32::MAX), 2049);
318 assert!(matches!(
319 locate_encrypted_metadata(&h, ®ion),
320 Err(FileVaultError::OutOfRange { .. })
321 ));
322 }
323
324 #[test]
325 fn descriptor_offset_out_of_region_is_missing() {
326 let h = header(4096, 1);
327 let mut region = build_region(4096, 6144, 2049);
328 let payload = BLOCK_HEADER_SIZE;
329 let beyond = region.len() as u32 + 100;
331 region[payload + PAYLOAD_VG_DESCRIPTOR_OFFSET..payload + PAYLOAD_VG_DESCRIPTOR_OFFSET + 4]
332 .copy_from_slice(&beyond.to_le_bytes());
333 assert!(matches!(
334 locate_encrypted_metadata(&h, ®ion),
335 Err(FileVaultError::MetadataStructureMissing { .. })
336 ));
337 }
338
339 #[test]
340 fn plaintext_size_reads_from_pointer_block() {
341 let h = header(4096, 1);
342 let region = build_region(4096, 6144, 2049);
343 let size = plaintext_metadata_size(&h, ®ion[..4096]).unwrap();
344 assert_eq!(size, 4096 * 4);
345 }
346
347 #[test]
348 fn plaintext_size_rejects_non_pointer_block() {
349 let h = header(4096, 1);
350 let block = vec![0u8; 4096];
351 assert!(matches!(
352 plaintext_metadata_size(&h, &block),
353 Err(FileVaultError::MetadataStructureMissing { .. })
354 ));
355 }
356
357 #[test]
358 fn plaintext_region_offset_is_block_scaled() {
359 let h = header(4096, 3);
360 assert_eq!(plaintext_metadata_region(&h), (3 * 4096, 0));
361 }
362
363 #[test]
364 fn parses_single_segment() {
365 let block_size = 4096usize;
366 let mut meta = vec![0u8; block_size];
367 meta[10..12].copy_from_slice(&BLOCK_TYPE_SEGMENT_DESCRIPTOR.to_le_bytes());
368 let payload = BLOCK_HEADER_SIZE;
369 meta[payload..payload + 4].copy_from_slice(&1u32.to_le_bytes()); let entry = payload + SEG_FIRST_ENTRY_PAYLOAD_OFFSET;
371 meta[entry + SEG_LOGICAL_BLOCK_OFFSET..entry + SEG_LOGICAL_BLOCK_OFFSET + 8]
372 .copy_from_slice(&0u64.to_le_bytes());
373 meta[entry + SEG_NUM_BLOCKS_OFFSET..entry + SEG_NUM_BLOCKS_OFFSET + 4]
374 .copy_from_slice(&40960u32.to_le_bytes());
375 let phys = 16384u64 | (0x1234u64 << 48);
376 meta[entry + SEG_PHYSICAL_BLOCK_OFFSET..entry + SEG_PHYSICAL_BLOCK_OFFSET + 8]
377 .copy_from_slice(&phys.to_le_bytes());
378 let segs = parse_segments(&meta, block_size);
379 assert_eq!(segs.len(), 1);
380 assert_eq!(segs[0].physical_block, 16384);
381 assert_eq!(segs[0].number_of_blocks, 40960);
382 assert_eq!(segs[0].logical_block, 0);
383 }
384
385 #[test]
386 fn no_segment_block_yields_empty() {
387 let meta = vec![0u8; 4096];
388 assert!(parse_segments(&meta, 4096).is_empty());
389 }
390
391 #[test]
392 fn decrypt_metadata_roundtrips() {
393 let mut h = header(4096, 1);
394 h.key_data = [0x11u8; 16];
395 h.physical_volume_identifier = [0x22u8; 16];
396 let plain: Vec<u8> = (0..8192u32).map(|i| (i & 0xff) as u8).collect();
397 let mut buf = plain.clone();
398 crate::xts::encrypt_units(
399 &mut buf,
400 &h.key_data,
401 &h.physical_volume_identifier,
402 8192,
403 0,
404 );
405 decrypt_metadata(&h, &mut buf);
406 assert_eq!(buf, plain);
407 }
408
409 #[test]
410 fn plaintext_size_rejects_zero_block_size() {
411 let h = header(0, 1);
412 let region = build_region(4096, 6144, 2049);
413 assert!(matches!(
414 plaintext_metadata_size(&h, ®ion[..4096]),
415 Err(FileVaultError::OutOfRange {
416 what: "block size is zero"
417 })
418 ));
419 }
420
421 #[test]
422 fn plaintext_size_rejects_absurd_size() {
423 let h = header(512, 1);
425 let mut block = vec![0u8; 4096];
426 block[10..12].copy_from_slice(&BLOCK_TYPE_ENCRYPTED_METADATA_POINTER.to_le_bytes());
427 block[BLOCK_HEADER_SIZE..BLOCK_HEADER_SIZE + 4].copy_from_slice(&u32::MAX.to_le_bytes());
428 assert!(matches!(
429 plaintext_metadata_size(&h, &block),
430 Err(FileVaultError::OutOfRange {
431 what: "plaintext metadata region size"
432 })
433 ));
434 }
435
436 #[test]
437 fn parse_segments_zero_block_size_is_empty() {
438 assert!(parse_segments(&[0u8; 4096], 0).is_empty());
440 }
441
442 #[test]
443 fn parse_segments_stops_on_lying_count() {
444 let block_size = 4096usize;
447 let mut meta = vec![0u8; block_size];
448 meta[10..12].copy_from_slice(&BLOCK_TYPE_SEGMENT_DESCRIPTOR.to_le_bytes());
449 let payload = BLOCK_HEADER_SIZE;
450 meta[payload..payload + 4].copy_from_slice(&1000u32.to_le_bytes());
452 let segs = parse_segments(&meta, block_size);
453 assert!(segs.len() < 1000);
455 }
456}