Coverage Report

Created: 2025-07-01 06:59

/src/sleuthkit/tsk/fs/decmpfs.cpp
Line
Count
Source (jump to first uncovered line)
1
/* This file contains decompression routines used by APFS and HFS
2
 * It has one method derived from public domain ZLIB and others
3
 * that are TSK-specific.
4
 *
5
 * It would probably be cleaner to separate these into two files.
6
 */
7
8
#include <cstdint>
9
#include <memory>
10
#include <new>
11
12
void error_detected(uint32_t errnum, const char* errstr, ...);
13
void error_returned(const char* errstr, ...);
14
15
#include "tsk/libtsk.h"
16
#include "tsk_fs_i.h"
17
#include "decmpfs.h"
18
19
#ifdef HAVE_LIBZ
20
#include <zlib.h>
21
#endif
22
23
#include "lzvn.h"
24
#include "tsk_hfs.h"
25
26
#ifdef HAVE_LIBZ
27
28
/***************** ZLIB stuff *******************************/
29
30
/* The zlib_inflate method is adapted from the public domain
31
 * zpipe.c (part of zlib) at http://zlib.net/zpipe.c
32
 *
33
 * zpipe.c: example of proper use of zlib's inflate() and deflate()
34
 * Not copyrighted -- provided to the public domain
35
 * Version 1.4  11 December 2005  Mark Adler */
36
37
0
#define CHUNK 16384
38
39
/*
40
 * Invokes the zlib library to inflate (uncompress) data.
41
 *
42
 * Returns and error code.  Places the uncompressed data in a buffer supplied by the caller.  Also
43
 * returns the uncompressed length, and the number of compressed bytes consumed.
44
 *
45
 * Will stop short of the end of compressed data, if a natural end of a compression unit is reached.  Using
46
 * bytesConsumed, the caller can then advance the source pointer, and re-invoke the function.  This will then
47
 * inflate the next following compression unit in the data stream.
48
 *
49
 * @param source - buffer of compressed data
50
 * @param sourceLen  - length of the compressed data.
51
 * @param dest  -- buffer to  hold the uncompressed results
52
 * @param destLen -- length of the dest buffer
53
 * @param uncompressedLength  -- return of the length of the uncompressed data found.
54
 * @param bytesConsumed  -- return of the number of input bytes of compressed data used.
55
 * @return 0 on success, a negative number on error
56
 */
57
int
58
zlib_inflate(char *source, uint64_t sourceLen, char *dest, uint64_t destLen, uint64_t * uncompressedLength, unsigned long *bytesConsumed)       // this is unsigned long because that's what zlib uses.
59
0
{
60
61
0
    int ret;
62
0
    unsigned have;
63
0
    z_stream strm;
64
0
    unsigned char in[CHUNK];
65
0
    unsigned char out[CHUNK];
66
67
    // Some vars to help with copying bytes into "in"
68
0
    char *srcPtr = source;
69
0
    char *destPtr = dest;
70
0
    uint64_t srcAvail = sourceLen;      //uint64_t
71
0
    uint64_t amtToCopy;
72
0
    uint64_t copiedSoFar = 0;
73
74
    /* allocate inflate state */
75
0
    strm.zalloc = Z_NULL;
76
0
    strm.zfree = Z_NULL;
77
0
    strm.opaque = Z_NULL;
78
0
    strm.avail_in = 0;
79
0
    strm.next_in = Z_NULL;
80
0
    ret = inflateInit(&strm);
81
0
    if (ret != Z_OK) {
82
0
        error_detected(TSK_ERR_FS_READ,
83
0
            "zlib_inflate: failed to initialize inflation engine (%d)",
84
0
            ret);
85
0
        return ret;
86
0
    }
87
88
    /* decompress until deflate stream ends or end of file */
89
0
    do {
90
91
        // Copy up to CHUNK bytes into "in" from source, advancing the pointer, and
92
        // setting strm.avail_in equal to the number of bytes copied.
93
0
        if (srcAvail >= CHUNK) {
94
0
            amtToCopy = CHUNK;
95
0
            srcAvail -= CHUNK;
96
0
        }
97
0
        else {
98
0
            amtToCopy = srcAvail;
99
0
            srcAvail = 0;
100
0
        }
101
        // wipe out any previous value, copy in the bytes, advance the pointer, record number of bytes.
102
0
        memset(in, 0, CHUNK);
103
0
        if (amtToCopy > SIZE_MAX || amtToCopy > UINT_MAX) {
104
0
            error_detected(TSK_ERR_FS_READ,
105
0
                "zlib_inflate: amtToCopy in one chunk is too large");
106
0
            return -100;
107
0
        }
108
0
        memcpy(in, srcPtr, (size_t) amtToCopy); // cast OK because of above test
109
0
        srcPtr += amtToCopy;
110
0
        strm.avail_in = (uInt) amtToCopy;       // cast OK because of above test
111
112
0
        if (strm.avail_in == 0)
113
0
            break;
114
0
        strm.next_in = in;
115
116
        /* run inflate() on input until output buffer not full */
117
0
        do {
118
0
            strm.avail_out = CHUNK;
119
0
            strm.next_out = out;
120
0
            ret = inflate(&strm, Z_NO_FLUSH);
121
0
            if (ret == Z_NEED_DICT)
122
0
                ret = Z_DATA_ERROR;     // we don't have a custom dict
123
0
            if (ret < 0 && ret != Z_BUF_ERROR) { // Z_BUF_ERROR is not fatal
124
0
                error_detected(TSK_ERR_FS_READ,
125
0
                    " zlib_inflate: zlib returned error %d (%s)", ret,
126
0
                    strm.msg);
127
0
                (void) inflateEnd(&strm);
128
0
                return ret;
129
0
            }
130
131
0
            have = CHUNK - strm.avail_out;
132
            // Is there enough space in dest to copy the current chunk?
133
0
            if (copiedSoFar + have > destLen) {
134
                // There is not enough space, so better return an error
135
0
                error_detected(TSK_ERR_FS_READ,
136
0
                    " zlib_inflate: not enough space in inflation destination\n");
137
0
                (void) inflateEnd(&strm);
138
0
                return -200;
139
0
            }
140
141
            // Copy "have" bytes from out to destPtr, advance destPtr
142
0
            memcpy(destPtr, out, have);
143
0
            destPtr += have;
144
0
            copiedSoFar += have;
145
146
0
        } while (strm.avail_out == 0 && ret != Z_STREAM_END);
147
148
149
        /* done when inflate() says it's done */
150
0
    } while (ret != Z_STREAM_END);
151
152
0
    if (ret == Z_STREAM_END)
153
0
        *uncompressedLength = copiedSoFar;
154
155
0
    *bytesConsumed = strm.total_in;
156
    /* clean up and return */
157
0
    (void) inflateEnd(&strm);
158
0
    return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
159
0
}
160
161
#endif
162
163
164
165
/********************* TSK STUFF **********************/
166
167
/*
168
 * The Sleuth Kit
169
 *
170
 * Brian Carrier [carrier <at> sleuthkit [dot] org]
171
 * Copyright (c) 2019-2020 Brian Carrier.  All Rights reserved
172
 * Copyright (c) 2018-2019 BlackBag Technologies.  All Rights reserved
173
 *
174
 * This software is distributed under the Common Public License 1.0
175
 */
