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