Coverage Report

Created: 2025-10-28 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sleuthkit/tsk/fs/ifind_lib.cpp
Line
Count
Source
1
/*
2
** ifind (inode find)
3
** The Sleuth Kit
4
**
5
** Given an image  and block number, identify which inode it is used by
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
** TCTUTILs
15
** Copyright (c) 2001 Brian Carrier.  All rights reserved
16
**
17
**
18
** This software is distributed under the Common Public License 1.0
19
**
20
*/
21
22
/**
23
 * \file ifind_lib.c
24
 * Contains the library API functions used by the TSK ifind command
25
 * line tool.
26
 */
27
28
#include "tsk_fs_i.h"
29
#include "tsk_hfs.h"
30
31
#include <memory>
32
33
34
/*******************************************************************************
35
 * Find an unallocated NTFS MFT entry based on its parent directory
36
 */
37
38
typedef struct {
39
    TSK_INUM_T parinode;
40
    TSK_FS_IFIND_FLAG_ENUM flags;
41
    uint8_t found;
42
} IFIND_PAR_DATA;
43
44
45
/* inode walk call back for tsk_fs_ifind_par to find unallocated files
46
 * based on parent directory
47
 */
48
static TSK_WALK_RET_ENUM
49
ifind_par_act(TSK_FS_FILE * fs_file, void *ptr)
50
0
{
51
0
    IFIND_PAR_DATA *data = (IFIND_PAR_DATA *) ptr;
52
0
    TSK_FS_META_NAME_LIST *fs_name_list;
53
54
    /* go through each file name attribute for this file */
55
0
    fs_name_list = fs_file->meta->name2;
56
0
    while (fs_name_list) {
57
58
        /* we found a file that has the target parent directory.
59
         * Make a FS_NAME structure and print it.  */
60
0
        if (fs_name_list->par_inode == data->parinode) {
61
0
            int i, cnt;
62
0
            uint8_t printed;
63
0
            TSK_FS_NAME *fs_name;
64
65
0
            if ((fs_name = tsk_fs_name_alloc(256, 0)) == NULL)
66
0
                return TSK_WALK_ERROR;
67
68
            /* Fill in the basics of the fs_name entry
69
             * so we can print in the fls formats */
70
0
            fs_name->meta_addr = fs_file->meta->addr;
71
0
            fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC;
72
0
            strncpy(fs_name->name, fs_name_list->name, fs_name->name_size);
73
74
            // now look for the $Data and $IDXROOT attributes
75
0
            fs_file->name = fs_name;
76
0
            printed = 0;
77
78
            // cycle through the attributes
79
0
            cnt = tsk_fs_file_attr_getsize(fs_file);
80
0
            for (i = 0; i < cnt; i++) {
81
0
                const TSK_FS_ATTR *fs_attr =
82
0
                    tsk_fs_file_attr_get_idx(fs_file, i);
83
0
                if (!fs_attr)
84
0
                    continue;
85
86
0
                if ((fs_attr->type == TSK_FS_ATTR_TYPE_NTFS_DATA)
87
0
                    || (fs_attr->type == TSK_FS_ATTR_TYPE_NTFS_IDXROOT)) {
88
89
0
                    if (data->flags & TSK_FS_IFIND_PAR_LONG) {
90
0
                        tsk_fs_name_print_long(stdout, fs_file, NULL,
91
0
                            fs_file->fs_info, fs_attr, 0, 0);
92
0
                        tsk_printf("\n");
93
0
                    }
94
0
                    else {
95
0
                        tsk_fs_name_print(stdout, fs_file, NULL,
96
0
                            fs_file->fs_info, fs_attr, 0);
97
0
                        tsk_printf("\n");
98
0
                    }
99
0
                    printed = 1;
100
0
                }
101
0
            }
102
103
            // if there were no attributes, print what we got
104
0
            if (printed == 0) {
105
0
                if (data->flags & TSK_FS_IFIND_PAR_LONG) {
106
0
                    tsk_fs_name_print_long(stdout, fs_file, NULL,
107
0
                        fs_file->fs_info, NULL, 0, 0);
108
0
                    tsk_printf("\n");
109
0
                }
110
0
                else {
111
0
                    tsk_fs_name_print(stdout, fs_file, NULL,
112
0
                        fs_file->fs_info, NULL, 0);
113
0
                    tsk_printf("\n");
114
0
                }
115
0
            }
116
0
            tsk_fs_name_free(fs_name);
117
0
            data->found = 1;
118
0
        }
119
0
        fs_name_list = fs_name_list->next;
120
0
    }
121
122
0
    return TSK_WALK_CONT;
123
0
}
124
125
126
127
/**
128
 * Searches for unallocated MFT entries that have a given
129
 * MFT entry as their parent directory (as reported in FILE_NAME).
130
 * @param fs File system to search
131
 * @param lclflags Flags
132
 * @param par Parent directory MFT entry address
133
 * @returns 1 on error and 0 on success
134
 */