176
177
typedef struct {
178
    uint32_t offset;
179
    uint32_t length;
180
} CMP_OFFSET_ENTRY;
181
182
/**
183
 * \internal
184
 * Reads the ZLIB compression block table from the attribute.
185
 *
186
 * @param rAtttr the attribute to read
187
 * @param tableSizeOut size of block table
188
 * @param tableOffsetOut the offset of the block table in the resource fork
189
 * @return 1 on success, 0 on error
190
 */
191
std::unique_ptr<CMP_OFFSET_ENTRY[]>
192
decmpfs_read_zlib_block_table(
193
  const TSK_FS_ATTR *rAttr,
194
  uint32_t* tableSizeOut,
195
  uint32_t* tableOffsetOut)
196
0
{
197
0
    ssize_t attrReadResult;
198
0
    hfs_resource_fork_header rfHeader;
199
0
    uint32_t dataOffset;
200
0
    uint32_t offsetTableOffset;
201
0
    char fourBytes[4];          // Size of the offset table, little endian
202
0
    uint32_t tableSize;         // Size of the offset table
203
0
    size_t indx;
204
205
    // Read the resource fork header
206
0
    attrReadResult = tsk_fs_attr_read(rAttr, 0, (char *) &rfHeader,
207
0
        sizeof(hfs_resource_fork_header), TSK_FS_FILE_READ_FLAG_NONE);
208
0
    if (attrReadResult != sizeof(hfs_resource_fork_header)) {
209
0
        error_returned
210
0
            (" %s: trying to read the resource fork header", __func__);
211
0
        return nullptr;
212
0
    }
213
214
    // Begin to parse the resource fork. For now, we just need the data offset.
215
0
    dataOffset = tsk_getu32(TSK_BIG_ENDIAN, rfHeader.dataOffset);
216
217
    // The resource's data begins with an offset table, which defines blocks
218
    // of (optionally) zlib-compressed data (so that the OS can do file seeks
219
    // efficiently; each uncompressed block is 64KB).
220
0
    offsetTableOffset = dataOffset + 4;
221
222
    // read 4 bytes, the number of table entries, little endian
223
0
    attrReadResult =
224
0
        tsk_fs_attr_read(rAttr, offsetTableOffset, fourBytes, 4,
225
0
        TSK_FS_FILE_READ_FLAG_NONE);
226
0
    if (attrReadResult != 4) {
227
0
        error_returned
228
0
            (" %s: trying to read the offset table size, "
229
0
            "return value of %u should have been 4", __func__, attrReadResult);
230
0
        return nullptr;
231
0
    }
232
0
    tableSize = tsk_getu32(TSK_LIT_ENDIAN, fourBytes);
233
234
0
    if (tableSize <= 0) {
235
0
        error_returned
236
0
           (" %s: table size is zero", __func__);
237
0
        return nullptr;
238
0
    }
239
240
    // Each table entry is 8 bytes long
241
0
    std::unique_ptr<char[]> offsetTableData{new(std::nothrow) char[tableSize * 8]};
242
0
    if (!offsetTableData) {
243
0
        error_returned
244
0
            (" %s: space for the offset table raw data", __func__);
245
0
        return nullptr;
246
0
    }
247
248
0
    std::unique_ptr<CMP_OFFSET_ENTRY[]> offsetTable{new(std::nothrow) CMP_OFFSET_ENTRY[tableSize]};
249
0
    if (!offsetTable) {
250
0
        error_returned
251
0
            (" %s: space for the offset table", __func__);
252
0
        return nullptr;
253
0
    }
254
255
0
    attrReadResult = tsk_fs_attr_read(rAttr, offsetTableOffset + 4,
256
0
        offsetTableData.get(), tableSize * 8, TSK_FS_FILE_READ_FLAG_NONE);
257
0
    if (attrReadResult != (ssize_t) tableSize * 8) {
258
0
        error_returned
259
0
            (" %s: reading in the compression offset table, "
260
0
            "return value %u should have been %u", __func__, attrReadResult,
261
0
            tableSize * 8);
262
0
        return nullptr;
263
0
    }
264
265
0
    for (indx = 0; indx < tableSize; ++indx) {
266
0
        offsetTable[indx].offset =
267
0
            tsk_getu32(TSK_LIT_ENDIAN, offsetTableData.get() + indx * 8);
268
0
        offsetTable[indx].length =
269
0
            tsk_getu32(TSK_LIT_ENDIAN, offsetTableData.get() + indx * 8 + 4);
270
0
    }
271
272
0
    *tableSizeOut = tableSize;
273
0
    *tableOffsetOut = offsetTableOffset;
274
0
    return offsetTable;
275
0
}
276
277
/**
278
 * \internal
279
 * Reads the LZVN compression block table from the attribute.
280
 *
281
 * @param rAtttr the attribute to read
282
 * @param offsetTableOut block table
283
 * @param tableSizeOut size of block table
284
 * @param tableOffsetOut the offset of the block table in the resource fork
285
 * @return 1 on success, 0 on error
286
 */
287
std::unique_ptr<CMP_OFFSET_ENTRY[]>
288
decmpfs_read_lzvn_block_table(
289
  const TSK_FS_ATTR *rAttr,
290
  uint32_t* tableSizeOut,
291
  uint32_t* tableOffsetOut)
