Coverage Report

Created: 2026-02-26 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sleuthkit/tsk/fs/ext2fs_dent.cpp
Line
Count
Source
1
/*
2
** ext2fs_dent
3
** The Sleuth Kit
4
**
5
** File name layer support for an Ext2 / Ext3 FS
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-2006 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 ext2fs_dent.c
24
 * Contains the internal TSK file name processing code for Ext2 / ext3
25
 */
26
27
#include <ctype.h>
28
#include "tsk_fs_i.h"
29
#include "tsk_ext2fs.h"
30
31
32
33
static uint8_t
34
ext2fs_dent_copy(EXT2FS_INFO * ext2fs,
35
    char *ext2_dent, TSK_FS_NAME * fs_name)
36
1.99M
{
37
1.99M
    TSK_FS_INFO *fs = &(ext2fs->fs_info);
38
39
1.99M
    if (ext2fs->deentry_type == EXT2_DE_V1) {
40
3.06k
        ext2fs_dentry1 *dir = (ext2fs_dentry1 *) ext2_dent;
41
42
3.06k
        fs_name->meta_addr = tsk_getu32(fs->endian, dir->inode);
43
44
        /* ext2 does not null terminate */
45
3.06k
        if (tsk_getu16(fs->endian, dir->name_len) >= fs_name->name_size) {
46
0
            tsk_error_reset();
47
0
            tsk_error_set_errno(TSK_ERR_FS_ARG);
48
0
            tsk_error_set_errstr
49
0
                ("ext2fs_dent_copy: Name Space too Small %d %" PRIuSIZE "",
50
0
                tsk_getu16(fs->endian, dir->name_len), fs_name->name_size);
51
0
            return 1;
52
0
        }
53
54
        /* Copy and Null Terminate */
55
3.06k
        strncpy(fs_name->name, dir->name, tsk_getu16(fs->endian,
56
3.06k
                dir->name_len));
57
3.06k
        fs_name->name[tsk_getu16(fs->endian, dir->name_len)] = '\0';
58
59
3.06k
        fs_name->type = TSK_FS_NAME_TYPE_UNDEF;
60
3.06k
    }
61
1.99M
    else {
62
1.99M
        ext2fs_dentry2 *dir = (ext2fs_dentry2 *) ext2_dent;
63
64
1.99M
        fs_name->meta_addr = tsk_getu32(fs->endian, dir->inode);
65
66
        /* ext2 does not null terminate */
67
1.99M
        if (dir->name_len >= fs_name->name_size) {
68
0
            tsk_error_reset();
69
0
            tsk_error_set_errno(TSK_ERR_FS_ARG);
70
0
            tsk_error_set_errstr
71
0
                ("ext2_dent_copy: Name Space too Small %d %" PRIuSIZE "",
72
0
                dir->name_len, fs_name->name_size);
73
0
            return 1;
74
0
        }
75
76
        /* Copy and Null Terminate */
77
1.99M
        strncpy(fs_name->name, dir->name, dir->name_len);
78
1.99M
        fs_name->name[dir->name_len] = '\0';
79
80
1.99M
        switch (dir->type) {
81
20.9k
        case EXT2_DE_REG:
82
20.9k
            fs_name->type = TSK_FS_NAME_TYPE_REG;
83
20.9k
            break;
84
1.41M
        case EXT2_DE_DIR:
85
1.41M
            fs_name->type = TSK_FS_NAME_TYPE_DIR;
86
1.41M
            break;
87
9.40k
        case EXT2_DE_CHR:
88
9.40k
            fs_name->type = TSK_FS_NAME_TYPE_CHR;
89
9.40k
            break;
90
6.06k
        case EXT2_DE_BLK:
91
6.06k
            fs_name->type = TSK_FS_NAME_TYPE_BLK;
92
6.06k
            break;
93
598
        case EXT2_DE_FIFO:
94
598
            fs_name->type = TSK_FS_NAME_TYPE_FIFO;
95
598
            break;
96
3.71k
        case EXT2_DE_SOCK:
97
3.71k
            fs_name->type = TSK_FS_NAME_TYPE_SOCK;
98
3.71k
            break;
99
4.03k
        case EXT2_DE_LNK:
100
4.03k
            fs_name->type = TSK_FS_NAME_TYPE_LNK;
101
4.03k
            break;
102
202k
        case EXT2_DE_UNKNOWN:
103
529k
        default:
104
529k
            fs_name->type = TSK_FS_NAME_TYPE_UNDEF;
105
529k
            break;
106
1.99M
        }
107
1.99M
    }
108
109
1.99M
    fs_name->flags = (TSK_FS_NAME_FLAG_ENUM) 0;
110
111
1.99M
    return 0;
112
1.99M
}
113
114
115
116
/*
117
 * @param a_is_del Set to 1 if block is from a deleted directory.
118
 */