135
uint8_t
136
tsk_fs_ifind_par(TSK_FS_INFO * fs, TSK_FS_IFIND_FLAG_ENUM lclflags,
137
    TSK_INUM_T par)
138
0
{
139
0
    IFIND_PAR_DATA data;
140
141
0
    data.found = 0;
142
0
    data.flags = lclflags;
143
0
    data.parinode = par;
144
145
    /* Walk unallocated MFT entries */
146
0
    if (fs->inode_walk(fs, fs->first_inum, fs->last_inum,
147
0
            TSK_FS_META_FLAG_UNALLOC, ifind_par_act, &data)) {
148
0
        return 1;
149
0
    }
150
151
0
    return 0;
152
0
}
153
154
155
156
157
/**
158
 * \ingroup fslib
159
 *
160
 * Find the meta data address for a given file name (UTF-8).
161
 * The basic idea of the function is to break the given name into its
162
 * subdirectories and start looking for each (starting in the root
163
 * directory).
164
 *
165
 * @param a_fs FS to analyze
166
 * @param a_path UTF-8 path of file to search for
167
 * @param [out] a_result Meta data address of file
168
 * @param [out] a_fs_name Copy of name details (or NULL if details not wanted)
169
 * @returns -1 on (system) error, 0 if found, and 1 if not found
170
 */
171
int8_t
172
tsk_fs_path2inum(TSK_FS_INFO * a_fs, const char *a_path,
173
    TSK_INUM_T * a_result, TSK_FS_NAME * a_fs_name)