292
0
{
293
    // The offset table is a sequence of 4-byte offsets of compressed
294
    // blocks. The first 4 bytes is thus the offset of the first block,
295
    // but also 4 times the number of entries in the table.
296
0
    char fourBytes[4];
297
0
    ssize_t attrReadResult = tsk_fs_attr_read(rAttr, 0, fourBytes, 4,
298
0
                                      TSK_FS_FILE_READ_FLAG_NONE);
299
0
    if (attrReadResult != 4) {
300
0
        error_returned
301
0
            (" %s: trying to read the offset table size, "
302
0
            "return value of %u should have been 4", __func__, attrReadResult);
303
0
        return nullptr;
304
0
    }
305
306
0
    const uint32_t tableDataSize = tsk_getu32(TSK_LIT_ENDIAN, fourBytes);
307
308
0
    if (tableDataSize <= 0) {
309
0
        error_returned
310
0
           (" %s: table size is zero", __func__);
311
0
        return nullptr;
312
0
    }
313
314
0
    std::unique_ptr<char[]> offsetTableData(new(std::nothrow) char[tableDataSize]);
315
0
    if (!offsetTableData) {
316
0
        error_returned
317
0
            (" %s: space for the offset table raw data", __func__);
318
0
        return nullptr;
319
0
    }
320
321
    // Size of the offset table
322
    // table entries are 4 bytes, last entry is end of data
323
0
    const uint32_t tableSize = tableDataSize / 4 - 1;
324
325
0
    std::unique_ptr<CMP_OFFSET_ENTRY[]> offsetTable(new(std::nothrow) CMP_OFFSET_ENTRY[tableSize]);
326
0
    if (!offsetTable) {
327
0
        error_returned
328
0
            (" %s: space for the offset table", __func__);
329
0
        return nullptr;
330
0
    }
331
332
0
    attrReadResult = tsk_fs_attr_read(rAttr, 0,
333
0
        offsetTableData.get(), tableDataSize, TSK_FS_FILE_READ_FLAG_NONE);
334
0
    if (attrReadResult != (ssize_t) tableDataSize) {
335
0
        error_returned
336
0
            (" %s: reading in the compression offset table, "
337
0
            "return value %u should have been %u", __func__, attrReadResult,
338
0
            tableDataSize);
339
0
        return nullptr;
340
0
    }
341
342
0
    uint32_t a = tableDataSize;
343
0
    uint32_t b;
344
0
    size_t i;
345
346
0
    for (i = 0; i < tableSize; ++i) {
347
0
        b = tsk_getu32(TSK_LIT_ENDIAN, offsetTableData.get() + 4*(i+1));
348
0
        offsetTable[i].offset = a;
349
0
        offsetTable[i].length = b - a;
350
0
        a = b;
351
0
    }
352
353
0
    *tableSizeOut = tableSize;
354
0
    *tableOffsetOut = 0;
355
0
    return offsetTable;
356
0
}
357
358
/**
359
 * \internal
360
 * "Decompress" a block which was stored uncompressed.
361
 *
362
 * @param rawBuf the compressed data
363
 * @param len length of the compressed data
364
 * @param uncBuf the decompressed data
365
 * @param uncLen length of the decompressed data
366
 * @return 1 on success, 0 on error
367
 */
368
0
static int decmpfs_decompress_noncompressed_block(char* rawBuf, uint32_t len, char* uncBuf, uint64_t* uncLen) {
369
    // actually an uncompressed block of data; just copy
370
0
    if (tsk_verbose)
371
0
        tsk_fprintf(stderr,
372
0
           "%s: Copying an uncompressed compression unit\n", __func__);
373
374
0
    if ((len - 1) > COMPRESSION_UNIT_SIZE) {
375
0
        error_detected(TSK_ERR_FS_READ,
376
0
            "%s: uncompressed block length %u is longer "
377
0
            "than compression unit size %u", __func__, len - 1,
378
0
            COMPRESSION_UNIT_SIZE);
379
0
        return 0;
380
0
    }
381
0
    memcpy(uncBuf, rawBuf + 1, len - 1);
382
0
    *uncLen = len - 1;
383
0
    return 1;
384
0
}
385
386
387
#ifdef HAVE_LIBZ
388
/**
389
 * \internal
390
 * Decompress a block which was stored with ZLIB.
391
 *
392
 * @param rawBuf the compressed data
393
 * @param len length of the compressed data
394
 * @param uncBuf the decompressed data
395
 * @param uncLen length of the decompressed data
396
 * @return 1 on success, 0 on error
397
 */
398
static int decmpfs_decompress_zlib_block(char* rawBuf, uint32_t len, char* uncBuf, uint64_t* uncLen)
399
0
{
400
    // see if this block is compressed
401
0
    if (len > 0 && (rawBuf[0] & 0x0F) != 0x0F) {
402
        // Uncompress the chunk of data
403
0
        if (tsk_verbose)
404
0
            tsk_fprintf(stderr,
405
0
                        "%s: Inflating the compression unit\n", __func__);
406
407
0
        unsigned long bytesConsumed;
408
0
        int infResult = zlib_inflate(rawBuf, (uint64_t) len,
409
0
            uncBuf, (uint64_t) COMPRESSION_UNIT_SIZE,
410
0
            uncLen, &bytesConsumed);
411
0
        if (infResult != 0) {
412
0
            error_returned
413
0
                  (" %s: zlib inflation (uncompression) failed",
414
0
                  __func__, infResult);
415
0
            return 0;
416
0
        }
417
418
0
        if (bytesConsumed != len) {
419
0
            error_detected(TSK_ERR_FS_READ,
420
0
                " %s, decompressor did not consume the whole compressed data",
421
0
                __func__);
422
0
            return 0;
423
0
        }
424
425
0
        return 1;
426
0
    }
427
0
    else {
428
        // actually an uncompressed block of data; just copy
429
0
        return decmpfs_decompress_noncompressed_block(rawBuf, len, uncBuf, uncLen);
430
0
    }
431
0
}
432
#endif
433
434
/**
435
 * \internal
436
 * Decompress a block which was stored with LZVN.
437
 *
438
 * @param rawBuf the compressed data
439
 * @param len length of the compressed data
440
 * @param uncBuf the decompressed data
441
 * @param uncLen length of the decompressed data
442
 * @return 1 on success, 0 on error
443
 */
444
static int decmpfs_decompress_lzvn_block(char* rawBuf, uint32_t len, char* uncBuf, uint64_t* uncLen)
445
0
{
446
    // see if this block is compressed
447
0
    if (len > 0 && rawBuf[0] != 0x06) {
448
0
        *uncLen = lzvn_decode_buffer(uncBuf, COMPRESSION_UNIT_SIZE, rawBuf, len);
449
0
        return 1;  // apparently this can't fail
450
0
    }
451
0
    else {
452
        // actually an uncompressed block of data; just copy
453
0
        return decmpfs_decompress_noncompressed_block(rawBuf, len, uncBuf, uncLen);
454
0
    }
455
0
}
456
457
/**
458
 * \internal
459
 * Decompress a block.
460
 *
461
 * @param rAttr the attribute to read
462
 * @param rawBuf the compressed data
463
 * @param uncBuf the decompressed data
464
 * @param offsetTable table of compressed block offsets
465
 * @param offsetTableSize size of table of compressed block offsets
466
 * @param offsetTableOffset offset of table of compressed block offsets
467
 * @param indx index of block to read
468
 * @param decompress_block pointer to decompression function
469
 * @return decompressed size on success, -1 on error
470
 */
