1use std::io::{Read, Seek, SeekFrom};
11
12use crate::attribute::{Attribute, AttributeBody};
13use crate::error::{NtfsError, Result};
14use crate::runlist::{self, Run};
15
16const MAX_VALUE_BYTES: u64 = 1 << 40;
19
20pub fn read_runs<R: Read + Seek>(
30 reader: &mut R,
31 runs: &[Run],
32 cluster_size: u64,
33 real_size: u64,
34) -> Result<Vec<u8>> {
35 read_runs_capped(reader, runs, cluster_size, real_size, u64::MAX)
36}
37
38pub fn read_runs_capped<R: Read + Seek>(
48 reader: &mut R,
49 runs: &[Run],
50 cluster_size: u64,
51 real_size: u64,
52 max_bytes: u64,
53) -> Result<Vec<u8>> {
54 let mut allocated = 0u64;
56 for r in runs {
57 let run_bytes = r
58 .length
59 .checked_mul(cluster_size)
60 .ok_or(NtfsError::BadRunlist("run byte length overflow"))?;
61 allocated = allocated
62 .checked_add(run_bytes)
63 .ok_or(NtfsError::BadRunlist("allocation overflow"))?;
64 }
65
66 let want = real_size.min(allocated).min(max_bytes);
67 if want > MAX_VALUE_BYTES {
68 return Err(NtfsError::TooLarge { bytes: want });
69 }
70 let want_usize = usize::try_from(want).map_err(|_| NtfsError::TooLarge { bytes: want })?;
71
72 let mut out: Vec<u8> = Vec::new();
73 out.try_reserve_exact(want_usize)
74 .map_err(|_| NtfsError::TooLarge { bytes: want })?;
75
76 let mut remaining = want;
77 for r in runs {
78 if remaining == 0 {
79 break;
80 }
81 let run_bytes = r.length * cluster_size; let take = run_bytes.min(remaining);
83 let take_usize = take as usize; match r.lcn {
86 None => out.resize(out.len() + take_usize, 0), Some(lcn) => {
88 let byte_off = lcn
89 .checked_mul(cluster_size)
90 .ok_or(NtfsError::BadRunlist("LCN byte offset overflow"))?;
91 reader.seek(SeekFrom::Start(byte_off))?;
92 let start = out.len();
93 out.resize(start + take_usize, 0);
94 reader.read_exact(&mut out[start..])?;
95 }
96 }
97 remaining -= take;
98 }
99
100 Ok(out)
101}
102
103pub fn read_attribute_value<R: Read + Seek>(
113 reader: &mut R,
114 record: &[u8],
115 attribute: &Attribute,
116 cluster_size: u64,
117) -> Result<Vec<u8>> {
118 match attribute.body {
119 AttributeBody::Resident { .. } => attribute
120 .resident_content(record)
121 .map(<[u8]>::to_vec)
122 .ok_or(NtfsError::BadAttribute {
123 offset: attribute.offset,
124 detail: "resident content out of bounds",
125 }),
126 AttributeBody::NonResident { real_size, .. } => {
127 let runs = attribute_runlist(record, attribute)?;
128 read_nonresident(reader, &runs, cluster_size, real_size, attribute)
129 }
130 }
131}
132
133pub(crate) fn read_nonresident<R: Read + Seek>(
148 reader: &mut R,
149 runs: &[Run],
150 cluster_size: u64,
151 real_size: u64,
152 attribute: &Attribute,
153) -> Result<Vec<u8>> {
154 read_nonresident_capped(reader, runs, cluster_size, real_size, attribute, u64::MAX)
155}
156
157pub(crate) fn read_nonresident_capped<R: Read + Seek>(
165 reader: &mut R,
166 runs: &[Run],
167 cluster_size: u64,
168 real_size: u64,
169 attribute: &Attribute,
170 max_bytes: u64,
171) -> Result<Vec<u8>> {
172 let cu = attribute.compression_unit();
173 if attribute.is_compressed() && cu != 0 {
174 let unit_clusters = 1u64
175 .checked_shl(u32::from(cu))
176 .ok_or(NtfsError::BadAttribute {
177 offset: attribute.offset,
178 detail: "implausible compression unit",
179 })?;
180 read_compressed_runs_capped(
181 reader,
182 runs,
183 cluster_size,
184 real_size,
185 unit_clusters,
186 max_bytes,
187 )
188 } else {
189 read_runs_capped(reader, runs, cluster_size, real_size, max_bytes)
190 }
191}
192
193fn read_compressed_runs_capped<R: Read + Seek>(
208 reader: &mut R,
209 runs: &[Run],
210 cluster_size: u64,
211 real_size: u64,
212 unit_clusters: u64,
213 max_bytes: u64,
214) -> Result<Vec<u8>> {
215 if real_size > MAX_VALUE_BYTES {
216 return Err(NtfsError::TooLarge { bytes: real_size });
217 }
218 let target = real_size.min(max_bytes);
221 let unit_bytes = unit_clusters
222 .checked_mul(cluster_size)
223 .ok_or(NtfsError::BadRunlist("compression unit byte size overflow"))?;
224
225 let mut queue: std::collections::VecDeque<(Option<u64>, u64)> =
227 runs.iter().map(|r| (r.lcn, r.length)).collect();
228 let target_usize =
229 usize::try_from(target).map_err(|_| NtfsError::TooLarge { bytes: target })?;
230 let mut out: Vec<u8> = Vec::new();
231
232 while (out.len() as u64) < target {
233 let mut real_bytes: Vec<u8> = Vec::new();
236 let mut real_clusters = 0u64;
237 let mut got = 0u64;
238 while got < unit_clusters {
239 let Some((lcn, avail)) = queue.front_mut() else {
240 break;
241 };
242 let take = (unit_clusters - got).min(*avail);
243 if let Some(l) = *lcn {
244 let byte_off = l
245 .checked_mul(cluster_size)
246 .ok_or(NtfsError::BadRunlist("LCN byte offset overflow"))?;
247 let nbytes =
248 usize::try_from(take * cluster_size).map_err(|_| NtfsError::TooLarge {
249 bytes: take * cluster_size, })?; reader.seek(SeekFrom::Start(byte_off))?;
253 let start = real_bytes.len();
254 real_bytes.resize(start + nbytes, 0);
255 reader.read_exact(&mut real_bytes[start..])?;
256 real_clusters += take;
257 *lcn = Some(l + take); }
259 *avail -= take;
260 if *avail == 0 {
261 queue.pop_front();
262 }
263 got += take;
264 }
265 if got == 0 {
266 break; }
268
269 if real_clusters == 0 {
270 let want = unit_bytes.min(target - out.len() as u64);
272 let want = usize::try_from(want).map_err(|_| NtfsError::TooLarge { bytes: want })?;
273 out.resize(out.len() + want, 0);
274 } else if real_clusters == unit_clusters {
275 out.extend_from_slice(&real_bytes);
277 } else {
278 let mut decompressed = Vec::new();
283 lznt1::decompress(&real_bytes, &mut decompressed)
284 .map_err(|_| NtfsError::BadCompression("LZNT1 decode failed"))?;
285 decompressed.truncate(unit_bytes as usize);
286 out.extend_from_slice(&decompressed);
287 }
288 }
289
290 out.truncate(target_usize);
291 Ok(out)
292}
293
294pub fn attribute_runlist(record: &[u8], attribute: &Attribute) -> Result<Vec<Run>> {
305 let AttributeBody::NonResident { runs_offset, .. } = attribute.body else {
306 return Err(NtfsError::BadAttribute {
307 offset: attribute.offset,
308 detail: "attribute is resident (no runlist)",
309 });
310 };
311 let attr_end = attribute
312 .offset
313 .checked_add(attribute.length as usize)
314 .ok_or(NtfsError::BadAttribute {
315 offset: attribute.offset,
316 detail: "attribute length overflow",
317 })?;
318 let runs_start =
319 attribute
320 .offset
321 .checked_add(runs_offset as usize)
322 .ok_or(NtfsError::BadAttribute {
323 offset: attribute.offset,
324 detail: "runs offset overflow",
325 })?;
326 let runs_bytes = record
327 .get(runs_start..attr_end)
328 .ok_or(NtfsError::BadAttribute {
329 offset: attribute.offset,
330 detail: "runlist out of bounds",
331 })?;
332 runlist::decode(runs_bytes)
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use std::io::Cursor;
339
340 fn volume(clusters: usize, cluster_size: usize) -> Cursor<Vec<u8>> {
342 let mut v = vec![0u8; clusters * cluster_size];
343 for c in 0..clusters {
344 let b = c as u8;
345 for x in &mut v[c * cluster_size..(c + 1) * cluster_size] {
346 *x = b;
347 }
348 }
349 Cursor::new(v)
350 }
351
352 #[test]
353 fn reads_single_run() {
354 let mut vol = volume(4, 512);
355 let runs = [Run {
357 length: 2,
358 lcn: Some(1),
359 }];
360 let out = read_runs(&mut vol, &runs, 512, 1024).unwrap();
361 assert_eq!(out.len(), 1024);
362 assert!(out[..512].iter().all(|&b| b == 1));
363 assert!(out[512..].iter().all(|&b| b == 2));
364 }
365
366 #[test]
367 fn sparse_run_yields_zeroes_without_reading() {
368 let mut vol = volume(1, 512); let runs = [Run {
370 length: 2,
371 lcn: None,
372 }];
373 let out = read_runs(&mut vol, &runs, 512, 1024).unwrap();
374 assert_eq!(out.len(), 1024);
375 assert!(out.iter().all(|&b| b == 0));
376 }
377
378 #[test]
379 fn truncates_to_real_size() {
380 let mut vol = volume(4, 512);
381 let runs = [Run {
382 length: 2,
383 lcn: Some(0),
384 }]; let out = read_runs(&mut vol, &runs, 512, 600).unwrap();
386 assert_eq!(out.len(), 600);
387 }
388
389 #[test]
390 fn mixed_data_and_sparse() {
391 let mut vol = volume(4, 512);
392 let runs = [
393 Run {
394 length: 1,
395 lcn: Some(3),
396 }, Run {
398 length: 1,
399 lcn: None,
400 }, ];
402 let out = read_runs(&mut vol, &runs, 512, 1024).unwrap();
403 assert!(out[..512].iter().all(|&b| b == 3));
404 assert!(out[512..].iter().all(|&b| b == 0));
405 }
406
407 #[test]
408 fn refuses_implausible_size() {
409 let mut vol = volume(1, 512);
413 let runs = [Run {
414 length: 1 << 40,
415 lcn: None,
416 }];
417 assert!(matches!(
418 read_runs(&mut vol, &runs, 512, u64::MAX),
419 Err(NtfsError::TooLarge { .. })
420 ));
421 }
422
423 #[test]
424 fn rejects_cluster_size_overflow() {
425 let mut vol = volume(1, 512);
426 let runs = [Run {
427 length: u64::MAX,
428 lcn: Some(0),
429 }];
430 assert!(matches!(
431 read_runs(&mut vol, &runs, 512, 1024),
432 Err(NtfsError::BadRunlist(_))
433 ));
434 }
435
436 #[test]
439 fn reads_resident_value() {
440 use forensicnomicon::ntfs::attr_types;
441 let content = b"hello";
443 let attr_off = 0x10usize;
445 let mut record = vec![0u8; attr_off];
446 let name_offset = 0x18u16;
448 let content_offset = 0x18u16;
449 let length = (content_offset as usize + content.len() + 7) & !7;
450 let mut a = vec![0u8; length];
451 a[0x00..0x04].copy_from_slice(&attr_types::DATA.to_le_bytes());
452 a[0x04..0x08].copy_from_slice(&(length as u32).to_le_bytes());
453 a[0x0A..0x0C].copy_from_slice(&name_offset.to_le_bytes());
454 a[0x10..0x14].copy_from_slice(&(content.len() as u32).to_le_bytes());
455 a[0x14..0x16].copy_from_slice(&content_offset.to_le_bytes());
456 a[content_offset as usize..content_offset as usize + content.len()]
457 .copy_from_slice(content);
458 record.extend_from_slice(&a);
459 record.extend_from_slice(&attr_types::END.to_le_bytes());
460
461 let attrs = crate::attribute::parse_attributes(&record, attr_off).unwrap();
462 let mut vol = volume(1, 512);
463 let out = read_attribute_value(&mut vol, &record, &attrs[0], 512).unwrap();
464 assert_eq!(out, b"hello");
465 }
466
467 #[test]
468 fn reads_nonresident_value_via_runlist() {
469 use forensicnomicon::ntfs::attr_types;
470 let runs_bytes = [0x11u8, 0x01, 0x02, 0x00]; let attr_off = 0x10usize;
473 let mut record = vec![0u8; attr_off];
474 let runs_offset = 0x40u16;
475 let length = ((runs_offset as usize + runs_bytes.len()) + 7) & !7;
476 let mut a = vec![0u8; length];
477 a[0x00..0x04].copy_from_slice(&attr_types::DATA.to_le_bytes());
478 a[0x04..0x08].copy_from_slice(&(length as u32).to_le_bytes());
479 a[0x08] = 1; a[0x0A..0x0C].copy_from_slice(&runs_offset.to_le_bytes()); a[0x20..0x22].copy_from_slice(&runs_offset.to_le_bytes()); a[0x28..0x30].copy_from_slice(&512u64.to_le_bytes()); a[0x30..0x38].copy_from_slice(&512u64.to_le_bytes()); a[runs_offset as usize..runs_offset as usize + runs_bytes.len()]
485 .copy_from_slice(&runs_bytes);
486 record.extend_from_slice(&a);
487 record.extend_from_slice(&attr_types::END.to_le_bytes());
488
489 let attrs = crate::attribute::parse_attributes(&record, attr_off).unwrap();
490 let mut vol = volume(4, 512); let out = read_attribute_value(&mut vol, &record, &attrs[0], 512).unwrap();
492 assert_eq!(out.len(), 512);
493 assert!(out.iter().all(|&b| b == 2));
494 }
495
496 #[test]
497 fn reads_compressed_nonresident_value() {
498 use forensicnomicon::ntfs::attr_types;
499 let content = vec![0xABu8; 100];
505 let mut stream = Vec::new();
506 stream.extend_from_slice(&(content.len() as u16 - 1).to_le_bytes()); stream.extend_from_slice(&content);
508
509 let runs_bytes = [0x11u8, 0x01, 0x02, 0x01, 0x0F, 0x00];
511 let attr_off = 0x10usize;
512 let mut record = vec![0u8; attr_off];
513 let runs_offset = 0x40u16;
514 let length = ((runs_offset as usize + runs_bytes.len()) + 7) & !7;
515 let mut a = vec![0u8; length];
516 a[0x00..0x04].copy_from_slice(&attr_types::DATA.to_le_bytes());
517 a[0x04..0x08].copy_from_slice(&(length as u32).to_le_bytes());
518 a[0x08] = 1; a[0x0A..0x0C].copy_from_slice(&runs_offset.to_le_bytes()); a[0x0C..0x0E].copy_from_slice(&0x0001u16.to_le_bytes()); a[0x20..0x22].copy_from_slice(&runs_offset.to_le_bytes()); a[0x22..0x24].copy_from_slice(&4u16.to_le_bytes()); a[0x28..0x30].copy_from_slice(&(16u64 * 512).to_le_bytes()); a[0x30..0x38].copy_from_slice(&(content.len() as u64).to_le_bytes()); a[runs_offset as usize..runs_offset as usize + runs_bytes.len()]
526 .copy_from_slice(&runs_bytes);
527 record.extend_from_slice(&a);
528 record.extend_from_slice(&attr_types::END.to_le_bytes());
529
530 let cluster_size = 512usize;
531 let mut disk = vec![0u8; 16 * cluster_size];
532 disk[2 * cluster_size..2 * cluster_size + stream.len()].copy_from_slice(&stream);
533 let mut vol = std::io::Cursor::new(disk);
534
535 let attrs = crate::attribute::parse_attributes(&record, attr_off).unwrap();
536 let out = read_attribute_value(&mut vol, &record, &attrs[0], 512).unwrap();
537 assert_eq!(
538 out, content,
539 "compressed $DATA must be LZNT1-decompressed, not returned raw"
540 );
541 }
542
543 #[test]
544 fn compressed_runs_fully_sparse_unit_is_zeroes() {
545 let mut vol = std::io::Cursor::new(vec![0u8; 16 * 512]);
547 let runs = [Run {
548 length: 16,
549 lcn: None,
550 }];
551 let out = read_compressed_runs_capped(&mut vol, &runs, 512, 100, 16, u64::MAX).unwrap();
552 assert_eq!(out, vec![0u8; 100]);
553 }
554
555 #[test]
556 fn compressed_runs_fully_allocated_unit_is_verbatim() {
557 let mut vol = std::io::Cursor::new(vec![0x5Au8; 16 * 512]);
560 let runs = [Run {
561 length: 16,
562 lcn: Some(0),
563 }];
564 let out =
565 read_compressed_runs_capped(&mut vol, &runs, 512, 16 * 512, 16, u64::MAX).unwrap();
566 assert_eq!(out.len(), 16 * 512);
567 assert!(out.iter().all(|&b| b == 0x5A));
568 }
569
570 #[test]
571 fn compressed_runs_stop_when_runlist_exhausted() {
572 let mut vol = std::io::Cursor::new(vec![0x11u8; 16 * 512]);
576 let runs = [Run {
577 length: 16,
578 lcn: Some(0),
579 }];
580 let out =
581 read_compressed_runs_capped(&mut vol, &runs, 512, 2 * 16 * 512, 16, u64::MAX).unwrap();
582 assert_eq!(out.len(), 16 * 512, "only the available unit is returned");
583 }
584
585 #[test]
586 fn compressed_runs_reject_implausible_real_size() {
587 let mut vol = std::io::Cursor::new(vec![0u8; 16]);
588 let runs = [Run {
589 length: 1,
590 lcn: Some(0),
591 }];
592 let err =
593 read_compressed_runs_capped(&mut vol, &runs, 512, MAX_VALUE_BYTES + 1, 16, u64::MAX);
594 assert!(matches!(err, Err(NtfsError::TooLarge { .. })));
595 }
596
597 #[test]
598 fn compressed_runs_capped_below_real_size_is_a_true_prefix() {
599 let runs = [Run {
602 length: 16,
603 lcn: Some(0),
604 }];
605 let mut full_vol = std::io::Cursor::new(vec![0x5Au8; 16 * 512]);
606 let full =
607 read_compressed_runs_capped(&mut full_vol, &runs, 512, 16 * 512, 16, u64::MAX).unwrap();
608
609 let cap = 700u64;
610 let mut vol = std::io::Cursor::new(vec![0x5Au8; 16 * 512]);
611 let out = read_compressed_runs_capped(&mut vol, &runs, 512, 16 * 512, 16, cap).unwrap();
612 assert!(out.len() as u64 <= cap);
613 assert_eq!(out.len(), cap as usize);
614 assert_eq!(
615 out[..],
616 full[..cap as usize],
617 "capped bytes are a true prefix"
618 );
619 }
620
621 #[test]
622 fn stops_reading_once_real_size_is_met() {
623 let mut vol = volume(4, 512);
625 let runs = [
626 Run {
627 length: 1,
628 lcn: Some(0),
629 },
630 Run {
631 length: 1,
632 lcn: Some(1),
633 },
634 ];
635 let out = read_runs(&mut vol, &runs, 512, 512).unwrap();
636 assert_eq!(out.len(), 512); }
638
639 #[test]
640 fn capped_read_stops_at_cap_and_is_a_true_prefix() {
641 let runs = [Run {
644 length: 3,
645 lcn: Some(1),
646 }];
647 let mut full_vol = volume(4, 512);
648 let full = read_runs(&mut full_vol, &runs, 512, 1536).unwrap();
649 assert_eq!(full.len(), 1536);
650
651 let cap = 700usize; let mut capped_vol = volume(4, 512);
653 let capped = read_runs_capped(&mut capped_vol, &runs, 512, 1536, cap as u64).unwrap();
654 assert!(capped.len() <= cap, "capped read must not exceed the cap");
655 assert_eq!(
656 capped.len(),
657 cap,
658 "cap is below the real size, so it bounds"
659 );
660 assert_eq!(capped[..], full[..cap], "capped bytes are a true prefix");
661 }
662
663 #[test]
664 fn rejects_runlist_region_out_of_bounds() {
665 use crate::attribute::{Attribute, AttributeBody};
666 let attr = Attribute {
668 type_code: forensicnomicon::ntfs::attr_types::DATA,
669 length: 0x48,
670 non_resident: true,
671 name: None,
672 flags: 0,
673 attribute_id: 0,
674 offset: 0,
675 body: AttributeBody::NonResident {
676 start_vcn: 0,
677 last_vcn: 0,
678 runs_offset: 0xFFFF,
679 compression_unit: 0,
680 allocated_size: 512,
681 real_size: 512,
682 initialized_size: 512,
683 },
684 };
685 let record = vec![0u8; 0x48];
686 let mut vol = volume(1, 512);
687 assert!(matches!(
688 read_attribute_value(&mut vol, &record, &attr, 512),
689 Err(NtfsError::BadAttribute { detail, .. }) if detail == "runlist out of bounds"
690 ));
691 }
692
693 #[test]
694 fn rejects_runs_offset_overflow() {
695 use crate::attribute::{Attribute, AttributeBody};
696 let attr = Attribute {
698 type_code: forensicnomicon::ntfs::attr_types::DATA,
699 length: 0x48,
700 non_resident: true,
701 name: None,
702 flags: 0,
703 attribute_id: 0,
704 offset: usize::MAX - 0x48,
705 body: AttributeBody::NonResident {
706 start_vcn: 0,
707 last_vcn: 0,
708 runs_offset: 0x49,
709 compression_unit: 0,
710 allocated_size: 512,
711 real_size: 512,
712 initialized_size: 512,
713 },
714 };
715 let record = vec![0u8; 1];
716 let mut vol = volume(1, 512);
717 assert!(matches!(
718 read_attribute_value(&mut vol, &record, &attr, 512),
719 Err(NtfsError::BadAttribute { detail, .. }) if detail == "runs offset overflow"
720 ));
721 }
722
723 #[test]
724 fn attribute_runlist_rejects_resident_attribute() {
725 let attr = Attribute {
726 type_code: forensicnomicon::ntfs::attr_types::DATA,
727 length: 0x20,
728 non_resident: false,
729 name: None,
730 flags: 0,
731 attribute_id: 0,
732 offset: 0,
733 body: AttributeBody::Resident {
734 content_offset: 0x18,
735 content_length: 4,
736 },
737 };
738 assert!(matches!(
739 attribute_runlist(&[0u8; 0x20], &attr),
740 Err(NtfsError::BadAttribute { detail, .. }) if detail.contains("resident")
741 ));
742 }
743}