174
5.05k
{
175
5.05k
    char *cpath;
176
5.05k
    size_t clen;
177
5.05k
    char *cur_dir;              // The "current" directory or file we are looking for
178
5.05k
    char *cur_attr;             // The "current" attribute of the dir we are looking for
179
5.05k
    TSK_INUM_T next_meta;
180
5.05k
    uint8_t is_done;
181
5.05k
    *a_result = 0;
182
183
    // copy path to a buffer that we can modify
184
5.05k
    clen = strlen(a_path) + 1;
185
5.05k
    if ((cpath = (char *) tsk_malloc(clen)) == NULL) {
186
0
        return -1;
187
0
    }
188
5.05k
    strncpy(cpath, a_path, clen);
189
190
    // Get the first part of the directory path.
191
5.05k
    UNUSED char *strtok_last; // not actually unused; some compilers complain
192
5.05k
    cur_dir = (char *) strtok_r(cpath, "/", &strtok_last);
193
5.05k
    cur_attr = NULL;
194
195
    /* If there is no token, then only a '/' was given */
196
5.05k
    if (cur_dir == NULL) {
197
0
        free(cpath);
198
0
        *a_result = a_fs->root_inum;
199
200
        // create the dummy entry if needed
201
0
        if (a_fs_name) {
202
0
            a_fs_name->meta_addr = a_fs->root_inum;
203
            // Note that we are not filling in meta_seq -- we could, we just aren't
204
205
0
            a_fs_name->type = TSK_FS_NAME_TYPE_DIR;
206
0
            a_fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
207
0
            if (a_fs_name->name)
208
0
                a_fs_name->name[0] = '\0';
209
0
            if (a_fs_name->shrt_name)
210
0
                a_fs_name->shrt_name[0] = '\0';
211
0
        }
212
0
        return 0;
213
0
    }
214
215
    /* If this is NTFS, separate out the attribute of the current directory */
216
5.05k
    if (TSK_FS_TYPE_ISNTFS(a_fs->ftype)
217
0
        && ((cur_attr = strchr(cur_dir, ':')) != NULL)) {
218
0
        *(cur_attr) = '\0';
219
0
        cur_attr++;
220
0
    }
221
222
5.05k
    if (tsk_verbose)
223
0
        tsk_fprintf(stderr, "Looking for %s\n", cur_dir);
224
225
    // initialize the first place to look, the root dir
226
5.05k
    next_meta = a_fs->root_inum;
227
228
    // we loop until we know the outcome and then exit.
229
    // everything should return from inside the loop.
230
5.05k
    is_done = 0;
231
6.08k
    while (is_done == 0) {
232
5.05k
        size_t i;
233
234
        // open the next directory in the recursion
235
5.05k
        std::unique_ptr<TSK_FS_DIR, decltype(&tsk_fs_dir_close)> fs_dir{
236
5.05k
           tsk_fs_dir_open_meta(a_fs, next_meta),
237
5.05k
           tsk_fs_dir_close
238
5.05k
        };
239
240
5.05k
        if (!fs_dir) {
241
3.24k
            free(cpath);
242
3.24k
            return -1;
243
3.24k
        }
244
245
        /* Verify this is indeed a directory.  We had one reported
246
         * problem where a file was a disk image and opening it as
247
         * a directory found the directory entries inside of the file
248
         * and this caused problems... */
249
1.81k
        if (!TSK_FS_IS_DIR_META(fs_dir->fs_file->meta->type)) {
250
536
            tsk_error_reset();
251
536
            tsk_error_set_errno(TSK_ERR_FS_GENFS);
252
536
            tsk_error_set_errstr("Address %" PRIuINUM
253
536
                " is not for a directory\n", next_meta);
254
536
            free(cpath);
255
536
            return -1;
256
536
        }
257
258
1.27k
        std::unique_ptr<TSK_FS_FILE, decltype(&tsk_fs_file_close)> fs_file_alloc{
259
1.27k
            nullptr,  // set to the allocated file that is our target
260
1.27k
            tsk_fs_file_close
261
1.27k
        };
262
263
1.27k
        std::unique_ptr<TSK_FS_FILE, decltype(&tsk_fs_file_close)> fs_file_del{
264
1.27k
            nullptr,  // set to an unallocated file that matches our criteria
265
1.27k
            tsk_fs_file_close
266
1.27k
        };
267
268
        // cycle through each entry
269
12.7k
        for (i = 0; i < tsk_fs_dir_getsize(fs_dir.get()); i++) {
270
271
11.7k
            uint8_t found_name = 0;
272
273
11.7k
            std::unique_ptr<TSK_FS_FILE, decltype(&tsk_fs_file_close)> fs_file{
274
11.7k
                tsk_fs_dir_get(fs_dir.get(), i),
275
11.7k
                tsk_fs_file_close
276
11.7k
            };
277
278
11.7k
            if (!fs_file) {
279
0
                free(cpath);
280
0
                return -1;
281
0
            }
282
283
            /*
284
             * Check if this is the name that we are currently looking for,
285
             * as identified in 'cur_dir'
286
             */
287
11.7k
            if ((fs_file->name->name)
288
11.7k
                && (a_fs->name_cmp(a_fs, fs_file->name->name,
289
11.7k
                        cur_dir) == 0)) {
290
251
                found_name = 1;
291
251
            }
292
11.4k
            else if ((fs_file->name->shrt_name)
293
0
                && (a_fs->name_cmp(a_fs, fs_file->name->shrt_name,
294
0
                        cur_dir) == 0)) {
295
0
                found_name = 1;
296
0
            }
297
298
            /* For NTFS, we have to check the attribute name. */
299
11.7k
            if ((found_name == 1) && (TSK_FS_TYPE_ISNTFS(a_fs->ftype))) {
300
                /*  ensure we have the right attribute name */
301
0
                if (cur_attr != NULL) {
302
0
                    found_name = 0;
303
0
                    if (fs_file->meta) {
304
0
                        int cnt, i;
305
306
                        // cycle through the attributes
307
0
                        cnt = tsk_fs_file_attr_getsize(fs_file.get());
308
0
                        for (i = 0; i < cnt; i++) {
309
0
                            const TSK_FS_ATTR *fs_attr =
310
0
                                tsk_fs_file_attr_get_idx(fs_file.get(), i);
311
0
                            if (!fs_attr)
312
0
                                continue;
313
314
0
                            if ((fs_attr->name)
315
0
                                && (a_fs->name_cmp(a_fs, fs_attr->name,
316
0
                                        cur_attr) == 0)) {
317
0
                                found_name = 1;
318
0
                                break;
319
0
                            }
320
0
                        }
321
0
                    }
322
0
                }
323
0
            }
324
325
11.7k
            if (found_name) {
326
                /* If we found our file and it is allocated, then stop. If
327
                 * it is unallocated, keep on going to see if we can get
328
                 * an allocated hit */
329
251
                if (fs_file->name->flags & TSK_FS_NAME_FLAG_ALLOC) {
330
251
                    fs_file_alloc = std::move(fs_file);
331
251
                    break;
332
251
                }
333
0
                else {
334
                    // if we already have an unalloc and its addr is 0, then use the new one
335
0
                    if (fs_file_del
336
0
                        && fs_file_del->name->meta_addr == 0) {
337
0
                        fs_file_del.reset();
338
0
                    }
339
0
                    fs_file_del = std::move(fs_file);
340
0
                }
341
251
            }
342
11.7k
        }
343
344
        // we found a directory, go into it
345
1.27k
        if (fs_file_alloc || fs_file_del) {
346
347
251
            const char *pname;
348
251
            TSK_FS_FILE *fs_file_tmp;
349
350
            // choose the alloc one first (if they both exist)
351
251
            if (fs_file_alloc)
352
251
                fs_file_tmp = fs_file_alloc.get();
353
0
            else
354
0
                fs_file_tmp = fs_file_del.get();
355
356
251
            pname = cur_dir;    // save a copy of the current name pointer
357
358
            // advance to the next name
359
251
            cur_dir = (char *) strtok_r(NULL, "/", &strtok_last);
360
251
            cur_attr = NULL;
361
362
251
            if (tsk_verbose)
363
0
                tsk_fprintf(stderr,
364
0
                    "Found it (%s), now looking for %s\n", pname, cur_dir);
365
366
            /* That was the last name in the path -- we found the file! */
367
251
            if (cur_dir == NULL) {
368
251
                *a_result = fs_file_tmp->name->meta_addr;
369
370
                // make a copy if one was requested
371
251
                if (a_fs_name) {
372
0
                    tsk_fs_name_copy(a_fs_name, fs_file_tmp->name);
373
0
                }
374
375
251
                free(cpath);
376
251
                return 0;
377
251
            }
378
379
            // update the attribute field, if needed
380
0
            if (TSK_FS_TYPE_ISNTFS(a_fs->ftype)
381
0
                && ((cur_attr = strchr(cur_dir, ':')) != NULL)) {
382
0
                *(cur_attr) = '\0';
383
0
                cur_attr++;
384
0
            }
385
386
            // update the value for the next directory to open
387
0
            next_meta = fs_file_tmp->name->meta_addr;
388
0
        }
389
390
        // no hit in directory
391
1.02k
        else {
392
1.02k
            is_done = 1;
393
1.02k
        }
394
1.27k
    }
395
396
1.02k
    free(cpath);
397
1.02k
    return 1;
398
5.05k
}
399
400
401
/**
402
 * Find the meta data address for a given file TCHAR name
403
 *
404
 * @param fs FS to analyze
405
 * @param tpath Path of file to search for
406
 * @param [out] result Meta data address of file
407
 * @returns -1 on error, 0 if found, and 1 if not found
408
 */
