/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 | } |