Coverage Report

Created: 2025-10-12 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sleuthkit/tsk/fs/exfatfs_dent.cpp
Line
Count
Source
1
/*
2
** The Sleuth Kit
3
**
4
** Copyright (c) 2013 Basis Technology Corp.  All rights reserved
5
** Contact: Brian Carrier [carrier <at> sleuthkit [dot] org]
6
**
7
** This software is distributed under the Common Public License 1.0
8
**
9
*/
10
11
/*
12
 * This code makes use of research presented in the following paper:
13
 * "Reverse Engineering the exFAT File System" by Robert Shullich
14
 * Retrieved May 2013 from:
15
 * http://www.sans.org/reading_room/whitepapers/forensics/reverse-engineering-microsoft-exfat-file-system_33274
16
 *
17
 * Some additional details concerning TexFAT were obtained in May 2013
18
 * from:
19
 * http://msdn.microsoft.com/en-us/library/ee490643(v=winembedded.60).aspx
20
*/
21
22
/**
23
 * \file exfatfs_dent.c
24
 * Contains the internal TSK exFAT file system code to handle name category
25
 * processing.
26
 */
27
28
#include "tsk_exfatfs.h" /* Include first to make sure it stands alone. */
29
#include "tsk_fs_i.h"
30
#include "tsk_fatfs.h"
31
#include <assert.h>
32
33
/**
34
 * \internal
35
 * \struct
36
 * Bundles a TSK_FS_NAME object and a TSK_FS_DIR object with additional data
37
 * required when assembling a name from file directory entry set. If the
38
 * TSK_FS_NAME is successfully populated, it is added to the TSK_FS_DIR.
39
 */
40
typedef struct {
41
    FATFS_INFO *fatfs;
42
    int8_t sector_is_allocated;
43
    EXFATFS_DIR_ENTRY_TYPE last_dentry_type;
44
    uint8_t expected_secondary_entry_count;
45
    uint8_t actual_secondary_entry_count;
46
    uint16_t expected_check_sum;
47
    uint8_t expected_name_length_utf16_chars;     /* Name length (in characters) as reported by the file stream dentry */
48
    uint8_t current_file_name_length_utf16_chars; /* Number of UTF16 name characters read in so far */
49
    uint8_t file_name_utf16[(EXFATFS_MAX_FILE_NAME_LENGTH_UTF16_CHARS + 1) * 2];  /* The UTF16 characters read in so far*/
50
    size_t actual_name_length_utf8_bytes;  /* Length of the UTF8 version of the name (stored in fs_name) */
51
    TSK_FS_NAME *fs_name;
52
    TSK_FS_DIR *fs_dir;
53
} EXFATFS_FS_NAME_INFO;
54
55
/**
56
 * \internal
57
 * Reset the fields of a EXFATFS_FS_NAME_INFO to their initialized state. This
58
 * allows for reuse of the object.
59
 *
60
 * @param a_name_info The name info object.
61
 */
62
static void
63
exfatfs_reset_name_info(EXFATFS_FS_NAME_INFO *a_name_info)
64
149k
{
65
149k
    assert(a_name_info != NULL);
66
149k
    assert(a_name_info->fs_name != NULL);
67
149k
    assert(a_name_info->fs_name->name != NULL);
68
149k
    assert(a_name_info->fs_name->name_size == FATFS_MAXNAMLEN_UTF8);
69
70
149k
    a_name_info->last_dentry_type = EXFATFS_DIR_ENTRY_TYPE_NONE;
71
149k
    a_name_info->expected_secondary_entry_count = 0;
72
149k
    a_name_info->actual_secondary_entry_count = 0;
73
149k
    a_name_info->expected_check_sum = 0;
74
149k
    a_name_info->expected_name_length_utf16_chars = 0;
75
149k
    a_name_info->current_file_name_length_utf16_chars = 0;
76
149k
    a_name_info->file_name_utf16[0] = '\0';
77
149k
    a_name_info->actual_name_length_utf8_bytes = 0;
78
149k
    a_name_info->fs_name->name[0] = '\0';
79
149k
    a_name_info->fs_name->meta_addr = 0;
80
149k
    a_name_info->fs_name->type = TSK_FS_NAME_TYPE_UNDEF;
81
149k
    a_name_info->fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
82
149k
}
83
84
/**
85
 * \internal
86
 * Add the TSK_FS_NAME object of an EXFATFS_FS_NAME_INFO object to its
87
 * TSK_FS_DIR object and reset the fields of a EXFATFS_FS_NAME_INFO to their
88
 * initialized state. This allows for reuse of the object.
89
 * The conversion from UTF16 to UTF8 happens here if needed.
90
 *
91
 * @param a_name_info The name info object.
92
 */