471
static ssize_t read_and_decompress_block(
472
  const TSK_FS_ATTR* rAttr,
473
  char* rawBuf,
474
  char* uncBuf,
475
  const CMP_OFFSET_ENTRY* offsetTable,
476
  [[maybe_unused]] uint32_t offsetTableSize,
477
  uint32_t offsetTableOffset,
478
  size_t indx,
479
  int (*decompress_block)(char* rawBuf,
480
                          uint32_t len,
481
                          char* uncBuf,
482
                          uint64_t* uncLen)
483
)
484
0
{
485
    // @@@ BC: Looks like we should have bounds checks that indx < offsetTableSize, but we should confirm
486
0
    ssize_t attrReadResult;
487
0
    uint32_t offset = offsetTableOffset + offsetTable[indx].offset;
488
0
    uint32_t len = offsetTable[indx].length;
489
0
    uint64_t uncLen;
490
491
0
    if (tsk_verbose)
492
0
        tsk_fprintf(stderr,
493
0
            "%s: Reading compression unit %d, length %d\n",
494
0
            __func__, indx, len);
495
496
    /* Github #383 referenced that if len is 0, then the below code causes
497
     * problems. Added this check, but I don't have data to verify this on.
498
     * it looks like it should at least not crash, but it isn't clear if it
499
     * will also do the right thing and if should actually break here
500
     * instead. */
501
0
    if (len == 0) {
502
0
        return 0;
503
0
    }
504
505
0
    if (len > COMPRESSION_UNIT_SIZE + 1) {
506
0
      error_detected(TSK_ERR_FS_READ,
507
0
          "%s: block size is too large: %u", __func__, len);
508
0
      return -1;
509
0
    }
510
511
    // Read in the block of compressed data
512
0
    attrReadResult = tsk_fs_attr_read(rAttr, offset,
513
0
        rawBuf, len, TSK_FS_FILE_READ_FLAG_NONE);
514
0
    if (attrReadResult != (ssize_t) len) {
515
0
        char msg[] =
516
0
            "%s%s: reading in the compression offset table, "
517
0
            "return value %u should have been %u";
518
519
0
        if (attrReadResult < 0 ) {
520
0
            error_returned(msg, " ", __func__, attrReadResult, len);
521
0
        }
522
0
        else {
523
0
            error_detected(TSK_ERR_FS_READ, "", __func__, attrReadResult, len);
524
0
        }
525
0
        return -1;
526
0
    }
527
528
0
    if (!decompress_block(rawBuf, len, uncBuf, &uncLen)) {
529
0
        return -1;
530
0
    }
531
/*
532
    // If size is a multiple of COMPRESSION_UNIT_SIZE,
533
    // expected uncompressed length is COMPRESSION_UNIT_SIZE
534
    const uint32_t expUncLen = indx == offsetTableSize - 1 ?
535
        ((rAttr->fs_file->meta->size - 1) % COMPRESSION_UNIT_SIZE) + 1 :
536
        COMPRESSION_UNIT_SIZE;
537
538
    if (uncLen != expUncLen) {
539
        error_detected(TSK_ERR_FS_READ,
540
            "%s: compressed block decompressed to %u bytes, "
541
            "should have been %u bytes", __func__, uncLen, expUncLen);
542
        return -1;
543
    }
544
*/
545
546
    // There are now uncLen bytes of uncompressed data available from
547
    // this comp unit.
548
0
    return (ssize_t)uncLen;
549
0
}
550
551
/**
552
 * \internal
553
 * Attr walk callback function for compressed resources
554
 *
555
 * @param fs_attr the attribute to read
556
 * @param flags
557
 * @param a_action action callback
558
 * @param ptr context for the action callback
559
 * @param read_block_table pointer to block table read function
560
 * @param decompress_block pointer to decompression function
561
 * @return 0 on success, 1 on error
562
 */
563
static uint8_t
564
decmpfs_attr_walk_compressed_rsrc(
565
  const TSK_FS_ATTR * fs_attr,
566
  [[maybe_unused]] int flags,
567
  TSK_FS_FILE_WALK_CB a_action,
568
  void *ptr,
569
  std::unique_ptr<CMP_OFFSET_ENTRY[]> (*read_block_table)(
570
    const TSK_FS_ATTR *rAttr,
571
    uint32_t* tableSizeOut,
572
    uint32_t* tableOffsetOut
573
  ),
574
  int (*decompress_block)(
575
    char* rawBuf,
576
    uint32_t len,
577
    char* uncBuf,
578
    uint64_t* uncLen
579
  )
580
)
581
0
{
582
0
    if (tsk_verbose)
583
0
        tsk_fprintf(stderr,
584
0
            "%s:  Entered, because this is a compressed file with compressed data in the resource fork\n", __func__);
585
586
    // clean up any error messages that are lying around
587
0
    tsk_error_reset();
588
0
    if ((fs_attr == NULL) || (fs_attr->fs_file == NULL)
589
0
        || (fs_attr->fs_file->meta == NULL)
590
0
        || (fs_attr->fs_file->fs_info == NULL)) {
591
0
        tsk_error_set_errno(TSK_ERR_FS_ARG);
592
0
        tsk_error_set_errstr("%s: Null arguments given\n", __func__);
593
0
        return 1;
594
0
    }
595
596
    // Check that the ATTR being read is the main DATA resource, 128-0,
597
    // because this is the only one that can be compressed in HFS+
598
0
    if ((fs_attr->id != HFS_FS_ATTR_ID_DATA) ||
599
0
        (fs_attr->type != TSK_FS_ATTR_TYPE_HFS_DATA)) {
600
0
        error_detected(TSK_ERR_FS_ARG,
601
0
            "%s: arg specified an attribute %u-%u that is not the data fork, "
602
0
            "Only the data fork can be compressed.", __func__, fs_attr->type,
603
0
            fs_attr->id);
604
0
        return 1;
605
0
    }
606
607
    /* This MUST be a compressed attribute     */
608
0
    if (!(fs_attr->flags & TSK_FS_ATTR_COMP)) {
609
0
        error_detected(TSK_ERR_FS_FWALK,
610
0
            "%s: called with non-special attribute: %x",
611
0
            __func__, fs_attr->flags);
612
0
        return 1;
613
0
    }
614
615
0
    TSK_FS_INFO* fs = fs_attr->fs_file->fs_info;
616
0
    TSK_FS_FILE* fs_file = fs_attr->fs_file;
617
618
    /********  Open the Resource Fork ***********/
619
620
    // find the attribute for the resource fork
621
0
    const TSK_FS_ATTR* rAttr =
622
0
        tsk_fs_file_attr_get_type(fs_file, TSK_FS_ATTR_TYPE_HFS_RSRC,
623
0
        HFS_FS_ATTR_ID_RSRC, FALSE);
624
0
    if (rAttr == NULL) {
625
0
        error_returned
626
0
            (" %s: could not get the attribute for the resource fork of the file", __func__);
627
0
        return 1;
628
0
    }
629
630
0
    uint32_t offsetTableOffset;
631
0
    uint32_t offsetTableSize;         // The number of table entries
632
633
    // read the offset table from the fork header
634
0
    std::unique_ptr<CMP_OFFSET_ENTRY[]> offsetTable = read_block_table(
635
0
      rAttr, &offsetTableSize, &offsetTableOffset
636
0
    );
637
0
    if (!offsetTable) {
638
0
      return 1;
639
0
    }
640
641
    // Allocate two buffers for the raw and uncompressed data
642
    /* Raw data can be COMPRESSION_UNIT_SIZE+1 if the data is not
643
     * compressed and there is a 1-byte flag that indicates that
644
     * the data is not compressed. */
645
0
    std::unique_ptr<char[]> rawBuf{new(std::nothrow) char[COMPRESSION_UNIT_SIZE + 1]};
646
0
    if (!rawBuf) {
647
0
        error_returned
648
0
            (" %s: buffers for reading and uncompressing", __func__);
649
0
        return 1;
650
0
    }
651
652
0
    std::unique_ptr<char[]> uncBuf{new(std::nothrow) char[COMPRESSION_UNIT_SIZE]};
653
0
    if (!uncBuf) {
654
0
        error_returned
655
0
            (" %s: buffers for reading and uncompressing", __func__);
656
0
        return 1;
657
0
    }
658
659
0
    TSK_OFF_T off = 0;          // the offset in the uncompressed data stream consumed thus far
660
661
    // FOR entry in the table DO
662
0
    for (size_t indx = 0; indx < offsetTableSize; ++indx) {
663
0
        ssize_t uncLen;        // uncompressed length
664
0
        unsigned int blockSize;
665
0
        uint64_t lumpSize;
666
0
        uint64_t remaining;
667
0
        char *lumpStart;
668
669
0
        switch ((uncLen = read_and_decompress_block(
670
0
                    rAttr, rawBuf.get(), uncBuf.get(),
671
0
                    offsetTable.get(), offsetTableSize, offsetTableOffset, indx,
672
0
                    decompress_block)))
673
0
        {
674
0
        case -1:
675
0
            return 1;
676
0
        case  0:
677
0
            continue;
678
0
        default:
679
0
            break;
680
0
        }
681
682
        // Call the a_action callback with "Lumps"
683
        // that are at most the block size.
684
0
        blockSize = fs->block_size;
685
0
        remaining = uncLen;
686
0
        lumpStart = uncBuf.get();
687
688
0
        while (remaining > 0) {
689
0
            int retval;         // action return value
690
0
            lumpSize = remaining <= blockSize ? remaining : blockSize;
691
692
            // Apply the callback function
693
0
            if (tsk_verbose)
694
0
                tsk_fprintf(stderr,
695
0
                    "%s: Calling action on lump of size %"
696
0
                    PRIu64 " offset %" PRIu64 " in the compression unit\n",
697
0
                    __func__, lumpSize, uncLen - remaining);
698
0
            if (lumpSize > SIZE_MAX) {
699
0
                error_detected(TSK_ERR_FS_FWALK,
700
0
                    " %s: lumpSize is too large for the action", __func__);
701
0
                return 1;
702
0
            }
703
704
0
            retval = a_action(fs_attr->fs_file, off, 0, lumpStart,
705
0
                (size_t) lumpSize,   // cast OK because of above test
706
0
                TSK_FS_BLOCK_FLAG_COMP, ptr);
707
708
0
            if (retval == TSK_WALK_ERROR) {
709
0
                error_detected(TSK_ERR_FS | 201,
710
0
                    "%s: callback returned an error", __func__);
711
0
                return 1;
712
0
            }
713
0
            else if (retval == TSK_WALK_STOP) {
714
0
                break;
715
0
            }
716
717
            // Find the next lump
718
0
            off += lumpSize;
719
0
            remaining -= lumpSize;
720
0
            lumpStart += lumpSize;
721
0
        }
722
0
    }
723
724
0
    return 0;
725
0
}
726
727
728
#ifdef HAVE_LIBZ
729
/**
730
 * \internal
731
 * Attr walk callback function for ZLIB compressed resources
732
 *
733
 * @param fs_attr the attribute to read
734
 * @param flags
735
 * @param a_action action callback
736
 * @param ptr context for the action callback
737
 * @return 0 on success, 1 on error
738
 */
