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 | |
f535537f |
30 | static size_t getDecompressionMargin(void const* compressed, size_t cSize, size_t srcSize, int hasSmallBlocks, int maxBlockSize) |
648db22b |
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)); |
f535537f |
40 | if (maxBlockSize == 0) { |
41 | maxBlockSize = zfh.blockSizeMax; |
42 | } else { |
43 | maxBlockSize = MIN(maxBlockSize, (int)zfh.blockSizeMax); |
44 | } |
45 | marginM = ZSTD_DECOMPRESSION_MARGIN(srcSize, maxBlockSize); |
648db22b |
46 | if (marginM < margin) |
47 | margin = marginM; |
48 | } |
49 | return margin; |
50 | } |
51 | |
52 | static size_t roundTripTest(void *result, size_t resultCapacity, |
53 | void *compressed, size_t compressedCapacity, |
54 | const void *src, size_t srcSize, |
55 | FUZZ_dataProducer_t *producer) |
56 | { |
57 | size_t cSize; |
58 | size_t dSize; |
59 | int targetCBlockSize = 0; |
f535537f |
60 | int maxBlockSize = 0; |
648db22b |
61 | if (FUZZ_dataProducer_uint32Range(producer, 0, 1)) { |
62 | size_t const remainingBytes = FUZZ_dataProducer_remainingBytes(producer); |
63 | FUZZ_setRandomParameters(cctx, srcSize, producer); |
64 | cSize = ZSTD_compress2(cctx, compressed, compressedCapacity, src, srcSize); |
65 | FUZZ_ZASSERT(cSize); |
66 | FUZZ_ZASSERT(ZSTD_CCtx_getParameter(cctx, ZSTD_c_targetCBlockSize, &targetCBlockSize)); |
f535537f |
67 | FUZZ_ZASSERT(ZSTD_CCtx_getParameter(cctx, ZSTD_c_maxBlockSize, &maxBlockSize)); |
648db22b |
68 | // Compress a second time and check for determinism |
69 | { |
70 | size_t const cSize0 = cSize; |
71 | XXH64_hash_t const hash0 = XXH64(compressed, cSize, 0); |
72 | FUZZ_dataProducer_rollBack(producer, remainingBytes); |
73 | FUZZ_setRandomParameters(cctx, srcSize, producer); |
74 | cSize = ZSTD_compress2(cctx, compressed, compressedCapacity, src, srcSize); |
75 | FUZZ_ASSERT(cSize == cSize0); |
76 | FUZZ_ASSERT(XXH64(compressed, cSize, 0) == hash0); |
77 | } |
78 | } else { |
79 | int const cLevel = FUZZ_dataProducer_int32Range(producer, kMinClevel, kMaxClevel); |
80 | cSize = ZSTD_compressCCtx( |
81 | cctx, compressed, compressedCapacity, src, srcSize, cLevel); |
82 | FUZZ_ZASSERT(cSize); |
83 | // Compress a second time and check for determinism |
84 | { |
85 | size_t const cSize0 = cSize; |
86 | XXH64_hash_t const hash0 = XXH64(compressed, cSize, 0); |
87 | cSize = ZSTD_compressCCtx( |
88 | cctx, compressed, compressedCapacity, src, srcSize, cLevel); |
89 | FUZZ_ASSERT(cSize == cSize0); |
90 | FUZZ_ASSERT(XXH64(compressed, cSize, 0) == hash0); |
91 | } |
92 | } |
f535537f |
93 | if (FUZZ_dataProducer_uint32Range(producer, 0, 1)) { |
94 | FUZZ_ZASSERT(ZSTD_DCtx_setParameter(dctx, ZSTD_d_maxBlockSize, maxBlockSize)); |
95 | } |
648db22b |
96 | dSize = ZSTD_decompressDCtx(dctx, result, resultCapacity, compressed, cSize); |
97 | FUZZ_ZASSERT(dSize); |
98 | FUZZ_ASSERT_MSG(dSize == srcSize, "Incorrect regenerated size"); |
99 | FUZZ_ASSERT_MSG(!FUZZ_memcmp(src, result, dSize), "Corruption!"); |
100 | |
101 | { |
f535537f |
102 | size_t margin = getDecompressionMargin(compressed, cSize, srcSize, targetCBlockSize, maxBlockSize); |
648db22b |
103 | size_t const outputSize = srcSize + margin; |
104 | char* const output = (char*)FUZZ_malloc(outputSize); |
105 | char* const input = output + outputSize - cSize; |
106 | FUZZ_ASSERT(outputSize >= cSize); |
107 | memcpy(input, compressed, cSize); |
108 | |
109 | dSize = ZSTD_decompressDCtx(dctx, output, outputSize, input, cSize); |
110 | FUZZ_ZASSERT(dSize); |
111 | FUZZ_ASSERT_MSG(dSize == srcSize, "Incorrect regenerated size"); |
112 | FUZZ_ASSERT_MSG(!FUZZ_memcmp(src, output, srcSize), "Corruption!"); |
113 | |
114 | free(output); |
115 | } |
116 | |
117 | /* When superblock is enabled make sure we don't expand the block more than expected. |
118 | * NOTE: This test is currently disabled because superblock mode can arbitrarily |
119 | * expand the block in the worst case. Once superblock mode has been improved we can |
120 | * re-enable this test. |
121 | */ |
122 | if (0 && targetCBlockSize != 0) { |
123 | size_t normalCSize; |
124 | FUZZ_ZASSERT(ZSTD_CCtx_setParameter(cctx, ZSTD_c_targetCBlockSize, 0)); |
125 | normalCSize = ZSTD_compress2(cctx, compressed, compressedCapacity, src, srcSize); |
126 | FUZZ_ZASSERT(normalCSize); |
127 | { |
128 | size_t const bytesPerBlock = 3 /* block header */ |
129 | + 5 /* Literal header */ |
130 | + 6 /* Huffman jump table */ |
131 | + 3 /* number of sequences */ |
132 | + 1 /* symbol compression modes */; |
133 | size_t const expectedExpansion = bytesPerBlock * (1 + (normalCSize / MAX(1, targetCBlockSize))); |
134 | size_t const allowedExpansion = (srcSize >> 3) + 5 * expectedExpansion + 10; |
135 | FUZZ_ASSERT(cSize <= normalCSize + allowedExpansion); |
136 | } |
137 | } |
138 | return dSize; |
139 | } |
140 | |
141 | int LLVMFuzzerTestOneInput(const uint8_t *src, size_t size) |
142 | { |
143 | FUZZ_SEQ_PROD_SETUP(); |
144 | |
145 | size_t const rBufSize = size; |
146 | void* rBuf = FUZZ_malloc(rBufSize); |
147 | size_t cBufSize = ZSTD_compressBound(size); |
148 | void* cBuf; |
149 | |
150 | /* Give a random portion of src data to the producer, to use for |
151 | parameter generation. The rest will be used for (de)compression */ |
152 | FUZZ_dataProducer_t *producer = FUZZ_dataProducer_create(src, size); |
153 | size = FUZZ_dataProducer_reserveDataPrefix(producer); |
154 | |
155 | /* Half of the time fuzz with a 1 byte smaller output size. |
156 | * This will still succeed because we don't use a dictionary, so the dictID |
157 | * field is empty, giving us 4 bytes of overhead. |
158 | */ |
159 | cBufSize -= FUZZ_dataProducer_uint32Range(producer, 0, 1); |
160 | |
161 | cBuf = FUZZ_malloc(cBufSize); |
162 | |
163 | if (!cctx) { |
164 | cctx = ZSTD_createCCtx(); |
165 | FUZZ_ASSERT(cctx); |
166 | } |
167 | if (!dctx) { |
168 | dctx = ZSTD_createDCtx(); |
169 | FUZZ_ASSERT(dctx); |
170 | } |
171 | |
172 | roundTripTest(rBuf, rBufSize, cBuf, cBufSize, src, size, producer); |
173 | free(rBuf); |
174 | free(cBuf); |
175 | FUZZ_dataProducer_free(producer); |
176 | #ifndef STATEFUL_FUZZING |
177 | ZSTD_freeCCtx(cctx); cctx = NULL; |
178 | ZSTD_freeDCtx(dctx); dctx = NULL; |
179 | #endif |
180 | FUZZ_SEQ_PROD_TEARDOWN(); |
181 | return 0; |
182 | } |