93
static void
94
exfatfs_add_name_to_dir_and_reset_info(EXFATFS_FS_NAME_INFO *a_name_info)
95
149k
{
96
149k
    assert(a_name_info != NULL);
97
149k
    assert(a_name_info->fs_name != NULL);
98
149k
    assert(a_name_info->fs_name->name != NULL);
99
149k
    assert(a_name_info->fs_name->name_size == FATFS_MAXNAMLEN_UTF8);
100
149k
    assert(a_name_info->fs_dir != NULL);
101
102
    /* If the name has not been converted to UTF8 yet, do it now */
103
149k
    if ((strlen(a_name_info->fs_name->name) == 0) &&
104
147k
        (a_name_info->current_file_name_length_utf16_chars > 0)) {
105
106
        /* Convert the UTF16 name to UTF8 */
107
94
        if (fatfs_utf16_inode_str_2_utf8(a_name_info->fatfs,
108
94
            (UTF16*)a_name_info->file_name_utf16, a_name_info->current_file_name_length_utf16_chars,
109
94
            (UTF8*)a_name_info->fs_name->name, a_name_info->fs_name->name_size,
110
94
            a_name_info->fs_name->meta_addr, "file name segment") != TSKconversionOK) {
111
112
            /* It might be that we have a partial name, so we want to
113
             * continue regardless of the result here */
114
0
        }
115
94
    }
116
117
    /* If the parsing of the directory entry or directory entry set produced
118
    * a name, add the TSK_FS_NAME object to the TSK_FS_DIR object. */
119
149k
    if (strlen(a_name_info->fs_name->name) > 0) {
120
1.48k
        tsk_fs_dir_add(a_name_info->fs_dir, a_name_info->fs_name);
121
1.48k
    }
122
123
149k
    exfatfs_reset_name_info(a_name_info);
124
149k
}
125
126
/**
127
 * \internal
128
 * Populates an EXFATFS_FS_NAME_INFO object with data parsed from a file
129
 * directory entry. Since this is the beginning of a new name, the name
130
 * previously stored on the EXFATFS_FS_NAME_INFO, if any, is saved.
131
 *
132
 * @param a_name_info The name info object.
133
 * @param a_dentry A buffer containing a file directory entry.
134
 * @param a_inum The inode address associated with the directory entry.
135
 */
136
static void
137
exfats_parse_file_dentry(EXFATFS_FS_NAME_INFO *a_name_info, FATFS_DENTRY *a_dentry, TSK_INUM_T a_inum)
138
12.8k
{
139
12.8k
    EXFATFS_FILE_DIR_ENTRY *dentry = (EXFATFS_FILE_DIR_ENTRY*)a_dentry;
140
141
12.8k
    assert(a_name_info != NULL);
142
12.8k
    assert(a_name_info->fatfs != NULL);
143
12.8k
    assert(a_name_info->fs_name != NULL);
144
12.8k
    assert(a_name_info->fs_name->name != NULL);
145
12.8k
    assert(a_name_info->fs_name->name_size == FATFS_MAXNAMLEN_UTF8);
146
12.8k
    assert(a_name_info->fs_dir != NULL);
147
12.8k
    assert(dentry != NULL);
148
12.8k
    assert(exfatfs_get_enum_from_type(dentry->entry_type) == EXFATFS_DIR_ENTRY_TYPE_FILE);
149
12.8k
    assert(fatfs_inum_is_in_range(a_name_info->fatfs, a_inum));
150
151
    /* Starting parse of a new name, so save the current name, if any. */
152
12.8k
    exfatfs_add_name_to_dir_and_reset_info(a_name_info);
153
154
    /* Set the current entry type. This is used to check the sequence and
155
     * in-use state of the entries in the set. */
156
12.8k
    a_name_info->last_dentry_type = (EXFATFS_DIR_ENTRY_TYPE)dentry->entry_type;
157
158
    /* The number of secondary entries and the check sum for the entry set are
159
     * stored in the file entry. */
160
12.8k
    a_name_info->expected_secondary_entry_count =
161
12.8k
        dentry->secondary_entries_count;
162
12.8k
    a_name_info->expected_check_sum =
163
12.8k
        tsk_getu16(a_name_info->fatfs->fs_info.endian, dentry->check_sum);
164
165
    /* The file type (regular file, directory) is stored in the file entry. */
166
12.8k
    if (dentry->attrs[0] & FATFS_ATTR_DIRECTORY) {
167
128
        a_name_info->fs_name->type = TSK_FS_NAME_TYPE_DIR;
168
128
    }
169
12.7k
    else {
170
12.7k
        a_name_info->fs_name->type = TSK_FS_NAME_TYPE_REG;
171
12.7k
    }
172
173
    /* If the in-use bit of the type byte is not set, the entry set is for a
174
     * deleted or renamed file. However, trust and verify - to be marked as
175
     * allocated, the inode must also be in an allocated sector. */
176
12.8k
    if (a_name_info->sector_is_allocated && exfatfs_get_alloc_status_from_type(dentry->entry_type)) {
177
111
        a_name_info->fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
178
111
    }
179
12.7k
    else {
180
12.7k
        a_name_info->fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC;
181
12.7k
    }
182
183
    /* Make the inum of the file entry the inode address for the entry set. */
184
12.8k
    a_name_info->fs_name->meta_addr = a_inum;
185
12.8k
}
186
187
/**
188
 * \internal
189
 * Populates an EXFATFS_FS_NAME_INFO object with data parsed from a file
190
 * stream directory entry.
191
 *
192
 * @param a_name_info The name info object.
193
 * @param a_dentry A buffer containing a file stream directory entry.
194
 * @param a_inum The inode address associated with the directory entry.
195
 */
