Coverage Report

Created: 2025-12-14 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sleuthkit/tsk/fs/ffs_dent.cpp
Line
Count
Source
1
/*
2
** ffs_dent
3
** The  Sleuth Kit
4
**
5
** File name layer for a FFS/UFS image
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 ffs_dent.c
24
 * Contains the internal TSK UFS/FFS file name (directory entry) processing  functions
25
 */
26
27
#include <ctype.h>
28
#include "tsk_fs_i.h"
29
#include "tsk_ffs.h"
30
31
32
static uint8_t
33
ffs_dent_copy(FFS_INFO * ffs, char *ffs_dent, TSK_FS_NAME * fs_name)
34
0
{
35
0
    TSK_FS_INFO *a_fs = &(ffs->fs_info);
36
37
    /* this one has the type field */
38
0
    if ((a_fs->ftype == TSK_FS_TYPE_FFS1)
39
0
        || (a_fs->ftype == TSK_FS_TYPE_FFS2)) {
40
0
        ffs_dentry1 *dir = (ffs_dentry1 *) ffs_dent;
41
42
0
        fs_name->meta_addr = tsk_getu32(a_fs->endian, dir->d_ino);
43
44
0
        if (fs_name->name_size != FFS_MAXNAMLEN) {
45
0
            if (tsk_fs_name_realloc(fs_name, FFS_MAXNAMLEN))
46
0
                return 1;
47
0
        }
48
49
        /* ffs null terminates so we can strncpy */
50
0
        strncpy(fs_name->name, dir->d_name, fs_name->name_size);
51
52
0
        switch (dir->d_type) {
53
0
        case FFS_DT_REG:
54
0
            fs_name->type = TSK_FS_NAME_TYPE_REG;
55
0
            break;
56
0
        case FFS_DT_DIR:
57
0
            fs_name->type = TSK_FS_NAME_TYPE_DIR;
58
0
            break;
59
0
        case FFS_DT_CHR:
60
0
            fs_name->type = TSK_FS_NAME_TYPE_CHR;
61
0
            break;
62
0
        case FFS_DT_BLK:
63
0
            fs_name->type = TSK_FS_NAME_TYPE_BLK;
64
0
            break;
65
0
        case FFS_DT_FIFO:
66
0
            fs_name->type = TSK_FS_NAME_TYPE_FIFO;
67
0
            break;
68
0
        case FFS_DT_SOCK:
69
0
            fs_name->type = TSK_FS_NAME_TYPE_SOCK;
70
0
            break;
71
0
        case FFS_DT_LNK:
72
0
            fs_name->type = TSK_FS_NAME_TYPE_LNK;
73
0
            break;
74
0
        case FFS_DT_WHT:
75
0
            fs_name->type = TSK_FS_NAME_TYPE_WHT;
76
0
            break;
77
0
        case FFS_DT_UNKNOWN:
78
0
        default:
79
0
            fs_name->type = TSK_FS_NAME_TYPE_UNDEF;
80
0
            break;
81
0
        }
82
0
    }
83
0
    else if (a_fs->ftype == TSK_FS_TYPE_FFS1B) {
84
0
        ffs_dentry2 *dir = (ffs_dentry2 *) ffs_dent;
85
86
0
        fs_name->meta_addr = tsk_getu32(a_fs->endian, dir->d_ino);
87
88
0
        if (fs_name->name_size != FFS_MAXNAMLEN) {
89
0
            if (tsk_fs_name_realloc(fs_name, FFS_MAXNAMLEN))
90
0
                return 1;
91
0
        }
92
93
        /* ffs null terminates so we can strncpy */
94
0
        strncpy(fs_name->name, dir->d_name, fs_name->name_size);
95
96
0
        fs_name->type = TSK_FS_NAME_TYPE_UNDEF;
97
0
    }
98
0
    else {
99
0
        tsk_error_reset();
100
0
        tsk_error_set_errno(TSK_ERR_FS_ARG);
101
0
        tsk_error_set_errstr("ffs_dent_copy: Unknown FS type");
102
0
        return 1;
103
0
    }
104
105
0
    fs_name->flags = (TSK_FS_NAME_FLAG_ENUM) 0;
106
0
    return 0;
107
0
}
108
109
110
/*
111
 * @param a_is_del Set to 1 if block is from a deleted directory.
112
 */
