Coverage Report

Created: 2025-08-26 06:11

/src/sleuthkit/tsk/fs/fatfs_utils.c
Line
Count
Source (jump to first uncovered line)
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
 * \file fatfs_utils.c
13
 * Contains utility functions for processing FAT file systems.
14
 */
15
16
#include "tsk_fs_i.h"
17
#include "tsk_fatfs.h"
18
#include <assert.h>
19
20
/**
21
 * \internal
22
 * Tests whether a pointer argument is set to NULL. If the pointer is NULL,
23
 * sets a TSK_ERR_FS_ARG error with an error message that includes a parameter
24
 * name and a function name supplied by the caller.
25
 *
26
 * @param a_ptr The pointer to test for NULL.
27
 * @param a_param_name The name of the parameter for which the pointer was
28
 * passed as an argument.
29
 * @param a_func_name The name of the function for which a_param is a
30
 * parameter.
31
 * @return Returns 1 if the pointer is NULL, 0 otherwise.
32
 */
33
uint8_t
34
fatfs_ptr_arg_is_null(void *a_ptr, const char *a_param_name, const char *a_func_name)
35
11.0M
{
36
11.0M
    const char *func_name = "fatfs_ptr_arg_is_null";
37
38
11.0M
    assert(a_param_name != NULL);
39
11.0M
    assert(a_func_name != NULL);
40
41
11.0M
    if (a_ptr == NULL) {
42
0
        tsk_error_reset();
43
0
        tsk_error_set_errno(TSK_ERR_FS_ARG);
44
0
        if ((a_param_name != NULL) && (a_func_name != NULL)) {
45
0
            tsk_error_set_errstr("%s: %s is NULL", a_param_name, a_func_name);
46
0
        }
47
0
        else {
48
0
            tsk_error_set_errstr("%s: NULL pointer", func_name);
49
0
        }
50
51
0
        return 1;
52
0
    }
53
54
11.0M
    return 0;
55
11.0M
}
56
57
/**
58
 * \internal
59
 * Tests whether an inode address is in the range of valid inode addresses for
60
 * a given file system.
61
 *
62
 * @param a_fatfs Generic FAT file system info structure.
63
 * @param a_inum An inode address.
64
 * @return Returns 1 if the address is in range, 0 otherwise.
65
 */
66
uint8_t
67
fatfs_inum_is_in_range(FATFS_INFO *a_fatfs, TSK_INUM_T a_inum)
68
2.77M
{
69
2.77M
    const char *func_name = "fatfs_inum_is_in_range";
70
2.77M
    TSK_FS_INFO *fs = (TSK_FS_INFO*)a_fatfs;
71
72
2.77M
    assert(a_fatfs != NULL);
73
74
2.77M
    if (fatfs_ptr_arg_is_null(a_fatfs, "a_fatfs", func_name)) {
75
0
        return 0;
76
0
    }
77
78
2.77M
    if ((a_inum < fs->first_inum) || (a_inum > fs->last_inum)) {
79
0
        return 0;
80
0
    }
81
82
2.77M
    return 1;
83
2.77M
}
84
85
/**
86
 * \internal
87
 * Tests whether an inode address argument is in the range of valid inode
88
 * addresses for a given file system. If the address is out of range,
89
 * sets a TSK_ERR_FS_ARG error with an error message that includes the inode
90
 * address and a function name supplied by the caller.
91
 *
92
 * @param a_fatfs Generic FAT file system info structure.
93
 * @param a_inum An inode address.
94
 * @param a_func_name The name of the function that received the inode address
95
 * as an argument.
96
 * @return Returns 1 if the address is in range, 0 otherwise.
97
 */
98
uint8_t
99
fatfs_inum_arg_is_in_range(FATFS_INFO *a_fatfs, TSK_INUM_T a_inum, const char *a_func_name)
100
1.98M
{
101
1.98M
    const char *func_name = "fatfs_inum_arg_is_in_range";
102
103
1.98M
    assert(a_fatfs != NULL);
104
1.98M
    assert(a_func_name != NULL);
105
106
1.98M
    if (fatfs_ptr_arg_is_null(a_fatfs, "a_fatfs", func_name)) {
107
0
        return 0;
108
0
    }
109
110
1.98M
    if (!fatfs_inum_is_in_range(a_fatfs, a_inum)) {
111
0
        tsk_error_reset();
112
0
        tsk_error_set_errno(TSK_ERR_FS_ARG);
113
0
        if (a_func_name != NULL) {
114
0
            tsk_error_set_errstr("%s: inode address: %" PRIuINUM " out of range", a_func_name, a_inum);
115
0
        }
116
0
        else {
117
0
            tsk_error_set_errstr("%s: inode address: %" PRIuINUM " out of range", func_name, a_inum);
118
0
        }
119
120
0
        return 0;
121
0
    }
122
1.98M
    return 1;
123
1.98M
}
124
125
/**
126
 * \internal
127
 * Convert a DOS time stamp into a UNIX time stamp. A DOS time stamp consists
128
 * of a date with the year specified as an offset from 1980. A UNIX time stamp
129
 * is seconds since January 1, 1970 in UTC.
130
 *
131
 * @param date Date part of a DOS time stamp.
132
 * @param time Time part of a DOS time stamp.
133
 * @param timetens Tenths of seconds part of a DOS time stamp, range is 0-199.
134
 * @return A UNIX time stamp.
135
 */