196
static void
197
exfats_parse_file_stream_dentry(EXFATFS_FS_NAME_INFO *a_name_info, FATFS_DENTRY *a_dentry, TSK_INUM_T a_inum)
198
331
{
199
331
    EXFATFS_FILE_STREAM_DIR_ENTRY *dentry = (EXFATFS_FILE_STREAM_DIR_ENTRY*)a_dentry;
200
201
331
    assert(a_name_info != NULL);
202
331
    assert(a_name_info->fatfs != NULL);
203
331
    assert(a_name_info->fs_name != NULL);
204
331
    assert(a_name_info->fs_name->name != NULL);
205
331
    assert(a_name_info->fs_name->name_size == FATFS_MAXNAMLEN_UTF8);
206
331
    assert(a_name_info->fs_dir != NULL);
207
331
    assert(dentry != NULL);
208
331
    assert(exfatfs_get_enum_from_type(dentry->entry_type) == EXFATFS_DIR_ENTRY_TYPE_FILE_STREAM);
209
331
    assert(fatfs_inum_is_in_range(a_name_info->fatfs, a_inum));
210
211
331
    if(exfatfs_get_enum_from_type(a_name_info->last_dentry_type) != EXFATFS_DIR_ENTRY_TYPE_FILE){
212
        /* A file stream entry must follow a file entry, so this entry is a
213
         * false positive or there is corruption. Save the current name,
214
         * if any, and ignore this buffer. */
215
163
        exfatfs_add_name_to_dir_and_reset_info(a_name_info);
216
163
        return;
217
163
    }
218
219
168
    if(exfatfs_get_alloc_status_from_type(a_name_info->last_dentry_type) !=
220
168
        exfatfs_get_alloc_status_from_type(dentry->entry_type)){
221
        /* The in-use bits of all of the entries in an entry set should be
222
         * same, so this entry is a false positive or there is corruption.
223
         * Save the current name, if any, and ignore this buffer. */
224
66
        exfatfs_add_name_to_dir_and_reset_info(a_name_info);
225
66
        return;
226
66
    }
227
228
    /* Set the current entry type. This is used to check the sequence and
229
     * in-use state of the entries in the set. */
230
102
    a_name_info->last_dentry_type =
231
102
        (EXFATFS_DIR_ENTRY_TYPE)dentry->entry_type;
232
233
    /* The file stream entry contains the length of the file name. */
234
102
    a_name_info->expected_name_length_utf16_chars = dentry->file_name_length_UTF16_chars;
235
236
    /* If all of the secondary entries for the set are present, save the name,
237
     * if any. Note that if this condition is satisfied here, the directory is
238
     * corrupted or this is a degenerate case - there should be at least one
239
     * file name entry in a directory entry set. */
240
102
    ++a_name_info->actual_secondary_entry_count;
241
102
    if (a_name_info->actual_secondary_entry_count ==
242
102
        a_name_info->expected_secondary_entry_count) {
243
0
        exfatfs_add_name_to_dir_and_reset_info(a_name_info);
244
0
    }
245
102
}
246
247
/**
248
 * \internal
249
 * Populates an EXFATFS_FS_NAME_INFO object with data parsed from a file
250
 * name directory entry.
251
 *
252
 * @param a_name_info The name info object.
253
 * @param a_dentry A buffer containing a file name directory entry.
254
 * @param a_inum The inode address associated with the directory entry.
255
 */