739
uint8_t
740
decmpfs_attr_walk_zlib_rsrc(const TSK_FS_ATTR * fs_attr,
741
    int flags, TSK_FS_FILE_WALK_CB a_action, void *ptr)
742
0
{
743
0
    return decmpfs_attr_walk_compressed_rsrc(
744
0
      fs_attr, flags, a_action, ptr,
745
0
      decmpfs_read_zlib_block_table,
746
0
      decmpfs_decompress_zlib_block
747
0
    );
748
0
}
749
#endif
750
751
/**
752
 * \internal
753
 * Attr walk callback function for LZVN compressed resources
754
 *
755
 * @param fs_attr the attribute to read
756
 * @param flags
757
 * @param a_action action callback
758
 * @param ptr context for the action callback
759
 * @return 0 on success, 1 on error
760
 */
761
uint8_t
762
decmpfs_attr_walk_lzvn_rsrc(const TSK_FS_ATTR * fs_attr,
763
    int flags, TSK_FS_FILE_WALK_CB a_action, void *ptr)
764
0
{
765
0
    return decmpfs_attr_walk_compressed_rsrc(
766
0
      fs_attr, flags, a_action, ptr,
767
0
      decmpfs_read_lzvn_block_table,
768
0
      decmpfs_decompress_lzvn_block
769
0
    );
770
0
}
771
772
773
/**
774
 * \internal
775
 * Read a compressed resource
776
 *
777
 * @param fs_attr the attribute to read
778
 * @param a_offset the offset from which to read
779
 * @param a_buf the buffer into which to read
780
 * @param a_len the length of the buffer
781
 * @param read_block_table pointer to block table read function
782
 * @param decompress_block pointer to decompression function
783
 * @return number of bytes read or -1 on error (incl if offset is past EOF)
784
 */
