diff --git a/CMakeLists.txt b/CMakeLists.txt index f33743fbf..f1bcb1167 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -822,6 +822,9 @@ if (ZLIB_ENABLE_TESTS) add_executable(minigzip test/minigzip.c) configure_test_executable(minigzip) + add_executable(switchlevels test/switchlevels.c) + configure_test_executable(switchlevels) + if(HAVE_OFF64_T) add_executable(example64 test/example.c) configure_test_executable(example64) @@ -866,6 +869,13 @@ if (ZLIB_ENABLE_TESTS) "-DCOMMAND=${GH_361_COMMAND}" -DINPUT=${CMAKE_CURRENT_SOURCE_DIR}/test/GH-361/test.txt -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/run-and-redirect.cmake) + + set(GH_364_COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $ 1 5 9 3) + add_test(NAME GH-364 + COMMAND ${CMAKE_COMMAND} + "-DCOMMAND=${GH_364_COMMAND}" + -DINPUT=${CMAKE_CURRENT_SOURCE_DIR}/test/GH-364/test.bin + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/run-and-redirect.cmake) endif() FEATURE_SUMMARY(WHAT ALL INCLUDE_QUIET_PACKAGES) diff --git a/configure b/configure index 5f50a33cb..66dfefd34 100755 --- a/configure +++ b/configure @@ -1468,6 +1468,7 @@ sed < $SRCDIR/test/Makefile.in " /^COMPATTESTS *=/s#=.*#=$COMPATTESTS# /^QEMU_RUN *=/s#=.*#=$QEMU_RUN# /^WITH_FUZZERS *=/s#=.*#=$with_fuzzers# +/^LIBNAME *=/s#=.*#=$LIBNAME# " > test/Makefile # create zlib.pc with the configure results diff --git a/match_p.h b/match_p.h index b956d8d52..6dda122b1 100644 --- a/match_p.h +++ b/match_p.h @@ -66,7 +66,7 @@ static inline unsigned longest_match(deflate_state *const s, IPos cur_match) { /* * Do not waste too much time if we already have a good match */ - best_len = s->prev_length; + best_len = s->prev_length ? s->prev_length : 1; chain_length = s->max_chain_length; if (best_len >= s->good_match) chain_length >>= 2; @@ -186,7 +186,7 @@ static inline unsigned longest_match(deflate_state *const s, IPos cur_match) { /* * Do not waste too much time if we already have a good match */ - best_len = s->prev_length; + best_len = s->prev_length ? s->prev_length : 1; chain_length = s->max_chain_length; if (best_len >= s->good_match) chain_length >>= 2; @@ -385,7 +385,7 @@ static inline unsigned longest_match(deflate_state *const s, IPos cur_match) { register unsigned char *scan = window + strstart; /* current string */ register unsigned char *match; /* matched string */ register unsigned int len; /* length of current match */ - unsigned int best_len = s->prev_length; /* best match length so far */ + unsigned int best_len = s->prev_length ? s->prev_length : 1; /* best match length so far */ unsigned int nice_match = s->nice_match; /* stop if match long enough */ IPos limit = strstart > (IPos)MAX_DIST(s) ? strstart - (IPos)MAX_DIST(s) : NIL; diff --git a/test/.gitignore b/test/.gitignore index 2c3af0a08..96a3cad07 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,2 +1,5 @@ # ignore Makefiles; they're all automatically generated Makefile +/switchlevels +/switchlevels.dSYM/ +/switchlevels.exe diff --git a/test/GH-364/test.bin b/test/GH-364/test.bin new file mode 100644 index 000000000..1b1cb4d44 Binary files /dev/null and b/test/GH-364/test.bin differ diff --git a/test/INDEX b/test/INDEX index a1e6b7280..9b66ef6cb 100644 --- a/test/INDEX +++ b/test/INDEX @@ -7,6 +7,8 @@ CVE-2005-1849 : CVE-2005-2096 : test cases for the relevant CVEs GH-361 : test case for overlapping matches https://github.com/zlib-ng/zlib-ng/issues/361 +GH-364 : test case for switching compression levels + https://github.com/zlib-ng/zlib-ng/issues/364 testCVEinputs.sh: script to run tests for CVEs where input data is supplied diff --git a/test/Makefile.in b/test/Makefile.in index caf49496d..79e79e015 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -7,7 +7,8 @@ CFLAGS= EXE= SRCDIR= SRCTOP= -TEST_LDFLAGS=-L.. ../libz.a +LIBNAME= +TEST_LDFLAGS=-L.. ../$(LIBNAME).a WITH_FUZZERS= COMPATTESTS = @@ -97,15 +98,22 @@ CVE-2003-0107$(EXE): CVE-2003-0107.o $(CC) $(CFLAGS) -o $@ CVE-2003-0107.o $(TEST_LDFLAGS) .PHONY: ghtests -ghtests: testGH-361 +ghtests: testGH-361 testGH-364 .PHONY: testGH-361 testGH-361: $(QEMU_RUN) ../minigzip$(EXE) -4 <$(SRCDIR)/GH-361/test.txt >/dev/null +switchlevels$(EXE): $(SRCDIR)/switchlevels.c + $(CC) $(CFLAGS) -I.. -I$(SRCTOP) -o $@ $< $(TEST_LDFLAGS) + +.PHONY: testGH-364 +testGH-364: switchlevels$(EXE) + $(QEMU_RUN) ./switchlevels$(EXE) 1 5 9 3 <$(SRCDIR)/GH-364/test.bin >/dev/null + clean: rm -f *.o *.gcda *.gcno *.gcov - rm -f CVE-2003-0107$(EXE) + rm -f CVE-2003-0107$(EXE) switchlevels$(EXE) distclean: rm -f Makefile diff --git a/test/switchlevels.c b/test/switchlevels.c new file mode 100644 index 000000000..67bb1524a --- /dev/null +++ b/test/switchlevels.c @@ -0,0 +1,112 @@ +/* Compresses a user-specified number of chunks from stdin into stdout as a single gzip stream. + * Each chunk is compressed with a user-specified level. + */ + +#include "zbuild.h" +#ifdef ZLIB_COMPAT +# include "zlib.h" +#else +# include "zlib-ng.h" +#endif + +#include +#include +#include + +static int read_all(unsigned char *buf, size_t size) +{ + for (size_t total_read = 0; total_read < size;) { + size_t n_read = fread(buf + total_read, 1, size - total_read, stdin); + if (ferror(stdin)) { + perror("fread\n"); + return 1; + } + if (n_read == 0) { + fprintf(stderr, "Premature EOF\n"); + return 1; + } + total_read += n_read; + } + return 0; +} + +static int write_all(unsigned char *buf, size_t size) +{ + for (size_t total_written = 0; total_written < size;) { + size_t n_written = fwrite(buf + total_written, 1, size - total_written, stdout); + if (ferror(stdout)) { + perror("fwrite\n"); + return 1; + } + total_written += n_written; + } + return 0; +} + +static int compress_chunk(PREFIX3(stream) *strm, int level, int size, int last) +{ + int ret = 1; + int err = PREFIX(deflateParams)(strm, level, Z_DEFAULT_STRATEGY); + if (err != Z_OK) { + fprintf(stderr, "deflateParams() failed with code %d\n", err); + goto done; + } + unsigned long compsize = 100 + 2 * PREFIX(deflateBound)(strm, size); + unsigned char *buf = malloc(size + compsize); + if (buf == NULL) { + fprintf(stderr, "Out of memory\n"); + goto done; + } + if (read_all(buf, size) != 0) { + goto free_buf; + } + strm->next_in = buf; + strm->avail_in = size; + strm->next_out = buf + size; + strm->avail_out = compsize; + err = PREFIX(deflate)(strm, last ? Z_FINISH : Z_SYNC_FLUSH); + if ((!last && err != Z_OK) || (last && err != Z_STREAM_END)) { + fprintf(stderr, "deflate() failed with code %d\n", err); + goto free_buf; + } + if (strm->avail_in != 0) { + fprintf(stderr, "deflate() did not consume %d bytes of input\n", strm->avail_in); + goto free_buf; + } + if (write_all(buf + size, compsize - strm->avail_out) != 0) { + goto free_buf; + } + ret = 0; +free_buf: + free(buf); +done: + return ret; +} + +/* =========================================================================== + * Usage: switchlevels level1 size1 [level2 size2 ...] + */ + +int main(int argc, char **argv) +{ + int ret = EXIT_FAILURE; + PREFIX3(stream) strm; + memset(&strm, 0, sizeof(strm)); + int err = PREFIX(deflateInit2)(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16, 8, Z_DEFAULT_STRATEGY); + if (err != Z_OK) { + fprintf(stderr, "deflateInit() failed with code %d\n", err); + goto done; + } + for (int i = 1; i < argc - 1; i += 2) { + int level = atoi(argv[i]); + int size = atoi(argv[i + 1]); + if (compress_chunk(&strm, level, size, i + 2 >= argc - 1) != 0) { + goto deflate_end; + } + } + ret = EXIT_SUCCESS; +deflate_end: + PREFIX(deflateEnd)(&strm); +done: + return ret; +}