256
static void
257
exfats_parse_file_name_dentry(EXFATFS_FS_NAME_INFO *a_name_info, FATFS_DENTRY *a_dentry, TSK_INUM_T a_inum)
258
422
{
259
422
    EXFATFS_FILE_NAME_DIR_ENTRY *dentry = (EXFATFS_FILE_NAME_DIR_ENTRY*)a_dentry;
260
422
    uint8_t num_utf16_chars_to_copy = 0;
261
262
422
    assert(a_name_info != NULL);
263
422
    assert(a_name_info->fatfs != NULL);
264
422
    assert(a_name_info->fs_name != NULL);
265
422
    assert(a_name_info->fs_name->name != NULL);
266
422
    assert(a_name_info->fs_name->name_size == FATFS_MAXNAMLEN_UTF8);
267
422
    assert(a_name_info->fs_dir != NULL);
268
422
    assert(dentry != NULL);
269
422
    assert(exfatfs_get_enum_from_type(dentry->entry_type) == EXFATFS_DIR_ENTRY_TYPE_FILE_NAME);
270
422
    assert(fatfs_inum_is_in_range(a_name_info->fatfs, a_inum));
271
272
422
    if (exfatfs_get_enum_from_type(a_name_info->last_dentry_type) != EXFATFS_DIR_ENTRY_TYPE_FILE_STREAM &&
273
328
        exfatfs_get_enum_from_type(a_name_info->last_dentry_type) != EXFATFS_DIR_ENTRY_TYPE_FILE_NAME) {
274
        /* A file name entry must follow a stream or name entry, so this entry is
275
         * is a false positive or there is corruption. Save the current name,
276
         * if any, and ignore this buffer. */
277
328
        exfatfs_add_name_to_dir_and_reset_info(a_name_info);
278
328
        return;
279
328
    }
280
281
94
    if (exfatfs_get_alloc_status_from_type(a_name_info->last_dentry_type) !=
282
94
        exfatfs_get_alloc_status_from_type(dentry->entry_type)) {
283
        /* The in-use bits of all of the entries in an entry set should be
284
         * same, so this entry is a false positive or there is corruption.
285
         * Save the current name, if any, and ignore this buffer. */
286
0
        exfatfs_add_name_to_dir_and_reset_info(a_name_info);
287
0
        return;
288
0
    }
289
290
    /* Set the current entry type. This is used to check the sequence and
291
     * in-use state of the entries in the set. */
292
94
    a_name_info->last_dentry_type =
293
94
        (EXFATFS_DIR_ENTRY_TYPE)dentry->entry_type;
294
295
    /* Determine how many name chars remain according to the name length from
296
     * the file stream entry and how many chars can be obtained from this
297
     * name entry. */
298
94
    num_utf16_chars_to_copy = a_name_info->expected_name_length_utf16_chars - a_name_info->current_file_name_length_utf16_chars;
299
94
    if (num_utf16_chars_to_copy > EXFATFS_MAX_FILE_NAME_SEGMENT_LENGTH_UTF16_CHARS) {
300
2
        num_utf16_chars_to_copy = EXFATFS_MAX_FILE_NAME_SEGMENT_LENGTH_UTF16_CHARS;
301
2
    }
302
303
    /* Copy two bytes per character */
304
94
    if (num_utf16_chars_to_copy <= EXFATFS_MAX_FILE_NAME_LENGTH_UTF16_CHARS - a_name_info->current_file_name_length_utf16_chars) {
305
94
        memcpy(&a_name_info->file_name_utf16[(a_name_info->current_file_name_length_utf16_chars * 2)], dentry->utf16_name_chars, num_utf16_chars_to_copy * 2);
306
94
        a_name_info->current_file_name_length_utf16_chars += num_utf16_chars_to_copy;
307
94
    }
308
309
    /* If all of the secondary entries for the set are present, save the name,
310
     * if any. */
311
94
    ++a_name_info->actual_secondary_entry_count;
312
94
    if (a_name_info->actual_secondary_entry_count ==
313
94
        a_name_info->expected_secondary_entry_count) {
314
94
        exfatfs_add_name_to_dir_and_reset_info(a_name_info);
315
94
    }
316
94
}
317
318
/**
319
 * \internal
320
 * Populates an EXFATFS_FS_NAME_INFO object with data parsed from a volume
321
 * label directory entry.
322
 *
323
 * @param a_name_info The name info object.
324
 * @param a_dentry A buffer containing a volume label directory entry.
325
 * @param a_inum The inode address associated with the directory entry.
326
 */