785
static ssize_t
786
decmpfs_file_read_compressed_rsrc(
787
    const TSK_FS_ATTR * a_fs_attr,
788
    TSK_OFF_T a_offset,
789
    char *a_buf,
790
    size_t a_len,
791
    std::unique_ptr<CMP_OFFSET_ENTRY[]> (*read_block_table)(
792
      const TSK_FS_ATTR *rAttr,
793
      uint32_t* tableSizeOut,
794
      uint32_t* tableOffsetOut),
795
    int (*decompress_block)(
796
      char* rawBuf,
797
      uint32_t len,
798
      char* uncBuf,
799
      uint64_t* uncLen)
800
)
801
0
{
802
0
    TSK_FS_FILE *fs_file;
803
0
    const TSK_FS_ATTR *rAttr;
804
0
    uint32_t offsetTableOffset;
805
0
    uint32_t offsetTableSize;         // Size of the offset table
806
0
    TSK_OFF_T indx;                // index for looping over the offset table
807
0
    TSK_OFF_T startUnit = 0;
808
0
    uint32_t startUnitOffset = 0;
809
0
    TSK_OFF_T endUnit = 0;
810
0
    uint64_t bytesCopied;
811
812
0
    if (tsk_verbose)
813
0
        tsk_fprintf(stderr,
814
0
            "%s: called because this file is compressed, with data in the resource fork\n", __func__);
815
816
    // Reading zero bytes?  OK at any offset, I say!
817
0
    if (a_len == 0)
818
0
        return 0;
819
820
0
    if (a_offset < 0) {
821
0
        error_detected(TSK_ERR_FS_ARG,
822
0
            "%s: reading from file at a negative offset",
823
0
             __func__);
824
0
        return -1;
825
0
    }
826
827
0
    if (a_len > SIZE_MAX / 2) {
828
0
        error_detected(TSK_ERR_FS_ARG,
829
0
            "%s: trying to read more than SIZE_MAX/2 is not supported.",
830
0
            __func__);
831
0
        return -1;
832
0
    }
833
834
0
    if ((a_fs_attr == NULL) || (a_fs_attr->fs_file == NULL)
835
0
        || (a_fs_attr->fs_file->meta == NULL)
836
0
        || (a_fs_attr->fs_file->fs_info == NULL)) {
837
0
        error_detected(TSK_ERR_FS_ARG,
838
0
            "%s: NULL parameters passed", __func__);
839
0
        return -1;
840
0
    }
841
842
    // This should be a compressed file.  If not, that's an error!
843
0
    if (!(a_fs_attr->flags & TSK_FS_ATTR_COMP)) {
844
0
        error_detected(TSK_ERR_FS_ARG,
845
0
            "%s: called with non-special attribute: %x",
846
0
            __func__, a_fs_attr->flags);
847
0
        return -1;
848
0
    }
849
850
    // Check that the ATTR being read is the main DATA resource, 4352-0,
851
    // because this is the only one that can be compressed in HFS+
852
0
    if ((a_fs_attr->id != HFS_FS_ATTR_ID_DATA) ||
853
0
        (a_fs_attr->type != TSK_FS_ATTR_TYPE_HFS_DATA)) {
854
0
        error_detected(TSK_ERR_FS_ARG,
855
0
            "%s: arg specified an attribute %u-%u that is not the data fork, "
856
0
            "Only the data fork can be compressed.", __func__,
857
0
            a_fs_attr->type, a_fs_attr->id);
858
0
        return -1;
859
0
    }
860
861
    /********  Open the Resource Fork ***********/
862
    // The file
863
0
    fs_file = a_fs_attr->fs_file;
864
865
    // find the attribute for the resource fork
866
0
    rAttr =
867
0
        tsk_fs_file_attr_get_type(fs_file, TSK_FS_ATTR_TYPE_HFS_RSRC,
868
0
        HFS_FS_ATTR_ID_RSRC, FALSE);
869
0
    if (rAttr == NULL) {
870
0
        error_returned
871
0
            (" %s: could not get the attribute for the resource fork of the file", __func__);
872
0
        return -1;
873
0
    }
874
875
    // read the offset table from the fork header
876
0
    std::unique_ptr<CMP_OFFSET_ENTRY[]> offsetTable = read_block_table(
877
0
      rAttr, &offsetTableSize, &offsetTableOffset
878
0
    );
879
0
    if (!offsetTable) {
880
0
      return -1;
881
0
    }
882
883
    // Compute the range of compression units needed for the request
884
0
    startUnit = a_offset / COMPRESSION_UNIT_SIZE;
885
0
    startUnitOffset = a_offset % COMPRESSION_UNIT_SIZE;
886
0
    endUnit = (a_offset + a_len - 1) / COMPRESSION_UNIT_SIZE;
887
888
0
    if (startUnit >= offsetTableSize || endUnit >= offsetTableSize) {
889
0
        error_detected(TSK_ERR_FS_ARG,
890
0
            "%s: range of bytes requested %lld - %lld falls past the "
891
0
            "end of the uncompressed stream %llu\n",
892
0
            __func__, a_offset, a_offset + a_len,
893
0
            offsetTable[offsetTableSize-1].offset +
894
0
            offsetTable[offsetTableSize-1].length);
895
0
        return -1;
896
0
    }
897
898
0
    if (tsk_verbose)
899
0
        tsk_fprintf(stderr,
900
0
            "%s: reading compression units: %" PRIdOFF
901
0
            " to %" PRIdOFF "\n", __func__, startUnit, endUnit);
902
0
   bytesCopied = 0;
903
904
    // Allocate buffers for the raw and uncompressed data
905
    /* Raw data can be COMPRESSION_UNIT_SIZE+1 if the zlib data is not
906
     * compressed and there is a 1-byte flag that indicates that
907
     * the data is not compressed. */
908
0
    std::unique_ptr<char[]> rawBuf{new(std::nothrow) char[COMPRESSION_UNIT_SIZE + 1]};
909
0
    if (!rawBuf) {
910
0
        error_returned
911
0
            (" %s: buffers for reading and uncompressing", __func__);
912
0
        return -1;
913
0
    }
914
915
0
    std::unique_ptr<char[]> uncBuf{new(std::nothrow) char[COMPRESSION_UNIT_SIZE]};
916
0
    if (!uncBuf) {
917
0
        error_returned
918
0
            (" %s: buffers for reading and uncompressing", __func__);
919
0
        return -1;
920
0
    }
921
922
    // Read from the indicated comp units
923
0
    for (indx = startUnit; indx <= endUnit; ++indx) {
924
0
        char *uncBufPtr = uncBuf.get();
925
0
        size_t bytesToCopy;
926
927
0
        const ssize_t ret = read_and_decompress_block(
928
0
          rAttr, rawBuf.get(), uncBuf.get(),
929
0
          offsetTable.get(), offsetTableSize, offsetTableOffset, (size_t)indx,
930
0
          decompress_block
931
0
        );
932
933
0
        switch (ret) {
934
0
        case -1:
935
0
            return -1;
936
0
        case  0:
937
0
            continue;
938
0
        default:
939
0
            break;
940
0
        }
941
942
0
        uint64_t uncLen = ret;
943
944
        // If this is the first comp unit, then we must skip over the
945
        // startUnitOffset bytes.
946
0
        if (indx == startUnit) {
947
0
            uncLen -= startUnitOffset;
948
0
            uncBufPtr += startUnitOffset;
949
0
        }
950
951
        // How many bytes to copy from this compression unit?
952
953
0
        if (bytesCopied + uncLen < (uint64_t) a_len)    // cast OK because a_len > 0
954
0
            bytesToCopy = (size_t) uncLen;      // uncLen <= size of compression unit, which is small, so cast is OK
955
0
        else
956
0
            bytesToCopy = (size_t) (((uint64_t) a_len) - bytesCopied);  // diff <= compression unit size, so cast is OK
957
958
        // Copy into the output buffer, and update bookkeeping.
959
0
        memcpy(a_buf + bytesCopied, uncBufPtr, bytesToCopy);
960
0
        bytesCopied += bytesToCopy;
961
0
    }
962
963
    // Well, we don't know (without a lot of work) what the
964
    // true uncompressed size of the stream is.  All we know is the "upper bound" which
965
    // assumes that all of the compression units expand to their full size.  If we did
966
    // know the true size, then we could reject requests that go beyond the end of the
967
    // stream.  Instead, we treat the stream as if it is padded out to the full size of
968
    // the last compression unit with zeros.
969
970
    // Have we read and copied all of the bytes requested?
971
0
    if (bytesCopied < a_len) {
972
        // set the remaining bytes to zero
973
0
        memset(a_buf + bytesCopied, 0, a_len - (size_t) bytesCopied);   // cast OK because diff must be < compression unit size
974
0
    }
975
976
0
    return (ssize_t) bytesCopied;       // cast OK, cannot be greater than a_len which cannot be greater than SIZE_MAX/2 (rounded down).
977
0
}
978
979
980
#ifdef HAVE_LIBZ
981
/**
982
 * \internal
983
 * Read a ZLIB compressed resource
984
 *
985
 * @param fs_attr the attribute to read
986
 * @param a_offset the offset from which to read
987
 * @param a_buf the buffer into which to read
988
 * @param a_len the length of the buffer
989
 * @return number of bytes read or -1 on error (incl if offset is past EOF)
990
 */
