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