summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/Makefile6
-rw-r--r--lib/lz4.c30
-rw-r--r--lib/lz4.h12
-rw-r--r--programs/util.h3
-rw-r--r--tests/Makefile3
-rw-r--r--tests/fuzzer.c25
6 files changed, 63 insertions, 16 deletions
diff --git a/lib/Makefile b/lib/Makefile
index 7b31239..bb45582 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -42,6 +42,7 @@ LIBVER_MINOR := $(shell echo $(LIBVER_MINOR_SCRIPT))
LIBVER_PATCH := $(shell echo $(LIBVER_PATCH_SCRIPT))
LIBVER := $(shell echo $(LIBVER_SCRIPT))
+BUILD_SHARED:=yes
BUILD_STATIC:=yes
CPPFLAGS+= -DXXH_NAMESPACE=LZ4_
@@ -92,6 +93,7 @@ ifeq ($(BUILD_STATIC),yes) # can be disabled on command line
endif
$(LIBLZ4): $(SRCFILES)
+ifeq ($(BUILD_SHARED),yes) # can be disabled on command line
@echo compiling dynamic library $(LIBVER)
ifneq (,$(filter Windows%,$(OS)))
@$(CC) $(FLAGS) -DLZ4_DLL_EXPORT=1 -shared $^ -o dll\$@.dll
@@ -102,6 +104,7 @@ else
@ln -sf $@ liblz4.$(SHARED_EXT_MAJOR)
@ln -sf $@ liblz4.$(SHARED_EXT)
endif
+endif
liblz4: $(LIBLZ4)
@@ -163,9 +166,11 @@ ifeq ($(BUILD_STATIC),yes)
@$(INSTALL_DATA) liblz4.a $(DESTDIR)$(LIBDIR)/liblz4.a
@$(INSTALL_DATA) lz4frame_static.h $(DESTDIR)$(INCLUDEDIR)/lz4frame_static.h
endif
+ifeq ($(BUILD_SHARED),yes)
@$(INSTALL_PROGRAM) liblz4.$(SHARED_EXT_VER) $(DESTDIR)$(LIBDIR)
@ln -sf liblz4.$(SHARED_EXT_VER) $(DESTDIR)$(LIBDIR)/liblz4.$(SHARED_EXT_MAJOR)
@ln -sf liblz4.$(SHARED_EXT_VER) $(DESTDIR)$(LIBDIR)/liblz4.$(SHARED_EXT)
+endif
@echo Installing headers in $(INCLUDEDIR)
@$(INSTALL_DATA) lz4.h $(DESTDIR)$(INCLUDEDIR)/lz4.h
@$(INSTALL_DATA) lz4hc.h $(DESTDIR)$(INCLUDEDIR)/lz4hc.h
@@ -181,6 +186,7 @@ uninstall:
@$(RM) $(DESTDIR)$(INCLUDEDIR)/lz4.h
@$(RM) $(DESTDIR)$(INCLUDEDIR)/lz4hc.h
@$(RM) $(DESTDIR)$(INCLUDEDIR)/lz4frame.h
+ @$(RM) $(DESTDIR)$(INCLUDEDIR)/lz4frame_static.h
@echo lz4 libraries successfully uninstalled
endif
diff --git a/lib/lz4.c b/lib/lz4.c
index bb6b619..eaabe5b 100644
--- a/lib/lz4.c
+++ b/lib/lz4.c
@@ -776,9 +776,10 @@ LZ4_FORCE_INLINE int LZ4_compress_generic(
forwardH = LZ4_hashPosition(forwardIp, tableType);
LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType);
- if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) continue; /* match outside of valid area */
- if ((tableType != byU16) && (current - matchIndex > MAX_DISTANCE)) continue; /* too far - note: works even if matchIndex overflows */
- if (tableType == byU16) assert((current - matchIndex) <= MAX_DISTANCE); /* too_far presumed impossible with byU16 */
+ if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) continue; /* match outside of valid area */
+ assert(matchIndex < current);
+ if ((tableType != byU16) && (matchIndex+MAX_DISTANCE < current)) continue; /* too far */
+ if (tableType == byU16) assert((current - matchIndex) <= MAX_DISTANCE); /* too_far presumed impossible with byU16 */
if (LZ4_read32(match) == LZ4_read32(ip)) {
if (maybe_extMem) offset = current - matchIndex;
@@ -918,8 +919,9 @@ _next_match:
match = base + matchIndex;
}
LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType);
+ assert(matchIndex < current);
if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1)
- && ((tableType==byU16) ? 1 : (current - matchIndex <= MAX_DISTANCE))
+ && ((tableType==byU16) ? 1 : (matchIndex+MAX_DISTANCE >= current))
&& (LZ4_read32(match) == LZ4_read32(ip)) ) {
token=op++;
*token=0;
@@ -1304,7 +1306,11 @@ int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize)
DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, dictionary, LZ4_dict);
- LZ4_prepareTable(dict, 0, tableType);
+ /* It's necessary to reset the context,
+ * and not just continue it with prepareTable()
+ * to avoid any risk of generating overflowing matchIndex
+ * when compressing using this dictionary */
+ LZ4_resetStream(LZ4_dict);
/* We always increment the offset by 64 KB, since, if the dict is longer,
* we truncate it to the last 64k, and if it's shorter, we still want to
@@ -1520,6 +1526,7 @@ LZ4_FORCE_INLINE int LZ4_decompress_generic(
if ((partialDecoding) && (oexit > oend-MFLIMIT)) oexit = oend-MFLIMIT; /* targetOutputSize too high => just decode everything */
if ((endOnInput) && (unlikely(outputSize==0))) return ((srcSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */
if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0?1:-1);
+ if ((endOnInput) && unlikely(srcSize==0)) return -1;
/* Main Loop : decode sequences */
while (1) {
@@ -1529,13 +1536,17 @@ LZ4_FORCE_INLINE int LZ4_decompress_generic(
unsigned const token = *ip++;
+ assert(!endOnInput || ip <= iend); /* ip < iend before the increment */
/* shortcut for common case :
* in most circumstances, we expect to decode small matches (<= 18 bytes) separated by few literals (<= 14 bytes).
* this shortcut was tested on x86 and x64, where it improves decoding speed.
- * it has not yet been benchmarked on ARM, Power, mips, etc. */
- if (((ip + 14 /*maxLL*/ + 2 /*offset*/ <= iend)
- & (op + 14 /*maxLL*/ + 18 /*maxML*/ <= oend))
- & ((token < (15<<ML_BITS)) & ((token & ML_MASK) != 15)) ) {
+ * it has not yet been benchmarked on ARM, Power, mips, etc.
+ * NOTE: The loop begins with a read, so we must have one byte left at the end. */
+ if (endOnInput
+ && ((ip + 14 /*maxLL*/ + 2 /*offset*/ < iend)
+ & (op + 14 /*maxLL*/ + 18 /*maxML*/ <= oend)
+ & (token < (15<<ML_BITS))
+ & ((token & ML_MASK) != 15) ) ) {
size_t const ll = token >> ML_BITS;
size_t const off = LZ4_readLE16(ip+ll);
const BYTE* const matchPtr = op + ll - off; /* pointer underflow risk ? */
@@ -1553,6 +1564,7 @@ LZ4_FORCE_INLINE int LZ4_decompress_generic(
/* decode literal length */
if ((length=(token>>ML_BITS)) == RUN_MASK) {
unsigned s;
+ if (unlikely(endOnInput ? ip >= iend-RUN_MASK : 0)) goto _output_error; /* overflow detection */
do {
s = *ip++;
length += s;
diff --git a/lib/lz4.h b/lib/lz4.h
index 0dfa19e..db6353c 100644
--- a/lib/lz4.h
+++ b/lib/lz4.h
@@ -206,15 +206,17 @@ LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePt
/*!
LZ4_decompress_fast() : **unsafe!**
This function is a bit faster than LZ4_decompress_safe(),
-but doesn't provide any security guarantee.
+but it may misbehave on malformed input because it doesn't perform full validation of compressed data.
originalSize : is the uncompressed size to regenerate
Destination buffer must be already allocated, and its size must be >= 'originalSize' bytes.
return : number of bytes read from source buffer (== compressed size).
If the source stream is detected malformed, the function stops decoding and return a negative result.
- note : This function respects memory boundaries for *properly formed* compressed data.
- However, it does not provide any protection against malicious input.
- It also doesn't know 'src' size, and implies it's >= compressed size.
- Use this function in trusted environment **only**.
+ note : This function is only usable if the originalSize of uncompressed data is known in advance.
+ The caller should also check that all the compressed input has been consumed properly,
+ i.e. that the return value matches the size of the buffer with compressed input.
+ The function never writes past the output buffer. However, since it doesn't know its 'src' size,
+ it may read past the intended input. Also, because match offsets are not validated during decoding,
+ reads from 'src' may underflow. Use this function in trusted environment **only**.
*/
LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize);
diff --git a/programs/util.h b/programs/util.h
index ff25106..ef6ca77 100644
--- a/programs/util.h
+++ b/programs/util.h
@@ -30,8 +30,9 @@ extern "C" {
* Dependencies
******************************************/
#include "platform.h" /* PLATFORM_POSIX_VERSION */
-#include <stdlib.h> /* malloc */
#include <stddef.h> /* size_t, ptrdiff_t */
+#include <stdlib.h> /* malloc */
+#include <string.h> /* strlen, strncpy */
#include <stdio.h> /* fprintf */
#include <sys/types.h> /* stat, utime */
#include <sys/stat.h> /* stat */
diff --git a/tests/Makefile b/tests/Makefile
index 2b93c9f..d4847b1 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -80,7 +80,8 @@ lz4c32: # create a 32-bits version for 32/64 interop tests
%.o : $(LZ4DIR)/%.c $(LZ4DIR)/%.h
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
-fullbench : lz4.o lz4hc.o lz4frame.o xxhash.o fullbench.c
+fullbench : DEBUGLEVEL=0
+fullbench : lz4.o lz4hc.o lz4frame.o xxhash.o fullbench.c
$(CC) $(FLAGS) $^ -o $@$(EXT)
$(LZ4DIR)/liblz4.a:
diff --git a/tests/fuzzer.c b/tests/fuzzer.c
index 8b21f8e..8cd4dec 100644
--- a/tests/fuzzer.c
+++ b/tests/fuzzer.c
@@ -492,6 +492,31 @@ static int FUZ_test(U32 seed, U32 nbCycles, const U32 startCycle, const double c
ret = LZ4_decompress_fast(compressedBuffer, decodedBuffer, blockSize+1);
FUZ_CHECKTEST(ret>=0, "LZ4_decompress_fast should have failed, due to Output Size being too large");
+ /* Test decoding with empty input */
+ FUZ_DISPLAYTEST("LZ4_decompress_safe() with empty input");
+ LZ4_decompress_safe(NULL, decodedBuffer, 0, blockSize);
+
+ /* Test decoding with a one byte input */
+ FUZ_DISPLAYTEST("LZ4_decompress_safe() with one byte input");
+ { char const tmp = 0xFF;
+ LZ4_decompress_safe(&tmp, decodedBuffer, 1, blockSize);
+ }
+
+ /* Test decoding shortcut edge case */
+ FUZ_DISPLAYTEST("LZ4_decompress_safe() with shortcut edge case");
+ { char tmp[17];
+ /* 14 bytes of literals, followed by a 14 byte match.
+ * Should not read beyond the end of the buffer.
+ * See https://github.com/lz4/lz4/issues/508. */
+ *tmp = 0xEE;
+ memset(tmp + 1, 0, 14);
+ tmp[15] = 14;
+ tmp[16] = 0;
+ ret = LZ4_decompress_safe(tmp, decodedBuffer, sizeof(tmp), blockSize);
+ FUZ_CHECKTEST(ret >= 0, "LZ4_decompress_safe() should fail");
+ }
+
+
/* Test decoding with output size exactly what's necessary => must work */
FUZ_DISPLAYTEST();
decodedBuffer[blockSize] = 0;