327
static void
328
exfats_parse_vol_label_dentry(EXFATFS_FS_NAME_INFO *a_name_info, FATFS_DENTRY *a_dentry, TSK_INUM_T a_inum)
329
0
{
330
0
    EXFATFS_VOL_LABEL_DIR_ENTRY *dentry = (EXFATFS_VOL_LABEL_DIR_ENTRY*)a_dentry;
331
0
    const char *tag = " (Volume Label Entry)";
332
0
    size_t tag_length = 0;
333
334
0
    assert(a_name_info != NULL);
335
0
    assert(a_name_info->fatfs != NULL);
336
0
    assert(a_name_info->fs_name != NULL);
337
0
    assert(a_name_info->fs_name->name != NULL);
338
0
    assert(a_name_info->fs_name->name_size == FATFS_MAXNAMLEN_UTF8);
339
0
    assert(a_name_info->fs_dir != NULL);
340
0
    assert(dentry != NULL);
341
0
    assert(exfatfs_get_enum_from_type(dentry->entry_type) == EXFATFS_DIR_ENTRY_TYPE_VOLUME_LABEL);
342
0
    assert(fatfs_inum_is_in_range(a_name_info->fatfs, a_inum));
343
344
    /* Starting parse of a new name, save the previous name, if any. */
345
0
    exfatfs_add_name_to_dir_and_reset_info(a_name_info);
346
347
    /* Set the current entry type. This is used to check the sequence and
348
     * in-use state of the entries in the set. */
349
0
    a_name_info->last_dentry_type =
350
0
        (EXFATFS_DIR_ENTRY_TYPE)dentry->entry_type;
351
352
    /* The volume label is supposed to be a max of 11 characters. In practice it is
353
     * sometimes possible to extend the name into the reserved area, making the
354
     * maximum 15 characters, which is what is stored in EXFATFS_MAX_VOLUME_LABEL_LEN_CHAR. */
355
0
    if (dentry->volume_label_length_chars > EXFATFS_MAX_VOLUME_LABEL_LEN_CHAR) {
356
0
        dentry->volume_label_length_chars = EXFATFS_MAX_VOLUME_LABEL_LEN_CHAR;
357
0
    }
358
359
0
    if(exfatfs_get_alloc_status_from_type(dentry->entry_type) == 1){
360
0
        if (fatfs_utf16_inode_str_2_utf8(a_name_info->fatfs,
361
0
            (UTF16*)dentry->volume_label, (size_t)dentry->volume_label_length_chars,
362
0
            (UTF8*)a_name_info->fs_name->name, a_name_info->fs_name->name_size,
363
0
            a_inum, "volume label") != TSKconversionOK) {
364
            /* Discard whatever was written by the failed conversion. */
365
0
            exfatfs_reset_name_info(a_name_info);
366
0
            return;
367
0
        }
368
0
    }
369
0
    else {
370
0
        strcpy(a_name_info->fs_name->name, EXFATFS_EMPTY_VOLUME_LABEL_DENTRY_NAME);
371
0
    }
372
373
0
    a_name_info->actual_name_length_utf8_bytes = strlen(a_name_info->fs_name->name);
374
375
0
    tag_length = strlen(tag);
376
0
    if (a_name_info->actual_name_length_utf8_bytes + tag_length <
377
0
        FATFS_MAXNAMLEN_UTF8) {
378
0
        strcat(a_name_info->fs_name->name, tag);
379
0
    }
380
381
    /* Record the inum associated with this name. */
382
0
    a_name_info->fs_name->meta_addr =  a_inum;
383
384
    /* Not a directory. */
385
0
    a_name_info->fs_name->type = TSK_FS_NAME_TYPE_REG;
386
387
0
    if (a_name_info->sector_is_allocated) {
388
0
        a_name_info->fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
389
0
    }
390
391
    /* Save the volume label. */
392
0
    exfatfs_add_name_to_dir_and_reset_info(a_name_info);
393
0
}
394
395
/**
396
 * \internal
397
 * Populates an EXFATFS_FS_NAME_INFO object with data parsed from a
398
 * special file directory entry.
399
 *
400
 * @param a_name_info The name info object.
401
 * @param a_dentry A buffer containing a special file directory entry.
402
 * @param a_inum The inode address associated with the directory entry.
403
 */