991
ssize_t
992
decmpfs_file_read_zlib_rsrc(const TSK_FS_ATTR * a_fs_attr,
993
    TSK_OFF_T a_offset, char *a_buf, size_t a_len)
994
0
{
995
0
    return decmpfs_file_read_compressed_rsrc(
996
0
        a_fs_attr, a_offset, a_buf, a_len,
997
0
        decmpfs_read_zlib_block_table,
998
0
        decmpfs_decompress_zlib_block
999
0
    );
1000
0
}
1001
#endif
1002
1003
/**
1004
 * Read an LZVN compressed resource
1005
 *
1006
 * @param fs_attr the attribute to read
1007
 * @param a_offset the offset from which to read
1008
 * @param a_buf the buffer into which to read
1009
 * @param a_len the length of the buffer
1010
 * @return number of bytes read or -1 on error (incl if offset is past EOF)
1011
 */
1012
ssize_t
1013
decmpfs_file_read_lzvn_rsrc(const TSK_FS_ATTR * a_fs_attr,
1014
    TSK_OFF_T a_offset, char *a_buf, size_t a_len)
1015
0
{
1016
0
    return decmpfs_file_read_compressed_rsrc(
1017
0
        a_fs_attr, a_offset, a_buf, a_len,
1018
0
        decmpfs_read_lzvn_block_table,
1019
0
        decmpfs_decompress_lzvn_block
1020
0
    );
1021
0
}
1022
1023
/**
1024
 * \internal
1025
 * "Decompress" an uncompressed attr
1026
 *
1027
 * HFS+ compression schemes allow for some blocks to be stored uncompressed.
1028
 *
1029
 * @param rawBuf source buffer
1030
 * @param rawSize size of source buffer
1031
 * @param uncSize expected uncompressed size
1032
 * @param dstBuf destination buffer
1033
 * @param dstSize size of destination buffer
1034
 * @return 1
1035
 */
1036
static int decmpfs_decompress_noncompressed_attr(
1037
  char* rawBuf,
1038
  [[maybe_unused]] uint32_t rawSize,
1039
  uint64_t uncSize,
1040
  char** dstBuf,
1041
  uint64_t* dstSize)
1042
0
{
1043
0
    if (tsk_verbose)
1044
0
        tsk_fprintf(stderr,
1045
0
            "%s: Leading byte, 0x%02x, indicates that the data is not really compressed.\n"
1046
0
            "%s:  Loading the default DATA attribute.", __func__, rawBuf[0], __func__);
1047
1048
0
    *dstBuf = rawBuf + 1;  // + 1 indicator byte
1049
0
    *dstSize = uncSize;
1050
0
    return 1;
1051
0
}
1052
1053
bool decmpfs_is_compressed_zlib_attr(
1054
  const char* rawBuf,
1055
  [[maybe_unused]] uint32_t rawSize)
1056
0
{
1057
    // ZLIB blocks cannot start with 0xF as the low nibble, so that's used
1058
    // as the flag for noncompressed blocks
1059
0
    return (rawBuf[0] & 0x0F) != 0x0F;
1060
0
}
1061
1062
/**
1063
 * \internal
1064
 * Decompress a ZLIB compressed attr
1065
 *
1066
 * @param rawBuf source buffer
1067
 * @param rawSize size of source buffer
1068
 * @param uncSize expected uncompressed size
1069
 * @param dstSize size of destination buffer
1070
 * @return 1 on success, 0 on error
1071
 */
1072
std::unique_ptr<char[]> decmpfs_decompress_zlib_attr(
1073
  char* rawBuf,
1074
  uint32_t rawSize,
1075
  uint64_t uncSize,
1076
  uint64_t* dstSize)
1077
0
{
1078
0
#ifdef HAVE_LIBZ
1079
0
    uint64_t uLen;
1080
0
    unsigned long bytesConsumed;
1081
0
    int infResult;
1082
1083
0
    if (tsk_verbose)
1084
0
        tsk_fprintf(stderr,
1085
0
                    "%s: Uncompressing (inflating) data.", __func__);
1086
    // Uncompress the remainder of the attribute, and load as 128-0
1087
    // Note: cast is OK because uncSize will be quite modest, < 4000.
1088
1089
    // add some extra space
1090
0
    std::unique_ptr<char[]> uncBuf{new(std::nothrow) char[uncSize + 100]};
1091
0
    if (!uncBuf) {
1092
0
        error_returned
1093
0
            (" - %s, space for the uncompressed attr", __func__);
1094
0
        return nullptr;
1095
0
    }
1096
1097
0
    infResult = zlib_inflate(rawBuf, (uint64_t) rawSize,
1098
0
                             uncBuf.get(), (uint64_t) (uncSize + 100),
1099
0
                             &uLen, &bytesConsumed);
1100
0
    if (infResult != 0) {
1101
0
        error_returned
1102
0
            (" %s, zlib could not uncompress attr", __func__);
1103
0
        return nullptr;
1104
0
    }
1105
1106
0
    if (bytesConsumed != rawSize) {
1107
0
        error_detected(TSK_ERR_FS_READ,
1108
0
            " %s, decompressor did not consume the whole compressed data",
1109
0
            __func__);
1110
0
        return nullptr;
1111
0
    }
1112
1113
0
    *dstSize = uncSize;
1114
0
    return uncBuf;
1115
#else
1116
    // ZLIB compression library is not available, so we will load a
1117
    // zero-length default DATA attribute. Without this, icat may
1118
    // misbehave.
1119
1120
    if (tsk_verbose)
1121
        tsk_fprintf(stderr,
1122
                    "%s: ZLIB not available, so loading an empty default DATA attribute.\n", __func__);
1123
1124
    // Dummy array is one byte long, so the ptr is not null, but we set the
1125
    // length to zero bytes, so it is never read.
1126
    *dstSize = 0;
1127
    return std::unique_ptr<char[]>{new(std::nothrow) char[1]};
1128
#endif
1129
0
}
1130
1131
bool decmpfs_is_compressed_lzvn_attr(
1132
  const char* rawBuf,
1133
  [[maybe_unused]] uint32_t rawSize)
