|  | 
|  | 1 | +// Copyright 2025 The Chromium Authors | 
|  | 2 | +// Use of this source code is governed by a BSD-style license that can be | 
|  | 3 | +// found in the LICENSE file. | 
|  | 4 | + | 
|  | 5 | +#include <algorithm> | 
|  | 6 | +#include <cstdint> | 
|  | 7 | +#include <cstring> | 
|  | 8 | +#include <vector> | 
|  | 9 | + | 
|  | 10 | +#include "unzip.h" | 
|  | 11 | + | 
|  | 12 | +// Fuzzer builds often have NDEBUG set, so roll our own assert macro. | 
|  | 13 | +#define ASSERT(cond)                                                           \ | 
|  | 14 | +  do {                                                                         \ | 
|  | 15 | +    if (!(cond)) {                                                             \ | 
|  | 16 | +      fprintf(stderr, "%s:%d Assert failed: %s\n", __FILE__, __LINE__, #cond); \ | 
|  | 17 | +      exit(1);                                                                 \ | 
|  | 18 | +    }                                                                          \ | 
|  | 19 | +  } while (0) | 
|  | 20 | + | 
|  | 21 | +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { | 
|  | 22 | +  // Mock read-only filesystem with only one file, file_data. In the calls | 
|  | 23 | +  // below, 'opaque' points to file_data, and 'strm' points to the file's seek | 
|  | 24 | +  // position, which is heap allocated so that failing to "close" it triggers a | 
|  | 25 | +  // leak error. | 
|  | 26 | +  std::vector<uint8_t> file_data(data, data + size); | 
|  | 27 | +  zlib_filefunc64_def file_func = { | 
|  | 28 | +      .zopen64_file = [](void* opaque, const void* filename, | 
|  | 29 | +                         int mode) -> void* { | 
|  | 30 | +        ASSERT(mode == (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_EXISTING)); | 
|  | 31 | +        return new size_t(0); | 
|  | 32 | +      }, | 
|  | 33 | +      .zread_file = [](void* opaque, void* strm, void* buf, | 
|  | 34 | +                       uLong size) -> uLong { | 
|  | 35 | +        std::vector<uint8_t>* vec = static_cast<std::vector<uint8_t>*>(opaque); | 
|  | 36 | +        size_t* pos = static_cast<size_t*>(strm); | 
|  | 37 | +        if (*pos >= vec->size()) { | 
|  | 38 | +          return 0; | 
|  | 39 | +        } | 
|  | 40 | +        size = std::min(static_cast<size_t>(size), vec->size() - *pos); | 
|  | 41 | +        memcpy(buf, vec->data() + *pos, size); | 
|  | 42 | +        (*pos) += size; | 
|  | 43 | +        return size; | 
|  | 44 | +      }, | 
|  | 45 | +      .zwrite_file = [](void*, void*, const void*, uLong) -> uLong { | 
|  | 46 | +        ASSERT(0 && "Writing is not supported."); | 
|  | 47 | +        return 0; | 
|  | 48 | +      }, | 
|  | 49 | +      .ztell64_file = [](void*, void* strm) -> ZPOS64_T { | 
|  | 50 | +        return *static_cast<size_t*>(strm); | 
|  | 51 | +      }, | 
|  | 52 | +      .zseek64_file = [](void* opaque, void* strm, ZPOS64_T offset, | 
|  | 53 | +                         int origin) -> long { | 
|  | 54 | +        std::vector<uint8_t>* vec = static_cast<std::vector<uint8_t>*>(opaque); | 
|  | 55 | +        size_t* pos = static_cast<size_t*>(strm); | 
|  | 56 | +        switch (origin) { | 
|  | 57 | +          case ZLIB_FILEFUNC_SEEK_SET: | 
|  | 58 | +            *pos = offset; | 
|  | 59 | +            break; | 
|  | 60 | +          case ZLIB_FILEFUNC_SEEK_CUR: | 
|  | 61 | +            *pos = *pos + offset; | 
|  | 62 | +            break; | 
|  | 63 | +          case ZLIB_FILEFUNC_SEEK_END: | 
|  | 64 | +            *pos = vec->size() + offset; | 
|  | 65 | +            break; | 
|  | 66 | +          default: | 
|  | 67 | +            ASSERT(0 && "Invalid origin"); | 
|  | 68 | +        } | 
|  | 69 | +        return 0; | 
|  | 70 | +      }, | 
|  | 71 | +      .zclose_file = [](void*, void* strm) -> int { | 
|  | 72 | +        delete static_cast<size_t*>(strm); | 
|  | 73 | +        return 0; | 
|  | 74 | +      }, | 
|  | 75 | +      .zerror_file = [](void*, void*) -> int { return 0; }, | 
|  | 76 | +      .opaque = &file_data}; | 
|  | 77 | + | 
|  | 78 | +  unzFile uzf = unzOpen2_64("foo.zip", &file_func); | 
|  | 79 | +  if (uzf == NULL) { | 
|  | 80 | +    return 0; | 
|  | 81 | +  } | 
|  | 82 | + | 
|  | 83 | +  while (true) { | 
|  | 84 | +    unz_file_info64 info = {0}; | 
|  | 85 | + | 
|  | 86 | +    // TODO: Pass nullptrs and different buffer sizes to cover more code. | 
|  | 87 | +    char filename[UINT16_MAX + 1];  // +1 for the null terminator. | 
|  | 88 | +    char extra[UINT16_MAX];         // No null terminator. | 
|  | 89 | +    char comment[UINT16_MAX + 1];   // +1 for the null terminator. | 
|  | 90 | + | 
|  | 91 | +    if (unzGetCurrentFileInfo64(uzf, &info, filename, sizeof(filename), extra, | 
|  | 92 | +                                sizeof(extra), comment, sizeof(comment)) == UNZ_OK) { | 
|  | 93 | +      ASSERT(info.size_filename <= UINT16_MAX); | 
|  | 94 | +      ASSERT(info.size_file_extra <= UINT16_MAX); | 
|  | 95 | +      ASSERT(info.size_file_comment <= UINT16_MAX); | 
|  | 96 | + | 
|  | 97 | +      ASSERT(filename[info.size_filename] == '\0'); | 
|  | 98 | +      ASSERT(comment[info.size_file_comment] == '\0'); | 
|  | 99 | +    } | 
|  | 100 | + | 
|  | 101 | +    if (unzOpenCurrentFile(uzf) == UNZ_OK) { | 
|  | 102 | +      while (true) { | 
|  | 103 | +        char buffer[4096]; | 
|  | 104 | +        int num_read = unzReadCurrentFile(uzf, buffer, sizeof(buffer)); | 
|  | 105 | +        if (num_read <= 0) { | 
|  | 106 | +          break; | 
|  | 107 | +        } | 
|  | 108 | +      } | 
|  | 109 | +      unzCloseCurrentFile(uzf); | 
|  | 110 | +    } | 
|  | 111 | + | 
|  | 112 | +    if (unzGoToNextFile(uzf) != UNZ_OK) { | 
|  | 113 | +      break; | 
|  | 114 | +    } | 
|  | 115 | +  } | 
|  | 116 | + | 
|  | 117 | +  unzClose(uzf); | 
|  | 118 | +  return 0; | 
|  | 119 | +} | 
0 commit comments