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