1134
0
{
1135
    // LZVN blocks cannot start with 0x06, so that's used as the flag for
1136
    // noncompressed blocks
1137
0
    return rawBuf[0] != 0x06;
1138
0
}
1139
1140
/**
1141
 * \internal
1142
 * Decompress an LZVN compressed attr
1143
 *
1144
 * @param rawBuf source buffer
1145
 * @param rawSize size of source buffer
1146
 * @param uncSize expected uncompressed size
1147
 * @param dstSize size of destination buffer
1148
 * @return 1 on success, 0 on error
1149
 */
1150
std::unique_ptr<char[]> decmpfs_decompress_lzvn_attr(
1151
  char* rawBuf,
1152
  uint32_t rawSize,
1153
  uint64_t uncSize,
1154
  uint64_t* dstSize)
1155
0
{
1156
0
    std::unique_ptr<char[]> uncBuf{new(std::nothrow) char[uncSize]};
1157
0
    *dstSize = lzvn_decode_buffer(uncBuf.get(), uncSize, rawBuf, rawSize);
1158
0
    return uncBuf;
1159
0
}
1160
1161
/**
1162
 * \internal
1163
 * Read a compressed attr
1164
 *
1165
 * @param fs_file the file
1166
 * @param cmpType compression type
1167
 * @param buffer destination buffer
1168
 * @param attributeLength length of the attribute
1169
 * @param uncSize uncompressed size
1170
 * @param decompress_attr pointer to the decompression function
1171
 * @return 1 on success, 0 on error
1172
 */
1173
static int
1174
decmpfs_file_read_compressed_attr(
1175
  TSK_FS_FILE* fs_file,
1176
  uint8_t cmpType,
1177
  char* buffer,
1178
  TSK_OFF_T attributeLength,
1179
  uint64_t uncSize,
1180
  bool (*is_compressed)(
1181
    const char* rawBuf,
1182
    uint32_t rawSize
1183
  ),
1184
  std::unique_ptr<char[]> (*decompress_attr)(
1185
    char* rawBuf,
1186
    uint32_t rawSize,
1187
    uint64_t uncSize,
1188
    uint64_t* dstSize
1189
  )
1190
)
1191
0
{
1192
    // Data is inline. We will load the uncompressed data as a
1193
    // resident attribute.
1194
0
    if (tsk_verbose)
1195
0
        tsk_fprintf(stderr,
1196
0
            "%s: Compressed data is inline in the attribute, will load this as the default DATA attribute.\n", __func__);
1197
1198
0
    if (attributeLength <= 16) {
1199
0
        if (tsk_verbose)
1200
0
            tsk_fprintf(stderr,
1201
0
                "%s: WARNING, Compression Record of type %u is not followed by"
1202
0
                " compressed data. No data will be loaded into the DATA"
1203
0
                " attribute.\n", __func__, cmpType);
1204
1205
        // oddly, this is not actually considered an error
1206
0
        return 1;
1207
0
    }
1208
1209
0
    TSK_FS_ATTR *fs_attr_unc;
1210
1211
    // There is data following the compression record, as there should be.
1212
0
    if ((fs_attr_unc = tsk_fs_attrlist_getnew(
1213
0
          fs_file->meta->attr, TSK_FS_ATTR_RES)) == NULL)
1214
0
    {
1215
0
        error_returned(" - %s, FS_ATTR for uncompressed data", __func__);
1216
0
        return 0;
1217
0
    }
1218
1219
0
    char* dstBuf = nullptr;
1220
0
    std::unique_ptr<char[]> dstBufStore;
1221
0
    uint64_t dstSize;
1222
1223
0
    if (is_compressed(buffer + 16, attributeLength - 16)) {
1224
0
        dstBufStore = decompress_attr(
1225
0
          buffer + 16, attributeLength - 16, uncSize, &dstSize
1226
0
        );
1227
0
        if (!dstBufStore) {
1228
0
            return 0;
1229
0
        }
1230
0
        dstBuf = dstBufStore.get();
1231
0
    }
1232
0
    else {
1233
0
        if (!decmpfs_decompress_noncompressed_attr(buffer + 16, attributeLength - 16, uncSize, &dstBuf, &dstSize)) {
1234
0
            return 0;
1235
0
        }
1236
0
    }
1237
1238
0
    if (dstSize != uncSize) {
1239
0
        error_detected(TSK_ERR_FS_READ,
1240
0
            " %s, actual uncompressed size not equal to the size in the compression record", __func__);
1241
0
        return 0;
1242
0
    }
1243
1244
0
    if (tsk_verbose)
1245
0
       tsk_fprintf(stderr,
1246
0
                   "%s: Loading decompressed data as default DATA attribute.",
1247
0
                   __func__);
1248
1249
    // Load the remainder of the attribute as 128-0
1250
    // set the details in the fs_attr structure.
1251
    // Note, we are loading this as a RESIDENT attribute.
1252
0
    if (tsk_fs_attr_set_str(fs_file,
1253
0
                            fs_attr_unc, "DECOMP",
1254
0
                            TSK_FS_ATTR_TYPE_HFS_DATA,
1255
0
                            TSK_FS_ATTR_ID_DEFAULT,
1256
0
                            dstBuf,
1257
0
                            dstSize))
1258
0
    {
1259
0
        error_returned(" - %s", __func__);
1260
0
        return 0;
1261
0
    }
1262
1263
0
    return 1;
1264
0
}
1265
1266
/**
1267
 * \internal
1268
 * Read a ZLIB compressed attr
1269
 *
1270
 * @param fs_file the file
1271
 * @param buffer destination buffer
1272
 * @param attributeLength length of the attribute
1273
 * @param uncSize uncompressed size
1274
 * @return 1 on success, 0 on error
1275
 */
1276
int decmpfs_file_read_zlib_attr(TSK_FS_FILE* fs_file,
1277
                            char* buffer,
1278
                            TSK_OFF_T attributeLength,
1279
                            uint64_t uncSize)
1280
0
{
1281
0
    return decmpfs_file_read_compressed_attr(
1282
0
        fs_file, DECMPFS_TYPE_ZLIB_ATTR,
1283
0
        buffer, attributeLength, uncSize,
1284
0
        decmpfs_is_compressed_zlib_attr,
1285
0
        decmpfs_decompress_zlib_attr
1286
0
    );
1287
0
}
1288
1289
/**
1290
 * \internal
1291
 * Read an LZVN compressed attr
1292
 *
1293
 * @param fs_file the file
1294
 * @param buffer destination buffer
1295
 * @param attributeLength length of the attribute
1296
 * @param uncSize uncompressed size
1297
 * @return 1 on success, 0 on error
1298
 */
1299
int decmpfs_file_read_lzvn_attr(TSK_FS_FILE* fs_file,
1300
                            char* buffer,
1301
                            TSK_OFF_T attributeLength,
1302
                            uint64_t uncSize)
1303
0
{
1304
0
    return decmpfs_file_read_compressed_attr(
1305
0
        fs_file, DECMPFS_TYPE_LZVN_ATTR,
1306
0
        buffer, attributeLength, uncSize,
1307
0
        decmpfs_is_compressed_lzvn_attr,
1308
0
        decmpfs_decompress_lzvn_attr
1309
0
    );
1310
0
}