Repository: facebook/zstd
Fuzzing engine: honggfuzz
Fuzz target: dictionary_stream_round_trip
Sanitizer: address
Crash type: UNKNOWN READ

Sanitizer report:
Accepting input from '/testcase/OSV-2021-727.poc'
Usage for fuzzing: honggfuzz -P [flags] -- /out/dictionary_stream_round_trip
AddressSanitizer:DEADLYSIGNAL
=================================================================
==7==ERROR: AddressSanitizer: SEGV on unknown address 0x52f10000b1a0 (pc 0x560c8d2be951 bp 0x7ffeb1d05ac0 sp 0x7ffeb1d05440 T0)
==7==The signal is caused by a READ memory access.
SCARINESS: 20 (wild-addr-read)
    #0 0x560c8d2be951 in MEM_read32 /src/zstd/tests/fuzz/../../lib/compress/../common/mem.h:214:14
    #1 0x560c8d2be951 in ZSTD_compressBlock_lazy_extDict_generic /src/zstd/tests/fuzz/../../lib/compress/zstd_lazy.c:2106:35
    #2 0x560c8d2be951 in ZSTD_compressBlock_greedy_extDict_row /src/zstd/tests/fuzz/../../lib/compress/zstd_lazy.c:2163:12
    #3 0x560c8d3968f3 in ZSTD_buildSeqStore /src/zstd/tests/fuzz/../../lib/compress/zstd_compress.c:2824:17
    #4 0x560c8d379630 in ZSTD_compressBlock_targetCBlockSize /src/zstd/tests/fuzz/../../lib/compress/zstd_compress.c:3804:24
    #5 0x560c8d379630 in ZSTD_compress_frameChunk /src/zstd/tests/fuzz/../../lib/compress/zstd_compress.c:3885:25
    #6 0x560c8d379630 in ZSTD_compressContinue_internal /src/zstd/tests/fuzz/../../lib/compress/zstd_compress.c:4064:30
    #7 0x560c8d38b70e in ZSTD_compressContinue /src/zstd/tests/fuzz/../../lib/compress/zstd_compress.c:4088:12
    #8 0x560c8d38b70e in ZSTD_compressStream_generic /src/zstd/tests/fuzz/../../lib/compress/zstd_compress.c:5304:29
    #9 0x560c8d388971 in ZSTD_compressStream2 /src/zstd/tests/fuzz/../../lib/compress/zstd_compress.c:5599:5
    #10 0x560c8d511cb4 in compress /src/zstd/tests/fuzz/dictionary_stream_round_trip.c:93:25
    #11 0x560c8d511cb4 in LLVMFuzzerTestOneInput /src/zstd/tests/fuzz/dictionary_stream_round_trip.c:181:30
    #12 0x560c8d512ecf in main (/out/dictionary_stream_round_trip+0x4e4ecf)
    #13 0x7f21dff80082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 0702430aef5fa3dda43986563e9ffcc47efbd75e)
    #14 0x560c8d07171d in _start (/out/dictionary_stream_round_trip+0x4371d)

DEDUP_TOKEN: MEM_read32--ZSTD_compressBlock_lazy_extDict_generic--ZSTD_compressBlock_greedy_extDict_row
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /src/zstd/tests/fuzz/../../lib/compress/../common/mem.h:214:14 in MEM_read32
==7==ABORTING