409
int8_t
410
tsk_fs_ifind_path(TSK_FS_INFO * fs, TSK_TCHAR * tpath, TSK_INUM_T * result)
411
0
{
412
413
#ifdef TSK_WIN32
414
    // Convert the UTF-16 path to UTF-8
415
    {
416
        size_t clen;
417
        UTF8 *ptr8;
418
        UTF16 *ptr16;
419
        int retval;
420
        char *cpath;
421
422
        clen = TSTRLEN(tpath) * 4;
423
        if ((cpath = (char *) tsk_malloc(clen)) == NULL) {
424
            return -1;
425
        }
426
        ptr8 = (UTF8 *) cpath;
427
        ptr16 = (UTF16 *) tpath;
428
429
        retval =
430
            tsk_UTF16toUTF8_lclorder((const UTF16 **) &ptr16, (UTF16 *)
431
            & ptr16[TSTRLEN(tpath) + 1], &ptr8,
432
            (UTF8 *) ((uintptr_t) ptr8 + clen), TSKlenientConversion);
433
        if (retval != TSKconversionOK) {
434
            tsk_error_reset();
435
            tsk_error_set_errno(TSK_ERR_FS_UNICODE);
436
            tsk_error_set_errstr
437
                ("tsk_fs_ifind_path: Error converting path to UTF-8: %d",
438
                retval);
439
            free(cpath);
440
            return -1;
441
        }
442
        return tsk_fs_path2inum(fs, cpath, result, NULL);
443
    }
444
#else
445
0
    return tsk_fs_path2inum(fs, (const char *) tpath, result, NULL);
446
0
#endif
447
0
}
448
449
450
451
452
453
454
/*******************************************************************************
455
 * Find an inode given a data unit
456
 */
