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 let mut allocated = 0u64;
37 for r in runs {
38 let run_bytes = r
39 .length
40 .checked_mul(cluster_size)
41 .ok_or(NtfsError::BadRunlist("run byte length overflow"))?;
42 allocated = allocated
43 .checked_add(run_bytes)
44 .ok_or(NtfsError::BadRunlist("allocation overflow"))?;
45 }
46
47 let want = real_size.min(allocated);
48 if want > MAX_VALUE_BYTES {
49 return Err(NtfsError::TooLarge { bytes: want });
50 }
51 let want_usize = usize::try_from(want).map_err(|_| NtfsError::TooLarge { bytes: want })?;
52
53 let mut out: Vec<u8> = Vec::new();
54 out.try_reserve_exact(want_usize)
55 .map_err(|_| NtfsError::TooLarge { bytes: want })?;
56
57 let mut remaining = want;
58 for r in runs {
59 if remaining == 0 {
60 break;
61 }
62 let run_bytes = r.length * cluster_size; let take = run_bytes.min(remaining);
64 let take_usize = take as usize; match r.lcn {
67 None => out.resize(out.len() + take_usize, 0), Some(lcn) => {
69 let byte_off = lcn
70 .checked_mul(cluster_size)
71 .ok_or(NtfsError::BadRunlist("LCN byte offset overflow"))?;
72 reader.seek(SeekFrom::Start(byte_off))?;
73 let start = out.len();
74 out.resize(start + take_usize, 0);
75 reader.read_exact(&mut out[start..])?;
76 }
77 }
78 remaining -= take;
79 }
80
81 Ok(out)
82}
83
84pub fn read_attribute_value<R: Read + Seek>(
94 reader: &mut R,
95 record: &[u8],
96 attribute: &Attribute,
97 cluster_size: u64,
98) -> Result<Vec<u8>> {
99 match attribute.body {
100 AttributeBody::Resident { .. } => attribute
101 .resident_content(record)
102 .map(<[u8]>::to_vec)
103 .ok_or(NtfsError::BadAttribute {
104 offset: attribute.offset,
105 detail: "resident content out of bounds",
106 }),
107 AttributeBody::NonResident { real_size, .. } => {
108 let runs = attribute_runlist(record, attribute)?;
109 read_runs(reader, &runs, cluster_size, real_size)
110 }
111 }
112}
113
114pub fn attribute_runlist(record: &[u8], attribute: &Attribute) -> Result<Vec<Run>> {
125 let AttributeBody::NonResident { runs_offset, .. } = attribute.body else {
126 return Err(NtfsError::BadAttribute {
127 offset: attribute.offset,
128 detail: "attribute is resident (no runlist)",
129 });
130 };
131 let attr_end = attribute
132 .offset
133 .checked_add(attribute.length as usize)
134 .ok_or(NtfsError::BadAttribute {
135 offset: attribute.offset,
136 detail: "attribute length overflow",
137 })?;
138 let runs_start =
139 attribute
140 .offset
141 .checked_add(runs_offset as usize)
142 .ok_or(NtfsError::BadAttribute {
143 offset: attribute.offset,
144 detail: "runs offset overflow",
145 })?;
146 let runs_bytes = record
147 .get(runs_start..attr_end)
148 .ok_or(NtfsError::BadAttribute {
149 offset: attribute.offset,
150 detail: "runlist out of bounds",
151 })?;
152 runlist::decode(runs_bytes)
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use std::io::Cursor;
159
160 fn volume(clusters: usize, cluster_size: usize) -> Cursor<Vec<u8>> {
162 let mut v = vec![0u8; clusters * cluster_size];
163 for c in 0..clusters {
164 let b = c as u8;
165 for x in &mut v[c * cluster_size..(c + 1) * cluster_size] {
166 *x = b;
167 }
168 }
169 Cursor::new(v)
170 }
171
172 #[test]
173 fn reads_single_run() {
174 let mut vol = volume(4, 512);
175 let runs = [Run {
177 length: 2,
178 lcn: Some(1),
179 }];
180 let out = read_runs(&mut vol, &runs, 512, 1024).unwrap();
181 assert_eq!(out.len(), 1024);
182 assert!(out[..512].iter().all(|&b| b == 1));
183 assert!(out[512..].iter().all(|&b| b == 2));
184 }
185
186 #[test]
187 fn sparse_run_yields_zeroes_without_reading() {
188 let mut vol = volume(1, 512); let runs = [Run {
190 length: 2,
191 lcn: None,
192 }];
193 let out = read_runs(&mut vol, &runs, 512, 1024).unwrap();
194 assert_eq!(out.len(), 1024);
195 assert!(out.iter().all(|&b| b == 0));
196 }
197
198 #[test]
199 fn truncates_to_real_size() {
200 let mut vol = volume(4, 512);
201 let runs = [Run {
202 length: 2,
203 lcn: Some(0),
204 }]; let out = read_runs(&mut vol, &runs, 512, 600).unwrap();
206 assert_eq!(out.len(), 600);
207 }
208
209 #[test]
210 fn mixed_data_and_sparse() {
211 let mut vol = volume(4, 512);
212 let runs = [
213 Run {
214 length: 1,
215 lcn: Some(3),
216 }, Run {
218 length: 1,
219 lcn: None,
220 }, ];
222 let out = read_runs(&mut vol, &runs, 512, 1024).unwrap();
223 assert!(out[..512].iter().all(|&b| b == 3));
224 assert!(out[512..].iter().all(|&b| b == 0));
225 }
226
227 #[test]
228 fn refuses_implausible_size() {
229 let mut vol = volume(1, 512);
233 let runs = [Run {
234 length: 1 << 40,
235 lcn: None,
236 }];
237 assert!(matches!(
238 read_runs(&mut vol, &runs, 512, u64::MAX),
239 Err(NtfsError::TooLarge { .. })
240 ));
241 }
242
243 #[test]
244 fn rejects_cluster_size_overflow() {
245 let mut vol = volume(1, 512);
246 let runs = [Run {
247 length: u64::MAX,
248 lcn: Some(0),
249 }];
250 assert!(matches!(
251 read_runs(&mut vol, &runs, 512, 1024),
252 Err(NtfsError::BadRunlist(_))
253 ));
254 }
255
256 #[test]
259 fn reads_resident_value() {
260 use forensicnomicon::ntfs::attr_types;
261 let content = b"hello";
263 let attr_off = 0x10usize;
265 let mut record = vec![0u8; attr_off];
266 let name_offset = 0x18u16;
268 let content_offset = 0x18u16;
269 let length = (content_offset as usize + content.len() + 7) & !7;
270 let mut a = vec![0u8; length];
271 a[0x00..0x04].copy_from_slice(&attr_types::DATA.to_le_bytes());
272 a[0x04..0x08].copy_from_slice(&(length as u32).to_le_bytes());
273 a[0x0A..0x0C].copy_from_slice(&name_offset.to_le_bytes());
274 a[0x10..0x14].copy_from_slice(&(content.len() as u32).to_le_bytes());
275 a[0x14..0x16].copy_from_slice(&content_offset.to_le_bytes());
276 a[content_offset as usize..content_offset as usize + content.len()]
277 .copy_from_slice(content);
278 record.extend_from_slice(&a);
279 record.extend_from_slice(&attr_types::END.to_le_bytes());
280
281 let attrs = crate::attribute::parse_attributes(&record, attr_off).unwrap();
282 let mut vol = volume(1, 512);
283 let out = read_attribute_value(&mut vol, &record, &attrs[0], 512).unwrap();
284 assert_eq!(out, b"hello");
285 }
286
287 #[test]
288 fn reads_nonresident_value_via_runlist() {
289 use forensicnomicon::ntfs::attr_types;
290 let runs_bytes = [0x11u8, 0x01, 0x02, 0x00]; let attr_off = 0x10usize;
293 let mut record = vec![0u8; attr_off];
294 let runs_offset = 0x40u16;
295 let length = ((runs_offset as usize + runs_bytes.len()) + 7) & !7;
296 let mut a = vec![0u8; length];
297 a[0x00..0x04].copy_from_slice(&attr_types::DATA.to_le_bytes());
298 a[0x04..0x08].copy_from_slice(&(length as u32).to_le_bytes());
299 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()]
305 .copy_from_slice(&runs_bytes);
306 record.extend_from_slice(&a);
307 record.extend_from_slice(&attr_types::END.to_le_bytes());
308
309 let attrs = crate::attribute::parse_attributes(&record, attr_off).unwrap();
310 let mut vol = volume(4, 512); let out = read_attribute_value(&mut vol, &record, &attrs[0], 512).unwrap();
312 assert_eq!(out.len(), 512);
313 assert!(out.iter().all(|&b| b == 2));
314 }
315
316 #[test]
317 fn stops_reading_once_real_size_is_met() {
318 let mut vol = volume(4, 512);
320 let runs = [
321 Run {
322 length: 1,
323 lcn: Some(0),
324 },
325 Run {
326 length: 1,
327 lcn: Some(1),
328 },
329 ];
330 let out = read_runs(&mut vol, &runs, 512, 512).unwrap();
331 assert_eq!(out.len(), 512); }
333
334 #[test]
335 fn rejects_runlist_region_out_of_bounds() {
336 use crate::attribute::{Attribute, AttributeBody};
337 let attr = Attribute {
339 type_code: forensicnomicon::ntfs::attr_types::DATA,
340 length: 0x48,
341 non_resident: true,
342 name: None,
343 flags: 0,
344 attribute_id: 0,
345 offset: 0,
346 body: AttributeBody::NonResident {
347 start_vcn: 0,
348 last_vcn: 0,
349 runs_offset: 0xFFFF,
350 compression_unit: 0,
351 allocated_size: 512,
352 real_size: 512,
353 initialized_size: 512,
354 },
355 };
356 let record = vec![0u8; 0x48];
357 let mut vol = volume(1, 512);
358 assert!(matches!(
359 read_attribute_value(&mut vol, &record, &attr, 512),
360 Err(NtfsError::BadAttribute { detail, .. }) if detail == "runlist out of bounds"
361 ));
362 }
363
364 #[test]
365 fn rejects_runs_offset_overflow() {
366 use crate::attribute::{Attribute, AttributeBody};
367 let attr = Attribute {
369 type_code: forensicnomicon::ntfs::attr_types::DATA,
370 length: 0x48,
371 non_resident: true,
372 name: None,
373 flags: 0,
374 attribute_id: 0,
375 offset: usize::MAX - 0x48,
376 body: AttributeBody::NonResident {
377 start_vcn: 0,
378 last_vcn: 0,
379 runs_offset: 0x49,
380 compression_unit: 0,
381 allocated_size: 512,
382 real_size: 512,
383 initialized_size: 512,
384 },
385 };
386 let record = vec![0u8; 1];
387 let mut vol = volume(1, 512);
388 assert!(matches!(
389 read_attribute_value(&mut vol, &record, &attr, 512),
390 Err(NtfsError::BadAttribute { detail, .. }) if detail == "runs offset overflow"
391 ));
392 }
393
394 #[test]
395 fn attribute_runlist_rejects_resident_attribute() {
396 let attr = Attribute {
397 type_code: forensicnomicon::ntfs::attr_types::DATA,
398 length: 0x20,
399 non_resident: false,
400 name: None,
401 flags: 0,
402 attribute_id: 0,
403 offset: 0,
404 body: AttributeBody::Resident {
405 content_offset: 0x18,
406 content_length: 4,
407 },
408 };
409 assert!(matches!(
410 attribute_runlist(&[0u8; 0x20], &attr),
411 Err(NtfsError::BadAttribute { detail, .. }) if detail.contains("resident")
412 ));
413 }
414}