404
static void
405
exfats_parse_special_file_dentry(EXFATFS_FS_NAME_INFO *a_name_info, FATFS_DENTRY *a_dentry, TSK_INUM_T a_inum)
406
1.39k
{
407
1.39k
    assert(a_name_info != NULL);
408
1.39k
    assert(a_name_info->fatfs != NULL);
409
1.39k
    assert(a_name_info->fs_name != NULL);
410
1.39k
    assert(a_name_info->fs_name->name != NULL);
411
1.39k
    assert(a_name_info->fs_name->name_size == FATFS_MAXNAMLEN_UTF8);
412
1.39k
    assert(a_name_info->fs_dir != NULL);
413
1.39k
    assert(a_dentry != NULL);
414
1.39k
    assert(fatfs_inum_is_in_range(a_name_info->fatfs, a_inum));
415
416
    /* Starting parse of a new name, save the previous name, if any. */
417
1.39k
    exfatfs_add_name_to_dir_and_reset_info(a_name_info);
418
419
    /* Record the inum associated with this name. */
420
1.39k
    a_name_info->fs_name->meta_addr = a_inum;
421
422
    /* Set the current entry type. This is used to check the sequence and
423
     * in-use state of the entries in the set. */
424
1.39k
    a_name_info->last_dentry_type =
425
1.39k
        (EXFATFS_DIR_ENTRY_TYPE)a_dentry->data[0];
426
427
1.39k
    switch (exfatfs_get_enum_from_type(a_dentry->data[0])) {
428
1.05k
        case EXFATFS_DIR_ENTRY_TYPE_VOLUME_GUID:
429
1.05k
            strcpy(a_name_info->fs_name->name, EXFATFS_VOLUME_GUID_DENTRY_NAME);
430
1.05k
            break;
431
71
        case EXFATFS_DIR_ENTRY_TYPE_ALLOC_BITMAP:
432
71
            strcpy(a_name_info->fs_name->name, EXFATFS_ALLOC_BITMAP_DENTRY_NAME);
433
71
            break;
434
30
        case EXFATFS_DIR_ENTRY_TYPE_UPCASE_TABLE:
435
30
            strcpy(a_name_info->fs_name->name, EXFATFS_UPCASE_TABLE_DENTRY_NAME);
436
30
            break;
437
242
        case EXFATFS_DIR_ENTRY_TYPE_TEXFAT:
438
242
            strcpy(a_name_info->fs_name->name, EXFATFS_TEX_FAT_DENTRY_NAME);
439
242
            break;
440
0
        case EXFATFS_DIR_ENTRY_TYPE_ACT:
441
0
            strcpy(a_name_info->fs_name->name, EXFATFS_ACT_DENTRY_NAME);
442
0
            break;
443
444
        // listed so that we don't get compile warnings
445
0
        case EXFATFS_DIR_ENTRY_TYPE_NONE:
446
0
        case EXFATFS_DIR_ENTRY_TYPE_VOLUME_LABEL:
447
0
        case EXFATFS_DIR_ENTRY_TYPE_FILE:
448
0
        case EXFATFS_DIR_ENTRY_TYPE_FILE_STREAM:
449
0
        case EXFATFS_DIR_ENTRY_TYPE_FILE_NAME:
450
0
        default:
451
0
            a_name_info->fs_name->name[0] = '\0';
452
0
            break;
453
1.39k
    }
454
455
    /* Not a directory. */
456
1.39k
    a_name_info->fs_name->type = TSK_FS_NAME_TYPE_REG;
457
458
1.39k
    if (a_name_info->sector_is_allocated) {
459
1.39k
        a_name_info->fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
460
1.39k
    }
461
462
    /* Save the virtual file name. */
463
1.39k
    exfatfs_add_name_to_dir_and_reset_info(a_name_info);
464
1.39k
}
465
466
/**
467
 * \internal
468
 * Parse a buffer containing the contents of a directory and add TSK_FS_NAME
469
 * objects for each named file found to the TSK_FS_DIR representation of the
470
 * directory.
471
 *
472
 * @param a_fatfs File system information structure for file system that
473
 * contains the directory.
474
 * @param a_fs_dir Directory structure into to which parsed file metadata will
475
 * be added.
476
 * @param a_buf Buffer that contains the directory contents.
477
 * @param a_buf_len Length of buffer in bytes (must be a multiple of sector
478
 *  size).
479
 * @param a_sector_addrs Array where each element is the original address of
480
 * the corresponding sector in a_buf (size of array is number of sectors in
481
 * the directory).
482
 * @param recursion_depth Recursion depth to limit the number of self-calls
483
 * @return TSK_RETVAL_ENUM
484
*/
485
TSK_RETVAL_ENUM
486
exfatfs_dent_parse_buf(
487
  FATFS_INFO *a_fatfs,
488
  TSK_FS_DIR *a_fs_dir,
489
  char *a_buf,
490
  TSK_OFF_T a_buf_len,
491
  TSK_DADDR_T *a_sector_addrs,
492
  [[maybe_unused]] int recursion_depth)
