1 /*
2 * Copyright (c) 2016-2020, Yann Collet, Facebook, Inc.
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 program takes a file in input,
13 * performs an LZ4 round-trip test (compress + decompress)
14 * compares the result with original
15 * and generates an abort() on corruption detection,
16 * in order for afl to register the event as a crash.
17 */
18
19
20 /*===========================================
21 * Tuning Constant
22 *==========================================*/
23 #ifndef MIN_CLEVEL
24 # define MIN_CLEVEL (int)(-5)
25 #endif
26
27
28
29 /*===========================================
30 * Dependencies
31 *==========================================*/
32 #include <stddef.h> /* size_t */
33 #include <stdlib.h> /* malloc, free, exit */
34 #include <stdio.h> /* fprintf */
35 #include <string.h> /* strcmp */
36 #include <assert.h>
37 #include <sys/types.h> /* stat */
38 #include <sys/stat.h> /* stat */
39 #include "xxhash.h"
40
41 #include "lz4.h"
42 #include "lz4hc.h"
43
44
45 /*===========================================
46 * Macros
47 *==========================================*/
48 #define MIN(a,b) ( (a) < (b) ? (a) : (b) )
49
50 #define MSG(...) fprintf(stderr, __VA_ARGS__)
51
52 #define CONTROL_MSG(c, ...) { \
53 if ((c)) { \
54 MSG(__VA_ARGS__); \
55 MSG(" \n"); \
56 abort(); \
57 } \
58 }
59
60
checkBuffers(const void * buff1,const void * buff2,size_t buffSize)61 static size_t checkBuffers(const void* buff1, const void* buff2, size_t buffSize)
62 {
63 const char* const ip1 = (const char*)buff1;
64 const char* const ip2 = (const char*)buff2;
65 size_t pos;
66
67 for (pos=0; pos<buffSize; pos++)
68 if (ip1[pos]!=ip2[pos])
69 break;
70
71 return pos;
72 }
73
74
75 /* select a compression level
76 * based on first bytes present in a reference buffer */
select_clevel(const void * refBuff,size_t refBuffSize)77 static int select_clevel(const void* refBuff, size_t refBuffSize)
78 {
79 const int minCLevel = MIN_CLEVEL;
80 const int maxClevel = LZ4HC_CLEVEL_MAX;
81 const int cLevelSpan = maxClevel - minCLevel;
82 size_t const hashLength = MIN(16, refBuffSize);
83 unsigned const h32 = XXH32(refBuff, hashLength, 0);
84 int const randL = h32 % (cLevelSpan+1);
85
86 return minCLevel + randL;
87 }
88
89
90 typedef int (*compressFn)(const char* src, char* dst, int srcSize, int dstSize, int cLevel);
91
92
93 /** roundTripTest() :
94 * Compresses `srcBuff` into `compressedBuff`,
95 * then decompresses `compressedBuff` into `resultBuff`.
96 * If clevel==0, compression level is derived from srcBuff's content head bytes.
97 * This function abort() if it detects any round-trip error.
98 * Therefore, if it returns, round trip is considered successfully validated.
99 * Note : `compressedBuffCapacity` should be `>= LZ4_compressBound(srcSize)`
100 * for compression to be guaranteed to work */
roundTripTest(void * resultBuff,size_t resultBuffCapacity,void * compressedBuff,size_t compressedBuffCapacity,const void * srcBuff,size_t srcSize,int clevel)101 static void roundTripTest(void* resultBuff, size_t resultBuffCapacity,
102 void* compressedBuff, size_t compressedBuffCapacity,
103 const void* srcBuff, size_t srcSize,
104 int clevel)
105 {
106 int const proposed_clevel = clevel ? clevel : select_clevel(srcBuff, srcSize);
107 int const selected_clevel = proposed_clevel < 0 ? -proposed_clevel : proposed_clevel; /* if level < 0, it becomes an accelearion value */
108 compressFn compress = selected_clevel >= LZ4HC_CLEVEL_MIN ? LZ4_compress_HC : LZ4_compress_fast;
109 int const cSize = compress((const char*)srcBuff, (char*)compressedBuff, (int)srcSize, (int)compressedBuffCapacity, selected_clevel);
110 CONTROL_MSG(cSize == 0, "Compression error !");
111
112 { int const dSize = LZ4_decompress_safe((const char*)compressedBuff, (char*)resultBuff, cSize, (int)resultBuffCapacity);
113 CONTROL_MSG(dSize < 0, "Decompression detected an error !");
114 CONTROL_MSG(dSize != (int)srcSize, "Decompression corruption error : wrong decompressed size !");
115 }
116
117 /* check potential content corruption error */
118 assert(resultBuffCapacity >= srcSize);
119 { size_t const errorPos = checkBuffers(srcBuff, resultBuff, srcSize);
120 CONTROL_MSG(errorPos != srcSize,
121 "Silent decoding corruption, at pos %u !!!",
122 (unsigned)errorPos);
123 }
124
125 }
126
roundTripCheck(const void * srcBuff,size_t srcSize,int clevel)127 static void roundTripCheck(const void* srcBuff, size_t srcSize, int clevel)
128 {
129 size_t const cBuffSize = LZ4_compressBound((int)srcSize);
130 void* const cBuff = malloc(cBuffSize);
131 void* const rBuff = malloc(cBuffSize);
132
133 if (!cBuff || !rBuff) {
134 fprintf(stderr, "not enough memory ! \n");
135 exit(1);
136 }
137
138 roundTripTest(rBuff, cBuffSize,
139 cBuff, cBuffSize,
140 srcBuff, srcSize,
141 clevel);
142
143 free(rBuff);
144 free(cBuff);
145 }
146
147
getFileSize(const char * infilename)148 static size_t getFileSize(const char* infilename)
149 {
150 int r;
151 #if defined(_MSC_VER)
152 struct _stat64 statbuf;
153 r = _stat64(infilename, &statbuf);
154 if (r || !(statbuf.st_mode & S_IFREG)) return 0; /* No good... */
155 #else
156 struct stat statbuf;
157 r = stat(infilename, &statbuf);
158 if (r || !S_ISREG(statbuf.st_mode)) return 0; /* No good... */
159 #endif
160 return (size_t)statbuf.st_size;
161 }
162
163
isDirectory(const char * infilename)164 static int isDirectory(const char* infilename)
165 {
166 int r;
167 #if defined(_MSC_VER)
168 struct _stat64 statbuf;
169 r = _stat64(infilename, &statbuf);
170 if (!r && (statbuf.st_mode & _S_IFDIR)) return 1;
171 #else
172 struct stat statbuf;
173 r = stat(infilename, &statbuf);
174 if (!r && S_ISDIR(statbuf.st_mode)) return 1;
175 #endif
176 return 0;
177 }
178
179
180 /** loadFile() :
181 * requirement : `buffer` size >= `fileSize` */
loadFile(void * buffer,const char * fileName,size_t fileSize)182 static void loadFile(void* buffer, const char* fileName, size_t fileSize)
183 {
184 FILE* const f = fopen(fileName, "rb");
185 if (isDirectory(fileName)) {
186 MSG("Ignoring %s directory \n", fileName);
187 exit(2);
188 }
189 if (f==NULL) {
190 MSG("Impossible to open %s \n", fileName);
191 exit(3);
192 }
193 { size_t const readSize = fread(buffer, 1, fileSize, f);
194 if (readSize != fileSize) {
195 MSG("Error reading %s \n", fileName);
196 exit(5);
197 } }
198 fclose(f);
199 }
200
201
fileCheck(const char * fileName,int clevel)202 static void fileCheck(const char* fileName, int clevel)
203 {
204 size_t const fileSize = getFileSize(fileName);
205 void* const buffer = malloc(fileSize + !fileSize /* avoid 0 */);
206 if (!buffer) {
207 MSG("not enough memory \n");
208 exit(4);
209 }
210 loadFile(buffer, fileName, fileSize);
211 roundTripCheck(buffer, fileSize, clevel);
212 free (buffer);
213 }
214
215
bad_usage(const char * exeName)216 int bad_usage(const char* exeName)
217 {
218 MSG(" \n");
219 MSG("bad usage: \n");
220 MSG(" \n");
221 MSG("%s [Options] fileName \n", exeName);
222 MSG(" \n");
223 MSG("Options: \n");
224 MSG("-# : use #=[0-9] compression level (default:0 == random) \n");
225 return 1;
226 }
227
228
main(int argCount,const char ** argv)229 int main(int argCount, const char** argv)
230 {
231 const char* const exeName = argv[0];
232 int argNb = 1;
233 int clevel = 0;
234
235 assert(argCount >= 1);
236 if (argCount < 2) return bad_usage(exeName);
237
238 if (argv[1][0] == '-') {
239 clevel = argv[1][1] - '0';
240 argNb = 2;
241 }
242
243 if (argNb >= argCount) return bad_usage(exeName);
244
245 fileCheck(argv[argNb], clevel);
246 MSG("no pb detected \n");
247 return 0;
248 }
249