Fix patch:
diff --git a/lib/compress/zstd_double_fast.c b/lib/compress/zstd_double_fast.c
index b99172e9..d0d3a784 100644
--- a/lib/compress/zstd_double_fast.c
+++ b/lib/compress/zstd_double_fast.c
@@ -409,7 +409,7 @@ static size_t ZSTD_compressBlock_doubleFast_extDict_generic(
         hashSmall[hSmall] = hashLong[hLong] = curr;   /* update hash table */
 
         if ((((U32)((prefixStartIndex-1) - repIndex) >= 3) /* intentional underflow : ensure repIndex doesn't overlap dict + prefix */
-            & (repIndex > dictStartIndex))
+            & (offset_1 < curr+1 - dictStartIndex)) /* note: we are searching at curr+1 */
           && (MEM_read32(repMatch) == MEM_read32(ip+1)) ) {
             const BYTE* repMatchEnd = repIndex < prefixStartIndex ? dictEnd : iend;
             mLength = ZSTD_count_2segments(ip+1+4, repMatch+4, iend, repMatchEnd, prefixStart) + 4;
@@ -477,7 +477,7 @@ static size_t ZSTD_compressBlock_doubleFast_extDict_generic(
                 U32 const repIndex2 = current2 - offset_2;
                 const BYTE* repMatch2 = repIndex2 < prefixStartIndex ? dictBase + repIndex2 : base + repIndex2;
                 if ( (((U32)((prefixStartIndex-1) - repIndex2) >= 3)   /* intentional overflow : ensure repIndex2 doesn't overlap dict + prefix */
-                    & (repIndex2 > dictStartIndex))
+                    & (offset_2 < current2 - dictStartIndex))
                   && (MEM_read32(repMatch2) == MEM_read32(ip)) ) {
                     const BYTE* const repEnd2 = repIndex2 < prefixStartIndex ? dictEnd : iend;
                     size_t const repLength2 = ZSTD_count_2segments(ip+4, repMatch2+4, iend, repEnd2, prefixStart) + 4;
diff --git a/lib/compress/zstd_fast.c b/lib/compress/zstd_fast.c
index 96b7d48e..4edc04dc 100644
--- a/lib/compress/zstd_fast.c
+++ b/lib/compress/zstd_fast.c
@@ -416,9 +416,9 @@ static size_t ZSTD_compressBlock_fast_extDict_generic(
         const BYTE* const repMatch = repBase + repIndex;
         hashTable[h] = curr;   /* update hash table */
         DEBUGLOG(7, "offset_1 = %u , curr = %u", offset_1, curr);
-        assert(offset_1 <= curr +1);   /* check repIndex */
 
-        if ( (((U32)((prefixStartIndex-1) - repIndex) >= 3) /* intentional underflow */ & (repIndex > dictStartIndex))
+        if ( ( ((U32)((prefixStartIndex-1) - repIndex) >= 3) /* intentional underflow */
+             & (offset_1 < curr+1 - dictStartIndex) ) /* note: we are searching at curr+1 */
            && (MEM_read32(repMatch) == MEM_read32(ip+1)) ) {
             const BYTE* const repMatchEnd = repIndex < prefixStartIndex ? dictEnd : iend;
             size_t const rLength = ZSTD_count_2segments(ip+1 +4, repMatch +4, iend, repMatchEnd, prefixStart) + 4;
@@ -453,7 +453,7 @@ static size_t ZSTD_compressBlock_fast_extDict_generic(
                 U32 const current2 = (U32)(ip-base);
                 U32 const repIndex2 = current2 - offset_2;
                 const BYTE* const repMatch2 = repIndex2 < prefixStartIndex ? dictBase + repIndex2 : base + repIndex2;
-                if ( (((U32)((prefixStartIndex-1) - repIndex2) >= 3) & (repIndex2 > dictStartIndex))  /* intentional overflow */
+                if ( (((U32)((prefixStartIndex-1) - repIndex2) >= 3) & (offset_2 < curr - dictStartIndex))  /* intentional overflow */
                    && (MEM_read32(repMatch2) == MEM_read32(ip)) ) {
                     const BYTE* const repEnd2 = repIndex2 < prefixStartIndex ? dictEnd : iend;
                     size_t const repLength2 = ZSTD_count_2segments(ip+4, repMatch2+4, iend, repEnd2, prefixStart) + 4;
diff --git a/lib/compress/zstd_lazy.c b/lib/compress/zstd_lazy.c
index 5d824bee..3d523e84 100644
--- a/lib/compress/zstd_lazy.c
+++ b/lib/compress/zstd_lazy.c
@@ -1994,7 +1994,8 @@ size_t ZSTD_compressBlock_lazy_extDict_generic(
             const U32 repIndex = (U32)(curr+1 - offset_1);
             const BYTE* const repBase = repIndex < dictLimit ? dictBase : base;
             const BYTE* const repMatch = repBase + repIndex;
-            if (((U32)((dictLimit-1) - repIndex) >= 3) & (repIndex > windowLow))   /* intentional overflow */
+            if ( ((U32)((dictLimit-1) - repIndex) >= 3) /* intentional overflow */
+               & (offset_1 < curr+1 - windowLow) ) /* note: we are searching at curr+1 */
             if (MEM_read32(ip+1) == MEM_read32(repMatch)) {
                 /* repcode detected we should take it */
                 const BYTE* const repEnd = repIndex < dictLimit ? dictEnd : iend;
@@ -2025,7 +2026,8 @@ size_t ZSTD_compressBlock_lazy_extDict_generic(
                 const U32 repIndex = (U32)(curr - offset_1);
                 const BYTE* const repBase = repIndex < dictLimit ? dictBase : base;
                 const BYTE* const repMatch = repBase + repIndex;
-                if (((U32)((dictLimit-1) - repIndex) >= 3) & (repIndex > windowLow))  /* intentional overflow */
+                if ( ((U32)((dictLimit-1) - repIndex) >= 3) /* intentional overflow : do not test positions overlapping 2 memory segments  */
+                   & (offset_1 < curr - windowLow) ) /* equivalent to `curr > repIndex >= windowLow` */
                 if (MEM_read32(ip) == MEM_read32(repMatch)) {
                     /* repcode detected */
                     const BYTE* const repEnd = repIndex < dictLimit ? dictEnd : iend;
@@ -2056,7 +2058,8 @@ size_t ZSTD_compressBlock_lazy_extDict_generic(
                     const U32 repIndex = (U32)(curr - offset_1);
                     const BYTE* const repBase = repIndex < dictLimit ? dictBase : base;
                     const BYTE* const repMatch = repBase + repIndex;
-                    if (((U32)((dictLimit-1) - repIndex) >= 3) & (repIndex > windowLow))  /* intentional overflow */
+                    if ( ((U32)((dictLimit-1) - repIndex) >= 3) /* intentional overflow : do not test positions overlapping 2 memory segments  */
+                       & (offset_1 < curr - windowLow) ) /* equivalent to `curr > repIndex >= windowLow` */
                     if (MEM_read32(ip) == MEM_read32(repMatch)) {
                         /* repcode detected */
                         const BYTE* const repEnd = repIndex < dictLimit ? dictEnd : iend;
@@ -2102,7 +2105,8 @@ _storeSequence:
             const U32 repIndex = repCurrent - offset_2;
             const BYTE* const repBase = repIndex < dictLimit ? dictBase : base;
             const BYTE* const repMatch = repBase + repIndex;
-            if (((U32)((dictLimit-1) - repIndex) >= 3) & (repIndex > windowLow))  /* intentional overflow */
+            if ( ((U32)((dictLimit-1) - repIndex) >= 3) /* intentional overflow : do not test positions overlapping 2 memory segments  */
+               & (offset_2 < repCurrent - windowLow) ) /* equivalent to `curr > repIndex >= windowLow` */
             if (MEM_read32(ip) == MEM_read32(repMatch)) {
                 /* repcode detected we should take it */
                 const BYTE* const repEnd = repIndex < dictLimit ? dictEnd : iend;