493
64
{
494
64
    const char *func_name = "exfatfs_parse_directory_buf";
495
64
    TSK_FS_INFO *fs = NULL;
496
64
    TSK_OFF_T num_sectors = 0;
497
64
    TSK_OFF_T sector_index = 0;
498
64
    TSK_INUM_T base_inum_of_sector = 0;
499
64
    EXFATFS_FS_NAME_INFO name_info;
500
64
    TSK_OFF_T dentry_index = 0;
501
64
    FATFS_DENTRY *dentry = NULL;
502
64
    int entries_count = 0;
503
64
    int invalid_entries_count = 0;
504
64
    uint8_t is_corrupt_dir = 0;
505
506
64
    tsk_error_reset();
507
64
    if (fatfs_ptr_arg_is_null(a_fatfs, "a_fatfs", func_name) ||
508
64
        fatfs_ptr_arg_is_null(a_fs_dir, "a_fs_dir", func_name) ||
509
64
        fatfs_ptr_arg_is_null(a_buf, "a_buf", func_name) ||
510
64
        fatfs_ptr_arg_is_null(a_sector_addrs, "a_sector_addrs", func_name)) {
511
0
        return TSK_ERR;
512
0
    }
513
514
64
    assert(a_buf_len > 0);
515
64
    if (a_buf_len < 0) {
516
0
        tsk_error_reset();
517
0
        tsk_error_set_errno(TSK_ERR_FS_ARG);
518
0
        tsk_error_set_errstr("%s: invalid buffer length", func_name);
519
0
        return TSK_ERR;
520
0
    }
521
522
64
    fs = (TSK_FS_INFO*)a_fatfs;
523
524
64
    memset((void*)&name_info, 0, sizeof(EXFATFS_FS_NAME_INFO));
525
64
    name_info.fatfs = a_fatfs;
526
64
    if ((name_info.fs_name = tsk_fs_name_alloc(FATFS_MAXNAMLEN_UTF8, 0)) == NULL) {
527
0
        return TSK_ERR;
528
0
    }
529
64
    name_info.fs_name->name[0] = '\0';
530
64
    name_info.fs_dir = a_fs_dir;
531
532
    /* Loop through the sectors in the buffer. */
533
64
    dentry = (FATFS_DENTRY*)a_buf;
534
64
    num_sectors = a_buf_len / a_fatfs->ssize;
535
9.29k
    for (sector_index = 0; sector_index < num_sectors; ++sector_index) {
536
        /* Convert the address of the current sector into an inode address. */
537
9.23k
        base_inum_of_sector =
538
9.23k
            FATFS_SECT_2_INODE(a_fatfs, a_sector_addrs[sector_index]);
539
9.23k
        if (base_inum_of_sector > fs->last_inum) {
540
0
            tsk_error_reset();
541
0
            tsk_error_set_errno(TSK_ERR_FS_ARG);
542
0
            tsk_error_set_errstr("%s: inode address for sector address %"
543
0
                PRIuDADDR " at addresses array index %" PRIuDADDR
544
0
                " is too large", func_name, base_inum_of_sector, sector_index);
545
0
            tsk_fs_name_free(name_info.fs_name);
546
0
            return TSK_COR;
547
0
        }
548
549
9.23k
        if (tsk_verbose) {
550
0
            tsk_fprintf(stderr,"%s: Parsing sector %" PRIuDADDR " for dir %"
551
0
                PRIuINUM "\n", func_name, a_sector_addrs[sector_index], a_fs_dir->addr);
552
0
        }
553
554
        /* Get the allocation status of the current sector. */
555
9.23k
        if ((name_info.sector_is_allocated =
556
9.23k
            fatfs_is_sectalloc(a_fatfs, a_sector_addrs[sector_index])) == -1) {
557
0
            if (tsk_verbose) {
558
0
                tsk_fprintf(stderr,
559
0
                    "%s: Error looking up allocation status of sector : %"
560
0
                    PRIuDADDR "\n", func_name, a_sector_addrs[sector_index]);
561
0
                tsk_error_print(stderr);
562
0
            }
563
0
            tsk_error_reset();
564
0
            continue;
565
0
        }
566
567
        /* Loop through the putative directory entries in the current sector. */
568
156k
        for (dentry_index = 0; dentry_index < a_fatfs->dentry_cnt_se; ++dentry_index, ++dentry) {
569
147k
            FATFS_DENTRY *current_dentry = dentry;
570
147k
            TSK_INUM_T current_inum = base_inum_of_sector + dentry_index;
571
147k
            EXFATFS_DIR_ENTRY_TYPE dentry_type = EXFATFS_DIR_ENTRY_TYPE_NONE;
572
573
147k
            ++entries_count;
574
575
147k
            if (!fatfs_inum_is_in_range(a_fatfs, current_inum)) {
576
0
                tsk_fs_name_free(name_info.fs_name);
577
0
                return TSK_ERR;
578
0
            }
579
580
147k
            if (exfatfs_is_dentry(a_fatfs, current_dentry,
581
147k
                (FATFS_DATA_UNIT_ALLOC_STATUS_ENUM)name_info.sector_is_allocated,
582
147k
                (uint8_t)(!is_corrupt_dir && name_info.sector_is_allocated))) {
583
15.0k
                dentry_type = (EXFATFS_DIR_ENTRY_TYPE)current_dentry->data[0];
584
15.0k
            }
585
132k
            else {
586
132k
                dentry_type = EXFATFS_DIR_ENTRY_TYPE_NONE;
587
132k
            }
588
589
147k
            switch(exfatfs_get_enum_from_type(dentry_type)) {
590
12.8k
            case EXFATFS_DIR_ENTRY_TYPE_FILE:
591
12.8k
                exfats_parse_file_dentry(&name_info, current_dentry, current_inum);
592
12.8k
                break;
593
331
            case EXFATFS_DIR_ENTRY_TYPE_FILE_STREAM:
594
331
                exfats_parse_file_stream_dentry(&name_info, current_dentry, current_inum);
595
331
                break;
596
422
            case EXFATFS_DIR_ENTRY_TYPE_FILE_NAME:
597
422
                exfats_parse_file_name_dentry(&name_info, current_dentry, current_inum);
598
422
                break;
599
0
            case EXFATFS_DIR_ENTRY_TYPE_VOLUME_LABEL:
600
0
                exfats_parse_vol_label_dentry(&name_info, current_dentry, current_inum);
601
0
                break;
602
1.05k
            case EXFATFS_DIR_ENTRY_TYPE_VOLUME_GUID:
603
1.12k
            case EXFATFS_DIR_ENTRY_TYPE_ALLOC_BITMAP:
604
1.15k
            case EXFATFS_DIR_ENTRY_TYPE_UPCASE_TABLE:
605
1.39k
            case EXFATFS_DIR_ENTRY_TYPE_TEXFAT:
606
1.39k
            case EXFATFS_DIR_ENTRY_TYPE_ACT:
607
1.39k
                exfats_parse_special_file_dentry(&name_info, current_dentry, current_inum);
608
1.39k
                break;
609
132k
            case EXFATFS_DIR_ENTRY_TYPE_NONE:
610
132k
            default:
611
132k
                ++invalid_entries_count;
612
132k
                if (entries_count == 4 && invalid_entries_count == 4) {
613
                    /* If the first four putative entries in the buffer are not
614
                     * entries, set the corrupt directory flag to make entry tests
615
                     * more in-depth, even for allocated sectors. */
616
47
                    is_corrupt_dir = 1;
617
47
                }
618
619
                /* Starting parse of a new name, save the previous name,
620
                 * if any. */
621
132k
                exfatfs_add_name_to_dir_and_reset_info(&name_info);
622
623
132k
                break;
624
147k
            }
625
147k
        }
626
9.23k
    }
627
628
     /* Save the last parsed name, if any. */
629
64
    exfatfs_add_name_to_dir_and_reset_info(&name_info);
630
64
    tsk_fs_name_free(name_info.fs_name);
631
632
64
    return TSK_OK;
633
64
}