git subrepo pull (merge) deps/libchdr
authornotaz <notasas@gmail.com>
Thu, 14 Nov 2024 23:40:02 +0000 (01:40 +0200)
committernotaz <notasas@gmail.com>
Thu, 14 Nov 2024 23:40:02 +0000 (01:40 +0200)
subrepo:
  subdir:   "deps/libchdr"
  merged:   "b3974651"
upstream:
  origin:   "https://github.com/rtissera/libchdr"
  branch:   "master"
  commit:   "b3974651"
git-subrepo:
  version:  "0.4.9"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "57de7d6"

deps/libchdr/.gitrepo
deps/libchdr/CMakeLists.txt
deps/libchdr/include/libchdr/huffman.h
deps/libchdr/src/libchdr_chd.c
deps/libchdr/src/libchdr_huffman.c
deps/libchdr/tests/CMakeLists.txt
deps/libchdr/tests/fuzz.c [new file with mode: 0644]

index 29ed4a1..a634b23 100644 (file)
@@ -6,7 +6,7 @@
 [subrepo]
        remote = https://github.com/rtissera/libchdr
        branch = master
-       commit = aaca599e18e43933fc193bd1b715c368c306208b
-       parent = e22f9f69d8191f859006cfa7d111423e18a41cd7
+       commit = b3974651d869c2f804e9879b063c23280d2ae617
+       parent = 39999efa35f7dd088e24c1e03cdb033d7add02af
        method = merge
        cmdver = 0.4.9
index a9625d4..fde6faf 100644 (file)
@@ -7,6 +7,7 @@ if(CMAKE_PROJECT_NAME STREQUAL "chdr")
 endif()
 option(INSTALL_STATIC_LIBS "Install static libraries" OFF)
 option(WITH_SYSTEM_ZLIB "Use system provided zlib library" OFF)
+option(WITH_SYSTEM_ZSTD "Use system provided zstd library" OFF)
 
 option(BUILD_LTO "Compile libchdr with link-time optimization if supported" OFF)
 if(BUILD_LTO)
@@ -17,6 +18,14 @@ if(BUILD_LTO)
   endif()
 endif()
 
+option(BUILD_FUZZER "Build instrumented binary for fuzzing with libfuzzer, requires clang")
+if(BUILD_FUZZER)
+  # Override CFLAGS early for instrumentation. Disable shared libs for instrumentation.
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,fuzzer-no-link")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,fuzzer-no-link")
+  set(BUILD_SHARED_LIBS OFF)
+endif()
+
 include(GNUInstallDirs)
 
 #--------------------------------------------------
@@ -41,11 +50,16 @@ else()
 endif()
 
 # zstd
-option(ZSTD_BUILD_SHARED "BUILD SHARED LIBRARIES" OFF)
-option(ZSTD_BUILD_PROGRAMS "BUILD PROGRAMS" OFF)
-option(ZSTD_LEGACY_SUPPORT "LEGACY SUPPORT" OFF)
-add_subdirectory(deps/zstd-1.5.6/build/cmake EXCLUDE_FROM_ALL)
-list(APPEND CHDR_LIBS libzstd_static)
+if (WITH_SYSTEM_ZSTD)
+  find_package(zstd REQUIRED)
+  list(APPEND PLATFORM_LIBS zstd::libzstd_shared)
+else()
+  option(ZSTD_BUILD_SHARED "BUILD SHARED LIBRARIES" OFF)
+  option(ZSTD_BUILD_PROGRAMS "BUILD PROGRAMS" OFF)
+  option(ZSTD_LEGACY_SUPPORT "LEGACY SUPPORT" OFF)
+  add_subdirectory(deps/zstd-1.5.6/build/cmake EXCLUDE_FROM_ALL)
+  list(APPEND CHDR_LIBS libzstd_static)
+endif()
 #--------------------------------------------------
 # chdr
 #--------------------------------------------------
