1 /*
2  *  Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
3  *  Copyright (C) 2013 Sourcefire, Inc.
4  *
5  *  Authors: David Raynor <draynor@sourcefire.com>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  *  MA 02110-1301, USA.
20  */
21 /**
22  * Documentation:
23  * - https://digital-forensics.sans.org/media/FOR518-Reference-Sheet.pdf
24  * - https://github.com/sleuthkit/sleuthkit/blob/develop/tsk/fs/tsk_hfs.h
25  * - https://github.com/unsound/hfsexplorer/tree/master/src/java/org/catacombae/hfs
26  */
27 
28 #if HAVE_CONFIG_H
29 #include "clamav-config.h"
30 #endif
31 
32 #include <fcntl.h>
33 
34 #include "clamav.h"
35 #include "others.h"
36 #include "hfsplus.h"
37 #include "scanners.h"
38 #include "entconv.h"
39 
40 #define DECMPFS_HEADER_MAGIC 0x636d7066
41 #define DECMPFS_HEADER_MAGIC_LE 0x66706d63
42 
43 static void headerrecord_to_host(hfsHeaderRecord *);
44 static void headerrecord_print(const char *, hfsHeaderRecord *);
45 static void nodedescriptor_to_host(hfsNodeDescriptor *);
46 static void nodedescriptor_print(const char *, hfsNodeDescriptor *);
47 static void forkdata_to_host(hfsPlusForkData *);
48 static void forkdata_print(const char *, hfsPlusForkData *);
49 
50 static int hfsplus_volumeheader(cli_ctx *, hfsPlusVolumeHeader **);
51 static int hfsplus_readheader(cli_ctx *, hfsPlusVolumeHeader *, hfsNodeDescriptor *,
52                               hfsHeaderRecord *, int, const char *);
53 static cl_error_t hfsplus_scanfile(cli_ctx *, hfsPlusVolumeHeader *, hfsHeaderRecord *,
54                                    hfsPlusForkData *, const char *, char **, char *);
55 static int hfsplus_validate_catalog(cli_ctx *, hfsPlusVolumeHeader *, hfsHeaderRecord *);
56 static int hfsplus_fetch_node(cli_ctx *, hfsPlusVolumeHeader *, hfsHeaderRecord *,
57                               hfsHeaderRecord *, hfsPlusForkData *, uint32_t, uint8_t *);
58 static cl_error_t hfsplus_walk_catalog(cli_ctx *, hfsPlusVolumeHeader *, hfsHeaderRecord *,
59                                        hfsHeaderRecord *, hfsHeaderRecord *, const char *);
60 
61 /* Header Record : fix endianness for useful fields */
headerrecord_to_host(hfsHeaderRecord * hdr)62 static void headerrecord_to_host(hfsHeaderRecord *hdr)
63 {
64     hdr->treeDepth     = be16_to_host(hdr->treeDepth);
65     hdr->rootNode      = be32_to_host(hdr->rootNode);
66     hdr->leafRecords   = be32_to_host(hdr->leafRecords);
67     hdr->firstLeafNode = be32_to_host(hdr->firstLeafNode);
68     hdr->lastLeafNode  = be32_to_host(hdr->lastLeafNode);
69     hdr->nodeSize      = be16_to_host(hdr->nodeSize);
70     hdr->maxKeyLength  = be16_to_host(hdr->maxKeyLength);
71     hdr->totalNodes    = be32_to_host(hdr->totalNodes);
72     hdr->freeNodes     = be32_to_host(hdr->freeNodes);
73     hdr->attributes    = be32_to_host(hdr->attributes); /* not too useful */
74 }
75 
76 /* Header Record : print details in debug mode */
headerrecord_print(const char * pfx,hfsHeaderRecord * hdr)77 static void headerrecord_print(const char *pfx, hfsHeaderRecord *hdr)
78 {
79     cli_dbgmsg("%s Header: depth %hu root %u leafRecords %u firstLeaf %u lastLeaf %u nodeSize %hu\n",
80                pfx, hdr->treeDepth, hdr->rootNode, hdr->leafRecords, hdr->firstLeafNode,
81                hdr->lastLeafNode, hdr->nodeSize);
82     cli_dbgmsg("%s Header: maxKeyLength %hu totalNodes %u freeNodes %u btreeType %hhu attributes %x\n",
83                pfx, hdr->maxKeyLength, hdr->totalNodes, hdr->freeNodes,
84                hdr->btreeType, hdr->attributes);
85 }
86 
87 /* Node Descriptor : fix endianness for useful fields */
nodedescriptor_to_host(hfsNodeDescriptor * node)88 static void nodedescriptor_to_host(hfsNodeDescriptor *node)
89 {
90     node->fLink      = be32_to_host(node->fLink);
91     node->bLink      = be32_to_host(node->bLink);
92     node->numRecords = be16_to_host(node->numRecords);
93 }
94 
95 /* Node Descriptor : print details in debug mode */
nodedescriptor_print(const char * pfx,hfsNodeDescriptor * node)96 static void nodedescriptor_print(const char *pfx, hfsNodeDescriptor *node)
97 {
98     cli_dbgmsg("%s Desc: fLink %u bLink %u kind %d height %u numRecords %u\n",
99                pfx, node->fLink, node->bLink, node->kind, node->height, node->numRecords);
100 }
101 
102 /* ForkData : fix endianness */
forkdata_to_host(hfsPlusForkData * fork)103 static void forkdata_to_host(hfsPlusForkData *fork)
104 {
105     int i;
106 
107     fork->logicalSize = be64_to_host(fork->logicalSize);
108     fork->clumpSize   = be32_to_host(fork->clumpSize); /* does this matter for read-only? */
109     fork->totalBlocks = be32_to_host(fork->totalBlocks);
110     for (i = 0; i < 8; i++) {
111         fork->extents[i].startBlock = be32_to_host(fork->extents[i].startBlock);
112         fork->extents[i].blockCount = be32_to_host(fork->extents[i].blockCount);
113     }
114 }
115 
116 /* ForkData : print details in debug mode */
forkdata_print(const char * pfx,hfsPlusForkData * fork)117 static void forkdata_print(const char *pfx, hfsPlusForkData *fork)
118 {
119     int i;
120     cli_dbgmsg("%s logicalSize " STDu64 " clumpSize " STDu32 " totalBlocks " STDu32 "\n", pfx,
121                fork->logicalSize, fork->clumpSize, fork->totalBlocks);
122     for (i = 0; i < 8; i++) {
123         if (fork->extents[i].startBlock == 0)
124             break;
125         cli_dbgmsg("%s extent[%d] startBlock " STDu32 " blockCount " STDu32 "\n", pfx, i,
126                    fork->extents[i].startBlock, fork->extents[i].blockCount);
127     }
128 }
129 
130 /* Read and convert the HFS+ volume header */
hfsplus_volumeheader(cli_ctx * ctx,hfsPlusVolumeHeader ** header)131 static int hfsplus_volumeheader(cli_ctx *ctx, hfsPlusVolumeHeader **header)
132 {
133     hfsPlusVolumeHeader *volHeader;
134     const uint8_t *mPtr;
135 
136     if (!header) {
137         return CL_ENULLARG;
138     }
139 
140     /* Start with volume header, 512 bytes at offset 1024 */
141     if (ctx->fmap->len < 1536) {
142         cli_dbgmsg("hfsplus_volumeheader: too short for HFS+\n");
143         return CL_EFORMAT;
144     }
145     mPtr = fmap_need_off_once(ctx->fmap, 1024, 512);
146     if (!mPtr) {
147         cli_errmsg("hfsplus_volumeheader: cannot read header from map\n");
148         return CL_EMAP;
149     }
150 
151     volHeader = cli_malloc(sizeof(hfsPlusVolumeHeader));
152     if (!volHeader) {
153         cli_errmsg("hfsplus_volumeheader: header malloc failed\n");
154         return CL_EMEM;
155     }
156     *header = volHeader;
157     memcpy(volHeader, mPtr, 512);
158 
159     volHeader->signature = be16_to_host(volHeader->signature);
160     volHeader->version   = be16_to_host(volHeader->version);
161     if ((volHeader->signature == 0x482B) && (volHeader->version == 4)) {
162         cli_dbgmsg("hfsplus_volumeheader: HFS+ signature matched\n");
163     } else if ((volHeader->signature == 0x4858) && (volHeader->version == 5)) {
164         cli_dbgmsg("hfsplus_volumeheader: HFSX v5 signature matched\n");
165     } else {
166         cli_dbgmsg("hfsplus_volumeheader: no matching signature\n");
167         return CL_EFORMAT;
168     }
169     /* skip fields that will definitely be ignored */
170     volHeader->attributes  = be32_to_host(volHeader->attributes);
171     volHeader->fileCount   = be32_to_host(volHeader->fileCount);
172     volHeader->folderCount = be32_to_host(volHeader->folderCount);
173     volHeader->blockSize   = be32_to_host(volHeader->blockSize);
174     volHeader->totalBlocks = be32_to_host(volHeader->totalBlocks);
175 
176     cli_dbgmsg("HFS+ Header:\n");
177     cli_dbgmsg("Signature: %x\n", volHeader->signature);
178     cli_dbgmsg("Attributes: %x\n", volHeader->attributes);
179     cli_dbgmsg("File Count: " STDu32 "\n", volHeader->fileCount);
180     cli_dbgmsg("Folder Count: " STDu32 "\n", volHeader->folderCount);
181     cli_dbgmsg("Block Size: " STDu32 "\n", volHeader->blockSize);
182     cli_dbgmsg("Total Blocks: " STDu32 "\n", volHeader->totalBlocks);
183 
184     /* Block Size must be power of 2 between 512 and 1 MB */
185     if ((volHeader->blockSize < 512) || (volHeader->blockSize > (1 << 20))) {
186         cli_dbgmsg("hfsplus_volumeheader: Invalid blocksize\n");
187         return CL_EFORMAT;
188     }
189     if (volHeader->blockSize & (volHeader->blockSize - 1)) {
190         cli_dbgmsg("hfsplus_volumeheader: Invalid blocksize\n");
191         return CL_EFORMAT;
192     }
193 
194     forkdata_to_host(&(volHeader->allocationFile));
195     forkdata_to_host(&(volHeader->extentsFile));
196     forkdata_to_host(&(volHeader->catalogFile));
197     forkdata_to_host(&(volHeader->attributesFile));
198     forkdata_to_host(&(volHeader->startupFile));
199 
200     if (cli_debug_flag) {
201         forkdata_print("allocationFile", &(volHeader->allocationFile));
202         forkdata_print("extentsFile", &(volHeader->extentsFile));
203         forkdata_print("catalogFile", &(volHeader->catalogFile));
204         forkdata_print("attributesFile", &(volHeader->attributesFile));
205         forkdata_print("startupFile", &(volHeader->startupFile));
206     }
207 
208     return CL_CLEAN;
209 }
210 
211 /* Read and convert the header node */
hfsplus_readheader(cli_ctx * ctx,hfsPlusVolumeHeader * volHeader,hfsNodeDescriptor * nodeDesc,hfsHeaderRecord * headerRec,int headerType,const char * name)212 static int hfsplus_readheader(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsNodeDescriptor *nodeDesc,
213                               hfsHeaderRecord *headerRec, int headerType, const char *name)
214 {
215     const uint8_t *mPtr = NULL;
216     off_t offset;
217     uint32_t minSize, maxSize;
218 
219     /* From TN1150: Node Size must be power of 2 between 512 and 32768 */
220     /* Node Size for Catalog or Attributes must be at least 4096 */
221     maxSize = 32768; /* Doesn't seem to vary */
222     switch (headerType) {
223         case HFS_FILETREE_ALLOCATION:
224             offset  = volHeader->allocationFile.extents[0].startBlock * volHeader->blockSize;
225             minSize = 512;
226             break;
227         case HFS_FILETREE_EXTENTS:
228             offset  = volHeader->extentsFile.extents[0].startBlock * volHeader->blockSize;
229             minSize = 512;
230             break;
231         case HFS_FILETREE_CATALOG:
232             offset  = volHeader->catalogFile.extents[0].startBlock * volHeader->blockSize;
233             minSize = 4096;
234             break;
235         case HFS_FILETREE_ATTRIBUTES:
236             offset  = volHeader->attributesFile.extents[0].startBlock * volHeader->blockSize;
237             minSize = 4096;
238             break;
239         case HFS_FILETREE_STARTUP:
240             offset  = volHeader->startupFile.extents[0].startBlock * volHeader->blockSize;
241             minSize = 512;
242             break;
243         default:
244             cli_errmsg("hfsplus_readheader: %s: invalid headerType %d\n", name, headerType);
245             return CL_EARG;
246     }
247     mPtr = fmap_need_off_once(ctx->fmap, offset, volHeader->blockSize);
248     if (!mPtr) {
249         cli_dbgmsg("hfsplus_readheader: %s: headerNode is out-of-range\n", name);
250         return CL_EFORMAT;
251     }
252 
253     /* Node descriptor first */
254     memcpy(nodeDesc, mPtr, sizeof(hfsNodeDescriptor));
255     nodedescriptor_to_host(nodeDesc);
256     nodedescriptor_print(name, nodeDesc);
257     if (nodeDesc->kind != HFS_NODEKIND_HEADER) {
258         cli_dbgmsg("hfsplus_readheader: %s: headerNode not header kind\n", name);
259         return CL_EFORMAT;
260     }
261     if ((nodeDesc->bLink != 0) || (nodeDesc->height != 0) || (nodeDesc->numRecords != 3)) {
262         cli_dbgmsg("hfsplus_readheader: %s: Invalid headerNode\n", name);
263         return CL_EFORMAT;
264     }
265 
266     /* Then header record */
267     memcpy(headerRec, mPtr + sizeof(hfsNodeDescriptor), sizeof(hfsHeaderRecord));
268     headerrecord_to_host(headerRec);
269     headerrecord_print(name, headerRec);
270 
271     if ((headerRec->nodeSize < minSize) || (headerRec->nodeSize > maxSize)) {
272         cli_dbgmsg("hfsplus_readheader: %s: Invalid nodesize\n", name);
273         return CL_EFORMAT;
274     }
275     if (headerRec->nodeSize & (headerRec->nodeSize - 1)) {
276         cli_dbgmsg("hfsplus_readheader: %s: Invalid nodesize\n", name);
277         return CL_EFORMAT;
278     }
279     /* KeyLength must be between 6 and 516 for catalog */
280     if (headerType == HFS_FILETREE_CATALOG) {
281         if ((headerRec->maxKeyLength < 6) || (headerRec->maxKeyLength > 516)) {
282             cli_dbgmsg("hfsplus_readheader: %s: Invalid cat maxKeyLength\n", name);
283             return CL_EFORMAT;
284         }
285         if (headerRec->maxKeyLength > (headerRec->nodeSize / 2)) {
286             cli_dbgmsg("hfsplus_readheader: %s: Invalid cat maxKeyLength based on nodeSize\n", name);
287             return CL_EFORMAT;
288         }
289     } else if (headerType == HFS_FILETREE_EXTENTS) {
290         if (headerRec->maxKeyLength != 10) {
291             cli_dbgmsg("hfsplus_readheader: %s: Invalid ext maxKeyLength\n", name);
292             return CL_EFORMAT;
293         }
294     }
295 
296     /* hdr->treeDepth = rootnode->height */
297     return CL_CLEAN;
298 }
299 
300 /**
301  * @brief Read and dump a file for scanning.
302  *
303  * If the filename pointer is provided, the file name will be set and the
304  * resulting file will __NOT__ be scanned. The returned pointer must be freed
305  * by the caller. If the pointer is NULL, the file will be scanned and,
306  * depending on the --leave-temps value, deleted or not.
307  *
308  * @param ctx           The current scan context
309  * @param volHeader     Volume header
310  * @param extHeader     Extent overflow file header
311  * @param fork          Fork Data
312  * @param dirname       Temp directory name
313  * @param[out] filename (optional) temp file name
314  * @param orig_filename (optiopnal) Original filename
315  * @return cl_error_t
316  */
hfsplus_scanfile(cli_ctx * ctx,hfsPlusVolumeHeader * volHeader,hfsHeaderRecord * extHeader,hfsPlusForkData * fork,const char * dirname,char ** filename,char * orig_filename)317 static cl_error_t hfsplus_scanfile(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *extHeader,
318                                    hfsPlusForkData *fork, const char *dirname, char **filename, char *orig_filename)
319 {
320     hfsPlusExtentDescriptor *currExt;
321     const uint8_t *mPtr = NULL;
322     char *tmpname       = NULL;
323     int ofd;
324     cl_error_t ret = CL_CLEAN;
325     uint64_t targetSize;
326     uint32_t outputBlocks = 0;
327     uint8_t ext;
328 
329     UNUSEDPARAM(extHeader);
330 
331     /* bad record checks */
332     if (!fork || (fork->logicalSize == 0) || (fork->totalBlocks == 0)) {
333         cli_dbgmsg("hfsplus_scanfile: Empty file.\n");
334         return CL_CLEAN;
335     }
336 
337     /* check limits */
338     targetSize = fork->logicalSize;
339 #if SIZEOF_LONG < 8
340     if (targetSize > ULONG_MAX) {
341         cli_dbgmsg("hfsplus_scanfile: File too large for limit check.\n");
342         return CL_EFORMAT;
343     }
344 #endif
345     ret = cli_checklimits("hfsplus_scanfile", ctx, (unsigned long)targetSize, 0, 0);
346     if (ret != CL_CLEAN) {
347         return ret;
348     }
349 
350     /* open file */
351     ret = cli_gentempfd(dirname, &tmpname, &ofd);
352     if (ret != CL_CLEAN) {
353         cli_dbgmsg("hfsplus_scanfile: Cannot generate temporary file.\n");
354         return ret;
355     }
356     cli_dbgmsg("hfsplus_scanfile: Extracting to %s\n", tmpname);
357 
358     ext = 0;
359     /* Dump file, extent by extent */
360     do {
361         uint32_t currBlock, endBlock, outputSize = 0;
362         if (targetSize == 0) {
363             cli_dbgmsg("hfsplus_scanfile: output complete\n");
364             break;
365         }
366         if (outputBlocks >= fork->totalBlocks) {
367             cli_dbgmsg("hfsplus_scanfile: output all blocks, remaining size " STDu64 "\n", targetSize);
368             break;
369         }
370         /* Prepare extent */
371         if (ext < 8) {
372             currExt = &(fork->extents[ext]);
373             cli_dbgmsg("hfsplus_scanfile: extent %u\n", ext);
374         } else {
375             cli_dbgmsg("hfsplus_scanfile: need next extent from ExtentOverflow\n");
376             /* Not implemented yet */
377             ret = CL_EFORMAT;
378             break;
379         }
380         /* have extent, so validate and get block range */
381         if ((currExt->startBlock == 0) || (currExt->blockCount == 0)) {
382             cli_dbgmsg("hfsplus_scanfile: next extent empty, done\n");
383             break;
384         }
385         if ((currExt->startBlock & 0x10000000) && (currExt->blockCount & 0x10000000)) {
386             cli_dbgmsg("hfsplus_scanfile: next extent illegal!\n");
387             ret = CL_EFORMAT;
388             break;
389         }
390         currBlock = currExt->startBlock;
391         endBlock  = currExt->startBlock + currExt->blockCount - 1;
392         if ((currBlock > volHeader->totalBlocks) || (endBlock > volHeader->totalBlocks) || (currExt->blockCount > volHeader->totalBlocks)) {
393             cli_dbgmsg("hfsplus_scanfile: bad extent!\n");
394             ret = CL_EFORMAT;
395             break;
396         }
397         /* Write the blocks, walking the map */
398         while (currBlock <= endBlock) {
399             size_t to_write = MIN(targetSize, volHeader->blockSize);
400             size_t written;
401             off_t offset = currBlock * volHeader->blockSize;
402             /* move map to next block */
403             mPtr = fmap_need_off_once(ctx->fmap, offset, volHeader->blockSize);
404             if (!mPtr) {
405                 cli_errmsg("hfsplus_scanfile: map error\n");
406                 ret = CL_EMAP;
407                 break;
408             }
409             written = cli_writen(ofd, mPtr, to_write);
410             if (written != to_write) {
411                 cli_errmsg("hfsplus_scanfile: write error\n");
412                 ret = CL_EWRITE;
413                 break;
414             }
415             targetSize -= to_write;
416             outputSize += to_write;
417             currBlock++;
418             if (targetSize == 0) {
419                 cli_dbgmsg("hfsplus_scanfile: all data written\n");
420                 break;
421             }
422             if (outputBlocks >= fork->totalBlocks) {
423                 cli_dbgmsg("hfsplus_scanfile: output all blocks, remaining size " STDu64 "\n", targetSize);
424                 break;
425             }
426         }
427         /* Finished the extent, move to next */
428         ext++;
429     } while (ret == CL_CLEAN);
430 
431     /* if successful so far, scan the output */
432     if (filename) {
433         *filename = tmpname;
434     } else {
435         if (ret == CL_CLEAN) {
436             ret = cli_magic_scan_desc(ofd, tmpname, ctx, orig_filename);
437         }
438 
439         if (!ctx->engine->keeptmp) {
440             if (cli_unlink(tmpname)) {
441                 ret = CL_EUNLINK;
442             }
443         }
444         free(tmpname);
445     }
446 
447     if (ofd >= 0) {
448         close(ofd);
449     }
450 
451     return ret;
452 }
453 
454 /* Calculate true node limit for catalogFile */
hfsplus_validate_catalog(cli_ctx * ctx,hfsPlusVolumeHeader * volHeader,hfsHeaderRecord * catHeader)455 static int hfsplus_validate_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *catHeader)
456 {
457     hfsPlusForkData *catFork;
458 
459     UNUSEDPARAM(ctx);
460 
461     catFork = &(volHeader->catalogFile);
462     if (catFork->totalBlocks >= volHeader->totalBlocks) {
463         cli_dbgmsg("hfsplus_validate_catalog: catFork totalBlocks too large!\n");
464         return CL_EFORMAT;
465     }
466     if (catFork->logicalSize > (catFork->totalBlocks * volHeader->blockSize)) {
467         cli_dbgmsg("hfsplus_validate_catalog: catFork logicalSize too large!\n");
468         return CL_EFORMAT;
469     }
470     if (catFork->logicalSize < (catHeader->totalNodes * catHeader->nodeSize)) {
471         cli_dbgmsg("hfsplus_validate_catalog: too many nodes for catFile\n");
472         return CL_EFORMAT;
473     }
474 
475     return CL_CLEAN;
476 }
477 
478 /* Check if an attribute is present in the attribute map */
hfsplus_check_attribute(cli_ctx * ctx,hfsPlusVolumeHeader * volHeader,hfsHeaderRecord * attrHeader,uint32_t expectedCnid,const uint8_t name[],uint32_t nameLen,int * found,uint8_t record[],unsigned * recordSize)479 static cl_error_t hfsplus_check_attribute(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *attrHeader, uint32_t expectedCnid, const uint8_t name[], uint32_t nameLen, int *found, uint8_t record[], unsigned *recordSize)
480 {
481     uint16_t nodeSize, recordNum, topOfOffsets;
482     uint16_t recordStart, nextDist, nextStart;
483     uint8_t *nodeBuf = NULL;
484     uint32_t thisNode, nodeLimit, nodesScanned = 0;
485     cl_error_t ret = CL_SUCCESS;
486     int foundAttr  = 0;
487 
488     if (found) {
489         *found = 0;
490     }
491 
492     if (!attrHeader) {
493         return CL_EARG;
494     }
495 
496     nodeLimit = MIN(attrHeader->totalNodes, HFSPLUS_NODE_LIMIT);
497     thisNode  = attrHeader->firstLeafNode;
498     nodeSize  = attrHeader->nodeSize;
499 
500     /* Need to buffer current node, map will keep moving */
501     nodeBuf = cli_malloc(nodeSize);
502     if (!nodeBuf) {
503         cli_dbgmsg("hfsplus_check_attribute: failed to acquire node buffer, "
504                    "size " STDu32 "\n",
505                    nodeSize);
506         return CL_EMEM;
507     }
508 
509     /* Walk catalog leaf nodes, and scan contents of each */
510     /* Because we want to scan them all, the index nodes add no value */
511     while (ret == CL_CLEAN && !foundAttr) {
512         hfsNodeDescriptor nodeDesc;
513 
514         if (thisNode == 0) {
515             cli_dbgmsg("hfsplus_check_attribute: reached end of leaf nodes.\n");
516             break;
517         }
518         if (nodesScanned++ > nodeLimit) {
519             cli_dbgmsg("hfsplus_check_attribute: node scan limit reached.\n");
520             break;
521         }
522 
523         /* fetch node into buffer */
524         ret = hfsplus_fetch_node(ctx, volHeader, attrHeader, NULL, &(volHeader->attributesFile), thisNode, nodeBuf);
525         if (ret != CL_CLEAN) {
526             cli_dbgmsg("hfsplus_check_attribute: node fetch failed.\n");
527             break;
528         }
529         memcpy(&nodeDesc, nodeBuf, 14);
530 
531         /* convert and validate node */
532         nodedescriptor_to_host(&nodeDesc);
533         nodedescriptor_print("leaf attribute node", &nodeDesc);
534         if ((nodeDesc.kind != HFS_NODEKIND_LEAF) || (nodeDesc.height != 1)) {
535             cli_dbgmsg("hfsplus_check_attribute: invalid leaf node!\n");
536             ret = CL_EFORMAT;
537             break;
538         }
539         if ((nodeSize / 4) < nodeDesc.numRecords) {
540             cli_dbgmsg("hfsplus_check_attribute: too many leaf records for one node!\n");
541             ret = CL_EFORMAT;
542             break;
543         }
544 
545         /* Walk this node's records and scan */
546         recordStart = 14; /* 1st record can be after end of node descriptor */
547         /* offsets take 1 u16 per at the end of the node, along with an empty space offset */
548         topOfOffsets = nodeSize - (nodeDesc.numRecords * 2) - 2;
549         for (recordNum = 0; recordNum < nodeDesc.numRecords; recordNum++) {
550             uint16_t keylen;
551             hfsPlusAttributeKey attrKey;
552             hfsPlusAttributeRecord attrRec;
553 
554             /* Locate next record */
555             nextDist  = nodeSize - (recordNum * 2) - 2;
556             nextStart = nodeBuf[nextDist] * 0x100 + nodeBuf[nextDist + 1];
557             /* Check record location */
558             if ((nextStart > topOfOffsets - 1) || (nextStart < recordStart)) {
559                 cli_dbgmsg("hfsplus_check_attribute: bad record location %x for %u!\n", nextStart, recordNum);
560                 ret = CL_EFORMAT;
561                 break;
562             }
563             recordStart = nextStart;
564             if (recordStart + sizeof(attrKey) >= topOfOffsets) {
565                 cli_dbgmsg("hfsplus_check_attribute: Not enough data for an attribute key at location %x for %u!\n",
566                            nextStart, recordNum);
567                 ret = CL_EFORMAT;
568                 break;
569             }
570 
571             memcpy(&attrKey, &nodeBuf[recordStart], sizeof(attrKey));
572             attrKey.keyLength  = be16_to_host(attrKey.keyLength);
573             attrKey.cnid       = be32_to_host(attrKey.cnid);
574             attrKey.startBlock = be32_to_host(attrKey.startBlock);
575             attrKey.nameLength = be16_to_host(attrKey.nameLength);
576 
577             /* Get record key length */
578             keylen = nodeBuf[recordStart] * 0x100 + nodeBuf[recordStart + 1];
579             keylen += keylen % 2; /* pad 1 byte if required to make 2-byte align */
580             /* Validate keylen */
581             if (recordStart + attrKey.keyLength + 4 >= topOfOffsets) {
582                 cli_dbgmsg("hfsplus_check_attribute: key too long for location %x for %u!\n",
583                            nextStart, recordNum);
584                 ret = CL_EFORMAT;
585                 break;
586             }
587 
588             if (recordStart + sizeof(hfsPlusAttributeKey) + attrKey.nameLength >= topOfOffsets) {
589                 cli_dbgmsg("hfsplus_check_attribute: Attribute name is longer than expected: %u\n", attrKey.nameLength);
590                 ret = CL_EFORMAT;
591                 break;
592             }
593 
594             if (attrKey.cnid == expectedCnid && attrKey.nameLength * 2 == nameLen && memcmp(&nodeBuf[recordStart + 14], name, nameLen) == 0) {
595                 memcpy(&attrRec, &(nodeBuf[recordStart + sizeof(hfsPlusAttributeKey) + attrKey.nameLength * 2]), sizeof(attrRec));
596                 attrRec.recordType    = be32_to_host(attrRec.recordType);
597                 attrRec.attributeSize = be32_to_host(attrRec.attributeSize);
598 
599                 if (attrRec.recordType != HFSPLUS_RECTYPE_INLINE_DATA_ATTRIBUTE) {
600                     cli_dbgmsg("hfsplus_check_attribute: Unexpected attribute record type 0x%x\n", attrRec.recordType);
601                     continue;
602                 }
603 
604                 if (found) {
605                     *found = 1;
606                 }
607 
608                 if (attrRec.attributeSize > *recordSize) {
609                     ret = CL_EMAXSIZE;
610                     break;
611                 }
612 
613                 memcpy(record, &(nodeBuf[recordStart + sizeof(hfsPlusAttributeKey) + attrKey.nameLength * 2 + sizeof(attrRec)]), attrRec.attributeSize);
614                 *recordSize = attrRec.attributeSize;
615 
616                 ret       = CL_SUCCESS;
617                 foundAttr = 1;
618                 break;
619             }
620         }
621     }
622 
623     if (nodeBuf != NULL) {
624         free(nodeBuf);
625         nodeBuf = NULL;
626     }
627     return ret;
628 }
629 
630 /* Fetch a node's contents into the buffer */
hfsplus_fetch_node(cli_ctx * ctx,hfsPlusVolumeHeader * volHeader,hfsHeaderRecord * catHeader,hfsHeaderRecord * extHeader,hfsPlusForkData * catFork,uint32_t node,uint8_t * buff)631 static int hfsplus_fetch_node(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *catHeader,
632                               hfsHeaderRecord *extHeader, hfsPlusForkData *catFork, uint32_t node, uint8_t *buff)
633 {
634     int foundBlock = 0;
635     uint64_t catalogOffset;
636     uint32_t startBlock, startOffset;
637     uint32_t endBlock, endSize;
638     uint32_t curBlock;
639     uint32_t extentNum = 0, realFileBlock;
640     uint32_t readSize;
641     size_t fileOffset = 0;
642     uint32_t searchBlock;
643     uint32_t buffOffset = 0;
644 
645     UNUSEDPARAM(extHeader);
646 
647     /* Make sure node is in range */
648     if (node >= catHeader->totalNodes) {
649         cli_dbgmsg("hfsplus_fetch_node: invalid node number " STDu32 "\n", node);
650         return CL_EFORMAT;
651     }
652 
653     /* Need one block */
654     /* First, calculate the node's offset within the catalog */
655     catalogOffset = (uint64_t)node * catHeader->nodeSize;
656     /* Determine which block of the catalog we need */
657     startBlock  = (uint32_t)(catalogOffset / volHeader->blockSize);
658     startOffset = (uint32_t)(catalogOffset % volHeader->blockSize);
659     endBlock    = (uint32_t)((catalogOffset + catHeader->nodeSize - 1) / volHeader->blockSize);
660     endSize     = (uint32_t)(((catalogOffset + catHeader->nodeSize - 1) % volHeader->blockSize) + 1);
661     cli_dbgmsg("hfsplus_fetch_node: need catalog block " STDu32 "\n", startBlock);
662     if (startBlock >= catFork->totalBlocks || endBlock >= catFork->totalBlocks) {
663         cli_dbgmsg("hfsplus_fetch_node: block number invalid!\n");
664         return CL_EFORMAT;
665     }
666 
667     for (curBlock = startBlock; curBlock <= endBlock; ++curBlock) {
668 
669         foundBlock  = 0;
670         searchBlock = curBlock;
671         /* Find which extent has that block */
672         for (extentNum = 0; extentNum < 8; extentNum++) {
673             hfsPlusExtentDescriptor *currExt = &(catFork->extents[extentNum]);
674 
675             /* Beware empty extent */
676             if ((currExt->startBlock == 0) || (currExt->blockCount == 0)) {
677                 cli_dbgmsg("hfsplus_fetch_node: extent " STDu32 " empty!\n", extentNum);
678                 return CL_EFORMAT;
679             }
680             /* Beware too long extent */
681             if ((currExt->startBlock & 0x10000000) && (currExt->blockCount & 0x10000000)) {
682                 cli_dbgmsg("hfsplus_fetch_node: extent " STDu32 " illegal!\n", extentNum);
683                 return CL_EFORMAT;
684             }
685             /* Check if block found in current extent */
686             if (searchBlock < currExt->blockCount) {
687                 cli_dbgmsg("hfsplus_fetch_node: found block in extent " STDu32 "\n", extentNum);
688                 realFileBlock = currExt->startBlock + searchBlock;
689                 foundBlock    = 1;
690                 break;
691             } else {
692                 cli_dbgmsg("hfsplus_fetch_node: not in extent " STDu32 "\n", extentNum);
693                 searchBlock -= currExt->blockCount;
694             }
695         }
696 
697         if (foundBlock == 0) {
698             cli_dbgmsg("hfsplus_fetch_node: not in first 8 extents\n");
699             cli_dbgmsg("hfsplus_fetch_node: finding this node requires extent overflow support\n");
700             return CL_EFORMAT;
701         }
702 
703         /* Block found */
704         if (realFileBlock >= volHeader->totalBlocks) {
705             cli_dbgmsg("hfsplus_fetch_node: block past end of volume\n");
706             return CL_EFORMAT;
707         }
708         fileOffset = realFileBlock * volHeader->blockSize;
709         readSize   = volHeader->blockSize;
710 
711         if (curBlock == startBlock) {
712             fileOffset += startOffset;
713         } else if (curBlock == endBlock) {
714             readSize = endSize;
715         }
716 
717         if (fmap_readn(ctx->fmap, buff + buffOffset, fileOffset, readSize) != readSize) {
718             cli_dbgmsg("hfsplus_fetch_node: not all bytes read\n");
719             return CL_EFORMAT;
720         }
721         buffOffset += readSize;
722     }
723 
724     return CL_CLEAN;
725 }
726 
hfsplus_seek_to_cmpf_resource(int fd,size_t * size)727 static cl_error_t hfsplus_seek_to_cmpf_resource(int fd, size_t *size)
728 {
729     hfsPlusResourceHeader resourceHeader;
730     hfsPlusResourceMap resourceMap;
731     hfsPlusResourceType resourceType;
732     hfsPlusReferenceEntry entry;
733     int i;
734     int cmpfInstanceIdx = -1;
735     int curInstanceIdx  = 0;
736     size_t dataOffset;
737     uint32_t dataLength;
738     cl_error_t ret = CL_SUCCESS;
739 
740     if (!size) {
741         ret = CL_ENULLARG;
742         goto done;
743     }
744 
745     if (cli_readn(fd, &resourceHeader, sizeof(resourceHeader)) != sizeof(resourceHeader)) {
746         cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to read resource header from temporary file\n");
747         ret = CL_EREAD;
748         goto done;
749     }
750 
751     resourceHeader.dataOffset = be32_to_host(resourceHeader.dataOffset);
752     resourceHeader.mapOffset  = be32_to_host(resourceHeader.mapOffset);
753     resourceHeader.dataLength = be32_to_host(resourceHeader.dataLength);
754     resourceHeader.mapLength  = be32_to_host(resourceHeader.mapLength);
755 
756     //TODO: Need to get offset of cmpf resource in data stream
757 
758     if (lseek(fd, resourceHeader.mapOffset, SEEK_SET) != resourceHeader.mapOffset) {
759         cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to seek to map in temporary file\n");
760         ret = CL_ESEEK;
761         goto done;
762     }
763 
764     if (cli_readn(fd, &resourceMap, sizeof(resourceMap)) != sizeof(resourceMap)) {
765         cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to read resource map from temporary file\n");
766         ret = CL_EREAD;
767         goto done;
768     }
769 
770     resourceMap.resourceForkAttributes = be16_to_host(resourceMap.resourceForkAttributes);
771     resourceMap.typeListOffset         = be16_to_host(resourceMap.typeListOffset);
772     resourceMap.nameListOffset         = be16_to_host(resourceMap.nameListOffset);
773     resourceMap.typeCount              = be16_to_host(resourceMap.typeCount);
774 
775     for (i = 0; i < resourceMap.typeCount + 1; ++i) {
776         if (cli_readn(fd, &resourceType, sizeof(resourceType)) != sizeof(resourceType)) {
777             cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to read resource type from temporary file\n");
778             ret = CL_EREAD;
779             goto done;
780         }
781         resourceType.instanceCount       = be16_to_host(resourceType.instanceCount);
782         resourceType.referenceListOffset = be16_to_host(resourceType.referenceListOffset);
783 
784         if (memcmp(resourceType.type, "cmpf", 4) == 0) {
785             if (cmpfInstanceIdx != -1) {
786                 cli_dbgmsg("hfsplus_seek_to_cmpf_resource: There are several cmpf resource types in the file\n");
787                 ret = CL_EFORMAT;
788                 goto done;
789             }
790 
791             cmpfInstanceIdx = curInstanceIdx;
792             cli_dbgmsg("Found compressed resource type!\n");
793         }
794 
795         curInstanceIdx += resourceType.instanceCount + 1;
796     }
797 
798     if (cmpfInstanceIdx < 0) {
799         cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Didn't find cmpf resource type\n");
800         ret = CL_EFORMAT;
801         goto done;
802     }
803 
804     if (lseek(fd, cmpfInstanceIdx * sizeof(hfsPlusReferenceEntry), SEEK_CUR) < 0) {
805         cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to seek to instance index\n");
806         ret = CL_ESEEK;
807         goto done;
808     }
809 
810     if (cli_readn(fd, &entry, sizeof(entry)) != sizeof(entry)) {
811         cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to read resource entry from temporary file\n");
812         ret = CL_EREAD;
813         goto done;
814     }
815 
816     dataOffset = (entry.resourceDataOffset[0] << 16) | (entry.resourceDataOffset[1] << 8) | entry.resourceDataOffset[2];
817 
818     if (lseek(fd, resourceHeader.dataOffset + dataOffset, SEEK_SET) < 0) {
819         cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to seek to data offset\n");
820         ret = CL_ESEEK;
821         goto done;
822     }
823 
824     if (cli_readn(fd, &dataLength, sizeof(dataLength)) != sizeof(dataLength)) {
825         cli_dbgmsg("hfsplus_seek_to_cmpf_resource: Failed to read data length from temporary file\n");
826         ret = CL_EREAD;
827         goto done;
828     }
829 
830     *size = be32_to_host(dataLength);
831 done:
832     return ret;
833 }
834 
hfsplus_read_block_table(int fd,uint32_t * numBlocks,hfsPlusResourceBlockTable ** table)835 static int hfsplus_read_block_table(int fd, uint32_t *numBlocks, hfsPlusResourceBlockTable **table)
836 {
837     uint32_t i;
838 
839     if (!table || !numBlocks) {
840         return CL_ENULLARG;
841     }
842 
843     if (cli_readn(fd, numBlocks, sizeof(*numBlocks)) != sizeof(*numBlocks)) {
844         cli_dbgmsg("hfsplus_read_block_table: Failed to read block count\n");
845         return CL_EREAD;
846     }
847 
848     *numBlocks = le32_to_host(*numBlocks); //Let's do a little little endian just for fun, shall we?
849     *table     = cli_malloc(sizeof(hfsPlusResourceBlockTable) * *numBlocks);
850     if (!*table) {
851         cli_dbgmsg("hfsplus_read_block_table: Failed to allocate memory for block table\n");
852         return CL_EMEM;
853     }
854 
855     if (cli_readn(fd, *table, *numBlocks * sizeof(hfsPlusResourceBlockTable)) != *numBlocks * sizeof(hfsPlusResourceBlockTable)) {
856         cli_dbgmsg("hfsplus_read_block_table: Failed to read table\n");
857         free(*table);
858         return CL_EREAD;
859     }
860 
861     for (i = 0; i < *numBlocks; ++i) {
862         (*table)[i].offset = le32_to_host((*table)[i].offset);
863         (*table)[i].length = le32_to_host((*table)[i].length);
864     }
865 
866     return CL_SUCCESS;
867 }
868 
869 /* Given the catalog and other details, scan all the volume contents */
hfsplus_walk_catalog(cli_ctx * ctx,hfsPlusVolumeHeader * volHeader,hfsHeaderRecord * catHeader,hfsHeaderRecord * extHeader,hfsHeaderRecord * attrHeader,const char * dirname)870 static cl_error_t hfsplus_walk_catalog(cli_ctx *ctx, hfsPlusVolumeHeader *volHeader, hfsHeaderRecord *catHeader,
871                                        hfsHeaderRecord *extHeader, hfsHeaderRecord *attrHeader, const char *dirname)
872 {
873     cl_error_t ret          = CL_SUCCESS;
874     unsigned int has_alerts = 0;
875     uint32_t thisNode, nodeLimit, nodesScanned = 0;
876     uint16_t nodeSize, recordNum, topOfOffsets;
877     uint16_t recordStart, nextDist, nextStart;
878     uint8_t *nodeBuf                = NULL;
879     const uint8_t COMPRESSED_ATTR[] = {0, 'c', 0, 'o', 0, 'm', 0, '.', 0, 'a', 0, 'p', 0, 'p', 0, 'l', 0, 'e', 0, '.', 0, 'd', 0, 'e', 0, 'c', 0, 'm', 0, 'p', 0, 'f', 0, 's'};
880     char *tmpname                   = NULL;
881     uint8_t *uncompressed           = NULL;
882     int ofd                         = -1;
883     char *name_utf8                 = NULL;
884     size_t name_utf8_size           = 0;
885 
886     nodeLimit = MIN(catHeader->totalNodes, HFSPLUS_NODE_LIMIT);
887     thisNode  = catHeader->firstLeafNode;
888     nodeSize  = catHeader->nodeSize;
889 
890     /* Need to buffer current node, map will keep moving */
891     nodeBuf = cli_malloc(nodeSize);
892     if (!nodeBuf) {
893         cli_dbgmsg("hfsplus_walk_catalog: failed to acquire node buffer, "
894                    "size " STDu32 "\n",
895                    nodeSize);
896         return CL_EMEM;
897     }
898 
899     /* Walk catalog leaf nodes, and scan contents of each */
900     /* Because we want to scan them all, the index nodes add no value */
901     while (ret == CL_SUCCESS) {
902         hfsNodeDescriptor nodeDesc;
903 
904         if (thisNode == 0) {
905             cli_dbgmsg("hfsplus_walk_catalog: reached end of leaf nodes.\n");
906             break;
907         }
908         if (nodesScanned++ > nodeLimit) {
909             cli_dbgmsg("hfsplus_walk_catalog: node scan limit reached.\n");
910             break;
911         }
912 
913         /* fetch node into buffer */
914         ret = hfsplus_fetch_node(ctx, volHeader, catHeader, extHeader, &(volHeader->catalogFile), thisNode, nodeBuf);
915         if (ret != CL_SUCCESS) {
916             cli_dbgmsg("hfsplus_walk_catalog: node fetch failed.\n");
917             break;
918         }
919         memcpy(&nodeDesc, nodeBuf, 14);
920 
921         /* convert and validate node */
922         nodedescriptor_to_host(&nodeDesc);
923         nodedescriptor_print("leaf node", &nodeDesc);
924         if ((nodeDesc.kind != HFS_NODEKIND_LEAF) || (nodeDesc.height != 1)) {
925             cli_dbgmsg("hfsplus_walk_catalog: invalid leaf node!\n");
926             ret = CL_EFORMAT;
927             break;
928         }
929         if ((nodeSize / 4) < nodeDesc.numRecords) {
930             cli_dbgmsg("hfsplus_walk_catalog: too many leaf records for one node!\n");
931             ret = CL_EFORMAT;
932             break;
933         }
934 
935         /* Walk this node's records and scan */
936         recordStart = 14; /* 1st record can be after end of node descriptor */
937         /* offsets take 1 u16 per at the end of the node, along with an empty space offset */
938         topOfOffsets = nodeSize - (nodeDesc.numRecords * 2) - 2;
939         for (recordNum = 0; recordNum < nodeDesc.numRecords; recordNum++) {
940             uint16_t keylen;
941             int16_t rectype;
942             hfsPlusCatalogFile fileRec;
943             name_utf8 = NULL;
944 
945             /* Locate next record */
946             nextDist  = nodeSize - (recordNum * 2) - 2;
947             nextStart = nodeBuf[nextDist] * 0x100 + nodeBuf[nextDist + 1];
948             /* Check record location */
949             if ((nextStart > topOfOffsets - 1) || (nextStart < recordStart)) {
950                 cli_dbgmsg("hfsplus_walk_catalog: bad record location %x for %u!\n", nextStart, recordNum);
951                 ret = CL_EFORMAT;
952                 break;
953             }
954             recordStart = nextStart;
955             /* Get record key length */
956             keylen = nodeBuf[recordStart] * 0x100 + nodeBuf[recordStart + 1];
957             keylen += keylen % 2; /* pad 1 byte if required to make 2-byte align */
958             /* Validate keylen */
959             if (recordStart + keylen + 4 >= topOfOffsets) {
960                 cli_dbgmsg("hfsplus_walk_catalog: key too long for location %x for %u!\n",
961                            nextStart, recordNum);
962                 ret = CL_EFORMAT;
963                 break;
964             }
965             /* Collect filename  */
966             if (keylen >= 6) {
967                 uint16_t name_length = (nodeBuf[recordStart + 2 + 4] << 8) | nodeBuf[recordStart + 2 + 4 + 1];
968                 char *index          = (char *)&nodeBuf[recordStart + 2 + 4 + 2];
969                 if ((name_length > 0) && (name_length * 2 <= keylen - 2 - 4)) {
970                     /*
971                      * The name is contained in nodeBuf[recordStart + 2 + 4 + 2 : recordStart + 2 + 4 + 2 + name_length * 2] encoded as UTF-16BE.
972                      */
973                     if (CL_SUCCESS != cli_codepage_to_utf8((char *)index, name_length * 2, CODEPAGE_UTF16_BE, &name_utf8, &name_utf8_size)) {
974                         cli_errmsg("hfsplus_walk_catalog: failed to convert UTF-16BE to UTF-8\n");
975                         name_utf8 = NULL;
976                     }
977                     cli_dbgmsg("hfsplus_walk_catalog: Extracting file %s\n", name_utf8);
978                 }
979             }
980             /* Copy type (after key, which is after keylength field) */
981             memcpy(&rectype, &(nodeBuf[recordStart + keylen + 2]), 2);
982             rectype = be16_to_host(rectype);
983             cli_dbgmsg("hfsplus_walk_catalog: record %u nextStart %x keylen %u type %d\n",
984                        recordNum, nextStart, keylen, rectype);
985             /* Non-file records are not needed */
986             if (rectype != HFSPLUS_RECTYPE_FILE) {
987                 if (NULL != name_utf8) {
988                     free(name_utf8);
989                     name_utf8 = NULL;
990                 }
991                 continue;
992             }
993             /* Check file record location */
994             if (recordStart + keylen + 2 + sizeof(hfsPlusCatalogFile) >= topOfOffsets) {
995                 cli_dbgmsg("hfsplus_walk_catalog: not enough bytes for file record!\n");
996                 ret = CL_EFORMAT;
997                 break;
998             }
999             memcpy(&fileRec, &(nodeBuf[recordStart + keylen + 2]), sizeof(hfsPlusCatalogFile));
1000 
1001             /* Only scan files */
1002             fileRec.fileID               = be32_to_host(fileRec.fileID);
1003             fileRec.permissions.fileMode = be16_to_host(fileRec.permissions.fileMode);
1004             if ((fileRec.permissions.fileMode & HFS_MODE_TYPEMASK) == HFS_MODE_FILE) {
1005                 int compressed = 0;
1006                 uint8_t attribute[8192];
1007                 unsigned attributeSize = sizeof(attribute);
1008 
1009                 /* Convert forks and scan */
1010                 forkdata_to_host(&(fileRec.dataFork));
1011                 forkdata_print("data fork:", &(fileRec.dataFork));
1012                 forkdata_to_host(&(fileRec.resourceFork));
1013                 forkdata_print("resource fork:", &(fileRec.resourceFork));
1014 
1015                 if (hfsplus_check_attribute(ctx, volHeader, attrHeader, fileRec.fileID, COMPRESSED_ATTR, sizeof(COMPRESSED_ATTR), &compressed, attribute, &attributeSize) != CL_SUCCESS) {
1016                     cli_dbgmsg("hfsplus_walk_catalog: Failed to check compressed attribute, assuming no compression\n");
1017                 }
1018 
1019                 if (compressed) {
1020                     hfsPlusCompressionHeader header;
1021                     cli_dbgmsg("hfsplus_walk_catalog: File is compressed\n");
1022 
1023                     if (attributeSize < sizeof(header)) {
1024                         cli_warnmsg("hfsplus_walk_catalog: Error: Compression attribute size is less than the compression header\n");
1025                         ret = CL_EFORMAT;
1026                         break;
1027                     }
1028 
1029                     memcpy(&header, attribute, sizeof(header));
1030                     //In the sample I had (12de189078b1e260d669a2b325d688a3a39cb5b9697e00fb1777e1ecc64f4e91), this was stored in little endian.
1031                     //According to the doc, it should be in big endian.
1032 
1033                     if (header.magic == DECMPFS_HEADER_MAGIC_LE) {
1034                         header.magic           = cbswap32(header.magic);
1035                         header.compressionType = cbswap32(header.compressionType);
1036                         header.fileSize        = cbswap64(header.fileSize);
1037                     }
1038 
1039                     if (header.magic != DECMPFS_HEADER_MAGIC) {
1040                         cli_dbgmsg("hfsplus_walk_catalog: Unexpected magic value for compression header: 0x%08x\n", header.magic);
1041                         ret = CL_EFORMAT;
1042                         break;
1043                     }
1044 
1045                     /* open file */
1046                     ret = cli_gentempfd(dirname, &tmpname, &ofd);
1047                     if (ret != CL_SUCCESS) {
1048                         cli_dbgmsg("hfsplus_walk_catalog: Cannot generate temporary file.\n");
1049                         break;
1050                     }
1051 
1052                     cli_dbgmsg("Found compressed file type %u size %" PRIu64 "\n", header.compressionType, header.fileSize);
1053                     switch (header.compressionType) {
1054                         case HFSPLUS_COMPRESSION_INLINE: {
1055                             size_t written;
1056                             if (attributeSize < sizeof(header) + 1) {
1057                                 cli_dbgmsg("hfsplus_walk_catalog: Unexpected end of stream, no compression flag\n");
1058                                 ret = CL_EFORMAT;
1059                                 break;
1060                             }
1061 
1062                             if ((attribute[sizeof(header)] & 0x0f) == 0x0f) { //Data is stored uncompressed
1063                                 if (attributeSize - sizeof(header) - 1 != header.fileSize) {
1064                                     cli_dbgmsg("hfsplus_walk_catalog: Expected file size different from size of data available\n");
1065                                     free(tmpname);
1066                                     ret = CL_EFORMAT;
1067                                     break;
1068                                 }
1069 
1070                                 written = cli_writen(ofd, &attribute[sizeof(header) + 1], header.fileSize);
1071                             } else {
1072                                 z_stream stream;
1073                                 int z_ret;
1074 
1075                                 if (header.fileSize > 65536) {
1076                                     cli_dbgmsg("hfsplus_walk_catalog: Uncompressed file seems too big, something is probably wrong\n");
1077                                     ret = CL_EFORMAT;
1078                                     break;
1079                                 }
1080 
1081                                 uncompressed = malloc(header.fileSize);
1082                                 if (!uncompressed) {
1083                                     cli_dbgmsg("hfsplus_walk_catalog: Failed to allocate memory for the uncompressed file contents\n");
1084                                     ret = CL_EMEM;
1085                                     break;
1086                                 }
1087 
1088                                 stream.zalloc    = Z_NULL;
1089                                 stream.zfree     = Z_NULL;
1090                                 stream.opaque    = Z_NULL;
1091                                 stream.avail_in  = attributeSize - sizeof(header);
1092                                 stream.next_in   = &attribute[sizeof(header)];
1093                                 stream.avail_out = header.fileSize;
1094                                 stream.next_out  = uncompressed;
1095 
1096                                 z_ret = inflateInit2(&stream, 15 /* maximum windowBits size */);
1097                                 if (z_ret != Z_OK) {
1098                                     switch (z_ret) {
1099                                         case Z_MEM_ERROR:
1100                                             cli_dbgmsg("hfsplus_walk_catalog: inflateInit2: out of memory!\n");
1101                                             break;
1102                                         case Z_VERSION_ERROR:
1103                                             cli_dbgmsg("hfsplus_walk_catalog: inflateinit2: zlib version error!\n");
1104                                             break;
1105                                         case Z_STREAM_ERROR:
1106                                             cli_dbgmsg("hfsplus_walk_catalog: inflateinit2: zlib stream error!\n");
1107                                             break;
1108                                         default:
1109                                             cli_dbgmsg("hfsplus_walk_catalog: inflateInit2: unknown error %d\n", ret);
1110                                             break;
1111                                     }
1112 
1113                                     ret = CL_EFORMAT;
1114                                     break;
1115                                 }
1116 
1117                                 z_ret = inflate(&stream, Z_NO_FLUSH);
1118                                 if (z_ret != Z_OK && z_ret != Z_STREAM_END) {
1119                                     cli_dbgmsg("hfsplus_walk_catalog: inflateSync failed to extract compressed stream (%d)\n", ret);
1120                                     ret = CL_EFORMAT;
1121                                     break;
1122                                 }
1123 
1124                                 z_ret = inflateEnd(&stream);
1125                                 if (z_ret == Z_STREAM_ERROR) {
1126                                     cli_dbgmsg("hfsplus_walk_catalog: inflateEnd failed (%d)\n", ret);
1127                                 }
1128 
1129                                 written = cli_writen(ofd, uncompressed, header.fileSize);
1130                                 free(uncompressed);
1131                                 uncompressed = NULL;
1132                             }
1133                             if (written != header.fileSize) {
1134                                 cli_errmsg("hfsplus_walk_catalog: write error\n");
1135                                 ret = CL_EWRITE;
1136                                 break;
1137                             }
1138 
1139                             ret = CL_SUCCESS;
1140                             break;
1141                         }
1142                         case HFSPLUS_COMPRESSION_RESOURCE: {
1143                             //FIXME: This is hackish. We're assuming (which is
1144                             //correct according to the spec) that there's only
1145                             //one resource, and that it's the compressed data.
1146                             //Ideally we should check that there is only one
1147                             //resource, that its type is correct, and that its
1148                             //name is cmpf.
1149                             char *resourceFile = NULL;
1150                             int ifd            = -1;
1151                             size_t written     = 0;
1152 
1153                             //4096 is an approximative value, there should be
1154                             //at least 16 (resource header) + 30 (map header) +
1155                             //4096 bytes (data that doesn't fit in an
1156                             //attribute)
1157                             if (fileRec.resourceFork.logicalSize < 4096) {
1158                                 cli_dbgmsg("hfsplus_walk_catalog: Error: Expected more data in the compressed resource fork\n");
1159                                 ret = CL_EFORMAT;
1160                                 break;
1161                             }
1162 
1163                             if ((ret = hfsplus_scanfile(ctx, volHeader, extHeader, &(fileRec.resourceFork), dirname, &resourceFile, name_utf8)) != CL_SUCCESS) {
1164                                 cli_dbgmsg("hfsplus_walk_catalog: Error while extracting the resource fork\n");
1165                                 if (resourceFile) {
1166                                     free(resourceFile);
1167                                 }
1168                                 break;
1169                             }
1170 
1171                             if (NULL == resourceFile) {
1172                                 cli_dbgmsg("hfsplus_walk_catalog: Error: hfsplus_scanfile returned no resource file\n");
1173                                 ret = CL_EFORMAT;
1174                                 break;
1175                             }
1176 
1177                             if ((ifd = safe_open(resourceFile, O_RDONLY | O_BINARY)) == -1) {
1178                                 cli_dbgmsg("hfsplus_walk_catalog: Failed to open temporary file %s\n", resourceFile);
1179                                 ret = CL_EOPEN;
1180                             } else {
1181                                 size_t resourceLen;
1182                                 if ((ret = hfsplus_seek_to_cmpf_resource(ifd, &resourceLen)) != CL_SUCCESS) {
1183                                     cli_dbgmsg("hfsplus_walk_catalog: Failed to find cmpf resource in resource fork\n");
1184                                 } else {
1185                                     hfsPlusResourceBlockTable *table = NULL;
1186                                     uint32_t numBlocks;
1187                                     uint32_t dataOffset = lseek(ifd, 0, SEEK_CUR);
1188 
1189                                     if ((ret = hfsplus_read_block_table(ifd, &numBlocks, &table)) != CL_SUCCESS) {
1190                                         cli_dbgmsg("hfsplus_walk_catalog: Failed to read block table\n");
1191                                     } else {
1192                                         uint8_t block[4096];
1193                                         uint8_t uncompressed[4096];
1194                                         unsigned curBlock;
1195 
1196                                         for (curBlock = 0; ret == CL_SUCCESS && curBlock < numBlocks; ++curBlock) {
1197                                             off_t blockOffset = dataOffset + table[curBlock].offset;
1198                                             size_t curOffset;
1199                                             size_t readLen;
1200                                             z_stream stream;
1201                                             int streamBeginning  = 1;
1202                                             int streamCompressed = 0;
1203 
1204                                             cli_dbgmsg("Handling block %u of %" PRIu32 " at offset %" PRIi64 " (size %u)\n", curBlock, numBlocks, (int64_t)blockOffset, table[curBlock].length);
1205 
1206                                             if (lseek(ifd, blockOffset, SEEK_SET) != blockOffset) {
1207                                                 cli_dbgmsg("hfsplus_walk_catalog: Failed to seek to beginning of block\n");
1208                                                 ret = CL_ESEEK;
1209                                                 break;
1210                                             }
1211 
1212                                             for (curOffset = 0; curOffset < table[curBlock].length;) {
1213                                                 readLen = table[curBlock].length - curOffset;
1214                                                 if (readLen > sizeof(block)) {
1215                                                     readLen = sizeof(block);
1216                                                 }
1217 
1218                                                 if (cli_readn(ifd, block, readLen) != readLen) {
1219                                                     cli_dbgmsg("hfsplus_walk_catalog: Failed to read block from temporary file\n");
1220                                                     ret = CL_EREAD;
1221                                                     break;
1222                                                 }
1223 
1224                                                 if (streamBeginning) {
1225                                                     streamCompressed = (block[0] & 0x0f) != 0x0f;
1226 
1227                                                     if (streamCompressed) {
1228                                                         cli_dbgmsg("Current stream is compressed\n");
1229                                                         stream.zalloc    = Z_NULL;
1230                                                         stream.zfree     = Z_NULL;
1231                                                         stream.opaque    = Z_NULL;
1232                                                         stream.avail_in  = readLen;
1233                                                         stream.next_in   = block;
1234                                                         stream.avail_out = sizeof(uncompressed);
1235                                                         stream.next_out  = uncompressed;
1236 
1237                                                         if ((ret = inflateInit2(&stream, 15)) != Z_OK) {
1238                                                             cli_dbgmsg("hfsplus_walk_catalog: inflateInit2 failed (%d)\n", ret);
1239                                                             ret = CL_EFORMAT;
1240                                                             break;
1241                                                         }
1242                                                     }
1243                                                 }
1244 
1245                                                 if (streamCompressed) {
1246                                                     stream.avail_in  = readLen;
1247                                                     stream.next_in   = block;
1248                                                     stream.avail_out = sizeof(uncompressed);
1249                                                     stream.next_out  = uncompressed;
1250 
1251                                                     while (stream.avail_in > 0) {
1252                                                         ret = inflate(&stream, Z_NO_FLUSH);
1253                                                         if (ret != Z_OK && ret != Z_STREAM_END) {
1254                                                             cli_dbgmsg("hfsplus_walk_catalog: Failed to extract (%d)\n", ret);
1255                                                             ret = CL_EFORMAT;
1256                                                             break;
1257                                                         }
1258                                                         ret = CL_SUCCESS;
1259                                                         if (cli_writen(ofd, &uncompressed, sizeof(uncompressed) - stream.avail_out) != sizeof(uncompressed) - stream.avail_out) {
1260                                                             cli_dbgmsg("hfsplus_walk_catalog: Failed to write to temporary file\n");
1261                                                             ret = CL_EWRITE;
1262                                                             break;
1263                                                         }
1264                                                         written += sizeof(uncompressed) - stream.avail_out;
1265                                                         stream.avail_out = sizeof(uncompressed);
1266                                                         stream.next_out  = uncompressed;
1267                                                     }
1268                                                 } else {
1269                                                     if (cli_writen(ofd, &block[streamBeginning ? 1 : 0], readLen - (streamBeginning ? 1 : 0)) != readLen - (streamBeginning ? 1 : 0)) {
1270                                                         cli_dbgmsg("hfsplus_walk_catalog: Failed to write to temporary file\n");
1271                                                         ret = CL_EWRITE;
1272                                                         break;
1273                                                     }
1274                                                     written += readLen - (streamBeginning ? 1 : 0);
1275                                                 }
1276 
1277                                                 curOffset += readLen;
1278                                                 streamBeginning = 0;
1279                                             }
1280 
1281                                             if (ret == CL_SUCCESS) {
1282                                                 if ((ret = inflateEnd(&stream)) != Z_OK) {
1283                                                     cli_dbgmsg("hfsplus_walk_catalog: inflateEnd failed (%d)\n", ret);
1284                                                     ret = CL_EFORMAT;
1285                                                 } else {
1286                                                     ret = CL_SUCCESS;
1287                                                 }
1288                                             }
1289                                         }
1290 
1291                                         cli_dbgmsg("hfsplus_walk_catalog: Extracted compressed file from resource fork to %s (size %zu)\n", tmpname, written);
1292 
1293                                         if (table) {
1294                                             free(table);
1295                                             table = NULL;
1296                                         }
1297                                     }
1298                                 }
1299                             }
1300 
1301                             if (!ctx->engine->keeptmp) {
1302                                 if (cli_unlink(resourceFile)) {
1303                                     ret = CL_EUNLINK;
1304                                 }
1305                             }
1306                             free(resourceFile);
1307                             resourceFile = NULL;
1308 
1309                             cli_dbgmsg("hfsplus_walk_catalog: Resource compression not implemented\n");
1310                             break;
1311                         }
1312                         default:
1313                             cli_dbgmsg("hfsplus_walk_catalog: Unknown compression type %u\n", header.compressionType);
1314                             break;
1315                     }
1316 
1317                     if (tmpname) {
1318                         if (ret == CL_SUCCESS) {
1319                             cli_dbgmsg("hfsplus_walk_catalog: Extracted to %s\n", tmpname);
1320 
1321                             /* if successful so far, scan the output */
1322                             ret = cli_magic_scan_desc(ofd, tmpname, ctx, name_utf8);
1323 
1324                             if (ret == CL_VIRUS) {
1325                                 has_alerts = 1;
1326                                 if (SCAN_ALLMATCHES) {
1327                                     /* Continue scanning in SCAN_ALLMATCHES mode */
1328                                     cli_dbgmsg("hfsplus_walk_catalog: Compressed file alert, continuing");
1329                                     ret = CL_SUCCESS;
1330                                 }
1331                             }
1332                         }
1333 
1334                         if (!ctx->engine->keeptmp) {
1335                             if (cli_unlink(tmpname)) {
1336                                 ret = CL_EUNLINK;
1337                             }
1338                         }
1339 
1340                         free(tmpname);
1341                         tmpname = NULL;
1342                     }
1343                     if (ofd >= 0) {
1344                         close(ofd);
1345                         ofd = -1;
1346                     }
1347 
1348                     if (ret != CL_SUCCESS) {
1349                         break;
1350                     }
1351                 }
1352 
1353                 if (fileRec.dataFork.logicalSize) {
1354                     ret = hfsplus_scanfile(ctx, volHeader, extHeader, &(fileRec.dataFork), dirname, NULL, name_utf8);
1355                 }
1356                 /* Check return code */
1357                 if (ret == CL_VIRUS) {
1358                     has_alerts = 1;
1359                     if (SCAN_ALLMATCHES) {
1360                         /* Continue scanning in SCAN_ALLMATCHES mode */
1361                         cli_dbgmsg("hfsplus_walk_catalog: data fork alert, continuing");
1362                         ret = CL_CLEAN;
1363                     }
1364                 }
1365                 if (ret != CL_SUCCESS) {
1366                     cli_dbgmsg("hfsplus_walk_catalog: data fork retcode %d\n", ret);
1367                     break;
1368                 }
1369                 /* Scan resource fork */
1370                 if (fileRec.resourceFork.logicalSize) {
1371                     ret = hfsplus_scanfile(ctx, volHeader, extHeader, &(fileRec.resourceFork), dirname, NULL, name_utf8);
1372                 }
1373                 /* Check return code */
1374                 if (ret == CL_VIRUS) {
1375                     has_alerts = 1;
1376                     if (SCAN_ALLMATCHES) {
1377                         /* Continue scanning in SCAN_ALLMATCHES mode */
1378                         cli_dbgmsg("hfsplus_walk_catalog: resource fork alert, continuing");
1379                         ret = CL_CLEAN;
1380                     }
1381                 }
1382                 if (ret != CL_SUCCESS) {
1383                     cli_dbgmsg("hfsplus_walk_catalog: resource fork retcode %d", ret);
1384                     break;
1385                 }
1386             } else {
1387                 cli_dbgmsg("hfsplus_walk_catalog: record mode %o is not File\n", fileRec.permissions.fileMode);
1388             }
1389 
1390             if (NULL != name_utf8) {
1391                 free(name_utf8);
1392                 name_utf8 = NULL;
1393             }
1394         }
1395         /* if return code, exit loop, message already logged */
1396         if (ret != CL_SUCCESS) {
1397             break;
1398         }
1399 
1400         /* After that, proceed to next node */
1401         if (thisNode == nodeDesc.fLink) {
1402             /* Future heuristic */
1403             cli_warnmsg("hfsplus_walk_catalog: simple cycle detected!\n");
1404             ret = CL_EFORMAT;
1405             break;
1406         } else {
1407             thisNode = nodeDesc.fLink;
1408         }
1409     }
1410 
1411     free(nodeBuf);
1412     if (NULL != name_utf8) {
1413         free(name_utf8);
1414     }
1415 
1416     if (has_alerts) {
1417         ret = CL_VIRUS;
1418     }
1419     return ret;
1420 }
1421 
1422 /* Base scan function for scanning HFS+ or HFSX partitions */
cli_scanhfsplus(cli_ctx * ctx)1423 cl_error_t cli_scanhfsplus(cli_ctx *ctx)
1424 {
1425     char *targetdir                = NULL;
1426     cl_error_t ret                 = CL_SUCCESS;
1427     hfsPlusVolumeHeader *volHeader = NULL;
1428     hfsNodeDescriptor catFileDesc;
1429     hfsHeaderRecord catFileHeader;
1430     hfsNodeDescriptor extentFileDesc;
1431     hfsHeaderRecord extentFileHeader;
1432     hfsNodeDescriptor attributesFileDesc;
1433     hfsHeaderRecord attributesFileHeader;
1434     int hasAttributesFileHeader = 0;
1435 
1436     if (!ctx || !ctx->fmap) {
1437         cli_errmsg("cli_scanhfsplus: Invalid context\n");
1438         return CL_ENULLARG;
1439     }
1440 
1441     cli_dbgmsg("cli_scanhfsplus: scanning partition content\n");
1442     /* first, read volume header contents */
1443     ret = hfsplus_volumeheader(ctx, &volHeader);
1444     if (ret != CL_SUCCESS) {
1445         goto freeHeader;
1446     }
1447 
1448     /*
1449 cli_dbgmsg("sizeof(hfsUniStr255) is %lu\n", sizeof(hfsUniStr255));
1450 cli_dbgmsg("sizeof(hfsPlusBSDInfo) is %lu\n", sizeof(hfsPlusBSDInfo));
1451 cli_dbgmsg("sizeof(hfsPlusExtentDescriptor) is %lu\n", sizeof(hfsPlusExtentDescriptor));
1452 cli_dbgmsg("sizeof(hfsPlusExtentRecord) is %lu\n", sizeof(hfsPlusExtentRecord));
1453 cli_dbgmsg("sizeof(hfsPlusForkData) is %lu\n", sizeof(hfsPlusForkData));
1454 cli_dbgmsg("sizeof(hfsPlusVolumeHeader) is %lu\n", sizeof(hfsPlusVolumeHeader));
1455 cli_dbgmsg("sizeof(hfsNodeDescriptor) is %lu\n", sizeof(hfsNodeDescriptor));
1456  */
1457 
1458     /* Get root node (header node) of extent overflow file */
1459     ret = hfsplus_readheader(ctx, volHeader, &extentFileDesc, &extentFileHeader, HFS_FILETREE_EXTENTS, "extentFile");
1460     if (ret != CL_SUCCESS) {
1461         goto freeHeader;
1462     }
1463     /* Get root node (header node) of catalog file */
1464     ret = hfsplus_readheader(ctx, volHeader, &catFileDesc, &catFileHeader, HFS_FILETREE_CATALOG, "catalogFile");
1465     if (ret != CL_SUCCESS) {
1466         goto freeHeader;
1467     }
1468 
1469     /* Get root node (header node) of attributes file */
1470     ret = hfsplus_readheader(ctx, volHeader, &attributesFileDesc, &attributesFileHeader, HFS_FILETREE_ATTRIBUTES, "attributesFile");
1471     if (ret == CL_SUCCESS) {
1472         hasAttributesFileHeader = 1;
1473     } else {
1474         hasAttributesFileHeader = 0;
1475         ret                     = CL_SUCCESS;
1476     }
1477 
1478     /* Create temp folder for contents */
1479     if (!(targetdir = cli_gentemp_with_prefix(ctx->sub_tmpdir, "hfsplus-tmp"))) {
1480         cli_errmsg("cli_scanhfsplus: cli_gentemp failed\n");
1481         ret = CL_ETMPDIR;
1482         goto freeHeader;
1483     }
1484     if (mkdir(targetdir, 0700)) {
1485         cli_errmsg("cli_scanhfsplus: Cannot create temporary directory %s\n", targetdir);
1486         ret = CL_ETMPDIR;
1487         goto freeDirname;
1488     }
1489     cli_dbgmsg("cli_scanhfsplus: Extracting into %s\n", targetdir);
1490 
1491     /* Can build and scan catalog file if we want ***
1492     ret = hfsplus_scanfile(ctx, volHeader, &extentFileHeader, &(volHeader->catalogFile), targetdir);
1493      */
1494     if (ret == CL_SUCCESS) {
1495         ret = hfsplus_validate_catalog(ctx, volHeader, &catFileHeader);
1496         if (ret == CL_SUCCESS) {
1497             cli_dbgmsg("cli_scanhfsplus: validation successful\n");
1498         } else {
1499             cli_dbgmsg("cli_scanhfsplus: validation returned %d : %s\n", ret, cl_strerror(ret));
1500         }
1501     }
1502 
1503     /* Walk through catalog to identify files to scan */
1504     if (ret == CL_SUCCESS) {
1505         ret = hfsplus_walk_catalog(ctx, volHeader, &catFileHeader, &extentFileHeader, hasAttributesFileHeader ? &attributesFileHeader : NULL, targetdir);
1506         cli_dbgmsg("cli_scanhfsplus: walk catalog finished\n");
1507     }
1508 
1509     /* Clean up extracted content, if needed */
1510     if (!ctx->engine->keeptmp) {
1511         cli_rmdirs(targetdir);
1512     }
1513 
1514 freeDirname:
1515     free(targetdir);
1516 freeHeader:
1517     free(volHeader);
1518     return ret;
1519 }
1520