457
458
typedef struct {
459
    TSK_DADDR_T block;          /* the block to find */
460
    TSK_FS_IFIND_FLAG_ENUM flags;
461
    uint8_t found;
462
463
    TSK_INUM_T curinode;        /* the inode being analyzed */
464
    uint32_t curtype;           /* the type currently being analyzed: NTFS */
465
    uint16_t curid;
466
} IFIND_DATA_DATA;
467
468
469
/*
470
 * file_walk action for non-ntfs
471
 */
472
static TSK_WALK_RET_ENUM
473
ifind_data_file_act(
474
  TSK_FS_FILE * fs_file,
475
  [[maybe_unused]] TSK_OFF_T a_off,
476
  TSK_DADDR_T addr,
477
  [[maybe_unused]] char *buf,
478
  [[maybe_unused]] size_t size,
479
  TSK_FS_BLOCK_FLAG_ENUM flags,
480
  void *ptr)
481
0
{
482
0
    TSK_FS_INFO *fs = fs_file->fs_info;
483
0
    IFIND_DATA_DATA *data = (IFIND_DATA_DATA *) ptr;
484
485
    /* Ignore sparse blocks because they do not reside on disk */
486
0
    if (flags & TSK_FS_BLOCK_FLAG_SPARSE)
487
0
        return TSK_WALK_CONT;
488
489
0
    if (addr == data->block) {
490
0
        if (TSK_FS_TYPE_ISNTFS(fs->ftype))
491
0
            tsk_printf("%" PRIuINUM "-%" PRIu32 "-%" PRIu16 "\n",
492
0
                data->curinode, data->curtype, data->curid);
493
0
        else
494
0
            tsk_printf("%" PRIuINUM "\n", data->curinode);
495
0
        data->found = 1;
496
0
        return TSK_WALK_STOP;
497
0
    }
498
0
    return TSK_WALK_CONT;
499
0
}
500
501
502
/*
503
** find_inode
504
**
505
** Callback action for inode_walk
506
*/
507
static TSK_WALK_RET_ENUM
508
ifind_data_act(TSK_FS_FILE * fs_file, void *ptr)
509
0
{
510
0
    IFIND_DATA_DATA *data = (IFIND_DATA_DATA *) ptr;
511
0
    int file_flags =
512
0
        (TSK_FS_FILE_WALK_FLAG_AONLY | TSK_FS_FILE_WALK_FLAG_SLACK);
513
0
    int i, cnt;
514
515
0
    data->curinode = fs_file->meta->addr;
516
517
    /* Search all attributes */
518
0
    cnt = tsk_fs_file_attr_getsize(fs_file);
519
0
    for (i = 0; i < cnt; i++) {
520
0
        const TSK_FS_ATTR *fs_attr = tsk_fs_file_attr_get_idx(fs_file, i);
521
0
        if (!fs_attr)
522
0
            continue;
523
524
0
        data->curtype = fs_attr->type;
525
0
        data->curid = fs_attr->id;
526
0
        if (fs_attr->flags & TSK_FS_ATTR_NONRES) {
527
0
            if (tsk_fs_attr_walk(fs_attr,
528
0
                    (TSK_FS_FILE_WALK_FLAG_ENUM) file_flags, ifind_data_file_act, ptr)) {
529
0
                if (tsk_verbose)
530
0
                    tsk_fprintf(stderr,
531
0
                        "Error walking file %" PRIuINUM
532
0
                        " Attribute: %i", fs_file->meta->addr, i);
533
534
                /* Ignore these errors */
535
0
                tsk_error_reset();
536
0
            }
537
538
            // stop if we only want one hit
539
0
            if ((data->found) && (!(data->flags & TSK_FS_IFIND_ALL)))
540
0
                break;
541
0
        }
542
0
    }
543
544
0
    if ((data->found) && (!(data->flags & TSK_FS_IFIND_ALL)))
545
0
        return TSK_WALK_STOP;
546
0
    else
547
0
        return TSK_WALK_CONT;
548
0
}
549
550
551
552
553
/*
554
 * Find the inode that has allocated block blk
555
 * Return 1 on error, 0 if no error */