index 6c9f511..d771c29 100644 (file)
@@ -85,6 +85,6 @@ int huffman_build_tree(struct huffman_decoder* decoder, uint32_t totaldata, uint
 enum huffman_error huffman_assign_canonical_codes(struct huffman_decoder* decoder);
 enum huffman_error huffman_compute_tree_from_histo(struct huffman_decoder* decoder);
 
-void huffman_build_lookup_table(struct huffman_decoder* decoder);
+enum huffman_error huffman_build_lookup_table(struct huffman_decoder* decoder);
 
 #endif
index 69d8a4f..19e9c4d 100644 (file)
@@ -41,6 +41,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <limits.h>
 #include <time.h>
 
 #include <libchdr/chd.h>
 
 #define CHD_V1_SECTOR_SIZE                     512                     /* size of a "sector" in the V1 header */
 
+#define CHD_MAX_HUNK_SIZE                              (128 * 1024 * 1024) /* hunk size probably shouldn't be more than 128MB */
+
+/* we're currently only using this for CD/DVDs, if we end up with more than 10GB data, it's probably invalid */
+#define CHD_MAX_FILE_SIZE                              (10ULL * 1024 * 1024 * 1024)
+
 #define COOKIE_VALUE                           0xbaadf00d
 #define MAX_ZLIB_ALLOCS                                64
 
@@ -298,6 +304,7 @@ struct _chd_file
        uint32_t                                        cookie;                 /* cookie, should equal COOKIE_VALUE */
 
        core_file *                             file;                   /* handle to the open core file */
+       uint64_t                                file_size;              /* size of the core file */
        chd_header                              header;                 /* header, extracted from file */
 
        chd_file *                              parent;                 /* pointer to parent file, or NULL */
@@ -712,22 +719,39 @@ static chd_error cdlz_codec_decompress(void *codec, const uint8_t *src, uint32_t
 {
        uint32_t framenum;
        cdlz_codec_data* cdlz = (cdlz_codec_data*)codec;
+       chd_error decomp_err;
+       uint32_t complen_base;
 
        /* determine header bytes */
-       uint32_t frames = destlen / CD_FRAME_SIZE;
-       uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
-       uint32_t ecc_bytes = (frames + 7) / 8;
-       uint32_t header_bytes = ecc_bytes + complen_bytes;
+       const uint32_t frames = destlen / CD_FRAME_SIZE;
+       const uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
+       const uint32_t ecc_bytes = (frames + 7) / 8;
+       const uint32_t header_bytes = ecc_bytes + complen_bytes;
+
+       /* input may be truncated, double-check */
+       if (complen < (ecc_bytes + 2))
+               return CHDERR_DECOMPRESSION_ERROR;
 
        /* extract compressed length of base */
-       uint32_t complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
+       complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
        if (complen_bytes > 2)
+       {
+               if (complen < (ecc_bytes + 3))
+                       return CHDERR_DECOMPRESSION_ERROR;
+
                complen_base = (complen_base << 8) | src[ecc_bytes + 2];
+       }
+       if (complen < (header_bytes + complen_base))
+               return CHDERR_DECOMPRESSION_ERROR;
 
        /* reset and decode */
-       lzma_codec_decompress(&cdlz->base_decompressor, &src[header_bytes], complen_base, &cdlz->buffer[0], frames * CD_MAX_SECTOR_DATA);
+       decomp_err = lzma_codec_decompress(&cdlz->base_decompressor, &src[header_bytes], complen_base, &cdlz->buffer[0], frames * CD_MAX_SECTOR_DATA);
+       if (decomp_err != CHDERR_NONE)
+               return decomp_err;
 #ifdef WANT_SUBCODE
-       zlib_codec_decompress(&cdlz->subcode_decompressor, &src[header_bytes + complen_base], complen - complen_base - header_bytes, &cdlz->buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA);
+       decomp_err = zlib_codec_decompress(&cdlz->subcode_decompressor, &src[header_bytes + complen_base], complen - complen_base - header_bytes, &cdlz->buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA);
+       if (decomp_err != CHDERR_NONE)
+               return decomp_err;
 #endif
 
        /* reassemble the data */
@@ -795,22 +819,39 @@ static chd_error cdzl_codec_decompress(void *codec, const uint8_t *src, uint32_t
 {
        uint32_t framenum;
        cdzl_codec_data* cdzl = (cdzl_codec_data*)codec;
+       chd_error decomp_err;
+       uint32_t complen_base;
 
        /* determine header bytes */
-       uint32_t frames = destlen / CD_FRAME_SIZE;
-       uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
-       uint32_t ecc_bytes = (frames + 7) / 8;
-       uint32_t header_bytes = ecc_bytes + complen_bytes;
+       const uint32_t frames = destlen / CD_FRAME_SIZE;
+       const uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
+       const uint32_t ecc_bytes = (frames + 7) / 8;
+       const uint32_t header_bytes = ecc_bytes + complen_bytes;
+
+       /* input may be truncated, double-check */
+       if (complen < (ecc_bytes + 2))
+               return CHDERR_DECOMPRESSION_ERROR;
 
        /* extract compressed length of base */
-       uint32_t complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
+       complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
        if (complen_bytes > 2)
+       {
+               if (complen < (ecc_bytes + 3))
+                       return CHDERR_DECOMPRESSION_ERROR;
+
                complen_base = (complen_base << 8) | src[ecc_bytes + 2];
+       }
+       if (complen < (header_bytes + complen_base))
+               return CHDERR_DECOMPRESSION_ERROR;
 
        /* reset and decode */
-       zlib_codec_decompress(&cdzl->base_decompressor, &src[header_bytes], complen_base, &cdzl->buffer[0], frames * CD_MAX_SECTOR_DATA);
+       decomp_err = zlib_codec_decompress(&cdzl->base_decompressor, &src[header_bytes], complen_base, &cdzl->buffer[0], frames * CD_MAX_SECTOR_DATA);
+       if (decomp_err != CHDERR_NONE)
+               return decomp_err;
 #ifdef WANT_SUBCODE
-       zlib_codec_decompress(&cdzl->subcode_decompressor, &src[header_bytes + complen_base], complen - complen_base - header_bytes, &cdzl->buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA);
+       decomp_err = zlib_codec_decompress(&cdzl->subcode_decompressor, &src[header_bytes + complen_base], complen - complen_base - header_bytes, &cdzl->buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA);
+       if (decomp_err != CHDERR_NONE)
+               return decomp_err;
 #endif
 
        /* reassemble the data */
@@ -1155,22 +1196,39 @@ static chd_error cdzs_codec_decompress(void *codec, const uint8_t *src, uint32_t
 {
        uint32_t framenum;
        cdzs_codec_data* cdzs = (cdzs_codec_data*)codec;
+       chd_error decomp_err;
+       uint32_t complen_base;
 
        /* determine header bytes */
-       uint32_t frames = destlen / CD_FRAME_SIZE;
-       uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
-       uint32_t ecc_bytes = (frames + 7) / 8;
-       uint32_t header_bytes = ecc_bytes + complen_bytes;
+       const uint32_t frames = destlen / CD_FRAME_SIZE;
+       const uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
+       const uint32_t ecc_bytes = (frames + 7) / 8;
+       const uint32_t header_bytes = ecc_bytes + complen_bytes;
+
+       /* input may be truncated, double-check */
+       if (complen < (ecc_bytes + 2))
+               return CHDERR_DECOMPRESSION_ERROR;
 
        /* extract compressed length of base */
-       uint32_t complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
+       complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
        if (complen_bytes > 2)
+       {
+               if (complen < (ecc_bytes + 3))
+                       return CHDERR_DECOMPRESSION_ERROR;
+
                complen_base = (complen_base << 8) | src[ecc_bytes + 2];
+       }
+       if (complen < (header_bytes + complen_base))
+               return CHDERR_DECOMPRESSION_ERROR;
 
        /* reset and decode */
-       zstd_codec_decompress(&cdzs->base_decompressor, &src[header_bytes], complen_base, &cdzs->buffer[0], frames * CD_MAX_SECTOR_DATA);
+       decomp_err = zstd_codec_decompress(&cdzs->base_decompressor, &src[header_bytes], complen_base, &cdzs->buffer[0], frames * CD_MAX_SECTOR_DATA);
+       if (decomp_err != CHDERR_NONE)
+               return decomp_err;
 #ifdef WANT_SUBCODE
-       zstd_codec_decompress(&cdzs->subcode_decompressor, &src[header_bytes + complen_base], complen - complen_base - header_bytes, &cdzs->buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA);
+       decomp_err = zstd_codec_decompress(&cdzs->subcode_decompressor, &src[header_bytes + complen_base], complen - complen_base - header_bytes, &cdzs->buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA);
+       if (decomp_err != CHDERR_NONE)
+               return decomp_err;
 #endif
 
        /* reassemble the data */
@@ -1491,6 +1549,11 @@ static inline void map_assemble(uint8_t *base, map_entry *entry)
 -------------------------------------------------*/
 static inline int map_size_v5(chd_header* header)
 {
+       // Avoid overflow due to corrupted data.
+       const uint32_t max_hunkcount = (UINT32_MAX / header->mapentrybytes);
+       if (header->hunkcount > max_hunkcount)
+               return -1;
+
        return header->hunkcount * header->mapentrybytes;
 }
 
@@ -1575,11 +1638,16 @@ static chd_error decompress_v5_map(chd_file* chd, chd_header* header)
        uint8_t rawbuf[16];
        struct huffman_decoder* decoder;
        enum huffman_error err;
-       uint64_t curoffset;     
+       uint64_t curoffset;
        int rawmapsize = map_size_v5(header);
+       if (rawmapsize < 0)
+               return CHDERR_INVALID_FILE;
 
        if (!chd_compressed(header))
        {
+               if ((header->mapoffset + rawmapsize) >= chd->file_size || (header->mapoffset + rawmapsize) < header->mapoffset)
+                       return CHDERR_INVALID_FILE;
+
                header->rawmap = (uint8_t*)malloc(rawmapsize);
                if (header->rawmap == NULL)
                        return CHDERR_OUT_OF_MEMORY;
@@ -1599,6 +1667,8 @@ static chd_error decompress_v5_map(chd_file* chd, chd_header* header)
        parentbits = rawbuf[14];
 
        /* now read the map */
+       if ((header->mapoffset + mapbytes) < header->mapoffset || (header->mapoffset + mapbytes) >= chd->file_size)
+               return CHDERR_INVALID_FILE;
        compressed_ptr = (uint8_t*)malloc(sizeof(uint8_t) * mapbytes);
        if (compressed_ptr == NULL)
                return CHDERR_OUT_OF_MEMORY;
@@ -1638,7 +1708,16 @@ static chd_error decompress_v5_map(chd_file* chd, chd_header* header)
                        rawmap[0] = lastcomp, repcount--;
                else
                {
-                       uint8_t val = huffman_decode_one(decoder, bitbuf);
+                       uint8_t val;
+                       if (bitstream_overflow(bitbuf))
+                       {
+                               free(compressed_ptr);
+                               free(bitbuf);
+                               delete_huffman_decoder(decoder);
+                               return CHDERR_DECOMPRESSION_ERROR;
+                       }
+
+                       val = huffman_decode_one(decoder, bitbuf);
                        if (val == COMPRESSION_RLE_SMALL)
                                rawmap[0] = lastcomp, repcount = 2 + huffman_decode_one(decoder, bitbuf);
                        else if (val == COMPRESSION_RLE_LARGE)
@@ -1788,6 +1867,9 @@ CHD_EXPORT chd_error chd_open_core_file(core_file *file, int mode, chd_file *par
        newchd->cookie = COOKIE_VALUE;
        newchd->parent = parent;
        newchd->file = file;
+       newchd->file_size = core_fsize(file);
+       if ((int64_t)newchd->file_size <= 0)
+               EARLY_EXIT(err = CHDERR_INVALID_FILE);
 
        /* now attempt to read the header */
        err = header_read(newchd, &newchd->header);
@@ -1888,7 +1970,8 @@ CHD_EXPORT chd_error chd_open_core_file(core_file *file, int mode, chd_file *par
        }
        else
        {
-               int decompnum;
+               int decompnum, needsinit;
+
                /* verify the compression types and initialize the codecs */
                for (decompnum = 0; decompnum < ARRAY_LENGTH(newchd->header.compression); decompnum++)
                {
@@ -1905,8 +1988,21 @@ CHD_EXPORT chd_error chd_open_core_file(core_file *file, int mode, chd_file *par
                        if (newchd->codecintf[decompnum] == NULL && newchd->header.compression[decompnum] != 0)
                                EARLY_EXIT(err = CHDERR_UNSUPPORTED_FORMAT);
 
+                       /* ensure we don't try to initialize the same codec twice */
+                       /* this is "normal" for chds where the user overrides the codecs, it'll have none repeated */
+                       needsinit = (newchd->codecintf[decompnum]->init != NULL);
+                       for (i = 0; i < decompnum; i++)
+                       {
+                               if (newchd->codecintf[decompnum] == newchd->codecintf[i])
+                               {
+                                       /* already initialized */
+                                       needsinit = 0;
+                                       break;
+                               }
+      }
+
                        /* initialize the codec */
-                       if (newchd->codecintf[decompnum]->init != NULL)
+                       if (needsinit)
                        {
                                void* codec = NULL;
                                switch (newchd->header.compression[decompnum])
@@ -1976,19 +2072,15 @@ cleanup:
 CHD_EXPORT chd_error chd_precache(chd_file *chd)
 {
        int64_t count;
-       uint64_t size;
 
        if (chd->file_cache == NULL)
        {
-               size = core_fsize(chd->file);
-               if ((int64_t)size <= 0)
-                       return CHDERR_INVALID_DATA;
-               chd->file_cache = malloc(size);
+               chd->file_cache = malloc(chd->file_size);
                if (chd->file_cache == NULL)
                        return CHDERR_OUT_OF_MEMORY;
                core_fseek(chd->file, 0, SEEK_SET);
-               count = core_fread(chd->file, chd->file_cache, size);
-               if (count != size)
+               count = core_fread(chd->file, chd->file_cache, chd->file_size);
+               if (count != chd->file_size)
                {
                        free(chd->file_cache);
                        chd->file_cache = NULL;
@@ -2066,10 +2158,24 @@ CHD_EXPORT void chd_close(chd_file *chd)
                for (i = 0 ; i < ARRAY_LENGTH(chd->codecintf); i++)
                {
                        void* codec = NULL;
+                       int j, needsfree;
 
                        if (chd->codecintf[i] == NULL)
                                continue;
 
+                       /* only free each codec at max once */
+                       needsfree = 1;
+                       for (j = 0; j < i; j++)
+                       {
+                               if (chd->codecintf[i] == chd->codecintf[j])
+                               {
+                                       needsfree = 0;
+                                       break;
+                               }
+                       }
+                       if (!needsfree)
+                               continue;
+
                        switch (chd->codecintf[i]->compression)
                        {
                                case CHD_CODEC_ZLIB:
@@ -2306,7 +2412,7 @@ CHD_EXPORT chd_error chd_get_metadata(chd_file *chd, uint32_t searchtag, uint32_
                        uint32_t faux_length;
 
                        /* fill in the faux metadata */
-                       sprintf(faux_metadata, HARD_DISK_METADATA_FORMAT, chd->header.obsolete_cylinders, chd->header.obsolete_heads, chd->header.obsolete_sectors, chd->header.hunkbytes / chd->header.obsolete_hunksize);
+                       sprintf(faux_metadata, HARD_DISK_METADATA_FORMAT, chd->header.obsolete_cylinders, chd->header.obsolete_heads, chd->header.obsolete_sectors, (chd->header.obsolete_hunksize != 0) ? (chd->header.hunkbytes / chd->header.obsolete_hunksize) : 0);
                        faux_length = (uint32_t)strlen(faux_metadata) + 1;
 
                        /* copy the metadata itself */
@@ -2428,6 +2534,10 @@ static chd_error header_validate(const chd_header *header)
                        return CHDERR_INVALID_PARAMETER;
        }
 
+       /* some basic size checks to prevent huge mallocs */
+       if (header->hunkbytes >= CHD_MAX_HUNK_SIZE || ((uint64_t)header->hunkbytes * (uint64_t)header->totalhunks) >= CHD_MAX_FILE_SIZE)
+               return CHDERR_INVALID_PARAMETER;
+
        return CHDERR_NONE;
 }
 
@@ -2621,10 +2731,17 @@ static uint8_t* hunk_read_compressed(chd_file *chd, uint64_t offset, size_t size
 #endif
        if (chd->file_cache != NULL)
        {
-               return chd->file_cache + offset;
+               if ((offset + size) > chd->file_size || (offset + size) < offset)
+                       return NULL;
+               else
+                       return chd->file_cache + offset;
        }
        else
        {
+               /* make sure it isn't larger than the compressed buffer */
+               if (size > chd->header.hunkbytes)
+                       return NULL;
+
                core_fseek(chd->file, offset, SEEK_SET);
                bytes = core_fread(chd->file, chd->compressed, size);
                if (bytes != size)
@@ -2647,6 +2764,9 @@ static chd_error hunk_read_uncompressed(chd_file *chd, uint64_t offset, size_t s
 #endif
        if (chd->file_cache != NULL)
        {
+               if ((offset + size) > chd->file_size || (offset + size) < offset)
+                       return CHDERR_READ_ERROR;
+
                memcpy(dest, chd->file_cache + offset, size);
        }
        else
@@ -2989,7 +3109,7 @@ static chd_error map_read(chd_file *chd)
        }
 
        /* verify the length */
-       if (maxoffset > core_fsize(chd->file))
+       if (maxoffset > chd->file_size)
        {
                err = CHDERR_INVALID_FILE;
                goto cleanup;
@@ -3024,7 +3144,8 @@ static chd_error metadata_find_entry(chd_file *chd, uint32_t metatag, uint32_t m
                uint32_t        count;
 
                /* read the raw header */
-               core_fseek(chd->file, metaentry->offset, SEEK_SET);
+               if (core_fseek(chd->file, metaentry->offset, SEEK_SET) != 0)
+                       break;
                count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header));
                if (count != sizeof(raw_meta_header))
                        break;
index 556aa34..2332104 100644 (file)
@@ -230,7 +230,9 @@ enum huffman_error huffman_import_tree_rle(struct huffman_decoder* decoder, stru
                return error;
 
        /* build the lookup table */
-       huffman_build_lookup_table(decoder);
+       error = huffman_build_lookup_table(decoder);
+       if (error != HUFFERR_NONE)
+               return error;
 
        /* determine final input length and report errors */
        return bitstream_overflow(bitbuf) ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE;
@@ -271,8 +273,16 @@ enum huffman_error huffman_import_tree_huffman(struct huffman_decoder* decoder,
        /* then regenerate the tree */
        error = huffman_assign_canonical_codes(smallhuff);
        if (error != HUFFERR_NONE)
+       {
+               delete_huffman_decoder(smallhuff);
+               return error;
+       }
+       error = huffman_build_lookup_table(smallhuff);
+       if (error != HUFFERR_NONE)
+       {
+               delete_huffman_decoder(smallhuff);
                return error;
-       huffman_build_lookup_table(smallhuff);
+       }
 
        /* determine the maximum length of an RLE count */
        temp = decoder->numcodes - 9;
@@ -308,7 +318,9 @@ enum huffman_error huffman_import_tree_huffman(struct huffman_decoder* decoder,
                return error;
 
        /* build the lookup table */
-       huffman_build_lookup_table(decoder);
+       error = huffman_build_lookup_table(decoder);
+       if (error != HUFFERR_NONE)
+               return error;
 
        /* determine final input length and report errors */
        return bitstream_overflow(bitbuf) ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE;
@@ -523,8 +535,9 @@ enum huffman_error huffman_assign_canonical_codes(struct huffman_decoder* decode
  *-------------------------------------------------
  */
 
-void huffman_build_lookup_table(struct huffman_decoder* decoder)
+enum huffman_error huffman_build_lookup_table(struct huffman_decoder* decoder)
 {
+       const lookup_value* lookupend = &decoder->lookup[(1u << decoder->maxbits)];
        uint32_t curcode;
        /* iterate over all codes */
        for (curcode = 0; curcode < decoder->numcodes; curcode++)
@@ -533,9 +546,10 @@ void huffman_build_lookup_table(struct huffman_decoder* decoder)
                struct node_t* node = &decoder->huffnode[curcode];
                if (node->numbits > 0)
                {
-         int shift;
-         lookup_value *dest;
-         lookup_value *destend;
+                       int shift;
+                       lookup_value *dest;
+                       lookup_value *destend;
+
                        /* set up the entry */
                        lookup_value value = MAKE_LOOKUP(curcode, node->numbits);
 
@@ -543,8 +557,12 @@ void huffman_build_lookup_table(struct huffman_decoder* decoder)
                        shift = decoder->maxbits - node->numbits;
                        dest = &decoder->lookup[node->bits << shift];
                        destend = &decoder->lookup[((node->bits + 1) << shift) - 1];
+                       if (dest >= lookupend || destend >= lookupend || destend < dest)
+                               return HUFFERR_INTERNAL_INCONSISTENCY;
                        while (dest <= destend)
                                *dest++ = value;
                }
        }
+
+       return HUFFERR_NONE;
 }
index f100483..b4da3ed 100644 (file)
@@ -1,2 +1,12 @@
 add_executable(chdr-benchmark benchmark.c)
 target_link_libraries(chdr-benchmark PRIVATE chdr-static)
+
+# fuzzing
+if(BUILD_FUZZER)
+  add_executable(chdr-fuzz fuzz.c)
+  target_link_options(chdr-fuzz PRIVATE "-fsanitize=address,fuzzer")
+  target_link_libraries(chdr-fuzz PRIVATE chdr-static)
+  add_custom_target(fuzz
+    COMMAND "$<TARGET_FILE:chdr-fuzz>" "-max_len=131072"
+    DEPENDS chdr-fuzz)
+endif()
diff --git a/deps/libchdr/tests/fuzz.c b/deps/libchdr/tests/fuzz.c
new file mode 100644 (file)
index 0000000..42a84e8
--- /dev/null
@@ -0,0 +1,80 @@
+#include <libchdr/chd.h>
+#include <libchdr/coretypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+  const uint8_t *buffer;
+  size_t buffer_size;
+  size_t buffer_pos;
+} membuf;
+
+static uint64_t membuf_fsize(struct chd_core_file *cf) {
+  return ((membuf *)cf->argp)->buffer_size;
+}
+
+static size_t membuf_fread(void *buf, size_t size, size_t count,
+                           struct chd_core_file *cf) {
+  membuf *mb = (membuf *)cf->argp;
+  if ((UINT32_MAX / size) < count)
+    return 0;
+  size_t copy = size * count;
+  size_t remain = mb->buffer_size - mb->buffer_pos;
+  if (remain < copy)
+    copy = remain;
+  memcpy(buf, &mb->buffer[mb->buffer_pos], copy);
+  mb->buffer_pos += copy;
+  return copy;
+}
+
+static int membuf_fclose(struct chd_core_file *cf) { return 0; }
+
+static int membuf_fseek(struct chd_core_file *cf, int64_t pos, int origin) {
+  membuf *mb = (membuf *)cf->argp;
+  if (origin == SEEK_SET) {
+    if (pos < 0 || (size_t)pos > mb->buffer_size)
+      return -1;
+    mb->buffer_pos = (size_t)pos;
+    return 0;
+  } else if (origin == SEEK_CUR) {
+    if (pos < 0 && (size_t)-pos > mb->buffer_pos)
+      return -1;
+    else if ((mb->buffer_pos + (size_t)pos) > mb->buffer_size)
+      return -1;
+    mb->buffer_pos =
+        (pos < 0) ? (mb->buffer_pos - (size_t)-pos) : (mb->buffer_pos + pos);
+    return 0;
+  } else if (origin == SEEK_END) {
+    mb->buffer_pos = mb->buffer_size;
+    return 0;
+  } else {
+    return -1;
+  }
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+  unsigned int i;
+  unsigned int totalbytes;
+  void *buffer;
+  membuf mb = {data, size, 0u};
+  struct chd_core_file cf = {&mb, membuf_fsize, membuf_fread, membuf_fclose,
+                             membuf_fseek};
+  chd_file *file;
+  const chd_header *header;
+  chd_error err = chd_open_core_file(&cf, CHD_OPEN_READ, NULL, &file);
+  if (err != CHDERR_NONE)
+    return 0;
+
+  header = chd_get_header(file);
+  totalbytes = header->hunkbytes * header->totalhunks;
+  buffer = malloc(header->hunkbytes);
+  for (i = 0; i < header->totalhunks; i++) {
+    err = chd_read(file, i, buffer);
+    if (err != CHDERR_NONE)
+      continue;
+  }
+  free(buffer);
+  chd_close(file);
+  return 0;
+}