136
time_t
137
fatfs_dos_2_unix_time(uint16_t date, uint16_t time, uint8_t timetens)
138
837k
{
139
837k
    struct tm tm1;
140
837k
    time_t ret;
141
142
837k
    if (date == 0)
143
0
        return 0;
144
145
837k
    memset(&tm1, 0, sizeof(struct tm));
146
147
837k
    tm1.tm_sec = ((time & FATFS_SEC_MASK) >> FATFS_SEC_SHIFT) * 2;
148
837k
    if ((tm1.tm_sec < 0) || (tm1.tm_sec > 60))
149
2.46k
        tm1.tm_sec = 0;
150
151
    /* The ctimetens value has a range of 0 to 199 */
152
837k
    if (timetens >= 100)
153
5.33k
        tm1.tm_sec++;
154
155
837k
    tm1.tm_min = ((time & FATFS_MIN_MASK) >> FATFS_MIN_SHIFT);
156
837k
    if ((tm1.tm_min < 0) || (tm1.tm_min > 59))
157
1.09k
        tm1.tm_min = 0;
158
159
837k
    tm1.tm_hour = ((time & FATFS_HOUR_MASK) >> FATFS_HOUR_SHIFT);
160
837k
    if ((tm1.tm_hour < 0) || (tm1.tm_hour > 23))
161
2.71k
        tm1.tm_hour = 0;
162
163
837k
    tm1.tm_mday = ((date & FATFS_DAY_MASK) >> FATFS_DAY_SHIFT);
164
837k
    if ((tm1.tm_mday < 1) || (tm1.tm_mday > 31))
165
0
        tm1.tm_mday = 0;
166
167
837k
    tm1.tm_mon = ((date & FATFS_MON_MASK) >> FATFS_MON_SHIFT) - 1;
168
837k
    if ((tm1.tm_mon < 0) || (tm1.tm_mon > 11))
169
0
        tm1.tm_mon = 0;
170
171
    /* There is a limit to the year because the UNIX time value is
172
     * a 32-bit value
173
     * the maximum UNIX time is Tue Jan 19 03:14:07 2038 */
174
837k
    tm1.tm_year = ((date & FATFS_YEAR_MASK) >> FATFS_YEAR_SHIFT) + 80;
175
837k
    if ((tm1.tm_year < 0) || (tm1.tm_year > 137))
176
7.05k
        tm1.tm_year = 0;
177
178
    /* set the daylight savings variable to -1 so that mktime() figures
179
     * it out */
180
837k
    tm1.tm_isdst = -1;
181
182
837k
    ret = mktime(&tm1);
183
184
837k
    if (ret < 0) {
185
7.05k
        if (tsk_verbose)
186
0
            tsk_fprintf(stderr,
187
0
                "fatfs_dos_2_unix_time: Error running mktime() on: %d:%d:%d %d/%d/%d\n",
188
0
                ((time & FATFS_HOUR_MASK) >> FATFS_HOUR_SHIFT),
189
0
                ((time & FATFS_MIN_MASK) >> FATFS_MIN_SHIFT),
190
0
                ((time & FATFS_SEC_MASK) >> FATFS_SEC_SHIFT) * 2,
191
0
                ((date & FATFS_MON_MASK) >> FATFS_MON_SHIFT) - 1,
192
0
                ((date & FATFS_DAY_MASK) >> FATFS_DAY_SHIFT),
193
0
                ((date & FATFS_YEAR_MASK) >> FATFS_YEAR_SHIFT) + 80);
194
7.05k
        return 0;
195
7.05k
    }
196
197
830k
    return ret;
198
837k
}
199
200
/**
201
 * \internal
202
 * Converts the tenths of seconds part a DOS time stamp into nanoseconds.
203
 * of a date with the year specified as an offset from 1980. A UNIX time stamp
204
 * is seconds since January 1, 1970 in UTC.
205
 *
206
 * @param timetens Tenths of seconds part of a DOS time stamp, range is 0-199.
207
 * @return A duration in nanoseconds.
208
 */
209
uint32_t
210
fatfs_dos_2_nanosec(uint8_t timetens)
211
423k
{
212
423k
    timetens %= 100;
213
423k
    return timetens * 10000000;
214
423k
}
215
216
/**
217
 * \internal
218
 * Cleans up a string so that it contains only ASCII characters. Useful when
219
 *
220
 * @param name The string
221
 */
