648db22b |
1 | /* |
2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | * All rights reserved. |
4 | * |
5 | * This source code is licensed under both the BSD-style license (found in the |
6 | * LICENSE file in the root directory of this source tree) and the GPLv2 (found |
7 | * in the COPYING file in the root directory of this source tree). |
8 | * You may select, at your option, one of the above-listed licenses. |
9 | */ |
10 | |
11 | /** |
12 | * This fuzz target performs a zstd round-trip test (compress & decompress), |
13 | * compares the result with the original, and calls abort() on corruption. |
14 | */ |
15 | |
16 | #define ZSTD_STATIC_LINKING_ONLY |
17 | |
18 | #include <stddef.h> |
19 | #include <stdlib.h> |
20 | #include <stdio.h> |
21 | #include <string.h> |
22 | #include "fuzz_helpers.h" |
23 | #include "zstd_helpers.h" |
24 | #include "fuzz_data_producer.h" |
25 | #include "fuzz_third_party_seq_prod.h" |
26 | |
27 | static ZSTD_CCtx *cctx = NULL; |
28 | static ZSTD_DCtx *dctx = NULL; |
29 | |
30 | static size_t getDecompressionMargin(void const* compressed, size_t cSize, size_t srcSize, int hasSmallBlocks) |
31 | { |
32 | size_t margin = ZSTD_decompressionMargin(compressed, cSize); |
33 | if (!hasSmallBlocks) { |
34 | /* The macro should be correct in this case, but it may be smaller |
35 | * because of e.g. block splitting, so take the smaller of the two. |
36 | */ |
37 | ZSTD_frameHeader zfh; |
38 | size_t marginM; |
39 | FUZZ_ZASSERT(ZSTD_getFrameHeader(&zfh, compressed, cSize)); |
40 | marginM = ZSTD_DECOMPRESSION_MARGIN(srcSize, zfh.blockSizeMax); |
41 | if (marginM < margin) |
42 | margin = marginM; |
43 | } |
44 | return margin; |
45 | } |
46 | |
47 | static size_t roundTripTest(void *result, size_t resultCapacity, |
48 | void *compressed, size_t compressedCapacity, |
49 | const void *src, size_t srcSize, |
50 | FUZZ_dataProducer_t *producer) |
51 | { |
52 | size_t cSize; |
53 | size_t dSize; |
54 | int targetCBlockSize = 0; |
55 | if (FUZZ_dataProducer_uint32Range(producer, 0, 1)) { |
56 | size_t const remainingBytes = FUZZ_dataProducer_remainingBytes(producer); |
57 | FUZZ_setRandomParameters(cctx, srcSize, producer); |
58 | cSize = ZSTD_compress2(cctx, compressed, compressedCapacity, src, srcSize); |
59 | FUZZ_ZASSERT(cSize); |
60 | FUZZ_ZASSERT(ZSTD_CCtx_getParameter(cctx, ZSTD_c_targetCBlockSize, &targetCBlockSize)); |
61 | // Compress a second time and check for determinism |
62 | { |
63 | size_t const cSize0 = cSize; |
64 | XXH64_hash_t const hash0 = XXH64(compressed, cSize, 0); |
65 | FUZZ_dataProducer_rollBack(producer, remainingBytes); |
66 | FUZZ_setRandomParameters(cctx, srcSize, producer); |
67 | cSize = ZSTD_compress2(cctx, compressed, compressedCapacity, src, srcSize); |
68 | FUZZ_ASSERT(cSize == cSize0); |
69 | FUZZ_ASSERT(XXH64(compressed, cSize, 0) == hash0); |
70 | } |
71 | } else { |
72 | int const cLevel = FUZZ_dataProducer_int32Range(producer, kMinClevel, kMaxClevel); |
73 | cSize = ZSTD_compressCCtx( |
74 | cctx, compressed, compressedCapacity, src, srcSize, cLevel); |
75 | FUZZ_ZASSERT(cSize); |
76 | // Compress a second time and check for determinism |
77 | { |
78 | size_t const cSize0 = cSize; |
79 | XXH64_hash_t const hash0 = XXH64(compressed, cSize, 0); |
80 | cSize = ZSTD_compressCCtx( |
81 | cctx, compressed, compressedCapacity, src, srcSize, cLevel); |
82 | FUZZ_ASSERT(cSize == cSize0); |
83 | FUZZ_ASSERT(XXH64(compressed, cSize, 0) == hash0); |
84 | } |
85 | } |
86 | dSize = ZSTD_decompressDCtx(dctx, result, resultCapacity, compressed, cSize); |
87 | FUZZ_ZASSERT(dSize); |
88 | FUZZ_ASSERT_MSG(dSize == srcSize, "Incorrect regenerated size"); |
89 | FUZZ_ASSERT_MSG(!FUZZ_memcmp(src, result, dSize), "Corruption!"); |
90 | |
91 | { |
92 | size_t margin = getDecompressionMargin(compressed, cSize, srcSize, targetCBlockSize); |
93 | size_t const outputSize = srcSize + margin; |
94 | char* const output = (char*)FUZZ_malloc(outputSize); |
95 | char* const input = output + outputSize - cSize; |
96 | FUZZ_ASSERT(outputSize >= cSize); |
97 | memcpy(input, compressed, cSize); |
98 | |
99 | dSize = ZSTD_decompressDCtx(dctx, output, outputSize, input, cSize); |
100 | FUZZ_ZASSERT(dSize); |
101 | FUZZ_ASSERT_MSG(dSize == srcSize, "Incorrect regenerated size"); |
102 | FUZZ_ASSERT_MSG(!FUZZ_memcmp(src, output, srcSize), "Corruption!"); |
103 | |
104 | free(output); |
105 | } |
106 | |
107 | /* When superblock is enabled make sure we don't expand the block more than expected. |
108 | * NOTE: This test is currently disabled because superblock mode can arbitrarily |
109 | * expand the block in the worst case. Once superblock mode has been improved we can |
110 | * re-enable this test. |
111 | */ |
112 | if (0 && targetCBlockSize != 0) { |
113 | size_t normalCSize; |
114 | FUZZ_ZASSERT(ZSTD_CCtx_setParameter(cctx, ZSTD_c_targetCBlockSize, 0)); |
115 | normalCSize = ZSTD_compress2(cctx, compressed, compressedCapacity, src, srcSize); |
116 | FUZZ_ZASSERT(normalCSize); |
117 | { |
118 | size_t const bytesPerBlock = 3 /* block header */ |
119 | + 5 /* Literal header */ |
120 | + 6 /* Huffman jump table */ |
121 | + 3 /* number of sequences */ |
122 | + 1 /* symbol compression modes */; |
123 | size_t const expectedExpansion = bytesPerBlock * (1 + (normalCSize / MAX(1, targetCBlockSize))); |
124 | size_t const allowedExpansion = (srcSize >> 3) + 5 * expectedExpansion + 10; |
125 | FUZZ_ASSERT(cSize <= normalCSize + allowedExpansion); |
126 | } |
127 | } |
128 | return dSize; |
129 | } |
130 | |
131 | int LLVMFuzzerTestOneInput(const uint8_t *src, size_t size) |
132 | { |
133 | FUZZ_SEQ_PROD_SETUP(); |
134 | |
135 | size_t const rBufSize = size; |
136 | void* rBuf = FUZZ_malloc(rBufSize); |
137 | size_t cBufSize = ZSTD_compressBound(size); |
138 | void* cBuf; |
139 | |
140 | /* Give a random portion of src data to the producer, to use for |
141 | parameter generation. The rest will be used for (de)compression */ |
142 | FUZZ_dataProducer_t *producer = FUZZ_dataProducer_create(src, size); |
143 | size = FUZZ_dataProducer_reserveDataPrefix(producer); |
144 | |
145 | /* Half of the time fuzz with a 1 byte smaller output size. |
146 | * This will still succeed because we don't use a dictionary, so the dictID |
147 | * field is empty, giving us 4 bytes of overhead. |
148 | */ |
149 | cBufSize -= FUZZ_dataProducer_uint32Range(producer, 0, 1); |
150 | |
151 | cBuf = FUZZ_malloc(cBufSize); |
152 | |
153 | if (!cctx) { |
154 | cctx = ZSTD_createCCtx(); |
155 | FUZZ_ASSERT(cctx); |
156 | } |
157 | if (!dctx) { |
158 | dctx = ZSTD_createDCtx(); |
159 | FUZZ_ASSERT(dctx); |
160 | } |
161 | |
162 | roundTripTest(rBuf, rBufSize, cBuf, cBufSize, src, size, producer); |
163 | free(rBuf); |
164 | free(cBuf); |
165 | FUZZ_dataProducer_free(producer); |
166 | #ifndef STATEFUL_FUZZING |
167 | ZSTD_freeCCtx(cctx); cctx = NULL; |
168 | ZSTD_freeDCtx(dctx); dctx = NULL; |
169 | #endif |
170 | FUZZ_SEQ_PROD_TEARDOWN(); |
171 | return 0; |
172 | } |