113
static TSK_RETVAL_ENUM
114
ffs_dent_parse_block(FFS_INFO * ffs, TSK_FS_DIR * fs_dir, uint8_t a_is_del,
115
    char *buf, unsigned int len)
116
0
{
117
0
    unsigned int idx;
118
0
    unsigned int inode = 0, dellen = 0, reclen = 0;
119
0
    unsigned int minreclen = 4;
120
0
    TSK_FS_INFO *fs = &(ffs->fs_info);
121
122
0
    char *dirPtr;
123
0
    TSK_FS_NAME *fs_name;
124
125
0
    if ((fs_name = tsk_fs_name_alloc(FFS_MAXNAMLEN + 1, 0)) == NULL)
126
0
        return TSK_ERR;
127
128
    /* update each time by the actual length instead of the
129
     ** recorded length so we can view the deleted entries
130
     */
131
0
    for (idx = 0; idx <= len - FFS_DIRSIZ_lcl(1); idx += minreclen) {
132
0
        unsigned int namelen = 0;
133
134
0
        dirPtr = (char *) &buf[idx];
135
136
        /* copy to local variables */
137
0
        if ((fs->ftype == TSK_FS_TYPE_FFS1)
138
0
            || (fs->ftype == TSK_FS_TYPE_FFS2)) {
139
0
            ffs_dentry1 *dir = (ffs_dentry1 *) dirPtr;
140
0
            inode = tsk_getu32(fs->endian, dir->d_ino);
141
0
            namelen = dir->d_namlen;
142
0
            reclen = tsk_getu16(fs->endian, dir->d_reclen);
143
0
        }
144
        /* TSK_FS_TYPE_FFS1B */
145
0
        else if (fs->ftype == TSK_FS_TYPE_FFS1B) {
146
0
            ffs_dentry2 *dir = (ffs_dentry2 *) dirPtr;
147
0
            inode = tsk_getu32(fs->endian, dir->d_ino);
148
0
            namelen = tsk_getu16(fs->endian, dir->d_namlen);
149
0
            reclen = tsk_getu16(fs->endian, dir->d_reclen);
150
0
        }
151
152
        /* what is the minimum size needed for this entry */
153
0
        minreclen = FFS_DIRSIZ_lcl(namelen);
154
155
        /* Perform a couple sanity checks
156
         ** OpenBSD never zeros the inode number, but solaris
157
         ** does.  These checks will hopefully catch all non
158
         ** entries
159
         */
160
0
        if ((inode > fs->last_inum) ||  // inode is unsigned
161
0
            (namelen > FFS_MAXNAMLEN) ||        // namelen is unsigned
162
0
            (namelen == 0) ||
163
0
            (reclen < minreclen) || (reclen % 4) || (idx + reclen > len)) {
164
165
            /* we don't have a valid entry, so skip ahead 4 */
166
0
            minreclen = 4;
167
0
            if (dellen > 0)
168
0
                dellen -= 4;
169
0
            continue;
170
0
        }
171
172
        /* Before we process an entry in unallocated space, make
173
         * sure that it also ends in the unalloc space */
174
0
        if ((dellen) && (dellen < minreclen)) {
175
0
            minreclen = 4;
176
0
            dellen -= 4;
177
0
            continue;
178
0
        }
179
180
        /* the entry is valid */
181
0
        if (ffs_dent_copy(ffs, dirPtr, fs_name)) {
182
0
            tsk_fs_name_free(fs_name);
183
0
            return TSK_ERR;
184
0
        }
185
186
187
        /* Do we have a deleted entry? (are we in a deleted space) */
188
0
        if ((dellen > 0) || (inode == 0) || (a_is_del)) {
189
0
            fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC;
190
0
            if (dellen)
191
0
                dellen -= minreclen;
192
0
        }
193
0
        else {
194
0
            fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
195
0
        }
196
0
        if (tsk_fs_dir_add(fs_dir, fs_name)) {
197
0
            tsk_fs_name_free(fs_name);
198
0
            return TSK_ERR;
199
0
        }
200
201
        /* If we have some slack and an entry could exist in it, the set dellen */
202
0
        if (dellen <= 0) {
203
0
            if (reclen - minreclen >= FFS_DIRSIZ_lcl(1))
204
0
                dellen = reclen - minreclen;
205
0
            else
206
0
                minreclen = reclen;
207
0
        }
208
0
    }
209
210
0
    tsk_fs_name_free(fs_name);
211
0
    return TSK_OK;
212
0
}                               /* end ffs_dent_parse_block */
213
214
/** \internal
215
 * Process a directory and load up FS_DIR with the entries. If a pointer to
216
 * an already allocated FS_DIR structure is given, it will be cleared.  If no existing
217
 * FS_DIR structure is passed (i.e. NULL), then a new one will be created. If the return
218
 * value is error or corruption, then the FS_DIR structure could
219
 * have entries (depending on when the error occurred).
220
 *
221
 * @param a_fs File system to analyze
222
 * @param a_fs_dir Pointer to FS_DIR pointer. Can contain an already allocated
223
 * structure or a new structure.
224
 * @param a_addr Address of directory to process.
225
 * @param recursion_depth Recursion depth to limit the number of self-calls
226
 * @returns error, corruption, ok etc.
227
 */
