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