/src/sleuthkit/tsk/fs/ntfs_dent.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | ** ntfs_dent |
3 | | ** The Sleuth Kit |
4 | | ** |
5 | | ** name layer support for the NTFS file system |
6 | | ** |
7 | | ** Brian Carrier [carrier <at> sleuthkit [dot] org] |
8 | | ** Copyright (c) 2006-2011 Brian Carrier, Basis Technology. All Rights reserved |
9 | | ** Copyright (c) 2003-2005 Brian Carrier. All rights reserved |
10 | | ** |
11 | | ** TASK |
12 | | ** Copyright (c) 2002 Brian Carrier, @stake Inc. All rights reserved |
13 | | ** |
14 | | ** |
15 | | ** This software is distributed under the Common Public License 1.0 |
16 | | ** |
17 | | ** Unicode added with support from I.D.E.A.L. Technology Corp (Aug '05) |
18 | | ** |
19 | | */ |
20 | | #include "tsk_fs_i.h" |
21 | | #include "tsk_ntfs.h" |
22 | | |
23 | | /** |
24 | | * \file ntfs_dent.cpp |
25 | | * NTFS file name processing internal functions. |
26 | | */ |
27 | | |
28 | | #include <memory> |
29 | | #include <unordered_map> |
30 | | #include <vector> |
31 | | |
32 | | /** |
33 | | * Class to hold the pair of MFT entry and sequence. |
34 | | */ |
35 | | class NTFS_META_ADDR { |
36 | | private: |
37 | | const uint64_t addr; ///< MFT entry |
38 | | const uint32_t seq; ///< Sequence |
39 | | const uint32_t hash; ///< Hash of the path |
40 | | |
41 | | public: |
42 | | NTFS_META_ADDR(uint64_t a_addr, uint32_t a_seq, uint32_t a_hash) : |
43 | 7.18k | addr(a_addr), |
44 | 7.18k | seq(a_seq), |
45 | 7.18k | hash(a_hash) |
46 | 7.18k | { |
47 | 7.18k | } |
48 | | |
49 | 46.1k | uint64_t getAddr() const { |
50 | 46.1k | return addr; |
51 | 46.1k | } |
52 | | |
53 | 21.8k | uint32_t getSeq() const { |
54 | 21.8k | return seq; |
55 | 21.8k | } |
56 | | |
57 | 24.3k | uint32_t getHash() const { |
58 | 24.3k | return hash; |
59 | 24.3k | } |
60 | | }; |
61 | | |
62 | | |
63 | | /* When we list a directory, we need to also look at MFT entries and what |
64 | | * they list as their parents. We used to do this only for orphan files, but |
65 | | * we were pointed to a case whereby allocated files were not in IDX_ALLOC, but were |
66 | | * shown in Windows (when mounted). They must have been found via the MFT entry, so |
67 | | * we now load all parent to child relationships into the map. |
68 | | * |
69 | | * One of these classes is created per parent folder */ |
70 | | class NTFS_PAR_MAP { |
71 | | private: |
72 | | // maps sequence number to list of inums for the folder at that seq. |
73 | | std::unordered_map <uint32_t, std::vector <NTFS_META_ADDR> > seq2addrs; |
74 | | public: |
75 | | /** |
76 | | * Add a child to this parent. |
77 | | * @param seq Sequence of the parent that this child belonged to |
78 | | * @param inum Address of child in the folder. |
79 | | * @param seq Sequence of child in the folder |
80 | | */ |
81 | 7.18k | void add (uint32_t parSeq, TSK_INUM_T inum, uint32_t seq, uint32_t hash) { |
82 | 7.18k | seq2addrs[parSeq].emplace_back(inum, seq, hash); |
83 | 7.18k | } |
84 | | |
85 | | /** |
86 | | * Test if there are any children for this directory at a given sequence. |
87 | | * @param seq Sequence to test. |
88 | | * @returns true if children exist |
89 | | */ |
90 | 17.8k | bool exists (uint32_t seq) { |
91 | 17.8k | if (seq2addrs.count(seq) > 0) |
92 | 15.5k | return true; |
93 | 2.21k | else |
94 | 2.21k | return false; |
95 | 17.8k | } |
96 | | |
97 | | /** |
98 | | * Get the children for this folder at a given sequence. Use exists first. |
99 | | * @param seq Sequence number to retrieve children for. |
100 | | * @returns list of INUMS for children. |
101 | | */ |
102 | 15.5k | const std::vector <NTFS_META_ADDR> &get (uint32_t seq) const { |
103 | 15.5k | return seq2addrs.at(seq); |
104 | 15.5k | } |
105 | | }; |
106 | | |
107 | | |
108 | | |
109 | | /** \internal |
110 | | * Casts the void * to a map. This obfuscation is done so that the rest of the library |
111 | | * can remain as C and only this code needs to be C++. |
112 | | * |
113 | | * Assumes that you already have the lock |
114 | | */ |
115 | 47.1k | static std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP> * getParentMap(NTFS_INFO *ntfs) { |
116 | | // allocate it if it hasn't already been |
117 | 47.1k | if (ntfs->orphan_map == NULL) { |
118 | 1.75k | auto inum_hash = [](const TSK_INUM_T& x) { return x; }; |
119 | 1.75k | ntfs->orphan_map = new std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP,decltype(inum_hash)>(0, inum_hash); |
120 | 1.75k | } |
121 | 47.1k | return (std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP> *)ntfs->orphan_map; |
122 | 47.1k | } |
123 | | |
124 | | |
125 | | |
126 | | /** \internal |
127 | | * Add a parent and child pair to the map stored in NTFS_INFO |
128 | | * |
129 | | * Note: This routine assumes &ntfs->orphan_map_lock is locked by the caller. |
130 | | * |
131 | | * @param ntfs structure to add the pair to |
132 | | * @param par Parent address |
133 | | * @param child_meta Child to add |
134 | | * @returns 1 on error |
135 | | */ |
136 | | static uint8_t |
137 | | ntfs_parent_map_add(NTFS_INFO * ntfs, TSK_FS_META_NAME_LIST *name_list, TSK_FS_META *child_meta) |
138 | 7.18k | { |
139 | 7.18k | std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP> *tmpParentMap = getParentMap(ntfs); |
140 | 7.18k | NTFS_PAR_MAP &tmpParMap = (*tmpParentMap)[name_list->par_inode]; |
141 | 7.18k | tmpParMap.add(name_list->par_seq, child_meta->addr, child_meta->seq, tsk_fs_dir_hash(name_list->name)); |
142 | 7.18k | return 0; |
143 | 7.18k | } |
144 | | |
145 | | /** \internal |
146 | | * Returns if a parent has children or not. |
147 | | * |
148 | | * Note: This routine assumes &ntfs->orphan_map_lock is locked by the caller. |
149 | | * |
150 | | * @param ntfs File system that has already been analyzed |
151 | | * @param par Parent inode to find child files for |
152 | | * @seq seq Sequence of parent folder |
153 | | * @returns true if parent has children. |
154 | | */ |
155 | | static bool |
156 | | ntfs_parent_map_exists(NTFS_INFO *ntfs, TSK_INUM_T par, uint32_t seq) |
157 | 20.8k | { |
158 | 20.8k | std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP> *tmpParentMap = getParentMap(ntfs); |
159 | 20.8k | if (tmpParentMap->count(par) > 0) { |
160 | 17.8k | NTFS_PAR_MAP &tmpParMap = (*tmpParentMap)[par]; |
161 | 17.8k | if (tmpParMap.exists(seq)) |
162 | 15.5k | return true; |
163 | 17.8k | } |
164 | 5.28k | return false; |
165 | 20.8k | } |
166 | | |
167 | | /** \internal |
168 | | * Look up a map entry by the parent address. You should call ntfs_parent_map_exists() before this, otherwise |
169 | | * an empty entry could be created. |
170 | | * |
171 | | * Note: This routine assumes &ntfs->orphan_map_lock is locked by the caller. |
172 | | * |
173 | | * @param ntfs File system that has already been analyzed |
174 | | * @param par Parent inode to find child files for |
175 | | * @param seq Sequence of parent inode |
176 | | * @returns address of children files in the parent directory |
177 | | */ |
178 | | static const std::vector <NTFS_META_ADDR> & |
179 | | ntfs_parent_map_get(NTFS_INFO * ntfs, TSK_INUM_T par, uint32_t seq) |
180 | 15.5k | { |
181 | 15.5k | std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP> *tmpParentMap = getParentMap(ntfs); |
182 | 15.5k | NTFS_PAR_MAP &tmpParMap = (*tmpParentMap)[par]; |
183 | 15.5k | return tmpParMap.get(seq); |
184 | 15.5k | } |
185 | | |
186 | | |
187 | | |
188 | | // note that for consistency, this should be called parent_map_free, but |
189 | | // that would have required an API change in a point release and this better |
190 | | // matches the name in NTFS_INFO |
191 | | void |
192 | | ntfs_orphan_map_free(NTFS_INFO * a_ntfs) |
193 | 1.75k | { |
194 | | // This routine is only called from ntfs_close, so it wouldn't |
195 | | // normally need a lock. However, it's an extern function, so be |
196 | | // safe in case someone else calls it. (Perhaps it's extern by |
197 | | // mistake?) |
198 | | |
199 | 1.75k | tsk_take_lock(&a_ntfs->orphan_map_lock); |
200 | | |
201 | 1.75k | if (a_ntfs->orphan_map == NULL) { |
202 | 0 | tsk_release_lock(&a_ntfs->orphan_map_lock); |
203 | 0 | return; |
204 | 0 | } |
205 | 1.75k | std::unordered_map<TSK_INUM_T, NTFS_PAR_MAP> *tmpParentMap = getParentMap(a_ntfs); |
206 | | |
207 | 1.75k | delete tmpParentMap; |
208 | 1.75k | a_ntfs->orphan_map = NULL; |
209 | 1.75k | tsk_release_lock(&a_ntfs->orphan_map_lock); |
210 | 1.75k | } |
211 | | |
212 | | |
213 | | /* inode_walk callback that is used to populate the orphan_map |
214 | | * structure in NTFS_INFO */ |
215 | | static TSK_WALK_RET_ENUM |
216 | | ntfs_parent_act(TSK_FS_FILE * fs_file, void * /*ptr*/) |
217 | 10.2k | { |
218 | 10.2k | NTFS_INFO *ntfs = (NTFS_INFO *) fs_file->fs_info; |
219 | 10.2k | TSK_FS_META_NAME_LIST *fs_name_list; |
220 | | |
221 | 10.2k | if ((fs_file->meta->flags & TSK_FS_META_FLAG_ALLOC) && |
222 | 10.2k | fs_file->meta->type == TSK_FS_META_TYPE_REG) { |
223 | 3.89k | ++ntfs->alloc_file_count; |
224 | 3.89k | } |
225 | | |
226 | | /* go through each file name structure */ |
227 | 10.2k | fs_name_list = fs_file->meta->name2; |
228 | 17.4k | while (fs_name_list) { |
229 | 7.18k | if (ntfs_parent_map_add(ntfs, fs_name_list, |
230 | 7.18k | fs_file->meta)) { |
231 | 0 | return TSK_WALK_ERROR; |
232 | 0 | } |
233 | 7.18k | fs_name_list = fs_name_list->next; |
234 | 7.18k | } |
235 | 10.2k | return TSK_WALK_CONT; |
236 | 10.2k | } |
237 | | |
238 | | |
239 | | |
240 | | /****************/ |
241 | | |
242 | | /** |
243 | | * @returns 1 on error |
244 | | */ |
245 | | static uint8_t |
246 | | ntfs_dent_copy(NTFS_INFO * ntfs, ntfs_idxentry * idxe, uintptr_t endaddr, |
247 | | TSK_FS_NAME * fs_name) |
248 | 1.01M | { |
249 | 1.01M | ntfs_attr_fname *fname = (ntfs_attr_fname *) & idxe->stream; |
250 | 1.01M | TSK_FS_INFO *fs = (TSK_FS_INFO *) & ntfs->fs_info; |
251 | 1.01M | UTF16 *name16; |
252 | 1.01M | UTF8 *name8; |
253 | 1.01M | int retVal; |
254 | | |
255 | 1.01M | tsk_fs_name_reset(fs_name); |
256 | | |
257 | 1.01M | fs_name->meta_addr = tsk_getu48(fs->endian, idxe->file_ref); |
258 | 1.01M | fs_name->meta_seq = tsk_getu16(fs->endian, idxe->seq_num); |
259 | | |
260 | 1.01M | name16 = (UTF16 *) & fname->name; |
261 | 1.01M | name8 = (UTF8 *) fs_name->name; |
262 | | |
263 | 1.01M | const UTF16 * sourceEnd = (UTF16 *) ((uintptr_t) name16 + fname->nlen * 2); |
264 | 1.01M | if (((uintptr_t) sourceEnd) >= endaddr) { |
265 | 302 | if (tsk_verbose) |
266 | 0 | tsk_fprintf(stderr, |
267 | 0 | "sourceEnd: %" PRIuINUM " is out of endaddr bounds: %" PRIuINUM, |
268 | 0 | sourceEnd, endaddr); |
269 | 302 | return 1; |
270 | 302 | } |
271 | | |
272 | 1.01M | retVal = tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &name16, |
273 | 1.01M | sourceEnd, &name8, |
274 | 1.01M | (UTF8 *) ((uintptr_t) name8 + |
275 | 1.01M | fs_name->name_size), TSKlenientConversion); |
276 | | |
277 | 1.01M | if (retVal != TSKconversionOK) { |
278 | 11.6k | *name8 = '\0'; |
279 | 11.6k | if (tsk_verbose) |
280 | 0 | tsk_fprintf(stderr, |
281 | 0 | "Error converting NTFS name to UTF8: %d %" PRIuINUM, |
282 | 0 | retVal, fs_name->meta_addr); |
283 | 11.6k | } |
284 | | |
285 | | /* Make sure it is NULL Terminated */ |
286 | 1.01M | if ((uintptr_t) name8 > (uintptr_t) fs_name->name + fs_name->name_size) |
287 | 0 | fs_name->name[fs_name->name_size] = '\0'; |
288 | 1.01M | else |
289 | 1.01M | *name8 = '\0'; |
290 | | |
291 | 1.01M | if (tsk_getu64(fs->endian, fname->flags) & NTFS_FNAME_FLAGS_DIR) |
292 | 276k | fs_name->type = TSK_FS_NAME_TYPE_DIR; |
293 | 733k | else |
294 | 733k | fs_name->type = TSK_FS_NAME_TYPE_REG; |
295 | | |
296 | 1.01M | fs_name->flags = (TSK_FS_NAME_FLAG_ENUM)0; |
297 | | |
298 | 1.01M | return 0; |
299 | 1.01M | } |
300 | | |
301 | | |
302 | | /* Copy the short file name pointed to by idxe into fs_name. |
303 | | * No other fields are copied. Just the name into shrt_name. */ |
304 | | static uint8_t |
305 | | ntfs_dent_copy_short_only(NTFS_INFO * ntfs, ntfs_idxentry * idxe, |
306 | | TSK_FS_NAME * fs_name) |
307 | 1.24k | { |
308 | 1.24k | ntfs_attr_fname *fname = (ntfs_attr_fname *) & idxe->stream; |
309 | 1.24k | TSK_FS_INFO *fs = (TSK_FS_INFO *) & ntfs->fs_info; |
310 | 1.24k | UTF16 *name16; |
311 | 1.24k | UTF8 *name8; |
312 | 1.24k | int retVal; |
313 | | |
314 | 1.24k | name16 = (UTF16 *) & fname->name; |
315 | 1.24k | name8 = (UTF8 *) fs_name->shrt_name; |
316 | | |
317 | 1.24k | retVal = tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &name16, |
318 | 1.24k | (UTF16 *) ((uintptr_t) name16 + |
319 | 1.24k | fname->nlen * 2), &name8, |
320 | 1.24k | (UTF8 *) ((uintptr_t) name8 + |
321 | 1.24k | fs_name->shrt_name_size), TSKlenientConversion); |
322 | | |
323 | 1.24k | if (retVal != TSKconversionOK) { |
324 | 286 | *name8 = '\0'; |
325 | 286 | if (tsk_verbose) |
326 | 0 | tsk_fprintf(stderr, |
327 | 0 | "Error converting NTFS 8.3 name to UTF8: %d %" PRIuINUM, |
328 | 0 | retVal, fs_name->meta_addr); |
329 | 286 | } |
330 | | |
331 | | /* Make sure it is NULL Terminated */ |
332 | 1.24k | if ((uintptr_t) name8 > (uintptr_t) fs_name->shrt_name + fs_name->shrt_name_size) |
333 | 0 | fs_name->shrt_name[fs_name->shrt_name_size] = '\0'; |
334 | 1.24k | else |
335 | 1.24k | *name8 = '\0'; |
336 | | |
337 | 1.24k | return 0; |
338 | 1.24k | } |
339 | | |
340 | | |
341 | | |
342 | | |
343 | | /* This is a sanity check to see if the time is valid |
344 | | * it is divided by 100 to keep it in a 32-bit integer |
345 | | */ |
346 | | |
347 | | static uint8_t |
348 | | is_time(uint64_t t) |
349 | 383k | { |
350 | 745k | #define SEC_BTWN_1601_1970_DIV100 ((369*365 + 89) * 24 * 36) |
351 | 383k | #define SEC_BTWN_1601_2020_DIV100 (SEC_BTWN_1601_1970_DIV100 + (50*365 + 6) * 24 * 36) |
352 | | |
353 | 383k | t /= 1000000000; /* put the time in seconds div by additional 100 */ |
354 | | |
355 | 383k | if (!t) |
356 | 3.53k | return 0; |
357 | | |
358 | 380k | if (t < SEC_BTWN_1601_1970_DIV100) |
359 | 15.0k | return 0; |
360 | | |
361 | 365k | if (t > SEC_BTWN_1601_2020_DIV100) |
362 | 17.3k | return 0; |
363 | | |
364 | 347k | return 1; |
365 | 365k | } |
366 | | |
367 | | |
368 | | |
369 | | /** |
370 | | * Process a list of index entries and add to FS_DIR |
371 | | * |
372 | | * @param a_is_del Set to 1 if these entries are for a deleted directory |
373 | | * @param idxe Buffer with index entries to process |
374 | | * @param idxe_len Length of idxe buffer (in bytes) |
375 | | * @param used_len Length of data as reported by idexlist header (everything |
376 | | * after which and less then idxe_len is considered deleted) |
377 | | * |
378 | | * @returns 1 to stop, 0 on success, and -1 on error |
379 | | */ |
380 | | |
381 | | // @@@ Should make a_idxe const and use internal pointer in function loop |
382 | | static TSK_RETVAL_ENUM |
383 | | ntfs_proc_idxentry(NTFS_INFO * a_ntfs, TSK_FS_DIR * a_fs_dir, |
384 | | uint8_t a_is_del, ntfs_idxentry * a_idxe, uint32_t a_idxe_len, |
385 | | uint32_t a_used_len) |
386 | 36.0k | { |
387 | 36.0k | uintptr_t endaddr, endaddr_alloc; |
388 | 36.0k | TSK_FS_NAME *fs_name; |
389 | 36.0k | TSK_FS_NAME *fs_name_preventry = NULL; |
390 | 36.0k | TSK_FS_INFO *fs = (TSK_FS_INFO *) & a_ntfs->fs_info; |
391 | | |
392 | 36.0k | if ((fs_name = tsk_fs_name_alloc(NTFS_MAXNAMLEN_UTF8, 16)) == NULL) { |
393 | 0 | return TSK_ERR; |
394 | 0 | } |
395 | | |
396 | 36.0k | if (tsk_verbose) |
397 | 0 | tsk_fprintf(stderr, |
398 | 0 | "ntfs_proc_idxentry: Processing index entry: %" PRIu64 |
399 | 0 | " Size: %" PRIu32 " Len: %" PRIu32 "\n", |
400 | 0 | (uint64_t) ((uintptr_t) a_idxe), a_idxe_len, a_used_len); |
401 | | |
402 | | /* Sanity check */ |
403 | 36.0k | if (a_idxe_len < a_used_len) { |
404 | 42 | tsk_error_reset(); |
405 | 42 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
406 | 42 | tsk_error_set_errstr |
407 | 42 | ("ntfs_proc_idxentry: Allocated length of index entries is larger than buffer length"); |
408 | 42 | return TSK_ERR; |
409 | 42 | } |
410 | | |
411 | | /* where is the end of the buffer */ |
412 | 35.9k | endaddr = ((uintptr_t) a_idxe + a_idxe_len); |
413 | | |
414 | | /* where is the end of the allocated data */ |
415 | 35.9k | endaddr_alloc = ((uintptr_t) a_idxe + a_used_len); |
416 | | |
417 | | /* cycle through the index entries, based on provided size */ |
418 | 902M | while (((uintptr_t) & (a_idxe->stream) + sizeof(ntfs_attr_fname)) < |
419 | 902M | endaddr) { |
420 | | |
421 | 902M | ntfs_attr_fname *fname = (ntfs_attr_fname *) & a_idxe->stream; |
422 | | |
423 | | |
424 | 902M | if (tsk_verbose) |
425 | 0 | tsk_fprintf(stderr, |
426 | 0 | "ntfs_proc_idxentry: New IdxEnt: %" PRIu64 |
427 | 0 | " $FILE_NAME Entry: %" PRIu64 " File Ref: %" PRIu64 |
428 | 0 | " IdxEnt Len: %" PRIu16 " StrLen: %" PRIu16 "\n", |
429 | 0 | (uint64_t) ((uintptr_t) a_idxe), |
430 | 0 | (uint64_t) ((uintptr_t) fname), |
431 | 0 | (uint64_t) tsk_getu48(fs->endian, a_idxe->file_ref), |
432 | 0 | tsk_getu16(fs->endian, a_idxe->idxlen), |
433 | 0 | tsk_getu16(fs->endian, a_idxe->strlen)); |
434 | | |
435 | | /* perform some sanity checks on index buffer head |
436 | | * and advance by 4-bytes if invalid |
437 | | */ |
438 | 902M | if ((tsk_getu48(fs->endian, a_idxe->file_ref) > fs->last_inum) || |
439 | 902M | (tsk_getu48(fs->endian, a_idxe->file_ref) < fs->first_inum) || |
440 | 902M | (tsk_getu16(fs->endian, |
441 | 446M | a_idxe->idxlen) <= tsk_getu16(fs->endian, |
442 | 446M | a_idxe->strlen)) |
443 | 902M | || (tsk_getu16(fs->endian, a_idxe->idxlen) % 4) |
444 | 902M | || (tsk_getu16(fs->endian, a_idxe->idxlen) > a_idxe_len)) { |
445 | 889M | a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4); |
446 | 889M | continue; |
447 | 889M | } |
448 | | |
449 | | #if 0 |
450 | | // @@@ BC: This hid a lot of entries in test images. They were |
451 | | // only partial images, but they were not junk and the idea was |
452 | | // that this check would strip out chunk. Commented it out and |
453 | | // keeping it here as a reminder in case I think about doing it |
454 | | // again. |
455 | | |
456 | | // verify name length would fit in stream |
457 | | if (fname->nlen > tsk_getu16(fs->endian, a_idxe->strlen)) { |
458 | | a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4); |
459 | | if (tsk_verbose) |
460 | | tsk_fprintf(stderr, |
461 | | "ntfs_proc_idxentry: Skipping because name is longer than stream\n"); |
462 | | continue; |
463 | | } |
464 | | #endif |
465 | | |
466 | | // verify it has the correct parent address |
467 | 12.5M | if (tsk_getu48(fs->endian, fname->par_ref) != a_fs_dir->addr) { |
468 | 11.3M | a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4); |
469 | 11.3M | if (tsk_verbose) |
470 | 0 | tsk_fprintf(stderr, |
471 | 0 | "ntfs_proc_idxentry: Skipping because of wrong parent address\n"); |
472 | 11.3M | continue; |
473 | 11.3M | } |
474 | | |
475 | | |
476 | | /* do some sanity checks on the deleted entries |
477 | | */ |
478 | 1.17M | if ((tsk_getu16(fs->endian, a_idxe->strlen) == 0) || |
479 | 1.17M | (((uintptr_t) a_idxe + tsk_getu16(fs->endian, |
480 | 1.16M | a_idxe->idxlen)) > endaddr_alloc)) { |
481 | | |
482 | | /* name space checks */ |
483 | 246k | if ((fname->nspace != NTFS_FNAME_POSIX) && |
484 | 246k | (fname->nspace != NTFS_FNAME_WIN32) && |
485 | 246k | (fname->nspace != NTFS_FNAME_DOS) && |
486 | 246k | (fname->nspace != NTFS_FNAME_WINDOS)) { |
487 | 63.8k | a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4); |
488 | 63.8k | if (tsk_verbose) |
489 | 0 | tsk_fprintf(stderr, |
490 | 0 | "ntfs_proc_idxentry: Skipping because of invalid name space\n"); |
491 | 63.8k | continue; |
492 | 63.8k | } |
493 | | |
494 | 182k | if ((tsk_getu64(fs->endian, fname->alloc_fsize) < |
495 | 182k | tsk_getu64(fs->endian, fname->real_fsize)) |
496 | 182k | || (fname->nlen == 0) |
497 | 182k | || (*(uint8_t *) & fname->name == 0)) { |
498 | | |
499 | 43.0k | a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4); |
500 | 43.0k | if (tsk_verbose) |
501 | 0 | tsk_fprintf(stderr, |
502 | 0 | "ntfs_proc_idxentry: Skipping because of reported file sizes, name length, or NULL name\n"); |
503 | 43.0k | continue; |
504 | 43.0k | } |
505 | | |
506 | 139k | if ((is_time(tsk_getu64(fs->endian, fname->crtime)) == 0) || |
507 | 139k | (is_time(tsk_getu64(fs->endian, fname->atime)) == 0) || |
508 | 139k | (is_time(tsk_getu64(fs->endian, fname->mtime)) == 0)) { |
509 | | |
510 | 35.9k | a_idxe = (ntfs_idxentry *) ((uintptr_t) a_idxe + 4); |
511 | 35.9k | if (tsk_verbose) |
512 | 0 | tsk_fprintf(stderr, |
513 | 0 | "ntfs_proc_idxentry: Skipping because of invalid times\n"); |
514 | 35.9k | continue; |
515 | 35.9k | } |
516 | 139k | } |
517 | | |
518 | | |
519 | | /* For all fname entries, there will exist a DOS style 8.3 |
520 | | * entry. |
521 | | * If the original name is 8.3 compliant, it will be in |
522 | | * a WINDOS type. If it is not compliant, then it will |
523 | | * exist in a POSIX or WIN32 type and the 8.3 compliant |
524 | | * one will be in DOS. The DOS entry typically follows |
525 | | * the WIN32 or POSIX. |
526 | | * |
527 | | * Our approach is to stash away the non-compliant names |
528 | | * for one more entry to see if the next try is its |
529 | | * corresponding 8.3 entry. |
530 | | * |
531 | | * If the 8.3 entry is not for the previous entry, we |
532 | | * skip it on the theory that it corresponds to a previous |
533 | | * WIN32 or POSIX entry. Note that we could be missing some info from deleted files |
534 | | * if the windows version was deleted and the DOS wasn't... |
535 | | */ |
536 | | |
537 | 1.03M | if (fname->nspace == NTFS_FNAME_DOS) { |
538 | | // Was the previous entry not 8.3 compliant? |
539 | 21.9k | if (fs_name_preventry) { |
540 | | // check its the same entry and if so, add short name |
541 | 15.0k | if (fs_name_preventry->meta_addr == tsk_getu48(fs->endian, a_idxe->file_ref)) { |
542 | 1.24k | ntfs_dent_copy_short_only(a_ntfs, a_idxe, fs_name_preventry); |
543 | 1.24k | } |
544 | | |
545 | | // regardless, add preventry to dir and move on to next entry. |
546 | 15.0k | if (tsk_fs_dir_add(a_fs_dir, fs_name_preventry)) { |
547 | 0 | tsk_fs_name_free(fs_name); |
548 | 0 | return TSK_ERR; |
549 | 0 | } |
550 | 15.0k | fs_name_preventry = NULL; |
551 | 15.0k | } |
552 | | |
553 | 21.9k | goto incr_entry; |
554 | 21.9k | } |
555 | | // if we stashed the previous entry and the next wasn't a DOS entry, add it to the list |
556 | 1.01M | else if (fs_name_preventry) { |
557 | 393k | if (tsk_fs_dir_add(a_fs_dir, fs_name_preventry)) { |
558 | 0 | tsk_fs_name_free(fs_name); |
559 | 0 | return TSK_ERR; |
560 | 0 | } |
561 | 393k | fs_name_preventry = NULL; |
562 | 393k | } |
563 | | |
564 | | /* Copy it into the generic form */ |
565 | 1.01M | if (ntfs_dent_copy(a_ntfs, a_idxe, endaddr, fs_name)) { |
566 | 302 | if (tsk_verbose) |
567 | 0 | tsk_fprintf(stderr, |
568 | 0 | "ntfs_proc_idxentry: Skipping because error copying dent_entry\n"); |
569 | 302 | goto incr_entry; |
570 | 302 | } |
571 | | |
572 | | /* |
573 | | * Check if this entry is deleted |
574 | | * |
575 | | * The final check is to see if the end of this entry is |
576 | | * within the space that the idxallocbuf claimed was valid OR |
577 | | * if the parent directory is deleted |
578 | | */ |
579 | 1.01M | if ((a_is_del == 1) || |
580 | 1.01M | (tsk_getu16(fs->endian, a_idxe->strlen) == 0) || |
581 | 1.01M | (((uintptr_t) a_idxe + tsk_getu16(fs->endian, |
582 | 1.00M | a_idxe->idxlen)) > endaddr_alloc)) { |
583 | 99.8k | fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC; |
584 | 99.8k | } |
585 | 910k | else { |
586 | 910k | fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; |
587 | 910k | } |
588 | | |
589 | 1.01M | if (tsk_verbose) |
590 | 0 | tsk_fprintf(stderr, |
591 | 0 | "ntfs_proc_idxentry: Entry Details of %s: Str Len: %" |
592 | 0 | PRIu16 " Len to end after current: %" PRIu64 |
593 | 0 | " flags: %x\n", fs_name->name, tsk_getu16(fs->endian, |
594 | 0 | a_idxe->strlen), |
595 | 0 | (uint64_t) (endaddr_alloc - (uintptr_t) a_idxe - |
596 | 0 | tsk_getu16(fs->endian, a_idxe->idxlen)), |
597 | 0 | fs_name->flags); |
598 | | |
599 | | // WINDOS entries will not have a short 8.3 version, so add them now. |
600 | | // otherwise, we stash the name to see if we get the 8.3 next. |
601 | 1.01M | if (fname->nspace == NTFS_FNAME_WINDOS) { |
602 | 597k | if (tsk_fs_dir_add(a_fs_dir, fs_name)) { |
603 | 0 | tsk_fs_name_free(fs_name); |
604 | 0 | return TSK_ERR; |
605 | 0 | } |
606 | 597k | fs_name_preventry = NULL; |
607 | 597k | } |
608 | 412k | else { |
609 | 412k | fs_name_preventry = fs_name; |
610 | 412k | } |
611 | | |
612 | 1.03M | incr_entry: |
613 | | |
614 | | /* the theory here is that deleted entries have strlen == 0 and |
615 | | * have been found to have idxlen == 16 |
616 | | * |
617 | | * if the strlen is 0, then guess how much the indexlen was |
618 | | * before it was deleted |
619 | | */ |
620 | | |
621 | | /* 16: size of idxentry before stream |
622 | | * 66: size of fname before name |
623 | | * 2*nlen: size of name (in unicode) |
624 | | */ |
625 | 1.03M | if (tsk_getu16(fs->endian, a_idxe->strlen) == 0) { |
626 | 3.09k | a_idxe = |
627 | 3.09k | (ntfs_idxentry |
628 | 3.09k | *) ((((uintptr_t) a_idxe + 16 + 66 + 2 * fname->nlen + |
629 | 3.09k | 3) / 4) * 4); |
630 | 3.09k | } |
631 | 1.02M | else { |
632 | 1.02M | a_idxe = |
633 | 1.02M | (ntfs_idxentry *) ((uintptr_t) a_idxe + |
634 | 1.02M | tsk_getu16(fs->endian, a_idxe->idxlen)); |
635 | 1.02M | } |
636 | | |
637 | 1.03M | } /* end of loop of index entries */ |
638 | | |
639 | | // final check in case we were looking for the short name, we never saw |
640 | 35.9k | if (fs_name_preventry) { |
641 | 3.95k | if (tsk_fs_dir_add(a_fs_dir, fs_name_preventry)) { |
642 | 0 | tsk_fs_name_free(fs_name); |
643 | 0 | return TSK_ERR; |
644 | 0 | } |
645 | 3.95k | fs_name_preventry = NULL; |
646 | 3.95k | } |
647 | | |
648 | 35.9k | tsk_fs_name_free(fs_name); |
649 | 35.9k | return TSK_OK; |
650 | 35.9k | } |
651 | | |
652 | | |
653 | | |
654 | | |
655 | | /* |
656 | | * remove the update sequence values that are changed in the last two |
657 | | * bytes of each sector |
658 | | * |
659 | | * return 1 on error and 0 on success |
660 | | */ |
661 | | static uint8_t |
662 | | ntfs_fix_idxrec(NTFS_INFO * ntfs, ntfs_idxrec * idxrec, uint32_t len) |
663 | 15.8k | { |
664 | 15.8k | int i; |
665 | 15.8k | uint16_t orig_seq; |
666 | 15.8k | TSK_FS_INFO *fs = (TSK_FS_INFO *) & ntfs->fs_info; |
667 | 15.8k | ntfs_upd *upd; |
668 | | |
669 | 15.8k | if (tsk_verbose) |
670 | 0 | tsk_fprintf(stderr, |
671 | 0 | "ntfs_fix_idxrec: Fixing idxrec: %" PRIu64 " Len: %" |
672 | 0 | PRIu32 "\n", (uint64_t) ((uintptr_t) idxrec), len); |
673 | | |
674 | | /* sanity check so we don't run over in the next loop */ |
675 | 15.8k | if ((unsigned int) ((tsk_getu16(fs->endian, idxrec->upd_cnt) - 1) * |
676 | 15.8k | NTFS_UPDATE_SEQ_STRIDE) > len) { |
677 | 46 | tsk_error_reset(); |
678 | 46 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
679 | 46 | tsk_error_set_errstr |
680 | 46 | ("ntfs_fix_idxrec: More Update Sequence Entries than idx record size"); |
681 | 46 | return 1; |
682 | 46 | } |
683 | | |
684 | 15.7k | uint16_t upd_off = tsk_getu16(fs->endian, idxrec->upd_off); |
685 | 15.7k | if (upd_off > len || sizeof(ntfs_upd) > (len - upd_off)) { |
686 | 23 | tsk_error_reset(); |
687 | 23 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
688 | 23 | tsk_error_set_errstr |
689 | 23 | ("ntfs_fix_idxrec: Corrupt idx record"); |
690 | 23 | return 1; |
691 | 23 | } |
692 | | |
693 | | /* Apply the update sequence structure template */ |
694 | 15.7k | upd = |
695 | 15.7k | (ntfs_upd *) ((uintptr_t) idxrec + tsk_getu16(fs->endian, |
696 | 15.7k | idxrec->upd_off)); |
697 | | |
698 | | /* Get the sequence value that each 16-bit value should be */ |
699 | 15.7k | orig_seq = tsk_getu16(fs->endian, upd->upd_val); |
700 | | |
701 | | /* cycle through each sector */ |
702 | 31.4k | for (i = 1; i < tsk_getu16(fs->endian, idxrec->upd_cnt); i++) { |
703 | | |
704 | | /* The offset into the buffer of the value to analyze */ |
705 | 15.7k | int offset = i * NTFS_UPDATE_SEQ_STRIDE - 2; |
706 | 15.7k | uint8_t *new_val, *old_val; |
707 | | |
708 | | /* get the current sequence value */ |
709 | 15.7k | uint16_t cur_seq = |
710 | 15.7k | tsk_getu16(fs->endian, (uintptr_t) idxrec + offset); |
711 | | |
712 | 15.7k | if (cur_seq != orig_seq) { |
713 | | /* get the replacement value */ |
714 | 66 | uint16_t cur_repl = |
715 | 66 | tsk_getu16(fs->endian, &upd->upd_seq + (i - 1) * 2); |
716 | | |
717 | 66 | tsk_error_reset(); |
718 | 66 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
719 | 66 | tsk_error_set_errstr |
720 | 66 | ("fix_idxrec: Incorrect update sequence value in index buffer\nUpdate Value: 0x%" |
721 | 66 | PRIx16 " Actual Value: 0x%" PRIx16 |
722 | 66 | " Replacement Value: 0x%" PRIx16 |
723 | 66 | "\nThis is typically because of a corrupted entry", |
724 | 66 | orig_seq, cur_seq, cur_repl); |
725 | 66 | return 1; |
726 | 66 | } |
727 | | |
728 | 15.7k | new_val = &upd->upd_seq + (i - 1) * 2; |
729 | 15.7k | old_val = (uint8_t *) ((uintptr_t) idxrec + offset); |
730 | | |
731 | 15.7k | if (tsk_verbose) |
732 | 0 | tsk_fprintf(stderr, |
733 | 0 | "ntfs_fix_idxrec: upd_seq %i Replacing: %.4" PRIx16 |
734 | 0 | " With: %.4" PRIx16 "\n", i, tsk_getu16(fs->endian, |
735 | 0 | old_val), tsk_getu16(fs->endian, new_val)); |
736 | | |
737 | 15.7k | *old_val++ = *new_val++; |
738 | 15.7k | *old_val = *new_val; |
739 | 15.7k | } |
740 | | |
741 | 15.6k | return 0; |
742 | 15.7k | } |
743 | | |
744 | | |
745 | | |
746 | | |
747 | | |
748 | | /** \internal |
749 | | * Process a directory and load up FS_DIR with the entries. If a pointer to |
750 | | * an already allocated FS_DIR structure is given, it will be cleared. If no existing |
751 | | * FS_DIR structure is passed (i.e. NULL), then a new one will be created. If the return |
752 | | * value is error or corruption, then the FS_DIR structure could |
753 | | * have entries (depending on when the error occurred). |
754 | | * |
755 | | * @param a_fs File system to analyze |
756 | | * @param a_fs_dir Pointer to FS_DIR pointer. Can contain an already allocated |
757 | | * structure or a new structure. |
758 | | * @param a_addr Address of directory to process. |
759 | | * @param recursion_depth Recursion depth to limit the number of self-calls |
760 | | * @returns error, corruption, ok etc. |
761 | | */ |
762 | | TSK_RETVAL_ENUM |
763 | | ntfs_dir_open_meta( |
764 | | TSK_FS_INFO * a_fs, |
765 | | TSK_FS_DIR ** a_fs_dir, |
766 | | TSK_INUM_T a_addr, |
767 | | [[maybe_unused]] int recursion_depth) |
768 | 33.9k | { |
769 | 33.9k | NTFS_INFO *ntfs = (NTFS_INFO *) a_fs; |
770 | 33.9k | TSK_FS_DIR *fs_dir; |
771 | 33.9k | const TSK_FS_ATTR *fs_attr_root = NULL; |
772 | 33.9k | const TSK_FS_ATTR *fs_attr_idx; |
773 | 33.9k | char *idxalloc; |
774 | 33.9k | ntfs_idxentry *idxe; |
775 | 33.9k | ntfs_idxroot *idxroot; |
776 | 33.9k | ntfs_idxelist *idxelist = NULL; |
777 | 33.9k | ntfs_idxrec *idxrec_p, *idxrec; |
778 | 33.9k | size_t idxalloc_len; |
779 | 33.9k | TSK_FS_LOAD_FILE load_file; |
780 | | |
781 | | /* In this function, we will return immediately if we get an error. |
782 | | * If we get corruption though, we will record that in 'retval_final' |
783 | | * and continue processing. |
784 | | */ |
785 | 33.9k | TSK_RETVAL_ENUM retval_final = TSK_OK; |
786 | 33.9k | TSK_RETVAL_ENUM retval_tmp; |
787 | | |
788 | | /* sanity check */ |
789 | 33.9k | if (a_addr < a_fs->first_inum || a_addr > a_fs->last_inum) { |
790 | 5 | tsk_error_reset(); |
791 | 5 | tsk_error_set_errno(TSK_ERR_FS_WALK_RNG); |
792 | 5 | tsk_error_set_errstr("ntfs_dir_open_meta: inode value: %" PRIuINUM |
793 | 5 | "\n", a_addr); |
794 | 5 | return TSK_ERR; |
795 | 5 | } |
796 | 33.8k | else if (a_fs_dir == NULL) { |
797 | 0 | tsk_error_reset(); |
798 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
799 | 0 | tsk_error_set_errstr |
800 | 0 | ("ntfs_dir_open_meta: NULL fs_attr argument given"); |
801 | 0 | return TSK_ERR; |
802 | 0 | } |
803 | | |
804 | 33.8k | if (tsk_verbose) |
805 | 0 | tsk_fprintf(stderr, |
806 | 0 | "ntfs_open_dir: Processing directory %" PRIuINUM "\n", a_addr); |
807 | | |
808 | | |
809 | 33.8k | fs_dir = *a_fs_dir; |
810 | 33.8k | if (fs_dir) { |
811 | 0 | tsk_fs_dir_reset(fs_dir); |
812 | 0 | fs_dir->addr = a_addr; |
813 | 0 | } |
814 | 33.8k | else { |
815 | 33.8k | if ((*a_fs_dir = fs_dir = |
816 | 33.8k | tsk_fs_dir_alloc(a_fs, a_addr, 128)) == NULL) { |
817 | 0 | return TSK_ERR; |
818 | 0 | } |
819 | 33.8k | } |
820 | | |
821 | | // handle the orphan directory if its contents were requested |
822 | 33.8k | if (a_addr == TSK_FS_ORPHANDIR_INUM(a_fs)) { |
823 | 7.50k | return tsk_fs_dir_find_orphans(a_fs, fs_dir); |
824 | 7.50k | } |
825 | | |
826 | | /* Get the inode and verify it has attributes */ |
827 | 26.3k | if ((fs_dir->fs_file = |
828 | 26.3k | tsk_fs_file_open_meta(a_fs, NULL, a_addr)) == NULL) { |
829 | 701 | tsk_error_errstr2_concat("- ntfs_dir_open_meta"); |
830 | 701 | return TSK_COR; |
831 | 701 | } |
832 | | |
833 | 25.6k | if (!(fs_dir->fs_file->meta->attr)) { |
834 | 0 | tsk_error_reset(); |
835 | 0 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
836 | 0 | tsk_error_set_errstr("dent_walk: Error: Directory address %" |
837 | 0 | PRIuINUM " has no attributes", a_addr); |
838 | 0 | return TSK_COR; |
839 | 0 | } |
840 | | |
841 | | // Update with the sequence number |
842 | 25.6k | fs_dir->seq = fs_dir->fs_file->meta->seq; |
843 | | |
844 | | /* |
845 | | * Read the Index Root Attribute -- we do some sanity checking here |
846 | | * to report errors before we start to make up data for the "." and ".." |
847 | | * entries |
848 | | */ |
849 | 25.6k | fs_attr_root = |
850 | 25.6k | tsk_fs_attrlist_get(fs_dir->fs_file->meta->attr, |
851 | 25.6k | TSK_FS_ATTR_TYPE_NTFS_IDXROOT); |
852 | | // NOTE: We had one error reported on a system that did not have IDX_ROOT, but did have IDX_ALLOC |
853 | 25.6k | if (fs_attr_root) { |
854 | 22.6k | if (fs_attr_root->flags & TSK_FS_ATTR_NONRES) { |
855 | 294 | tsk_error_reset(); |
856 | 294 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
857 | 294 | tsk_error_set_errstr |
858 | 294 | ("dent_walk: $IDX_ROOT is not resident - it should be"); |
859 | 294 | return TSK_COR; |
860 | 294 | } |
861 | 22.3k | idxroot = (ntfs_idxroot *)fs_attr_root->rd.buf; |
862 | | |
863 | | /* Verify that the attribute type is $FILE_NAME */ |
864 | 22.3k | if (tsk_getu32(a_fs->endian, idxroot->type) == 0) { |
865 | 391 | tsk_error_reset(); |
866 | 391 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
867 | 391 | tsk_error_set_errstr |
868 | 391 | ("dent_walk: Attribute type in index root is 0"); |
869 | 391 | return TSK_COR; |
870 | 391 | } |
871 | 21.9k | else if (tsk_getu32(a_fs->endian, idxroot->type) != NTFS_ATYPE_FNAME) { |
872 | 165 | tsk_error_reset(); |
873 | 165 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
874 | 165 | tsk_error_set_errstr("ERROR: Directory index is sorted by type: %" |
875 | 165 | PRIu32 ".\nOnly $FNAME is currently supported", |
876 | 165 | tsk_getu32(a_fs->endian, idxroot->type)); |
877 | 165 | return TSK_COR; |
878 | 165 | } |
879 | 22.3k | } |
880 | | |
881 | | /* |
882 | | * NTFS does not have "." and ".." entries in the index trees |
883 | | * (except for a "." entry in the root directory) |
884 | | * |
885 | | * So, we'll make 'em up by making a TSK_FS_NAME structure for |
886 | | * a '.' and '..' entry and call the action |
887 | | */ |
888 | 24.8k | if (a_addr != a_fs->root_inum) { // && (flags & TSK_FS_NAME_FLAG_ALLOC)) { |
889 | 7.81k | TSK_FS_NAME *fs_name; |
890 | 7.81k | TSK_FS_META_NAME_LIST *fs_name_list; |
891 | | |
892 | 7.81k | if (tsk_verbose) |
893 | 0 | tsk_fprintf(stderr, |
894 | 0 | "ntfs_dir_open_meta: Creating . and .. entries\n"); |
895 | | |
896 | 7.81k | if ((fs_name = tsk_fs_name_alloc(16, 0)) == NULL) { |
897 | 0 | return TSK_ERR; |
898 | 0 | } |
899 | | /* |
900 | | * "." |
901 | | */ |
902 | | |
903 | 7.81k | fs_name->type = TSK_FS_NAME_TYPE_DIR; |
904 | 7.81k | strcpy(fs_name->name, "."); |
905 | | |
906 | 7.81k | fs_name->meta_addr = a_addr; |
907 | 7.81k | if (fs_dir->fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC) { |
908 | 2.99k | fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC; |
909 | | /* If the folder was deleted, the MFT entry sequence will have been incremented. |
910 | | * File name entries are not incremented on delete, so make it one less to |
911 | | * be consistent. */ |
912 | 2.99k | fs_name->meta_seq = fs_dir->fs_file->meta->seq - 1; |
913 | 2.99k | } |
914 | 4.81k | else { |
915 | 4.81k | fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; |
916 | 4.81k | fs_name->meta_seq = fs_dir->fs_file->meta->seq; |
917 | 4.81k | } |
918 | 7.81k | if (tsk_fs_dir_add(fs_dir, fs_name)) { |
919 | 0 | tsk_fs_name_free(fs_name); |
920 | 0 | return TSK_ERR; |
921 | 0 | } |
922 | | |
923 | | |
924 | | /* |
925 | | * ".." |
926 | | */ |
927 | 7.81k | strcpy(fs_name->name, ".."); |
928 | 7.81k | fs_name->type = TSK_FS_NAME_TYPE_DIR; |
929 | | |
930 | | /* The fs_name structure holds the parent inode value, so we |
931 | | * just cycle using those |
932 | | */ |
933 | 7.81k | for (fs_name_list = fs_dir->fs_file->meta->name2; |
934 | 14.5k | fs_name_list != NULL; fs_name_list = fs_name_list->next) { |
935 | 6.70k | fs_name->meta_addr = fs_name_list->par_inode; |
936 | 6.70k | fs_name->meta_seq = fs_name_list->par_seq; |
937 | 6.70k | if (tsk_fs_dir_add(fs_dir, fs_name)) { |
938 | 0 | tsk_fs_name_free(fs_name); |
939 | 0 | return TSK_ERR; |
940 | 0 | } |
941 | 6.70k | } |
942 | | |
943 | 7.81k | tsk_fs_name_free(fs_name); |
944 | 7.81k | fs_name = NULL; |
945 | 7.81k | } |
946 | | |
947 | | |
948 | | /* Now we return to processing the Index Root Attribute */ |
949 | 24.8k | if (fs_attr_root) { |
950 | | |
951 | 21.8k | if (tsk_verbose) |
952 | 0 | tsk_fprintf(stderr, |
953 | 0 | "ntfs_dir_open_meta: Processing $IDX_ROOT of inum %" PRIuINUM |
954 | 0 | "\n", a_addr); |
955 | | |
956 | | /* Get the header of the index entry list */ |
957 | 21.8k | idxelist = &idxroot->list; |
958 | | |
959 | | /* Verify the offset pointers */ |
960 | 21.8k | if ((tsk_getu32(a_fs->endian, idxelist->seqend_off) < |
961 | 21.8k | tsk_getu32(a_fs->endian, idxelist->begin_off)) || |
962 | 21.8k | (tsk_getu32(a_fs->endian, idxelist->bufend_off) < |
963 | 21.6k | tsk_getu32(a_fs->endian, idxelist->seqend_off)) || |
964 | 21.8k | (((uintptr_t)idxelist + tsk_getu32(a_fs->endian, |
965 | 21.3k | idxelist->bufend_off)) > |
966 | 21.3k | ((uintptr_t)fs_attr_root->rd.buf + |
967 | 21.3k | fs_attr_root->rd.buf_size))) { |
968 | 1.36k | tsk_error_reset(); |
969 | 1.36k | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
970 | 1.36k | tsk_error_set_errstr |
971 | 1.36k | ("Error: Index list offsets are invalid on entry: %" PRIuINUM, |
972 | 1.36k | fs_dir->fs_file->meta->addr); |
973 | 1.36k | return TSK_COR; |
974 | 1.36k | } |
975 | | |
976 | | /* Get the offset to the start of the index entry list */ |
977 | 20.4k | idxe = (ntfs_idxentry *)((uintptr_t)idxelist + |
978 | 20.4k | tsk_getu32(a_fs->endian, idxelist->begin_off)); |
979 | | |
980 | 20.4k | retval_tmp = ntfs_proc_idxentry(ntfs, fs_dir, |
981 | 20.4k | (fs_dir->fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC) ? 1 : 0, |
982 | 20.4k | idxe, |
983 | 20.4k | tsk_getu32(a_fs->endian, idxelist->bufend_off) - |
984 | 20.4k | tsk_getu32(a_fs->endian, idxelist->begin_off), |
985 | 20.4k | tsk_getu32(a_fs->endian, idxelist->seqend_off) - |
986 | 20.4k | tsk_getu32(a_fs->endian, idxelist->begin_off)); |
987 | | |
988 | | // stop if we get an error, continue if we got corruption |
989 | 20.4k | if (retval_tmp == TSK_ERR) { |
990 | 0 | return TSK_ERR; |
991 | 0 | } |
992 | 20.4k | else if (retval_tmp == TSK_COR) { |
993 | 0 | retval_final = TSK_COR; |
994 | 0 | } |
995 | 20.4k | } |
996 | | |
997 | | /* |
998 | | * get the index allocation attribute if it exists (it doesn't for |
999 | | * small directories |
1000 | | */ |
1001 | 23.4k | fs_attr_idx = |
1002 | 23.4k | tsk_fs_attrlist_get(fs_dir->fs_file->meta->attr, |
1003 | 23.4k | TSK_FS_ATTR_TYPE_NTFS_IDXALLOC); |
1004 | | |
1005 | | |
1006 | | /* if we don't have an index alloc then return, we have processed |
1007 | | * all of the entries |
1008 | | */ |
1009 | 23.4k | if (!fs_attr_idx) { |
1010 | 3.81k | if ((idxelist) && (tsk_getu32(a_fs->endian, |
1011 | 1.18k | idxelist->flags) & NTFS_IDXELIST_CHILD)) { |
1012 | 296 | tsk_error_reset(); |
1013 | 296 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
1014 | 296 | tsk_error_set_errstr |
1015 | 296 | ("Error: $IDX_ROOT says there should be children, but there isn't"); |
1016 | 296 | return TSK_COR; |
1017 | 296 | } |
1018 | 3.81k | } |
1019 | 19.6k | else { |
1020 | 19.6k | unsigned int off; |
1021 | | |
1022 | 19.6k | if (fs_attr_idx->flags & TSK_FS_ATTR_RES) { |
1023 | 241 | tsk_error_reset(); |
1024 | 241 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
1025 | 241 | tsk_error_set_errstr |
1026 | 241 | ("$IDX_ALLOC is Resident - it shouldn't be"); |
1027 | 241 | return TSK_COR; |
1028 | 241 | } |
1029 | | |
1030 | | // Taking 128 MiB as an arbitrary upper bound |
1031 | 19.4k | if (fs_attr_idx->nrd.allocsize > (128 * 1024 * 1024)) { |
1032 | 113 | tsk_error_reset(); |
1033 | 113 | tsk_error_set_errno(TSK_ERR_FS_LARGE_DIR_ERROR); |
1034 | 113 | tsk_error_set_errstr("ntfs_dir_open_meta: fs_attr_idx->nrd.allocsize value out of bounds (addr: %" PRIuINUM", fs offset: %" PRIdOFF ")", a_addr, a_fs->offset); |
1035 | 113 | return TSK_COR; |
1036 | 0 | return TSK_COR; |
1037 | 113 | } |
1038 | | |
1039 | | /* |
1040 | | * Copy the index allocation run into a big buffer |
1041 | | */ |
1042 | | // default to null unless length is greater than 0 |
1043 | 19.3k | idxalloc = NULL; |
1044 | 19.3k | idxalloc_len = (size_t) fs_attr_idx->nrd.allocsize; |
1045 | | |
1046 | 19.3k | if (idxalloc_len > 0 && (idxalloc = (char *)tsk_malloc(idxalloc_len)) == NULL) { |
1047 | 13 | return TSK_ERR; |
1048 | 13 | } |
1049 | | |
1050 | | /* Fill in the loading data structure */ |
1051 | 19.3k | load_file.total = load_file.left = idxalloc_len; |
1052 | 19.3k | load_file.cur = load_file.base = idxalloc; |
1053 | | |
1054 | 19.3k | if (tsk_verbose) |
1055 | 0 | tsk_fprintf(stderr, |
1056 | 0 | "ntfs_dir_open_meta: Copying $IDX_ALLOC into buffer\n"); |
1057 | | |
1058 | 19.3k | if (tsk_fs_attr_walk(fs_attr_idx, |
1059 | 19.3k | TSK_FS_FILE_WALK_FLAG_SLACK, tsk_fs_load_file_action, |
1060 | 19.3k | (void *) &load_file)) { |
1061 | 723 | free(idxalloc); |
1062 | 723 | tsk_error_errstr2_concat(" - ntfs_dir_open_meta"); |
1063 | 723 | return TSK_COR; // this could be an error though |
1064 | 723 | } |
1065 | | |
1066 | | /* Not all of the directory was copied, so we exit */ |
1067 | 18.5k | if (load_file.left > 0) { |
1068 | 315 | free(idxalloc); |
1069 | | |
1070 | 315 | tsk_error_reset(); |
1071 | 315 | tsk_error_set_errno(TSK_ERR_FS_FWALK); |
1072 | 315 | tsk_error_set_errstr("Error reading directory contents: %" |
1073 | 315 | PRIuINUM "\n", a_addr); |
1074 | 315 | return TSK_COR; |
1075 | 315 | } |
1076 | | |
1077 | | /* |
1078 | | * The idxalloc is a big buffer that contains one or more |
1079 | | * idx buffer structures. Each idxrec is a node in the B-Tree. |
1080 | | * We do not process the tree as a tree because then we could |
1081 | | * not find the deleted file names. |
1082 | | * |
1083 | | * Therefore, we scan the big buffer looking for the index record |
1084 | | * structures. We save a pointer to the known beginning (idxrec_p). |
1085 | | * Then we scan for the beginning of the next one (idxrec) and process |
1086 | | * everything in the middle as an ntfs_idxrec. We can't use the |
1087 | | * size given because then we wouldn't see the deleted names |
1088 | | */ |
1089 | | |
1090 | | /* Set the previous pointer to NULL */ |
1091 | 18.2k | idxrec_p = idxrec = NULL; |
1092 | | |
1093 | | /* Loop by cluster size */ |
1094 | 1.17M | for (off = 0; off < idxalloc_len; off += ntfs->csize_b) { |
1095 | 1.15M | uint32_t list_len, rec_len; |
1096 | | |
1097 | | // Ensure that there is enough data for an idxrec |
1098 | 1.15M | if (idxalloc_len < sizeof(ntfs_idxrec) || off > idxalloc_len - sizeof(ntfs_idxrec)) { |
1099 | 41 | tsk_error_reset(); |
1100 | 41 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
1101 | 41 | tsk_error_set_errstr |
1102 | 41 | ("ntfs_dir_open_meta: Not enough data in idxalloc buffer for an idxrec."); |
1103 | 41 | free(idxalloc); |
1104 | 41 | return TSK_COR; |
1105 | 41 | } |
1106 | | |
1107 | 1.15M | idxrec = (ntfs_idxrec *) & idxalloc[off]; |
1108 | | |
1109 | 1.15M | if (tsk_verbose) |
1110 | 0 | tsk_fprintf(stderr, |
1111 | 0 | "ntfs_dir_open_meta: Index Buffer Offset: %d Magic: %" |
1112 | 0 | PRIx32 "\n", off, tsk_getu32(a_fs->endian, |
1113 | 0 | idxrec->magic)); |
1114 | | |
1115 | | /* Is this the beginning of an index record? */ |
1116 | 1.15M | if (tsk_getu32(a_fs->endian, |
1117 | 1.15M | idxrec->magic) != NTFS_IDXREC_MAGIC) |
1118 | 1.14M | continue; |
1119 | | |
1120 | | |
1121 | | /* idxrec_p is only NULL for the first time |
1122 | | * Set it and start again to find the next one */ |
1123 | 15.9k | if (idxrec_p == NULL) { |
1124 | 15.5k | idxrec_p = idxrec; |
1125 | 15.5k | continue; |
1126 | 15.5k | } |
1127 | | |
1128 | | /* Process the previous structure */ |
1129 | | |
1130 | | /* idxrec points to the next idxrec structure, idxrec_p |
1131 | | * points to the one we are going to process |
1132 | | */ |
1133 | 385 | rec_len = |
1134 | 385 | (uint32_t) ((uintptr_t) idxrec - (uintptr_t) idxrec_p); |
1135 | | |
1136 | 385 | if (tsk_verbose) |
1137 | 0 | tsk_fprintf(stderr, |
1138 | 0 | "ntfs_dir_open_meta: Processing previous index record (len: %" |
1139 | 0 | PRIu32 ")\n", rec_len); |
1140 | | |
1141 | | /* remove the update sequence in the index record */ |
1142 | 385 | if (ntfs_fix_idxrec(ntfs, idxrec_p, rec_len)) { |
1143 | 23 | free(idxalloc); |
1144 | 23 | return TSK_COR; |
1145 | 23 | } |
1146 | | |
1147 | | /* Locate the start of the index entry list */ |
1148 | 362 | idxelist = &idxrec_p->list; |
1149 | 362 | idxe = (ntfs_idxentry *) ((uintptr_t) idxelist + |
1150 | 362 | tsk_getu32(a_fs->endian, idxelist->begin_off)); |
1151 | | |
1152 | | /* the length from the start of the next record to where our |
1153 | | * list starts. |
1154 | | * This should be the same as bufend_off in idxelist, but we don't |
1155 | | * trust it. |
1156 | | */ |
1157 | 362 | list_len = (uint32_t) ((uintptr_t) idxrec - (uintptr_t) idxe); |
1158 | | |
1159 | | /* Verify the offset pointers */ |
1160 | 362 | if (((uintptr_t) idxe > (uintptr_t) idxrec) || |
1161 | 362 | ((uintptr_t) idxelist + |
1162 | 341 | tsk_getu32(a_fs->endian, |
1163 | 341 | idxelist->seqend_off) > (uintptr_t) idxrec)) { |
1164 | 34 | tsk_error_reset(); |
1165 | 34 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
1166 | 34 | tsk_error_set_errstr |
1167 | 34 | ("Error: Index list offsets are invalid on entry: %" |
1168 | 34 | PRIuINUM, fs_dir->fs_file->meta->addr); |
1169 | 34 | free(idxalloc); |
1170 | 34 | return TSK_COR; |
1171 | 34 | } |
1172 | | |
1173 | | |
1174 | | /* process the list of index entries */ |
1175 | 328 | retval_tmp = ntfs_proc_idxentry(ntfs, fs_dir, |
1176 | 328 | (fs_dir->fs_file->meta-> |
1177 | 328 | flags & TSK_FS_META_FLAG_UNALLOC) ? 1 : 0, idxe, |
1178 | 328 | list_len, tsk_getu32(a_fs->endian, |
1179 | 328 | idxelist->seqend_off) - tsk_getu32(a_fs->endian, |
1180 | 328 | idxelist->begin_off)); |
1181 | | // stop if we get an error, record if we get corruption |
1182 | 328 | if (retval_tmp == TSK_ERR) { |
1183 | 19 | free(idxalloc); |
1184 | 19 | return TSK_ERR; |
1185 | 19 | } |
1186 | 309 | else if (retval_tmp == TSK_COR) { |
1187 | 0 | retval_final = TSK_COR; |
1188 | 0 | } |
1189 | | |
1190 | | /* reset the pointer to the next record */ |
1191 | 309 | idxrec_p = idxrec; |
1192 | | |
1193 | 309 | } /* end of cluster loop */ |
1194 | | |
1195 | | |
1196 | | /* Process the final record */ |
1197 | 18.1k | if (idxrec_p) { |
1198 | 15.4k | uint32_t list_len, rec_len; |
1199 | | |
1200 | | /* Length from end of attribute to start of this */ |
1201 | 15.4k | rec_len = |
1202 | 15.4k | (uint32_t) (idxalloc_len - ((uintptr_t) idxrec_p - |
1203 | 15.4k | (uintptr_t) idxalloc)); |
1204 | | |
1205 | 15.4k | if (tsk_verbose) |
1206 | 0 | tsk_fprintf(stderr, |
1207 | 0 | "ntfs_dir_open_meta: Processing final index record (len: %" |
1208 | 0 | PRIu32 ")\n", rec_len); |
1209 | | |
1210 | | /* remove the update sequence */ |
1211 | 15.4k | if (ntfs_fix_idxrec(ntfs, idxrec_p, rec_len)) { |
1212 | 112 | free(idxalloc); |
1213 | 112 | return TSK_COR; |
1214 | 112 | } |
1215 | | |
1216 | 15.3k | idxelist = &idxrec_p->list; |
1217 | 15.3k | if (tsk_getu32(a_fs->endian, idxelist->begin_off) > rec_len) { |
1218 | 39 | tsk_error_reset(); |
1219 | 39 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
1220 | 39 | tsk_error_set_errstr |
1221 | 39 | ("Error: Index list offsets are invalid on entry: %" |
1222 | 39 | PRIuINUM, fs_dir->fs_file->meta->addr); |
1223 | 39 | free(idxalloc); |
1224 | 39 | return TSK_COR; |
1225 | 39 | } |
1226 | | |
1227 | 15.2k | idxe = (ntfs_idxentry *) ((uintptr_t) idxelist + |
1228 | 15.2k | tsk_getu32(a_fs->endian, idxelist->begin_off)); |
1229 | | |
1230 | | /* This is the length of the idx entries */ |
1231 | 15.2k | list_len = |
1232 | 15.2k | (uint32_t) (((uintptr_t) idxalloc + idxalloc_len) - |
1233 | 15.2k | (uintptr_t) idxe); |
1234 | | |
1235 | | /* Verify the offset pointers */ |
1236 | 15.2k | if ((list_len > rec_len) || |
1237 | 15.2k | ((uintptr_t) idxelist + |
1238 | 15.2k | tsk_getu32(a_fs->endian, idxelist->seqend_off) > |
1239 | 15.2k | (uintptr_t) idxalloc + idxalloc_len)) { |
1240 | 30 | tsk_error_reset(); |
1241 | 30 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
1242 | 30 | tsk_error_set_errstr |
1243 | 30 | ("Error: Index list offsets are invalid on entry: %" |
1244 | 30 | PRIuINUM, fs_dir->fs_file->meta->addr); |
1245 | 30 | free(idxalloc); |
1246 | 30 | return TSK_COR; |
1247 | 30 | } |
1248 | | |
1249 | | /* process the list of index entries */ |
1250 | 15.2k | retval_tmp = ntfs_proc_idxentry(ntfs, fs_dir, |
1251 | 15.2k | (fs_dir->fs_file->meta-> |
1252 | 15.2k | flags & TSK_FS_META_FLAG_UNALLOC) ? 1 : 0, idxe, |
1253 | 15.2k | list_len, tsk_getu32(a_fs->endian, |
1254 | 15.2k | idxelist->seqend_off) - tsk_getu32(a_fs->endian, |
1255 | 15.2k | idxelist->begin_off)); |
1256 | | // stop if we get an error, record if we get corruption |
1257 | 15.2k | if (retval_tmp == TSK_ERR) { |
1258 | 23 | free(idxalloc); |
1259 | 23 | return TSK_ERR; |
1260 | 23 | } |
1261 | 15.2k | else if (retval_tmp == TSK_COR) { |
1262 | 0 | retval_final = TSK_COR; |
1263 | 0 | } |
1264 | 15.2k | } |
1265 | | |
1266 | 17.9k | free(idxalloc); |
1267 | 17.9k | } |
1268 | | |
1269 | | |
1270 | | // get the orphan files |
1271 | | // load and cache the map if it has not already been done |
1272 | 21.4k | tsk_take_lock(&ntfs->orphan_map_lock); |
1273 | 21.4k | if (ntfs->orphan_map == NULL) { |
1274 | | // we do this to make it non-NULL. WE had some images that |
1275 | | // had no orphan files and it repeatedly did inode_walks |
1276 | | // because orphan_map was always NULL |
1277 | 1.75k | getParentMap(ntfs); |
1278 | | |
1279 | 1.75k | if (a_fs->inode_walk(a_fs, a_fs->first_inum, a_fs->last_inum, |
1280 | 1.75k | (TSK_FS_META_FLAG_ENUM)(TSK_FS_META_FLAG_UNALLOC | TSK_FS_META_FLAG_ALLOC), ntfs_parent_act, NULL)) { |
1281 | 586 | tsk_release_lock(&ntfs->orphan_map_lock); |
1282 | 586 | return TSK_ERR; |
1283 | 586 | } |
1284 | 1.75k | } |
1285 | | |
1286 | | |
1287 | | /* see if there are any entries in MFT for this dir that we didn't see. |
1288 | | * Need to make sure it is for this version (sequence) though. |
1289 | | * NTFS Updates the sequence when a directory is deleted and not when |
1290 | | * it is allocated. So, if we have a deleted directory, then use |
1291 | | * its previous sequence number to find the files that were in it when |
1292 | | * it was allocated. |
1293 | | */ |
1294 | 20.8k | uint16_t seqToSrch = fs_dir->fs_file->meta->seq; |
1295 | 20.8k | if (fs_dir->fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC) { |
1296 | 2.02k | if (seqToSrch > 0) |
1297 | 1.82k | seqToSrch--; |
1298 | 197 | else |
1299 | | // I can't imagine how we get here or what we should do except maybe not do the search. |
1300 | 197 | seqToSrch = 0; |
1301 | 2.02k | } |
1302 | | |
1303 | 20.8k | if (ntfs_parent_map_exists(ntfs, a_addr, seqToSrch)) { |
1304 | 15.5k | TSK_FS_NAME *fs_name; |
1305 | | |
1306 | 15.5k | const std::vector <NTFS_META_ADDR> &childFiles = ntfs_parent_map_get(ntfs, a_addr, seqToSrch); |
1307 | | |
1308 | 15.5k | if ((fs_name = tsk_fs_name_alloc(256, 0)) == NULL){ |
1309 | 0 | tsk_release_lock(&ntfs->orphan_map_lock); |
1310 | 0 | return TSK_ERR; |
1311 | 0 | } |
1312 | | |
1313 | 15.5k | fs_name->type = TSK_FS_NAME_TYPE_UNDEF; |
1314 | 15.5k | fs_name->par_addr = a_addr; |
1315 | 15.5k | fs_name->par_seq = fs_dir->fs_file->meta->seq; |
1316 | | |
1317 | 24.3k | for(const NTFS_META_ADDR& childFile: childFiles) { |
1318 | 24.3k | TSK_FS_FILE *fs_file_orp = NULL; |
1319 | | |
1320 | | /* Check if fs_dir already has an allocated entry for this |
1321 | | * file. If so, ignore it. We used to rely on fs_dir_add |
1322 | | * to get rid of this, but it wasted a lot of lookups. If |
1323 | | * We have only unalloc for this same entry (from idx entries), |
1324 | | * then try to add it. If we got an allocated entry from |
1325 | | * the idx entries, then assume we have everything. */ |
1326 | 24.3k | if (tsk_fs_dir_contains(fs_dir, childFile.getAddr(), childFile.getHash()) == TSK_FS_NAME_FLAG_ALLOC) { |
1327 | 2.47k | continue; |
1328 | 2.47k | } |
1329 | | |
1330 | | /* Fill in the basics of the fs_name entry |
1331 | | * so we can print in the fls formats */ |
1332 | 21.8k | fs_name->meta_addr = childFile.getAddr(); |
1333 | 21.8k | fs_name->meta_seq = childFile.getSeq(); |
1334 | | |
1335 | | // lookup the file to get more info (we did not cache that) |
1336 | 21.8k | fs_file_orp = |
1337 | 21.8k | tsk_fs_file_open_meta(a_fs, fs_file_orp, fs_name->meta_addr); |
1338 | 21.8k | if (fs_file_orp) { |
1339 | 21.7k | if (fs_file_orp->meta) { |
1340 | 21.7k | if (fs_file_orp->meta->flags & TSK_FS_META_FLAG_ALLOC) { |
1341 | 17.7k | fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; |
1342 | 17.7k | } |
1343 | 4.02k | else { |
1344 | 4.02k | fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC; |
1345 | | /* This sequence is the MFT entry, which gets |
1346 | | * incremented when it is unallocated. So, |
1347 | | * decrement it back down so that it is more |
1348 | | * similar to the usual situation, where the |
1349 | | * name sequence is 1 smaller than the meta |
1350 | | * sequence. */ |
1351 | 4.02k | fs_name->meta_seq--; |
1352 | 4.02k | } |
1353 | | |
1354 | 21.7k | if (fs_file_orp->meta->name2) { |
1355 | 21.7k | TSK_FS_META_NAME_LIST *n2 = fs_file_orp->meta->name2; |
1356 | | |
1357 | 44.4k | while (n2) { |
1358 | 22.6k | if (n2->par_inode == a_addr) { |
1359 | 19.4k | strncpy(fs_name->name, n2->name, fs_name->name_size); |
1360 | 19.4k | tsk_fs_dir_add(fs_dir, fs_name); |
1361 | 19.4k | } |
1362 | 22.6k | n2 = n2->next; |
1363 | 22.6k | } |
1364 | 21.7k | } |
1365 | 21.7k | } |
1366 | 21.7k | tsk_fs_file_close(fs_file_orp); |
1367 | 21.7k | } |
1368 | 21.8k | } |
1369 | 15.5k | tsk_fs_name_free(fs_name); |
1370 | 15.5k | } |
1371 | 20.8k | tsk_release_lock(&ntfs->orphan_map_lock); |
1372 | | |
1373 | | // if we are listing the root directory, add the Orphan directory entry |
1374 | 20.8k | if (a_addr == a_fs->root_inum) { |
1375 | 16.1k | TSK_FS_NAME *fs_name; |
1376 | | |
1377 | 16.1k | if ((fs_name = tsk_fs_name_alloc(256, 0)) == NULL) |
1378 | 0 | return TSK_ERR; |
1379 | | |
1380 | 16.1k | if (tsk_fs_dir_make_orphan_dir_name(a_fs, fs_name)) { |
1381 | 0 | tsk_fs_name_free(fs_name); |
1382 | 0 | return TSK_ERR; |
1383 | 0 | } |
1384 | | |
1385 | 16.1k | if (tsk_fs_dir_add(fs_dir, fs_name)) { |
1386 | 0 | tsk_fs_name_free(fs_name); |
1387 | 0 | return TSK_ERR; |
1388 | 0 | } |
1389 | 16.1k | tsk_fs_name_free(fs_name); |
1390 | 16.1k | } |
1391 | | |
1392 | | |
1393 | 20.8k | return retval_final; |
1394 | 20.8k | } |
1395 | | |
1396 | | |
1397 | | |
1398 | | /**************************************************************************** |
1399 | | * FIND_FILE ROUTINES |
1400 | | * |
1401 | | */ |
1402 | | |
1403 | 0 | #define MAX_DEPTH 128 |
1404 | 0 | #define DIR_STRSZ 4096 |
1405 | | |
1406 | | typedef struct { |
1407 | | /* Recursive path stuff */ |
1408 | | |
1409 | | /* how deep in the directory tree are we */ |
1410 | | unsigned int depth; |
1411 | | |
1412 | | /* pointer in dirs string to where '/' is for given depth */ |
1413 | | char *didx[MAX_DEPTH]; |
1414 | | |
1415 | | /* The current directory name string */ |
1416 | | char dirs[DIR_STRSZ]; |
1417 | | |
1418 | | } NTFS_DINFO; |
1419 | | |
1420 | | |
1421 | | /* |
1422 | | * Looks up the parent inode described in fs_name. |
1423 | | * |
1424 | | * fs_name was filled in by ntfs_find_file and will get the final path |
1425 | | * added to it before action is called |
1426 | | * |
1427 | | * return 1 on error and 0 on success |
1428 | | */ |
1429 | | static uint8_t |
1430 | | ntfs_find_file_rec(TSK_FS_INFO * fs, NTFS_DINFO * dinfo, |
1431 | | TSK_FS_FILE * fs_file, TSK_FS_META_NAME_LIST * fs_name_list, |
1432 | | TSK_FS_DIR_WALK_CB action, void *ptr) |
1433 | 0 | { |
1434 | 0 | TSK_FS_META_NAME_LIST *fs_name_list_par; |
1435 | 0 | uint8_t decrem = 0; |
1436 | 0 | size_t len = 0, i; |
1437 | 0 | char *begin = NULL; |
1438 | | |
1439 | |
|
1440 | 0 | if (fs_name_list->par_inode < fs->first_inum || |
1441 | 0 | fs_name_list->par_inode > fs->last_inum) { |
1442 | 0 | tsk_error_reset(); |
1443 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1444 | 0 | tsk_error_set_errstr("invalid inode value: %" PRIuINUM "\n", |
1445 | 0 | fs_name_list->par_inode); |
1446 | 0 | return 1; |
1447 | 0 | } |
1448 | | |
1449 | 0 | std::unique_ptr<TSK_FS_FILE, decltype(&tsk_fs_file_close)> fs_file_par{ |
1450 | 0 | tsk_fs_file_open_meta(fs, NULL, fs_name_list->par_inode), |
1451 | 0 | tsk_fs_file_close |
1452 | 0 | }; |
1453 | |
|
1454 | 0 | if (!fs_file_par) { |
1455 | 0 | tsk_error_errstr2_concat(" - ntfs_find_file_rec"); |
1456 | 0 | return 1; |
1457 | 0 | } |
1458 | | |
1459 | | /* |
1460 | | * Orphan File |
1461 | | * This occurs when the file is deleted and either: |
1462 | | * - The parent is no longer a directory |
1463 | | * - The sequence number of the parent is no longer correct |
1464 | | */ |
1465 | 0 | if (( ! TSK_FS_IS_DIR_META(fs_file_par->meta->type)) |
1466 | 0 | || (fs_file_par->meta->seq != fs_name_list->par_seq)) { |
1467 | 0 | const char *str = TSK_FS_ORPHAN_STR; |
1468 | 0 | int retval; |
1469 | 0 | len = strlen(str); |
1470 | | |
1471 | | /* @@@ There should be a sanity check here to verify that the |
1472 | | * previous name was unallocated ... but how do I get it again? |
1473 | | */ |
1474 | 0 | if ((((uintptr_t) dinfo->didx[dinfo->depth - 1] - len) >= |
1475 | 0 | (uintptr_t) & dinfo->dirs[0]) |
1476 | 0 | && (dinfo->depth < MAX_DEPTH)) { |
1477 | 0 | begin = dinfo->didx[dinfo->depth] = |
1478 | 0 | (char *) ((uintptr_t) dinfo->didx[dinfo->depth - 1] - len); |
1479 | |
|
1480 | 0 | dinfo->depth++; |
1481 | 0 | decrem = 1; |
1482 | |
|
1483 | 0 | for (i = 0; i < len; i++) |
1484 | 0 | begin[i] = str[i]; |
1485 | 0 | } |
1486 | |
|
1487 | 0 | retval = action(fs_file, begin, ptr); |
1488 | |
|
1489 | 0 | if (decrem) |
1490 | 0 | dinfo->depth--; |
1491 | |
|
1492 | 0 | return (retval == TSK_WALK_ERROR) ? 1 : 0; |
1493 | 0 | } |
1494 | | |
1495 | 0 | for (fs_name_list_par = fs_file_par->meta->name2; |
1496 | 0 | fs_name_list_par != NULL; |
1497 | 0 | fs_name_list_par = fs_name_list_par->next) { |
1498 | |
|
1499 | 0 | len = strlen(fs_name_list_par->name); |
1500 | | |
1501 | | /* do some length checks on the dir structure |
1502 | | * if we can't fit it then forget about it */ |
1503 | 0 | if ((((uintptr_t) dinfo->didx[dinfo->depth - 1] - len - 1) >= |
1504 | 0 | (uintptr_t) & dinfo->dirs[0]) |
1505 | 0 | && (dinfo->depth < MAX_DEPTH)) { |
1506 | 0 | begin = dinfo->didx[dinfo->depth] = |
1507 | 0 | (char *) ((uintptr_t) dinfo->didx[dinfo->depth - 1] - len - |
1508 | 0 | 1); |
1509 | |
|
1510 | 0 | dinfo->depth++; |
1511 | 0 | decrem = 1; |
1512 | |
|
1513 | 0 | *begin = '/'; |
1514 | 0 | for (i = 0; i < len; i++) |
1515 | 0 | begin[i + 1] = fs_name_list_par->name[i]; |
1516 | 0 | } |
1517 | 0 | else { |
1518 | 0 | begin = dinfo->didx[dinfo->depth]; |
1519 | 0 | decrem = 0; |
1520 | 0 | } |
1521 | | |
1522 | | |
1523 | | /* if we are at the root, then fill out the rest of fs_name with |
1524 | | * the full path and call the action |
1525 | | */ |
1526 | 0 | if (fs_name_list_par->par_inode == NTFS_ROOTINO) { |
1527 | | /* increase the path by one so that we do not pass the '/' |
1528 | | * if we do then the printed result will have '//' at |
1529 | | * the beginning |
1530 | | */ |
1531 | 0 | if (TSK_WALK_ERROR == action(fs_file, |
1532 | 0 | (const char *) ((uintptr_t) begin + 1), ptr)) { |
1533 | 0 | return 1; |
1534 | 0 | } |
1535 | 0 | } |
1536 | | |
1537 | | /* otherwise, recurse some more */ |
1538 | 0 | else { |
1539 | 0 | if (ntfs_find_file_rec(fs, dinfo, fs_file, fs_name_list_par, |
1540 | 0 | action, ptr)) { |
1541 | 0 | return 1; |
1542 | 0 | } |
1543 | 0 | } |
1544 | | |
1545 | | /* if we incremented before, then decrement the depth now */ |
1546 | 0 | if (decrem) |
1547 | 0 | dinfo->depth--; |
1548 | 0 | } |
1549 | | |
1550 | 0 | return 0; |
1551 | 0 | } |
1552 | | |
1553 | | /* \ingroup fslib |
1554 | | * NTFS can map a meta address to its name much faster than in other file systems |
1555 | | * because each entry stores the address of its parent. |
1556 | | * |
1557 | | * This can not be called with dent_walk because the path |
1558 | | * structure will get messed up! |
1559 | | * |
1560 | | * @param fs File system being analyzed |
1561 | | * @param inode_toid Address of file to find the name for. |
1562 | | * @param type_toid Attribute type to find the more specific name for (if you want more than just the base file name) |
1563 | | * @param type_used 1 if the type_toid value was passed a valid value. 0 otherwise. |
1564 | | * @param id_toid Attribute id to find the more specific name for (if you want more than just the base file name) |
1565 | | * @param id_used 1 if the id_toid value was passed a valid value. 0 otherwise. |
1566 | | * @param dir_walk_flags Flags to use during search |
1567 | | * @param action Callback that will be called for each name that uses the specified addresses. |
1568 | | * @param ptr Pointer that will be passed into action when it is called (so that you can pass in other data) |
1569 | | * @returns 1 on error, 0 on success |
1570 | | */ |
1571 | | |
1572 | | uint8_t |
1573 | | ntfs_find_file(TSK_FS_INFO * fs, TSK_INUM_T inode_toid, uint32_t type_toid, |
1574 | | uint8_t type_used, uint16_t id_toid, uint8_t id_used, |
1575 | | TSK_FS_DIR_WALK_FLAG_ENUM dir_walk_flags, TSK_FS_DIR_WALK_CB action, |
1576 | | void *ptr) |
1577 | 0 | { |
1578 | 0 | TSK_FS_META_NAME_LIST *fs_name_list; |
1579 | 0 | char *attr = NULL; |
1580 | 0 | NTFS_DINFO dinfo; |
1581 | 0 | TSK_RETVAL_ENUM r_enum; |
1582 | 0 | NTFS_INFO *ntfs = (NTFS_INFO *) fs; |
1583 | | |
1584 | | /* sanity check */ |
1585 | 0 | if (inode_toid < fs->first_inum || inode_toid > fs->last_inum) { |
1586 | 0 | tsk_error_reset(); |
1587 | 0 | tsk_error_set_errno(TSK_ERR_FS_ARG); |
1588 | 0 | tsk_error_set_errstr("ntfs_find_file: invalid inode value: %" |
1589 | 0 | PRIuINUM "\n", inode_toid); |
1590 | 0 | return 1; |
1591 | 0 | } |
1592 | | |
1593 | 0 | std::unique_ptr<ntfs_mft, decltype(&free)> mft{ |
1594 | 0 | (ntfs_mft *) tsk_malloc(ntfs->mft_rsize_b), |
1595 | 0 | free |
1596 | 0 | }; |
1597 | |
|
1598 | 0 | if (!mft) { |
1599 | 0 | return 1; |
1600 | 0 | } |
1601 | | |
1602 | 0 | r_enum = ntfs_dinode_lookup(ntfs, (char *) mft.get(), inode_toid, 0); |
1603 | 0 | if (r_enum == TSK_ERR) { |
1604 | 0 | return 1; |
1605 | 0 | } |
1606 | | |
1607 | | // open the file to ID |
1608 | 0 | std::unique_ptr<TSK_FS_FILE, decltype(&tsk_fs_file_close)> fs_file{ |
1609 | 0 | tsk_fs_file_open_meta(fs, NULL, inode_toid), |
1610 | 0 | tsk_fs_file_close |
1611 | 0 | }; |
1612 | |
|
1613 | 0 | if (!fs_file) { |
1614 | 0 | tsk_error_errstr2_concat("- ntfs_find_file"); |
1615 | 0 | return 1; |
1616 | 0 | } |
1617 | | |
1618 | | // see if its allocation status meets the callback needs |
1619 | 0 | if ((fs_file->meta->flags & TSK_FS_META_FLAG_ALLOC) |
1620 | 0 | && ((dir_walk_flags & TSK_FS_DIR_WALK_FLAG_ALLOC) == 0)) { |
1621 | 0 | return 1; |
1622 | 0 | } |
1623 | 0 | else if ((fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC) |
1624 | 0 | && ((dir_walk_flags & TSK_FS_DIR_WALK_FLAG_UNALLOC) == 0)) { |
1625 | 0 | return 1; |
1626 | 0 | } |
1627 | | |
1628 | | /* Allocate a name and fill in some details */ |
1629 | 0 | if ((fs_file->name = |
1630 | 0 | tsk_fs_name_alloc(NTFS_MAXNAMLEN_UTF8, 0)) == NULL) { |
1631 | 0 | return 1; |
1632 | 0 | } |
1633 | 0 | fs_file->name->meta_addr = inode_toid; |
1634 | 0 | fs_file->name->meta_seq = 0; |
1635 | 0 | fs_file->name->flags = |
1636 | 0 | ((tsk_getu16(fs->endian, |
1637 | 0 | mft->flags) & NTFS_MFT_INUSE) ? TSK_FS_NAME_FLAG_ALLOC : |
1638 | 0 | TSK_FS_NAME_FLAG_UNALLOC); |
1639 | |
|
1640 | 0 | memset(&dinfo, 0, sizeof(NTFS_DINFO)); |
1641 | | |
1642 | | /* in this function, we use the dinfo->dirs array in the opposite order. |
1643 | | * we set the end of it to NULL and then prepend the |
1644 | | * directories to it |
1645 | | * |
1646 | | * dinfo->didx[dinfo->depth] will point to where the current level started their |
1647 | | * dir name |
1648 | | */ |
1649 | 0 | dinfo.dirs[DIR_STRSZ - 2] = '/'; |
1650 | 0 | dinfo.dirs[DIR_STRSZ - 1] = '\0'; |
1651 | 0 | dinfo.didx[0] = &dinfo.dirs[DIR_STRSZ - 2]; |
1652 | 0 | dinfo.depth = 1; |
1653 | | |
1654 | | |
1655 | | /* Get the name for the attribute - if specified */ |
1656 | 0 | if (type_used) { |
1657 | 0 | const TSK_FS_ATTR *fs_attr; |
1658 | |
|
1659 | 0 | if (id_used) |
1660 | 0 | fs_attr = |
1661 | 0 | tsk_fs_attrlist_get_id(fs_file->meta->attr, (TSK_FS_ATTR_TYPE_ENUM)type_toid, |
1662 | 0 | id_toid); |
1663 | 0 | else |
1664 | 0 | fs_attr = tsk_fs_attrlist_get(fs_file->meta->attr, (TSK_FS_ATTR_TYPE_ENUM)type_toid); |
1665 | |
|
1666 | 0 | if (!fs_attr) { |
1667 | 0 | tsk_error_reset(); |
1668 | 0 | tsk_error_set_errno(TSK_ERR_FS_INODE_COR); |
1669 | 0 | tsk_error_set_errstr("find_file: Type %" PRIu32 " Id %" PRIu16 |
1670 | 0 | " not found in MFT %" PRIuINUM "", type_toid, id_toid, |
1671 | 0 | inode_toid); |
1672 | 0 | return 1; |
1673 | 0 | } |
1674 | | |
1675 | | /* only add the attribute name if it is the non-default data stream */ |
1676 | 0 | if (fs_attr->name != NULL) |
1677 | 0 | attr = fs_attr->name; |
1678 | 0 | } |
1679 | | |
1680 | | /* loop through all the names it may have */ |
1681 | 0 | for (fs_name_list = fs_file->meta->name2; fs_name_list != NULL; |
1682 | 0 | fs_name_list = fs_name_list->next) { |
1683 | 0 | int retval; |
1684 | | |
1685 | | /* Append on the attribute name, if it exists */ |
1686 | 0 | if (attr != NULL) { |
1687 | 0 | snprintf(fs_file->name->name, fs_file->name->name_size, |
1688 | 0 | "%s:%s", fs_name_list->name, attr); |
1689 | 0 | } |
1690 | 0 | else { |
1691 | 0 | strncpy(fs_file->name->name, fs_name_list->name, |
1692 | 0 | fs_file->name->name_size); |
1693 | 0 | } |
1694 | | |
1695 | | /* if this is in the root directory, then call back */ |
1696 | 0 | if (fs_name_list->par_inode == NTFS_ROOTINO) { |
1697 | |
|
1698 | 0 | retval = action(fs_file.get(), dinfo.didx[0], ptr); |
1699 | 0 | if (retval == TSK_WALK_STOP) { |
1700 | 0 | return 0; |
1701 | 0 | } |
1702 | 0 | else if (retval == TSK_WALK_ERROR) { |
1703 | 0 | return 1; |
1704 | 0 | } |
1705 | 0 | } |
1706 | | /* call the recursive function on the parent to get the full path */ |
1707 | 0 | else { |
1708 | 0 | if (ntfs_find_file_rec(fs, &dinfo, fs_file.get(), fs_name_list, |
1709 | 0 | action, ptr)) { |
1710 | 0 | return 1; |
1711 | 0 | } |
1712 | 0 | } |
1713 | 0 | } /* end of name loop */ |
1714 | | |
1715 | 0 | return 0; |
1716 | 0 | } |
1717 | | |
1718 | | |
1719 | | int |
1720 | | ntfs_name_cmp(TSK_FS_INFO * /*a_fs_info*/, const char *s1, const char *s2) |
1721 | 0 | { |
1722 | 0 | return strcasecmp(s1, s2); |
1723 | 0 | } |