1*01826a49SYabin Cui /*
2*01826a49SYabin Cui * Copyright (c) Meta Platforms, Inc. and affiliates.
3*01826a49SYabin Cui * All rights reserved.
4*01826a49SYabin Cui *
5*01826a49SYabin Cui * This source code is licensed under both the BSD-style license (found in the
6*01826a49SYabin Cui * LICENSE file in the root directory of this source tree) and the GPLv2 (found
7*01826a49SYabin Cui * in the COPYING file in the root directory of this source tree).
8*01826a49SYabin Cui * You may select, at your option, one of the above-listed licenses.
9*01826a49SYabin Cui */
10*01826a49SYabin Cui
11*01826a49SYabin Cui /*
12*01826a49SYabin Cui This program takes a file in input,
13*01826a49SYabin Cui performs a zstd round-trip test (compression - decompress)
14*01826a49SYabin Cui compares the result with original
15*01826a49SYabin Cui and generates a crash (double free) on corruption detection.
16*01826a49SYabin Cui */
17*01826a49SYabin Cui
18*01826a49SYabin Cui /*===========================================
19*01826a49SYabin Cui * Dependencies
20*01826a49SYabin Cui *==========================================*/
21*01826a49SYabin Cui #include <stddef.h> /* size_t */
22*01826a49SYabin Cui #include <stdlib.h> /* malloc, free, exit */
23*01826a49SYabin Cui #include <stdio.h> /* fprintf */
24*01826a49SYabin Cui #include <string.h> /* strcmp */
25*01826a49SYabin Cui #include <sys/types.h> /* stat */
26*01826a49SYabin Cui #include <sys/stat.h> /* stat */
27*01826a49SYabin Cui #include "xxhash.h"
28*01826a49SYabin Cui
29*01826a49SYabin Cui #define ZSTD_STATIC_LINKING_ONLY
30*01826a49SYabin Cui #include "zstd.h"
31*01826a49SYabin Cui
32*01826a49SYabin Cui /*===========================================
33*01826a49SYabin Cui * Macros
34*01826a49SYabin Cui *==========================================*/
35*01826a49SYabin Cui #define MIN(a,b) ( (a) < (b) ? (a) : (b) )
36*01826a49SYabin Cui
crash(int errorCode)37*01826a49SYabin Cui static void crash(int errorCode){
38*01826a49SYabin Cui /* abort if AFL/libfuzzer, exit otherwise */
39*01826a49SYabin Cui #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION /* could also use __AFL_COMPILER */
40*01826a49SYabin Cui abort();
41*01826a49SYabin Cui #else
42*01826a49SYabin Cui exit(errorCode);
43*01826a49SYabin Cui #endif
44*01826a49SYabin Cui }
45*01826a49SYabin Cui
46*01826a49SYabin Cui #define CHECK_Z(f) { \
47*01826a49SYabin Cui size_t const err = f; \
48*01826a49SYabin Cui if (ZSTD_isError(err)) { \
49*01826a49SYabin Cui fprintf(stderr, \
50*01826a49SYabin Cui "Error=> %s: %s", \
51*01826a49SYabin Cui #f, ZSTD_getErrorName(err)); \
52*01826a49SYabin Cui crash(1); \
53*01826a49SYabin Cui } }
54*01826a49SYabin Cui
55*01826a49SYabin Cui /** roundTripTest() :
56*01826a49SYabin Cui * Compresses `srcBuff` into `compressedBuff`,
57*01826a49SYabin Cui * then decompresses `compressedBuff` into `resultBuff`.
58*01826a49SYabin Cui * Compression level used is derived from first content byte.
59*01826a49SYabin Cui * @return : result of decompression, which should be == `srcSize`
60*01826a49SYabin Cui * or an error code if either compression or decompression fails.
61*01826a49SYabin Cui * Note : `compressedBuffCapacity` should be `>= ZSTD_compressBound(srcSize)`
62*01826a49SYabin Cui * for compression to be guaranteed to work */
roundTripTest(void * resultBuff,size_t resultBuffCapacity,void * compressedBuff,size_t compressedBuffCapacity,const void * srcBuff,size_t srcBuffSize)63*01826a49SYabin Cui static size_t roundTripTest(void* resultBuff, size_t resultBuffCapacity,
64*01826a49SYabin Cui void* compressedBuff, size_t compressedBuffCapacity,
65*01826a49SYabin Cui const void* srcBuff, size_t srcBuffSize)
66*01826a49SYabin Cui {
67*01826a49SYabin Cui static const int maxClevel = 19;
68*01826a49SYabin Cui size_t const hashLength = MIN(128, srcBuffSize);
69*01826a49SYabin Cui unsigned const h32 = XXH32(srcBuff, hashLength, 0);
70*01826a49SYabin Cui int const cLevel = h32 % maxClevel;
71*01826a49SYabin Cui size_t const cSize = ZSTD_compress(compressedBuff, compressedBuffCapacity, srcBuff, srcBuffSize, cLevel);
72*01826a49SYabin Cui if (ZSTD_isError(cSize)) {
73*01826a49SYabin Cui fprintf(stderr, "Compression error : %s \n", ZSTD_getErrorName(cSize));
74*01826a49SYabin Cui return cSize;
75*01826a49SYabin Cui }
76*01826a49SYabin Cui return ZSTD_decompress(resultBuff, resultBuffCapacity, compressedBuff, cSize);
77*01826a49SYabin Cui }
78*01826a49SYabin Cui
79*01826a49SYabin Cui /** cctxParamRoundTripTest() :
80*01826a49SYabin Cui * Same as roundTripTest() except allows experimenting with ZSTD_CCtx_params. */
cctxParamRoundTripTest(void * resultBuff,size_t resultBuffCapacity,void * compressedBuff,size_t compressedBuffCapacity,const void * srcBuff,size_t srcBuffSize)81*01826a49SYabin Cui static size_t cctxParamRoundTripTest(void* resultBuff, size_t resultBuffCapacity,
82*01826a49SYabin Cui void* compressedBuff, size_t compressedBuffCapacity,
83*01826a49SYabin Cui const void* srcBuff, size_t srcBuffSize)
84*01826a49SYabin Cui {
85*01826a49SYabin Cui ZSTD_CCtx* const cctx = ZSTD_createCCtx();
86*01826a49SYabin Cui ZSTD_CCtx_params* const cctxParams = ZSTD_createCCtxParams();
87*01826a49SYabin Cui ZSTD_inBuffer inBuffer = { srcBuff, srcBuffSize, 0 };
88*01826a49SYabin Cui ZSTD_outBuffer outBuffer = { compressedBuff, compressedBuffCapacity, 0 };
89*01826a49SYabin Cui
90*01826a49SYabin Cui static const int maxClevel = 19;
91*01826a49SYabin Cui size_t const hashLength = MIN(128, srcBuffSize);
92*01826a49SYabin Cui unsigned const h32 = XXH32(srcBuff, hashLength, 0);
93*01826a49SYabin Cui int const cLevel = h32 % maxClevel;
94*01826a49SYabin Cui
95*01826a49SYabin Cui /* Set parameters */
96*01826a49SYabin Cui CHECK_Z( ZSTD_CCtxParams_setParameter(cctxParams, ZSTD_c_compressionLevel, cLevel) );
97*01826a49SYabin Cui CHECK_Z( ZSTD_CCtxParams_setParameter(cctxParams, ZSTD_c_nbWorkers, 2) );
98*01826a49SYabin Cui CHECK_Z( ZSTD_CCtxParams_setParameter(cctxParams, ZSTD_c_overlapLog, 5) );
99*01826a49SYabin Cui
100*01826a49SYabin Cui
101*01826a49SYabin Cui /* Apply parameters */
102*01826a49SYabin Cui CHECK_Z( ZSTD_CCtx_setParametersUsingCCtxParams(cctx, cctxParams) );
103*01826a49SYabin Cui
104*01826a49SYabin Cui CHECK_Z (ZSTD_compressStream2(cctx, &outBuffer, &inBuffer, ZSTD_e_end) );
105*01826a49SYabin Cui
106*01826a49SYabin Cui ZSTD_freeCCtxParams(cctxParams);
107*01826a49SYabin Cui ZSTD_freeCCtx(cctx);
108*01826a49SYabin Cui
109*01826a49SYabin Cui return ZSTD_decompress(resultBuff, resultBuffCapacity, compressedBuff, outBuffer.pos);
110*01826a49SYabin Cui }
111*01826a49SYabin Cui
checkBuffers(const void * buff1,const void * buff2,size_t buffSize)112*01826a49SYabin Cui static size_t checkBuffers(const void* buff1, const void* buff2, size_t buffSize)
113*01826a49SYabin Cui {
114*01826a49SYabin Cui const char* ip1 = (const char*)buff1;
115*01826a49SYabin Cui const char* ip2 = (const char*)buff2;
116*01826a49SYabin Cui size_t pos;
117*01826a49SYabin Cui
118*01826a49SYabin Cui for (pos=0; pos<buffSize; pos++)
119*01826a49SYabin Cui if (ip1[pos]!=ip2[pos])
120*01826a49SYabin Cui break;
121*01826a49SYabin Cui
122*01826a49SYabin Cui return pos;
123*01826a49SYabin Cui }
124*01826a49SYabin Cui
roundTripCheck(const void * srcBuff,size_t srcBuffSize,int testCCtxParams)125*01826a49SYabin Cui static void roundTripCheck(const void* srcBuff, size_t srcBuffSize, int testCCtxParams)
126*01826a49SYabin Cui {
127*01826a49SYabin Cui size_t const cBuffSize = ZSTD_compressBound(srcBuffSize);
128*01826a49SYabin Cui void* cBuff = malloc(cBuffSize);
129*01826a49SYabin Cui void* rBuff = malloc(cBuffSize);
130*01826a49SYabin Cui
131*01826a49SYabin Cui if (!cBuff || !rBuff) {
132*01826a49SYabin Cui fprintf(stderr, "not enough memory ! \n");
133*01826a49SYabin Cui exit (1);
134*01826a49SYabin Cui }
135*01826a49SYabin Cui
136*01826a49SYabin Cui { size_t const result = testCCtxParams ?
137*01826a49SYabin Cui cctxParamRoundTripTest(rBuff, cBuffSize, cBuff, cBuffSize, srcBuff, srcBuffSize)
138*01826a49SYabin Cui : roundTripTest(rBuff, cBuffSize, cBuff, cBuffSize, srcBuff, srcBuffSize);
139*01826a49SYabin Cui if (ZSTD_isError(result)) {
140*01826a49SYabin Cui fprintf(stderr, "roundTripTest error : %s \n", ZSTD_getErrorName(result));
141*01826a49SYabin Cui crash(1);
142*01826a49SYabin Cui }
143*01826a49SYabin Cui if (result != srcBuffSize) {
144*01826a49SYabin Cui fprintf(stderr, "Incorrect regenerated size : %u != %u\n", (unsigned)result, (unsigned)srcBuffSize);
145*01826a49SYabin Cui crash(1);
146*01826a49SYabin Cui }
147*01826a49SYabin Cui if (checkBuffers(srcBuff, rBuff, srcBuffSize) != srcBuffSize) {
148*01826a49SYabin Cui fprintf(stderr, "Silent decoding corruption !!!");
149*01826a49SYabin Cui crash(1);
150*01826a49SYabin Cui }
151*01826a49SYabin Cui }
152*01826a49SYabin Cui
153*01826a49SYabin Cui free(cBuff);
154*01826a49SYabin Cui free(rBuff);
155*01826a49SYabin Cui }
156*01826a49SYabin Cui
157*01826a49SYabin Cui
getFileSize(const char * infilename)158*01826a49SYabin Cui static size_t getFileSize(const char* infilename)
159*01826a49SYabin Cui {
160*01826a49SYabin Cui int r;
161*01826a49SYabin Cui #if defined(_MSC_VER)
162*01826a49SYabin Cui struct _stat64 statbuf;
163*01826a49SYabin Cui r = _stat64(infilename, &statbuf);
164*01826a49SYabin Cui if (r || !(statbuf.st_mode & S_IFREG)) return 0; /* No good... */
165*01826a49SYabin Cui #else
166*01826a49SYabin Cui struct stat statbuf;
167*01826a49SYabin Cui r = stat(infilename, &statbuf);
168*01826a49SYabin Cui if (r || !S_ISREG(statbuf.st_mode)) return 0; /* No good... */
169*01826a49SYabin Cui #endif
170*01826a49SYabin Cui return (size_t)statbuf.st_size;
171*01826a49SYabin Cui }
172*01826a49SYabin Cui
173*01826a49SYabin Cui
isDirectory(const char * infilename)174*01826a49SYabin Cui static int isDirectory(const char* infilename)
175*01826a49SYabin Cui {
176*01826a49SYabin Cui int r;
177*01826a49SYabin Cui #if defined(_MSC_VER)
178*01826a49SYabin Cui struct _stat64 statbuf;
179*01826a49SYabin Cui r = _stat64(infilename, &statbuf);
180*01826a49SYabin Cui if (!r && (statbuf.st_mode & _S_IFDIR)) return 1;
181*01826a49SYabin Cui #else
182*01826a49SYabin Cui struct stat statbuf;
183*01826a49SYabin Cui r = stat(infilename, &statbuf);
184*01826a49SYabin Cui if (!r && S_ISDIR(statbuf.st_mode)) return 1;
185*01826a49SYabin Cui #endif
186*01826a49SYabin Cui return 0;
187*01826a49SYabin Cui }
188*01826a49SYabin Cui
189*01826a49SYabin Cui
190*01826a49SYabin Cui /** loadFile() :
191*01826a49SYabin Cui * requirement : `buffer` size >= `fileSize` */
loadFile(void * buffer,const char * fileName,size_t fileSize)192*01826a49SYabin Cui static void loadFile(void* buffer, const char* fileName, size_t fileSize)
193*01826a49SYabin Cui {
194*01826a49SYabin Cui FILE* const f = fopen(fileName, "rb");
195*01826a49SYabin Cui if (isDirectory(fileName)) {
196*01826a49SYabin Cui fprintf(stderr, "Ignoring %s directory \n", fileName);
197*01826a49SYabin Cui exit(2);
198*01826a49SYabin Cui }
199*01826a49SYabin Cui if (f==NULL) {
200*01826a49SYabin Cui fprintf(stderr, "Impossible to open %s \n", fileName);
201*01826a49SYabin Cui exit(3);
202*01826a49SYabin Cui }
203*01826a49SYabin Cui { size_t const readSize = fread(buffer, 1, fileSize, f);
204*01826a49SYabin Cui if (readSize != fileSize) {
205*01826a49SYabin Cui fprintf(stderr, "Error reading %s \n", fileName);
206*01826a49SYabin Cui exit(5);
207*01826a49SYabin Cui } }
208*01826a49SYabin Cui fclose(f);
209*01826a49SYabin Cui }
210*01826a49SYabin Cui
211*01826a49SYabin Cui
fileCheck(const char * fileName,int testCCtxParams)212*01826a49SYabin Cui static void fileCheck(const char* fileName, int testCCtxParams)
213*01826a49SYabin Cui {
214*01826a49SYabin Cui size_t const fileSize = getFileSize(fileName);
215*01826a49SYabin Cui void* const buffer = malloc(fileSize + !fileSize /* avoid 0 */);
216*01826a49SYabin Cui if (!buffer) {
217*01826a49SYabin Cui fprintf(stderr, "not enough memory \n");
218*01826a49SYabin Cui exit(4);
219*01826a49SYabin Cui }
220*01826a49SYabin Cui loadFile(buffer, fileName, fileSize);
221*01826a49SYabin Cui roundTripCheck(buffer, fileSize, testCCtxParams);
222*01826a49SYabin Cui free (buffer);
223*01826a49SYabin Cui }
224*01826a49SYabin Cui
main(int argCount,const char ** argv)225*01826a49SYabin Cui int main(int argCount, const char** argv) {
226*01826a49SYabin Cui int argNb = 1;
227*01826a49SYabin Cui int testCCtxParams = 0;
228*01826a49SYabin Cui if (argCount < 2) {
229*01826a49SYabin Cui fprintf(stderr, "Error : no argument : need input file \n");
230*01826a49SYabin Cui exit(9);
231*01826a49SYabin Cui }
232*01826a49SYabin Cui
233*01826a49SYabin Cui if (!strcmp(argv[argNb], "--cctxParams")) {
234*01826a49SYabin Cui testCCtxParams = 1;
235*01826a49SYabin Cui argNb++;
236*01826a49SYabin Cui }
237*01826a49SYabin Cui
238*01826a49SYabin Cui fileCheck(argv[argNb], testCCtxParams);
239*01826a49SYabin Cui fprintf(stderr, "no pb detected\n");
240*01826a49SYabin Cui return 0;
241*01826a49SYabin Cui }
242