ntfs_reader/
mft.rs

1// Copyright (c) 2022, Matteo Bernacchia <dev@kikijiki.com>. All rights reserved.
2// This project is dual licensed under the Apache License 2.0 and the MIT license.
3// See the LICENSE files in the project root for details.
4
5use std::io::{Read, Seek, SeekFrom};
6use std::mem::size_of;
7
8use crate::{
9    aligned_reader::open_volume,
10    api::*,
11    attribute::{DataRun, NtfsAttribute},
12    errors::{NtfsReaderError, NtfsReaderResult},
13    file::NtfsFile,
14    volume::Volume,
15};
16
17pub struct Mft {
18    pub volume: Volume,
19    pub data: Vec<u8>,
20    pub bitmap: Vec<u8>,
21    pub max_record: u64,
22}
23
24impl Mft {
25    pub fn new(volume: Volume) -> NtfsReaderResult<Self> {
26        let mut reader = open_volume(&volume.path)?;
27
28        let mft_record = Self::get_record_fs(
29            &mut reader,
30            volume.file_record_size as usize,
31            volume.mft_position,
32        )?;
33
34        let mut data =
35            Self::read_data_fs(&volume, &mut reader, &mft_record, NtfsAttributeType::Data)?
36                .ok_or_else(|| NtfsReaderError::MissingMftAttribute("Data".to_string()))?;
37        let bitmap =
38            Self::read_data_fs(&volume, &mut reader, &mft_record, NtfsAttributeType::Bitmap)?
39                .ok_or_else(|| NtfsReaderError::MissingMftAttribute("Bitmap".to_string()))?;
40
41        let max_record = (data.len() / volume.file_record_size as usize) as u64;
42
43        // Fixup all records so we are non mutable from now on.
44        for number in 0..max_record {
45            let start = number as usize * volume.file_record_size as usize;
46            let end = start + volume.file_record_size as usize;
47            let data = &mut data[start..end];
48            Self::fixup_record(number, data)?;
49        }
50
51        Ok(Mft {
52            volume,
53            data,
54            bitmap,
55            max_record,
56        })
57    }
58
59    pub fn record_exists(&self, number: u64) -> bool {
60        if number >= self.max_record {
61            return false;
62        }
63
64        let bitmap_idx = (number / 8) as usize;
65        let bitmap_off = (number % 8) as u8;
66
67        if bitmap_idx >= self.bitmap.len() {
68            return false;
69        }
70
71        let bit = self.bitmap[bitmap_idx];
72        (bit & (1u8 << bitmap_off)) != 0
73    }
74
75    pub fn iterate_files<F>(&self, mut f: F)
76    where
77        F: FnMut(&NtfsFile),
78    {
79        for number in FIRST_NORMAL_RECORD..self.max_record {
80            if self.record_exists(number) {
81                if let Some(file) = self.get_record(number) {
82                    if file.is_used() {
83                        f(&file);
84                    }
85                }
86            }
87        }
88    }
89
90    pub fn get_record_data(&self, number: u64) -> &[u8] {
91        let start = number as usize * self.volume.file_record_size as usize;
92        let end = start + self.volume.file_record_size as usize;
93        &self.data[start..end]
94    }
95
96    pub fn get_record(&self, number: u64) -> Option<NtfsFile<'_>> {
97        if number >= self.max_record {
98            return None;
99        }
100        let data = self.get_record_data(number);
101
102        if NtfsFile::is_valid(data) {
103            return Some(NtfsFile::new(number, data));
104        }
105
106        None
107    }
108
109    pub fn get_record_fs<R>(
110        fs: &mut R,
111        file_record_size: usize,
112        position: u64,
113    ) -> NtfsReaderResult<Vec<u8>>
114    where
115        R: Seek + Read,
116    {
117        let mut data = vec![0; file_record_size];
118        fs.seek(SeekFrom::Start(position))?;
119        fs.read_exact(&mut data)?;
120
121        if !NtfsFile::is_valid(&data) {
122            return Err(NtfsReaderError::InvalidMftRecord { position });
123        }
124        Self::fixup_record(0, &mut data)?;
125        Ok(data)
126    }
127
128    pub fn read_data_fs<R>(
129        volume: &Volume,
130        reader: &mut R,
131        record: &[u8],
132        attribute_type: NtfsAttributeType,
133    ) -> NtfsReaderResult<Option<Vec<u8>>>
134    where
135        R: Seek + Read,
136    {
137        let header = unsafe { &*(record.as_ptr() as *const NtfsFileRecordHeader) };
138        let mut att_offset = header.attributes_offset as usize;
139        let used = usize::min(header.used_size as usize, record.len());
140
141        // First pass: look for the attribute directly in this record
142        while att_offset < used {
143            let slice = &record[att_offset..used];
144            let attr = match NtfsAttribute::new(slice) {
145                Some(attr) => attr,
146                None => break,
147            };
148
149            if attr.header.type_id == NtfsAttributeType::End as u32 {
150                break;
151            }
152
153            if attr.header.type_id == attribute_type as u32 {
154                return Ok(Some(Self::read_attribute_data(reader, &attr, volume)?));
155            }
156
157            let attr_len = attr.len();
158            if attr_len == 0 {
159                break;
160            }
161            att_offset = match att_offset.checked_add(attr_len) {
162                Some(next) if next <= used => next,
163                _ => break,
164            };
165        }
166
167        // Second pass: if not found, check attribute list entries
168        att_offset = header.attributes_offset as usize;
169        while att_offset < used {
170            let slice = &record[att_offset..used];
171            let attr = match NtfsAttribute::new(slice) {
172                Some(attr) => attr,
173                None => break,
174            };
175
176            if attr.header.type_id == NtfsAttributeType::End as u32 {
177                break;
178            }
179
180            if attr.header.type_id == NtfsAttributeType::AttributeList as u32 {
181                let att_list_data = if attr.header.is_non_resident != 0 {
182                    Self::read_attribute_data(reader, &attr, volume)?
183                } else {
184                    match attr.as_resident_data() {
185                        Some(data) => data.to_vec(),
186                        None => break,
187                    }
188                };
189
190                let mut list_offset = 0usize;
191
192                while list_offset < att_list_data.len() {
193                    let entry_slice = &att_list_data[list_offset..];
194                    let entry = match parse_attribute_list_entry(entry_slice) {
195                        Some(entry) => entry,
196                        None => break,
197                    };
198
199                    let type_id = entry.type_id;
200                    let reference = entry.reference();
201                    let entry_len = entry.length as usize;
202
203                    if type_id == attribute_type as u32 {
204                        let record_position =
205                            volume.mft_position + (reference * volume.file_record_size);
206                        if let Ok(target_record) = Self::get_record_fs(
207                            reader,
208                            volume.file_record_size as usize,
209                            record_position,
210                        ) {
211                            let target_header = unsafe {
212                                &*(target_record.as_ptr() as *const NtfsFileRecordHeader)
213                            };
214                            let mut target_offset = target_header.attributes_offset as usize;
215                            let target_used =
216                                usize::min(target_header.used_size as usize, target_record.len());
217
218                            while target_offset < target_used {
219                                let target_slice = &target_record[target_offset..target_used];
220                                let target_attr = match NtfsAttribute::new(target_slice) {
221                                    Some(attr) => attr,
222                                    None => break,
223                                };
224
225                                if target_attr.header.type_id == NtfsAttributeType::End as u32 {
226                                    break;
227                                }
228
229                                if target_attr.header.type_id == attribute_type as u32 {
230                                    return Ok(Some(Self::read_attribute_data(
231                                        reader,
232                                        &target_attr,
233                                        volume,
234                                    )?));
235                                }
236
237                                let len = target_attr.len();
238                                if len == 0 {
239                                    break;
240                                }
241                                target_offset = match target_offset.checked_add(len) {
242                                    Some(next) if next <= target_used => next,
243                                    _ => break,
244                                };
245                            }
246                        }
247                    }
248
249                    if entry_len == 0 {
250                        break;
251                    }
252                    list_offset = match list_offset.checked_add(entry_len) {
253                        Some(next) if next <= att_list_data.len() => next,
254                        _ => break,
255                    };
256                    let align = (8 - (list_offset % 8)) % 8;
257                    list_offset = match list_offset.checked_add(align) {
258                        Some(next) if next <= att_list_data.len() => next,
259                        _ => break,
260                    };
261                }
262            }
263
264            let attr_len = attr.len();
265            if attr_len == 0 {
266                break;
267            }
268            att_offset = match att_offset.checked_add(attr_len) {
269                Some(next) if next <= used => next,
270                _ => break,
271            };
272        }
273
274        Ok(None)
275    }
276
277    fn read_attribute_data<R>(
278        reader: &mut R,
279        att: &NtfsAttribute,
280        volume: &Volume,
281    ) -> NtfsReaderResult<Vec<u8>>
282    where
283        R: Seek + Read,
284    {
285        if att.header.is_non_resident == 0 {
286            let data = att
287                .as_resident_data()
288                .ok_or(NtfsReaderError::InvalidDataRun {
289                    details: "resident attribute missing value",
290                })?;
291            Ok(data.to_vec())
292        } else {
293            let (size, runs) = att.get_nonresident_data_runs(volume)?;
294            let total_size =
295                usize::try_from(size).map_err(|_| NtfsReaderError::InvalidDataRun {
296                    details: "attribute size exceeds addressable memory",
297                })?;
298
299            let mut data = Vec::with_capacity(total_size);
300            let mut buffer = Vec::new();
301            let mut copied = 0usize;
302
303            for run in runs.iter() {
304                if copied >= total_size {
305                    break;
306                }
307
308                match run {
309                    DataRun::Data { lcn, length } => {
310                        let run_len = usize::try_from(*length).map_err(|_| {
311                            NtfsReaderError::InvalidDataRun {
312                                details: "run length exceeds addressable memory",
313                            }
314                        })?;
315                        let buf_size = usize::min(run_len, total_size - copied);
316                        buffer.resize(buf_size, 0u8);
317
318                        reader.seek(SeekFrom::Start(*lcn))?;
319                        reader.read_exact(&mut buffer)?;
320
321                        data.extend_from_slice(&buffer);
322                        copied += buf_size;
323                    }
324                    DataRun::Sparse { length } => {
325                        let run_len = usize::try_from(*length).map_err(|_| {
326                            NtfsReaderError::InvalidDataRun {
327                                details: "run length exceeds addressable memory",
328                            }
329                        })?;
330                        let buf_size = usize::min(run_len, total_size - copied);
331                        data.resize(data.len() + buf_size, 0);
332                        copied += buf_size;
333                    }
334                }
335            }
336
337            Ok(data)
338        }
339    }
340
341    fn fixup_record(record_number: u64, data: &mut [u8]) -> NtfsReaderResult<()> {
342        if data.len() < core::mem::size_of::<NtfsFileRecordHeader>() {
343            return Err(NtfsReaderError::CorruptMftRecord {
344                number: record_number,
345            });
346        }
347        let header =
348            unsafe { core::ptr::read_unaligned(data.as_ptr() as *const NtfsFileRecordHeader) };
349
350        let usn_start = header.update_sequence_offset as usize;
351        if usn_start + 2 > data.len() {
352            return Err(NtfsReaderError::CorruptMftRecord {
353                number: record_number,
354            });
355        }
356        let usa_start = usn_start + 2;
357        let usa_end =
358            usn_start.saturating_add((header.update_sequence_length as usize).saturating_mul(2));
359        if usa_end > data.len() {
360            return Err(NtfsReaderError::CorruptMftRecord {
361                number: record_number,
362            });
363        }
364
365        let usn0 = data[usn_start];
366        let usn1 = data[usn_start + 1];
367
368        let mut sector_off = SECTOR_SIZE - 2;
369        for usa_off in (usa_start..usa_end).step_by(2) {
370            if sector_off + 2 > data.len() {
371                break;
372            }
373
374            let mut usa = [0u8; 2];
375            usa.copy_from_slice(&data[usa_off..usa_off + 2]);
376
377            let d0 = data[sector_off];
378            let d1 = data[sector_off + 1];
379            if d0 != usn0 || d1 != usn1 {
380                return Err(NtfsReaderError::CorruptMftRecord {
381                    number: record_number,
382                });
383            }
384
385            data[sector_off..sector_off + 2].copy_from_slice(&usa);
386            sector_off += SECTOR_SIZE;
387        }
388        Ok(())
389    }
390}
391
392fn parse_attribute_list_entry(data: &[u8]) -> Option<&NtfsAttributeListEntry> {
393    if data.len() < size_of::<NtfsAttributeListEntry>() {
394        return None;
395    }
396    let entry = unsafe { &*(data.as_ptr() as *const NtfsAttributeListEntry) };
397    let length = entry.length as usize;
398    if length < size_of::<NtfsAttributeListEntry>() || length > data.len() {
399        return None;
400    }
401    Some(entry)
402}