556
uint8_t
557
tsk_fs_ifind_data(TSK_FS_INFO * fs, TSK_FS_IFIND_FLAG_ENUM lclflags,
558
    TSK_DADDR_T blk)
559
0
{
560
0
    IFIND_DATA_DATA data;
561
562
0
    memset(&data, 0, sizeof(IFIND_DATA_DATA));
563
0
    data.flags = lclflags;
564
0
    data.block = blk;
565
566
0
    if (fs->inode_walk(fs, fs->first_inum, fs->last_inum,
567
0
            (TSK_FS_META_FLAG_ENUM) (TSK_FS_META_FLAG_ALLOC | TSK_FS_META_FLAG_UNALLOC),
568
0
            ifind_data_act, &data)) {
569
0
        return 1;
570
0
    }
571
572
    /* If we did not find an inode yet, get the block's
573
     * flags so we can identify it as a meta data block */
574
0
    if (!data.found) {
575
0
        TSK_FS_BLOCK *fs_block;
576
577
0
        if ((fs_block = tsk_fs_block_get(fs, NULL, blk)) != NULL) {
578
0
            if (fs_block->flags & TSK_FS_BLOCK_FLAG_META) {
579
0
                tsk_printf("Meta Data\n");
580
0
                data.found = 1;
581
0
            }
582
0
            tsk_fs_block_free(fs_block);
583
0
        }
584
0
    }
585
586
0
    if (!data.found) {
587
0
        tsk_printf("Inode not found\n");
588
0
    }
589
0
    return 0;
590
0
}