1 /*
2 ** The Sleuth Kit
3 **
4 ** This software is subject to the IBM Public License ver. 1.0,
5 ** which was displayed prior to download and is included in the readme.txt
6 ** file accompanying the Sleuth Kit files.  It may also be requested from:
7 ** Crucial Security Inc.
8 ** 14900 Conference Center Drive
9 ** Chantilly, VA 20151
10 **
11 ** Copyright (c) 2009-2011 Brian Carrier.  All rights reserved.
12 **
13 ** Judson Powers [jpowers@atc-nycorp.com]
14 ** Matt Stillerman [matt@atc-nycorp.com]
15 ** Copyright (c) 2008, 2012 ATC-NY.  All rights reserved.
16 ** This file contains data developed with support from the National
17 ** Institute of Justice, Office of Justice Programs, U.S. Department of Justice.
18 **
19 ** Wyatt Banks [wbanks@crucialsecurity.com]
20 ** Copyright (c) 2005 Crucial Security Inc.  All rights reserved.
21 **
22 ** Brian Carrier [carrier@sleuthkit.org]
23 ** Copyright (c) 2003-2005 Brian Carrier.  All rights reserved
24 **
25 ** Copyright (c) 1997,1998,1999, International Business Machines
26 ** Corporation and others. All Rights Reserved.
27 */
28 
29 /* TCT
30  * LICENSE
31  *      This software is distributed under the IBM Public License.
32  * AUTHOR(S)
33  *      Wietse Venema
34  *      IBM T.J. Watson Research
35  *      P.O. Box 704
36  *      Yorktown Heights, NY 10598, USA
37  --*/
38 
39 /*
40 ** You may distribute the Sleuth Kit, or other software that incorporates
41 ** part of all of the Sleuth Kit, in object code form under a license agreement,
42 ** provided that:
43 ** a) you comply with the terms and conditions of the IBM Public License
44 **    ver 1.0; and
45 ** b) the license agreement
46 **     i) effectively disclaims on behalf of all Contributors all warranties
47 **        and conditions, express and implied, including warranties or
48 **        conditions of title and non-infringement, and implied warranties
49 **        or conditions of merchantability and fitness for a particular
50 **        purpose.
51 **    ii) effectively excludes on behalf of all Contributors liability for
52 **        damages, including direct, indirect, special, incidental and
53 **        consequential damages such as lost profits.
54 **   iii) states that any provisions which differ from IBM Public License
55 **        ver. 1.0 are offered by that Contributor alone and not by any
56 **        other party; and
57 **    iv) states that the source code for the program is available from you,
58 **        and informs licensees how to obtain it in a reasonable manner on or
59 **        through a medium customarily used for software exchange.
60 **
61 ** When the Sleuth Kit or other software that incorporates part or all of
62 ** the Sleuth Kit is made available in source code form:
63 **     a) it must be made available under IBM Public License ver. 1.0; and
64 **     b) a copy of the IBM Public License ver. 1.0 must be included with
65 **        each copy of the program.
66 */
67 
68 /** \file hfs_dent.c
69  * Contains the file name layer code for HFS+ file systems -- not included in
70  * code by default.
71  */
72 
73 #include "tsk_fs_i.h"
74 #include "tsk_hfs.h"
75 
76 /* convert HFS+'s UTF16 to UTF8
77  * replaces null characters with another character (0xfffd)
78  * replaces slashes (permitted by HFS+ but causes problems with TSK)
79  *   with colons (generally not allowed by Mac OS X)
80  * note that at least one directory on HFS+ volumes begins with
81  *   four nulls, so we do need to handle nulls; also, Apple chooses
82  *   to encode nulls as UTF8 \xC0\x80, which is not a valid UTF8 sequence
83  *
84  *   @param fs  the file system
85  *   @param uni  the UTF16 string as a sequence of bytes
86  *   @param ulen  then length of the UTF16 string in characters
87  *   @param asc   a buffer to hold the UTF8 result
88  *   @param alen  the length of that buffer
89  *   @param flags  control some aspects of the conversion
90  *   @return 0 on success, 1 on failure; sets up to error string 1
91  *
92  *   HFS_U16U8_FLAG_REPLACE_SLASH  if this flag is set, then slashes will be replaced
93  *   by colons.  Otherwise, they will not be replaced.
94  *
95  *   HFS_U16U8_FLAG_REPLACE_CONTROL if this flag is set, then all control characters
96  *   will be replaced by the UTF16_NULL_REPLACE character. N.B., always replaces
97  *   null characters regardless of this flag.
98  */
99 uint8_t
hfs_UTF16toUTF8(TSK_FS_INFO * fs,uint8_t * uni,int ulen,char * asc,int alen,uint32_t flags)100 hfs_UTF16toUTF8(TSK_FS_INFO * fs, uint8_t * uni, int ulen, char *asc,
101     int alen, uint32_t flags)
102 {
103     UTF8 *ptr8;
104     uint8_t *uniclean;
105     UTF16 *ptr16;
106     int i;
107     TSKConversionResult r;
108 
109     // remove nulls from the Unicode string
110     // convert / to :
111     uniclean = (uint8_t *) tsk_malloc(ulen * 2);
112     if (!uniclean)
113         return 1;
114 
115     memcpy(uniclean, uni, ulen * 2);
116 
117     for (i = 0; i < ulen; ++i) {
118         uint16_t uc = tsk_getu16(fs->endian, uniclean + i * 2);
119 
120 
121         int changed = 0;
122         if (uc == UTF16_NULL) {
123             uc = UTF16_NULL_REPLACE;
124             changed = 1;
125         }
126         else if ((flags & HFS_U16U8_FLAG_REPLACE_SLASH)
127             && uc == UTF16_SLASH) {
128             uc = UTF16_COLON;
129             changed = 1;
130         }
131 
132         else if ((flags & HFS_U16U8_FLAG_REPLACE_CONTROL)
133             && uc < UTF16_LEAST_PRINTABLE) {
134             uc = (uint16_t) UTF16_NULL_REPLACE;
135             changed = 1;
136         }
137 
138         if (changed)
139             *((uint16_t *) (uniclean + i * 2)) =
140                 tsk_getu16(fs->endian, (uint8_t *) & uc);
141     }
142 
143     // convert to UTF-8
144     memset(asc, 0, alen);
145 
146     ptr8 = (UTF8 *) asc;
147     ptr16 = (UTF16 *) uniclean;
148     r = tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &ptr16,
149         (const UTF16 *) (&uniclean[ulen * 2]), &ptr8,
150         (UTF8 *) & asc[alen], TSKstrictConversion);
151 
152     free(uniclean);
153     if (r != TSKconversionOK) {
154         tsk_error_set_errno(TSK_ERR_FS_UNICODE);
155         tsk_error_set_errstr
156             ("hfs_UTF16toUTF8: unicode conversion failed (%d)", (int) r);
157         return 1;
158     }
159 
160     return 0;
161 }
162 
163 static TSK_FS_NAME_TYPE_ENUM
hfsmode2tsknametype(uint16_t a_mode)164 hfsmode2tsknametype(uint16_t a_mode)
165 {
166     switch (a_mode & HFS_IN_IFMT) {
167     case HFS_IN_IFIFO:
168         return TSK_FS_NAME_TYPE_FIFO;
169     case HFS_IN_IFCHR:
170         return TSK_FS_NAME_TYPE_CHR;
171     case HFS_IN_IFDIR:
172         return TSK_FS_NAME_TYPE_DIR;
173     case HFS_IN_IFBLK:
174         return TSK_FS_NAME_TYPE_BLK;
175     case HFS_IN_IFREG:
176         return TSK_FS_NAME_TYPE_REG;
177     case HFS_IN_IFLNK:
178         return TSK_FS_NAME_TYPE_LNK;
179     case HFS_IN_IFSOCK:
180         return TSK_FS_NAME_TYPE_SOCK;
181     case HFS_IFWHT:
182         return TSK_FS_NAME_TYPE_WHT;
183     case HFS_IFXATTR:
184         return TSK_FS_NAME_TYPE_UNDEF;
185     default:
186         /* error */
187         return TSK_FS_NAME_TYPE_UNDEF;
188     }
189 }
190 
191 
192 // used to pass data to the callback
193 typedef struct {
194     TSK_FS_DIR *fs_dir;
195     TSK_FS_NAME *fs_name;
196     uint32_t cnid;
197 } HFS_DIR_OPEN_META_INFO;
198 
199 static uint8_t
hfs_dir_open_meta_cb(HFS_INFO * hfs,int8_t level_type,const hfs_btree_key_cat * cur_key,TSK_OFF_T key_off,void * ptr)200 hfs_dir_open_meta_cb(HFS_INFO * hfs, int8_t level_type,
201     const hfs_btree_key_cat * cur_key,
202     TSK_OFF_T key_off, void *ptr)
203 {
204     HFS_DIR_OPEN_META_INFO *info = (HFS_DIR_OPEN_META_INFO *) ptr;
205     TSK_FS_INFO *fs = &hfs->fs_info;
206 
207     if (tsk_verbose)
208         fprintf(stderr,
209             "hfs_dir_open_meta_cb: want %" PRIu32 " vs got %" PRIu32
210             " (%s node)\n", info->cnid, tsk_getu32(hfs->fs_info.endian,
211                 cur_key->parent_cnid),
212             (level_type == HFS_BT_NODE_TYPE_IDX) ? "Index" : "Leaf");
213 
214     if (level_type == HFS_BT_NODE_TYPE_IDX) {
215         if (tsk_getu32(hfs->fs_info.endian,
216                 cur_key->parent_cnid) < info->cnid) {
217             return HFS_BTREE_CB_IDX_LT;
218         }
219         else {
220             return HFS_BTREE_CB_IDX_EQGT;
221         }
222     }
223     else {
224         uint8_t *rec_buf = (uint8_t *) cur_key;
225         uint16_t rec_type;
226         size_t rec_off2;
227 
228         if (tsk_getu32(hfs->fs_info.endian,
229                 cur_key->parent_cnid) < info->cnid) {
230             return HFS_BTREE_CB_LEAF_GO;
231         }
232         else if (tsk_getu32(hfs->fs_info.endian,
233                 cur_key->parent_cnid) > info->cnid) {
234             return HFS_BTREE_CB_LEAF_STOP;
235         }
236         rec_off2 = 2 + tsk_getu16(hfs->fs_info.endian, cur_key->key_len);
237         rec_type = tsk_getu16(hfs->fs_info.endian, &rec_buf[rec_off2]);
238 
239         // Catalog entry is for a file
240         if (rec_type == HFS_FILE_THREAD) {
241             tsk_error_set_errno(TSK_ERR_FS_GENFS);
242             tsk_error_set_errstr("hfs_dir_open_meta: Entry"
243                 " is a file, not a folder");
244             return HFS_BTREE_CB_ERR;
245         }
246 
247         /* This will link the folder to its parent, which is the ".." entry */
248         else if (rec_type == HFS_FOLDER_THREAD) {
249             hfs_thread *thread = (hfs_thread *) & rec_buf[rec_off2];
250             strcpy(info->fs_name->name, "..");
251             info->fs_name->meta_addr =
252                 tsk_getu32(hfs->fs_info.endian, thread->parent_cnid);
253             info->fs_name->type = TSK_FS_NAME_TYPE_DIR;
254             info->fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
255         }
256 
257         /* This is a folder in the folder */
258         else if (rec_type == HFS_FOLDER_RECORD) {
259             hfs_folder *folder = (hfs_folder *) & rec_buf[rec_off2];
260 
261             info->fs_name->meta_addr =
262                 tsk_getu32(hfs->fs_info.endian, folder->std.cnid);
263             info->fs_name->type = TSK_FS_NAME_TYPE_DIR;
264             info->fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
265 
266             // Make sure there is enough space in cur_key for the name
267             // (name is unicode so each characters is two bytes; 6 bytes
268             // of non-name characters)
269             const int32_t nameLength =
270                 tsk_getu16(hfs->fs_info.endian, cur_key->name.length);
271 
272             if (2*nameLength > tsk_getu16(hfs->fs_info.endian, cur_key->key_len) - 6) {
273                 error_returned
274                 ("hfs_dir_open_meta_cb: name length is too long");
275                 return HFS_BTREE_CB_ERR;
276             }
277             if (hfs_UTF16toUTF8(fs, (uint8_t *) cur_key->name.unicode,
278                     nameLength, info->fs_name->name, HFS_MAXNAMLEN + 1,
279                     HFS_U16U8_FLAG_REPLACE_SLASH)) {
280                 return HFS_BTREE_CB_ERR;
281             }
282         }
283 
284         /* This is a normal file in the folder */
285         else if (rec_type == HFS_FILE_RECORD) {
286             hfs_file *file = (hfs_file *) & rec_buf[rec_off2];
287             // This could be a hard link.  We need to test this CNID, and follow it if necessary.
288             unsigned char is_err;
289             TSK_INUM_T file_cnid =
290                 tsk_getu32(hfs->fs_info.endian, file->std.cnid);
291             TSK_INUM_T target_cnid =
292                 hfs_follow_hard_link(hfs, file, &is_err);
293             if (is_err > 1) {
294                 error_returned
295                     ("hfs_dir_open_meta_cb: trying to follow a possible hard link in the directory");
296                 return HFS_BTREE_CB_ERR;
297             }
298             if (target_cnid != file_cnid) {
299                 HFS_ENTRY entry;
300                 uint8_t lkup;   // lookup result
301 
302                 // This is a hard link.  We need to fill in the name->type and name->meta_addr from the target
303                 info->fs_name->meta_addr = target_cnid;
304                 // get the Catalog entry for the target CNID
305 
306                 lkup = hfs_cat_file_lookup(hfs, target_cnid, &entry,
307                     FALSE);
308                 if (lkup != 0) {
309                     error_returned
310                         ("hfs_dir_open_meta_cb: retrieving the catalog entry for the target of a hard link");
311                     return HFS_BTREE_CB_ERR;
312                 }
313                 info->fs_name->type =
314                     hfsmode2tsknametype(tsk_getu16(hfs->fs_info.endian,
315                         entry.cat.std.perm.mode));
316             }
317             else {
318                 // This is NOT a hard link.
319                 info->fs_name->meta_addr =
320                     tsk_getu32(hfs->fs_info.endian, file->std.cnid);
321                 info->fs_name->type =
322                     hfsmode2tsknametype(tsk_getu16(hfs->fs_info.endian,
323                         file->std.perm.mode));
324             }
325             info->fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
326 
327             // Make sure there is enough space in cur_key for the name
328             // (name is unicode so each characters is two bytes; 6 bytes
329             // of non-name characters)
330             const int32_t nameLength =
331                 tsk_getu16(hfs->fs_info.endian, cur_key->name.length);
332             if (2*nameLength > tsk_getu16(hfs->fs_info.endian, cur_key->key_len) - 6) {
333                 error_returned
334                 ("hfs_dir_open_meta_cb: name length is too long");
335                 return HFS_BTREE_CB_ERR;
336             }
337             if (hfs_UTF16toUTF8(fs, (uint8_t *) cur_key->name.unicode,
338                     nameLength, info->fs_name->name, HFS_MAXNAMLEN + 1,
339                     HFS_U16U8_FLAG_REPLACE_SLASH)) {
340                 return HFS_BTREE_CB_ERR;
341             }
342         }
343         else {
344             tsk_error_set_errno(TSK_ERR_FS_GENFS);
345             // @@@ MAY NEED TO IMPROVE BELOW MESSAGE
346             tsk_error_set_errstr
347                 ("hfs_dir_open_meta: Unknown record type %d in leaf node",
348                 rec_type);
349             return HFS_BTREE_CB_ERR;
350         }
351 
352         if (tsk_fs_dir_add(info->fs_dir, info->fs_name)) {
353             return HFS_BTREE_CB_ERR;
354         }
355         return HFS_BTREE_CB_LEAF_GO;
356     }
357 }
358 
359 /** \internal
360 * Process a directory and load up FS_DIR with the entries. If a pointer to
361 * an already allocated FS_DIR structure is given, it will be cleared.  If no existing
362 * FS_DIR structure is passed (i.e. NULL), then a new one will be created. If the return
363 * value is error or corruption, then the FS_DIR structure could
364 * have entries (depending on when the error occurred).
365 *
366 * @param a_fs File system to analyze
367 * @param a_fs_dir Pointer to FS_DIR pointer. Can contain an already allocated
368 * structure or a new structure.
369 * @param a_addr Address of directory to process.
370 * @returns error, corruption, ok etc.
371 */
372 TSK_RETVAL_ENUM
hfs_dir_open_meta(TSK_FS_INFO * fs,TSK_FS_DIR ** a_fs_dir,TSK_INUM_T a_addr)373 hfs_dir_open_meta(TSK_FS_INFO * fs, TSK_FS_DIR ** a_fs_dir,
374     TSK_INUM_T a_addr)
375 {
376     HFS_INFO *hfs = (HFS_INFO *) fs;
377     uint32_t cnid;              /* catalog node ID of the entry (= inum) */
378     TSK_FS_DIR *fs_dir;
379     TSK_FS_NAME *fs_name;
380     HFS_DIR_OPEN_META_INFO info;
381 
382 
383     tsk_error_reset();
384 
385     cnid = (uint32_t) a_addr;
386 
387     if (tsk_verbose)
388         fprintf(stderr,
389             "hfs_dir_open_meta: called for directory %" PRIu32 "\n", cnid);
390 
391     if (a_addr < fs->first_inum || a_addr > fs->last_inum) {
392         tsk_error_reset();
393         tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
394         tsk_error_set_errstr("hfs_dir_open_meta: Invalid inode value: %"
395             PRIuINUM, a_addr);
396         return TSK_ERR;
397     }
398     else if (a_fs_dir == NULL) {
399         tsk_error_reset();
400         tsk_error_set_errno(TSK_ERR_FS_ARG);
401         tsk_error_set_errstr
402             ("hfs_dir_open_meta: NULL fs_dir argument given");
403         return TSK_ERR;
404     }
405 
406     if (tsk_verbose)
407         tsk_fprintf(stderr,
408             "hfs_dir_open_meta: Processing directory %" PRIuINUM "\n",
409             a_addr);
410 
411     fs_dir = *a_fs_dir;
412     if (fs_dir) {
413         tsk_fs_dir_reset(fs_dir);
414         fs_dir->addr = a_addr;
415     }
416     else if ((*a_fs_dir = fs_dir =
417             tsk_fs_dir_alloc(fs, a_addr, 128)) == NULL) {
418         return TSK_ERR;
419     }
420 
421     if ((fs_name = tsk_fs_name_alloc(HFS_MAXNAMLEN + 1, 0)) == NULL) {
422         return TSK_ERR;
423     }
424     info.fs_dir = fs_dir;
425     info.fs_name = fs_name;
426 
427     if ((fs_dir->fs_file =
428             tsk_fs_file_open_meta(fs, NULL, a_addr)) == NULL) {
429         tsk_error_errstr2_concat(" - hfs_dir_open_meta");
430         tsk_fs_name_free(fs_name);
431         return TSK_ERR;
432     }
433 
434     // if we are listing the root directory, add the Orphan directory and special HFS file entries
435     if (a_addr == fs->root_inum) {
436         int i;
437         for (i = 0; i < 6; i++) {
438             switch (i) {
439             case 0:
440                 if (!hfs->has_extents_file)
441                     continue;
442                 strncpy(fs_name->name, HFS_EXTENTS_FILE_NAME,
443                     fs_name->name_size);
444                 fs_name->meta_addr = HFS_EXTENTS_FILE_ID;
445                 break;
446             case 1:
447                 strncpy(fs_name->name, HFS_CATALOG_FILE_NAME,
448                     fs_name->name_size);
449                 fs_name->meta_addr = HFS_CATALOG_FILE_ID;
450                 break;
451             case 2:
452                 // Note: the Extents file and the BadBlocks file are really the same.
453                 if (!hfs->has_extents_file)
454                     continue;
455                 strncpy(fs_name->name, HFS_BAD_BLOCK_FILE_NAME,
456                     fs_name->name_size);
457                 fs_name->meta_addr = HFS_BAD_BLOCK_FILE_ID;
458                 break;
459             case 3:
460                 strncpy(fs_name->name, HFS_ALLOCATION_FILE_NAME,
461                     fs_name->name_size);
462                 fs_name->meta_addr = HFS_ALLOCATION_FILE_ID;
463                 break;
464             case 4:
465                 if (!hfs->has_startup_file)
466                     continue;
467                 strncpy(fs_name->name, HFS_STARTUP_FILE_NAME,
468                     fs_name->name_size);
469                 fs_name->meta_addr = HFS_STARTUP_FILE_ID;
470                 break;
471             case 5:
472                 if (!hfs->has_attributes_file)
473                     continue;
474                 strncpy(fs_name->name, HFS_ATTRIBUTES_FILE_NAME,
475                     fs_name->name_size);
476                 fs_name->meta_addr = HFS_ATTRIBUTES_FILE_ID;
477                 break;
478                 /*
479                    case 6:
480                    strncpy(fs_name->name, HFS_REPAIR_CATALOG_FILE_NAME, fs_name->name_size);
481                    fs_name->meta_addr = HFS_REPAIR_CATALOG_FILE_ID;
482                    break;
483                    case 7:
484                    strncpy(fs_name->name, HFS_BOGUS_EXTENT_FILE_NAME, fs_name->name_size);
485                    fs_name->meta_addr = HFS_BOGUS_EXTENT_FILE_ID;
486                    break;
487                  */
488             }
489             fs_name->type = TSK_FS_NAME_TYPE_REG;
490             fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
491             if (tsk_fs_dir_add(fs_dir, fs_name)) {
492                 tsk_fs_name_free(fs_name);
493                 return TSK_ERR;
494             }
495         }
496     }
497 
498     info.cnid = cnid;
499     if (hfs_cat_traverse(hfs, hfs_dir_open_meta_cb, &info)) {
500         tsk_fs_name_free(fs_name);
501         return TSK_ERR;
502     }
503 
504     tsk_fs_name_free(fs_name);
505     return TSK_OK;
506 }
507 
508 int
hfs_name_cmp(TSK_FS_INFO * a_fs_info,const char * s1,const char * s2)509 hfs_name_cmp(TSK_FS_INFO * a_fs_info, const char *s1, const char *s2)
510 {
511     HFS_INFO *hfs = (HFS_INFO *) a_fs_info;
512     if (hfs->is_case_sensitive)
513         return strcmp(s1, s2);
514     else
515         return strcasecmp(s1, s2);
516 }
517