228
TSK_RETVAL_ENUM
229
ffs_dir_open_meta(
230
  TSK_FS_INFO * a_fs,
231
  TSK_FS_DIR ** a_fs_dir,
232
  TSK_INUM_T a_addr,
233
  [[maybe_unused]] int recursion_depth)
234
0
{
235
0
    TSK_OFF_T size;
236
0
    FFS_INFO *ffs = (FFS_INFO *) a_fs;
237
0
    char *dirbuf;
238
0
    int nchnk, cidx;
239
0
    TSK_FS_DIR *fs_dir;
240
241
    /* If we get corruption in one of the blocks, then continue processing.
242
     * retval_final will change when corruption is detected.  Errors are
243
     * returned immediately. */
244
0
    TSK_RETVAL_ENUM retval_tmp;
245
0
    TSK_RETVAL_ENUM retval_final = TSK_OK;
246
247
248
0
    if (a_addr < a_fs->first_inum || a_addr > a_fs->last_inum) {
249
0
        tsk_error_reset();
250
0
        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
251
0
        tsk_error_set_errstr("ffs_dir_open_meta: Invalid inode value: %"
252
0
            PRIuINUM, a_addr);
253
0
        return TSK_ERR;
254
0
    }
255
0
    else if (a_fs_dir == NULL) {
256
0
        tsk_error_reset();
257
0
        tsk_error_set_errno(TSK_ERR_FS_ARG);
258
0
        tsk_error_set_errstr
259
0
            ("ffs_dir_open_meta: NULL fs_attr argument given");
260
0
        return TSK_ERR;
261
0
    }
262
263
0
    if (tsk_verbose)
264
0
        tsk_fprintf(stderr,
265
0
            "ffs_dir_open_meta: Processing directory %" PRIuINUM "\n",
266
0
            a_addr);
267
268
0
    fs_dir = *a_fs_dir;
269
0
    if (fs_dir) {
270
0
        tsk_fs_dir_reset(fs_dir);
271
0
        fs_dir->addr = a_addr;
272
0
    }
273
0
    else {
274
0
        if ((*a_fs_dir = fs_dir =
275
0
                tsk_fs_dir_alloc(a_fs, a_addr, 128)) == NULL) {
276
0
            return TSK_ERR;
277
0
        }
278
0
    }
279
280
    //  handle the orphan directory if its contents were requested
281
0
    if (a_addr == TSK_FS_ORPHANDIR_INUM(a_fs)) {
282
0
        return tsk_fs_dir_find_orphans(a_fs, fs_dir);
283
0
    }
284
285
0
    if ((fs_dir->fs_file =
286
0
            tsk_fs_file_open_meta(a_fs, NULL, a_addr)) == NULL) {
287
0
        tsk_error_reset();
288
0
        tsk_error_errstr2_concat("- ffs_dir_open_meta");
289
0
        return TSK_COR;
290
0
    }
291
292
    /* dirbuf will only be used to process one block at a time */
293
0
    size = roundup(fs_dir->fs_file->meta->size, FFS_DIRBLKSIZ);
294
0
    if ((dirbuf = (char*) tsk_malloc((size_t)FFS_DIRBLKSIZ)) == NULL) {
295
0
        return TSK_ERR;
296
0
    }
297
298
    /* Directory entries are written in chunks of DIRBLKSIZ
299
     ** determine how many chunks of this size we have to read to
300
     ** get a full block
301
     **
302
     ** Entries do not cross over the DIRBLKSIZ boundary
303
     */
304
0
    nchnk = (int) (size) / (FFS_DIRBLKSIZ) + 1;
305
306
0
    TSK_OFF_T offset = 0;
307
0
    for (cidx = 0; cidx < nchnk && (int64_t) size > 0; cidx++) {
308
0
        int len = (FFS_DIRBLKSIZ < size) ? FFS_DIRBLKSIZ : (int) size;
309
310
0
        ssize_t cnt = tsk_fs_file_read(fs_dir->fs_file, offset, dirbuf, len, (TSK_FS_FILE_READ_FLAG_ENUM)0);
311
0
        if (cnt != len) {
312
0
            tsk_error_reset();
313
0
            tsk_error_set_errno(TSK_ERR_FS_FWALK);
314
0
            tsk_error_set_errstr
315
0
            ("ffs_dir_open_meta: Error reading directory contents: %"
316
0
                PRIuINUM "\n", a_addr);
317
0
            free(dirbuf);
318
0
            return TSK_COR;
319
0
        }
320
321
0
        retval_tmp =
322
0
            ffs_dent_parse_block(ffs, fs_dir,
323
0
            (fs_dir->fs_file->meta->
324
0
                flags & TSK_FS_META_FLAG_UNALLOC) ? 1 : 0,
325
0
            dirbuf, len);
326
327
0
        if (retval_tmp == TSK_ERR) {
328
0
            retval_final = TSK_ERR;
329
0
            break;
330
0
        }
331
0
        else if (retval_tmp == TSK_COR) {
332
0
            retval_final = TSK_COR;
333
0
        }
334
0
        size -= len;
335
0
        offset += len;
336
0
    }
337
0
    free(dirbuf);
338
339
    // if we are listing the root directory, add the Orphan directory entry
340
0
    if (a_addr == a_fs->root_inum) {
341
0
        TSK_FS_NAME *fs_name = tsk_fs_name_alloc(256, 0);
342
0
        if (fs_name == NULL)
343
0
            return TSK_ERR;
344
345
0
        if (tsk_fs_dir_make_orphan_dir_name(a_fs, fs_name)) {
346
0
            tsk_fs_name_free(fs_name);
347
0
            return TSK_ERR;
348
0
        }
349
350
0
        if (tsk_fs_dir_add(fs_dir, fs_name)) {
351
0
            tsk_fs_name_free(fs_name);
352
0
            return TSK_ERR;
353
0
        }
354
0
        tsk_fs_name_free(fs_name);
355
0
    }
356
357
0
    return retval_final;
358
0
}