From 279aa4829507ccd45cee00bc3c908a922d25adc6 Mon Sep 17 00:00:00 2001 From: notaz Date: Fri, 15 Nov 2024 01:40:02 +0200 Subject: [PATCH] git subrepo pull (merge) deps/libchdr 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 | 4 +- deps/libchdr/CMakeLists.txt | 24 ++- deps/libchdr/include/libchdr/huffman.h | 2 +- deps/libchdr/src/libchdr_chd.c | 193 ++++++++++++++++++++----- deps/libchdr/src/libchdr_huffman.c | 32 +++- deps/libchdr/tests/CMakeLists.txt | 10 ++ deps/libchdr/tests/fuzz.c | 80 ++++++++++ 7 files changed, 294 insertions(+), 51 deletions(-) create mode 100644 deps/libchdr/tests/fuzz.c diff --git a/deps/libchdr/.gitrepo b/deps/libchdr/.gitrepo index 29ed4a17..a634b234 100644 --- a/deps/libchdr/.gitrepo +++ b/deps/libchdr/.gitrepo @@ -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 diff --git a/deps/libchdr/CMakeLists.txt b/deps/libchdr/CMakeLists.txt index a9625d43..fde6fafa 100644 --- a/deps/libchdr/CMakeLists.txt +++ b/deps/libchdr/CMakeLists.txt @@ -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 #-------------------------------------------------- diff --git a/deps/libchdr/include/libchdr/huffman.h b/deps/libchdr/include/libchdr/huffman.h index 6c9f5113..d771c299 100644 --- a/deps/libchdr/include/libchdr/huffman.h +++ b/deps/libchdr/include/libchdr/huffman.h @@ -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 diff --git a/deps/libchdr/src/libchdr_chd.c b/deps/libchdr/src/libchdr_chd.c index 69d8a4f0..19e9c4d5 100644 --- a/deps/libchdr/src/libchdr_chd.c +++ b/deps/libchdr/src/libchdr_chd.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -88,6 +89,11 @@ #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; diff --git a/deps/libchdr/src/libchdr_huffman.c b/deps/libchdr/src/libchdr_huffman.c index 556aa346..2332104b 100644 --- a/deps/libchdr/src/libchdr_huffman.c +++ b/deps/libchdr/src/libchdr_huffman.c @@ -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; } diff --git a/deps/libchdr/tests/CMakeLists.txt b/deps/libchdr/tests/CMakeLists.txt index f1004832..b4da3ed1 100644 --- a/deps/libchdr/tests/CMakeLists.txt +++ b/deps/libchdr/tests/CMakeLists.txt @@ -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 "$" "-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 index 00000000..42a84e8b --- /dev/null +++ b/deps/libchdr/tests/fuzz.c @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include + +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; +} -- 2.39.5