119
static TSK_RETVAL_ENUM
120
ext2fs_dent_parse_block(
121
  EXT2FS_INFO * ext2fs,
122
  TSK_FS_DIR * a_fs_dir,
123
  uint8_t a_is_del,
124
  [[maybe_unused]] TSK_LIST ** list_seen,
125
  char *buf,
126
  int len)
127
10.5M
{
128
10.5M
    TSK_FS_INFO *fs = &(ext2fs->fs_info);
129
130
10.5M
    int dellen = 0;
131
10.5M
    int idx;
132
10.5M
    uint16_t reclen;
133
10.5M
    uint32_t inode;
134
10.5M
    char *dirPtr;
135
10.5M
    TSK_FS_NAME *fs_name;
136
10.5M
    int minreclen = 4;
137
138
10.5M
    if ((fs_name = tsk_fs_name_alloc(EXT2FS_MAXNAMLEN + 1, 0)) == NULL)
139
0
        return TSK_ERR;
140
141
    /* update each time by the actual length instead of the
142
     ** recorded length so we can view the deleted entries
143
     */
144
2.71G
    for (idx = 0; idx <= len - EXT2FS_DIRSIZ_lcl(1); idx += minreclen) {
145
146
2.70G
        unsigned int namelen;
147
2.70G
        dirPtr = &buf[idx];
148
149
2.70G
        if (ext2fs->deentry_type == EXT2_DE_V1) {
150
1.82G
            ext2fs_dentry1 *dir = (ext2fs_dentry1 *) dirPtr;
151
1.82G
            inode = tsk_getu32(fs->endian, dir->inode);
152
1.82G
            namelen = tsk_getu16(fs->endian, dir->name_len);
153
1.82G
            reclen = tsk_getu16(fs->endian, dir->rec_len);
154
1.82G
        }
155
881M
        else {
156
881M
            ext2fs_dentry2 *dir = (ext2fs_dentry2 *) dirPtr;
157
881M
            inode = tsk_getu32(fs->endian, dir->inode);
158
881M
            namelen = dir->name_len;
159
881M
            reclen = tsk_getu16(fs->endian, dir->rec_len);
160
881M
        }
161
162
2.70G
        minreclen = EXT2FS_DIRSIZ_lcl(namelen);
163
164
        /*
165
         ** Check if we may have a valid directory entry.  If we don't,
166
         ** then increment to the next word and try again.
167
         */
168
2.70G
        if ((inode > fs->last_inum) ||  // inode is unsigned
169
2.68G
            (namelen > EXT2FS_MAXNAMLEN) || (namelen == 0) ||   // namelen is unsigned
170
2.70G
            (reclen < minreclen) || (reclen % 4) || (idx + reclen > len)) {
171
172
2.70G
            minreclen = 4;
173
2.70G
            if (dellen > 0)
174
11.8M
                dellen -= 4;
175
2.70G
            continue;
176
2.70G
        }
177
178
        /* Before we process an entry in unallocated space, make
179
         * sure that it also ends in the unalloc space */
180
2.01M
        if ((dellen) && (dellen < minreclen)) {
181
21.2k
            minreclen = 4;
182
21.2k
            dellen -= 4;
183
21.2k
            continue;
184
21.2k
        }
185
186
1.99M
        if (ext2fs_dent_copy(ext2fs, dirPtr, fs_name)) {
187
0
            tsk_fs_name_free(fs_name);
188
0
            return TSK_ERR;
189
0
        }
190
191
        /* Do we have a deleted entry? */
192
1.99M
        if ((dellen > 0) || (inode == 0) || (a_is_del)) {
193
1.27M
            fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC;
194
1.27M
            if (dellen > 0)
195
442k
                dellen -= minreclen;
196
1.27M
        }
197
        /* We have a non-deleted entry */
198
717k
        else {
199
717k
            fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
200
717k
        }
201
1.99M
        if (tsk_fs_dir_add(a_fs_dir, fs_name)) {
202
0
            tsk_fs_name_free(fs_name);
203
0
            return TSK_ERR;
204
0
        }
205
206
        /* If the actual length is shorter then the
207
         ** recorded length, then the next entry(ies) have been
208
         ** deleted.  Set dellen to the length of data that
209
         ** has been deleted
210
         **
211
         ** Because we aren't guaranteed with Ext2FS that the next
212
         ** entry begins right after this one, we will check to
213
         ** see if the difference is less than a possible entry
214
         ** before we waste time searching it
215
         */
216
1.99M
        if (dellen <= 0) {
217
1.55M
            if (reclen - minreclen >= EXT2FS_DIRSIZ_lcl(1))
218
284k
                dellen = reclen - minreclen;
219
1.27M
            else
220
1.27M
                minreclen = reclen;
221
1.55M
        }
222
1.99M
    }
223
224
10.5M
    tsk_fs_name_free(fs_name);
225
10.5M
    return TSK_OK;
226
10.5M
}
227
228
229
/** \internal
230
* Process a directory and load up FS_DIR with the entries. If a pointer to
231
* an already allocated FS_DIR structure is given, it will be cleared.  If no existing
232
* FS_DIR structure is passed (i.e. NULL), then a new one will be created. If the return
233
* value is error or corruption, then the FS_DIR structure could
234
* have entries (depending on when the error occurred).
235
*
236
* @param a_fs File system to analyze
237
* @param a_fs_dir Pointer to FS_DIR pointer. Can contain an already allocated
238
* structure or a new structure.
239
* @param a_addr Address of directory to process.
240
* @param recursion_depth Recursion depth to limit the number of self-calls
241
* @returns error, corruption, ok etc.
242
*/
243
244
TSK_RETVAL_ENUM
245
ext2fs_dir_open_meta(
246
  TSK_FS_INFO * a_fs,
247
  TSK_FS_DIR ** a_fs_dir,
248
  TSK_INUM_T a_addr,
249
  [[maybe_unused]] int recursion_depth)