222
void
223
fatfs_cleanup_ascii(char *str)
224
976k
{
225
976k
    const char *func_name = "fatfs_cleanup_ascii";
226
227
976k
    assert(str != NULL);
228
229
976k
    if (!fatfs_ptr_arg_is_null(str, "str", func_name)) {
230
976k
        int i;
231
2.90M
        for (i = 0; str[i] != '\0'; i++) {
232
1.93M
            if ((unsigned char) (str[i]) > 0x7e) {
233
81.1k
                str[i] = '^';
234
81.1k
            }
235
1.93M
        }
236
976k
    }
237
976k
}
238
239
/**
240
 * \internal
241
 * Converts a UTF-16 string from an inode into a null-terminated UTF-8 string. If the
242
 * conversion fails, sets a TSK_ERR_FS_UNICODE error with an error message
243
 * that includes the inode address and a description of the UTF-16 string
244
 * supplied by the caller.
245
 *
246
 * Unlike tsk_UTF16toUTF8, a_src and a_dest will not be updated to point
247
 * to where the conversion stopped reading/writing.
248
 *
249
 * @param a_fatfs Generic FAT file system info structure.
250
 * @param a_src The UTF-16 string to convert.
251
 * @param a_src_len The number of UTF16 items in a_src.
252
 * @param a_dest The buffer for the UTF-8 string.
253
 * @param a_dest_len The number of bytes in a_dest.
254
 * @param a_inum The address of the source inode, used if an error message is
255
 * generated.
256
 * @param a_desc A description of the source string, used if an error message
257
 * is generated.
258
 * @return TSKConversionResult.
259
 */
260
TSKConversionResult
261
fatfs_utf16_inode_str_2_utf8(FATFS_INFO *a_fatfs, UTF16 *a_src, size_t a_src_len, UTF8 *a_dest, size_t a_dest_len, TSK_INUM_T a_inum, const char *a_desc)
262
122
{
263
122
    const char *func_name = "fatfs_copy_utf16_str";
264
122
    TSK_FS_INFO *fs = &(a_fatfs->fs_info);
265
122
    TSKConversionResult conv_result = TSKconversionOK;
266
122
    UTF8* dest_start;
267
122
    UTF8* dest_end;
268
269
122
    assert(a_fatfs != NULL);
270
122
    assert(a_src != NULL);
271
122
    assert(a_src_len > 0);
272
122
    assert(a_dest != NULL);
273
122
    assert(a_dest_len > 0);
274
122
    assert(a_desc != NULL);
275
276
122
    if (fatfs_ptr_arg_is_null(a_fatfs, "a_fatfs", func_name)) {
277
0
        return TSKsourceIllegal;
278
0
    }
279
280
122
    if (fatfs_ptr_arg_is_null(a_fatfs, "a_src", func_name)) {
281
0
        return TSKsourceExhausted;
282
0
    }
283
284
122
    if (a_src_len <= 0) {
285
0
        return TSKsourceExhausted;
286
0
    }
287
288
122
    if (fatfs_ptr_arg_is_null(a_fatfs, "a_dest", func_name)) {
289
0
        return TSKtargetExhausted;
290
0
    }
291
292
122
    if (a_dest_len <= 0) {
293
0
        return TSKtargetExhausted;
294
0
    }
295
296
122
    if (fatfs_ptr_arg_is_null(a_fatfs, "a_desc", func_name)) {
297
0
        return TSKsourceIllegal;
298
0
    }
299
300
    /* Do the conversion. Note that a_dest and a_src will point to where the conversion
301
     * stopped reading/writing. */
302
122
    dest_start = a_dest;
303
122
    dest_end = (UTF8*)&a_dest[a_dest_len];
304
122
    conv_result = tsk_UTF16toUTF8(fs->endian, (const UTF16**)&a_src, (UTF16*)&a_src[a_src_len], &a_dest, dest_end, TSKlenientConversion);
305
306
122
    if (conv_result != TSKconversionOK) {
307
0
        tsk_error_reset();
308
0
        tsk_error_set_errno(TSK_ERR_FS_UNICODE);
309
0
        tsk_error_set_errstr("%s: Error converting %s for inum %" PRIuINUM " from UTF16 to UTF8: %d", func_name, a_desc, a_inum, conv_result);
310
0
        *a_dest = '\0';
311
0
        return conv_result;
312
0
    }
313
314
    /* Make sure the result is NULL-terminated. */
315
122
    if((uintptr_t)a_dest >= (uintptr_t)dest_end)
316
0
        dest_start[a_dest_len - 1] = '\0';
317
122
    else
318
122
        *a_dest = '\0';
319
320
122
    return conv_result;
321
122
}