250
231k
{
251
231k
    EXT2FS_INFO *ext2fs = (EXT2FS_INFO *) a_fs;
252
231k
    char *dirbuf;
253
231k
    TSK_FS_DIR *fs_dir;
254
231k
    TSK_LIST *list_seen = NULL;
255
256
    /* If we get corruption in one of the blocks, then continue processing.
257
     * retval_final will change when corruption is detected.  Errors are
258
     * returned immediately. */
259
231k
    TSK_RETVAL_ENUM retval_tmp;
260
231k
    TSK_RETVAL_ENUM retval_final = TSK_OK;
261
262
231k
    if (a_addr < a_fs->first_inum || a_addr > a_fs->last_inum) {
263
0
        tsk_error_reset();
264
0
        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
265
0
        tsk_error_set_errstr("ext2fs_dir_open_meta: inode value: %"
266
0
            PRIuINUM "\n", a_addr);
267
0
        return TSK_ERR;
268
0
    }
269
231k
    else if (a_fs_dir == NULL) {
270
0
        tsk_error_reset();
271
0
        tsk_error_set_errno(TSK_ERR_FS_ARG);
272
0
        tsk_error_set_errstr
273
0
            ("ext2fs_dir_open_meta: NULL fs_attr argument given");
274
0
        return TSK_ERR;
275
0
    }
276
277
231k
    if (tsk_verbose) {
278
0
        tsk_fprintf(stderr,
279
0
            "ext2fs_dir_open_meta: Processing directory %" PRIuINUM
280
0
            "\n", a_addr);
281
#ifdef Ext4_DBG
282
        tsk_fprintf(stderr,
283
            "ext2fs_dir_open_meta: $OrphanFiles Inum %" PRIuINUM
284
            " == %" PRIuINUM ": %d\n", TSK_FS_ORPHANDIR_INUM(a_fs), a_addr,
285
            a_addr == TSK_FS_ORPHANDIR_INUM(a_fs));
286
#endif
287
0
    }
288
289
231k
    fs_dir = *a_fs_dir;
290
231k
    if (fs_dir) {
291
0
        tsk_fs_dir_reset(fs_dir);
292
0
        fs_dir->addr = a_addr;
293
0
    }
294
231k
    else {
295
231k
        if ((*a_fs_dir = fs_dir =
296
231k
                tsk_fs_dir_alloc(a_fs, a_addr, 128)) == NULL) {
297
0
            return TSK_ERR;
298
0
        }
299
231k
    }
300
301
    //  handle the orphan directory if its contents were requested
302
231k
    if (a_addr == TSK_FS_ORPHANDIR_INUM(a_fs)) {
303
#ifdef Ext4_DBG
304
        tsk_fprintf(stderr, "DEBUG: Getting ready to process ORPHANS\n");
305
#endif
306
12.4k
        return tsk_fs_dir_find_orphans(a_fs, fs_dir);
307
12.4k
    }
308
218k
    else {
309
#ifdef Ext4_DBG
310
        tsk_fprintf(stderr,
311
            "DEBUG: not orphan %" PRIuINUM "!=%" PRIuINUM "\n", a_addr,
312
            TSK_FS_ORPHANDIR_INUM(a_fs));
313
#endif
314
218k
    }
315
316
218k
    if ((fs_dir->fs_file =
317
218k
            tsk_fs_file_open_meta(a_fs, NULL, a_addr)) == NULL) {
318
2.15k
        tsk_error_reset();
319
2.15k
        tsk_error_errstr2_concat("- ext2fs_dir_open_meta");
320
2.15k
        return TSK_COR;
321
2.15k
    }
322
323
    // We only read in and process a single block at a time
324
216k
    if ((dirbuf = (char*) tsk_malloc((size_t)a_fs->block_size)) == NULL) {
325
0
        return TSK_ERR;
326
0
    }
327
216k
    TSK_OFF_T size = 0;
328
329
216k
    if (fs_dir->fs_file->meta->content_type == TSK_FS_META_CONTENT_TYPE_EXT4_INLINE) {
330
        // For inline dirs, don't try to read past the end of the data
331
13.6k
        size = fs_dir->fs_file->meta->size;
332
13.6k
    }
333
202k
    else {
334
202k
        if (fs_dir->fs_file->meta->size <= 0 || a_fs->block_size <= 0
335
201k
                || (INT64_MAX - (a_fs->block_size - 1) < fs_dir->fs_file->meta->size)) {
336
1.51k
            tsk_error_set_errstr("ext2fs_dir_open_meta: invalid data size value out of bounds.\n");
337
1.51k
            free(dirbuf);
338
1.51k
            return TSK_ERR;
339
1.51k
        }
340
201k
        size = roundup(fs_dir->fs_file->meta->size, a_fs->block_size);
341
201k
    }
342
214k
    TSK_OFF_T offset = 0;
343
344
10.7M
    while (size > 0) {
345
10.5M
        ssize_t len = (a_fs->block_size < size) ? a_fs->block_size : size;
346
10.5M
        ssize_t cnt = tsk_fs_file_read(fs_dir->fs_file, offset, dirbuf, len, (TSK_FS_FILE_READ_FLAG_ENUM)0);
347
10.5M
        if (cnt != len) {
348
24.0k
            tsk_error_reset();
349
24.0k
            tsk_error_set_errno(TSK_ERR_FS_FWALK);
350
24.0k
            tsk_error_set_errstr
351
24.0k
            ("ext2fs_dir_open_meta: Error reading directory contents: %"
352
24.0k
                PRIuINUM "\n", a_addr);
353
24.0k
            free(dirbuf);
354
24.0k
            return TSK_COR;
355
24.0k
        }
356
357
10.5M
        retval_tmp =
358
10.5M
            ext2fs_dent_parse_block(ext2fs, fs_dir,
359
10.5M
            (fs_dir->fs_file->meta->
360
10.5M
                flags & TSK_FS_META_FLAG_UNALLOC) ? 1 : 0, &list_seen,
361
10.5M
            dirbuf, len);
362
363
10.5M
        if (retval_tmp == TSK_ERR) {
364
0
            retval_final = TSK_ERR;
365
0
            break;
366
0
        }
367
10.5M
        else if (retval_tmp == TSK_COR) {
368
0
            retval_final = TSK_COR;
369
0
        }
370
371
10.5M
        size -= len;
372
10.5M
        offset += len;
373
10.5M
    }
374
190k
    free(dirbuf);
375
376
    // if we are listing the root directory, add the Orphan directory entry
377
190k
    if (a_addr == a_fs->root_inum) {
378
45.8k
        TSK_FS_NAME *fs_name = tsk_fs_name_alloc(256, 0);
379
45.8k
        if (fs_name == NULL)
380
0
            return TSK_ERR;
381
382
45.8k
        if (tsk_fs_dir_make_orphan_dir_name(a_fs, fs_name)) {
383
0
            tsk_fs_name_free(fs_name);
384
0
            return TSK_ERR;
385
0
        }
386
387
45.8k
        if (tsk_fs_dir_add(fs_dir, fs_name)) {
388
0
            tsk_fs_name_free(fs_name);
389
0
            return TSK_ERR;
390
0
        }
391
45.8k
        tsk_fs_name_free(fs_name);
392
45.8k
    }
393
394
190k
    return retval_final;
395
190k
}