Skip to content

NULL + offset has undefined behavior #1899

@QrczakMK

Description

@QrczakMK

In C adding an offset to a null pointer has undefined behavior (in C++ that would be valid only if the offset was 0).

zstd may perform such operations (with both zero and non-zero offsets), which breaks execution under UBSan.

Here are places that I could find by running some of our tests under UBSan (in 1.4.4):

diff --git a/google3/third_party/zstdlib/compress/zstd_compress.c b/google3/third_party/zstdlib/compress/zstd_compress.c
--- a/google3/third_party/zstdlib/compress/zstd_compress.c
+++ b/google3/third_party/zstdlib/compress/zstd_compress.c
@@ -1321,6 +1321,7 @@ ZSTD_reset_matchState(ZSTD_matchState_t*
     DEBUGLOG(4, "reset indices : %u", forceResetIndex == ZSTDirp_reset);
     if (forceResetIndex == ZSTDirp_reset) {
         memset(&ms->window, 0, sizeof(ms->window));
+        ms->window.base = "";
         ms->window.dictLimit = 1;    /* start from 1, so that 1st position is valid */
         ms->window.lowLimit = 1;     /* it ensures first and later CCtx usages compress the same */
         ms->window.nextSrc = ms->window.base + 1;   /* see issue #1241 */
@@ -3655,11 +3656,11 @@ static size_t ZSTD_compressStream_generi
                                           ZSTD_EndDirective const flushMode)
 {
     const char* const istart = (const char*)input->src;
-    const char* const iend = istart + input->size;
-    const char* ip = istart + input->pos;
+    const char* const iend = input->size ? istart + input->size : istart;
+    const char* ip = input->pos ? istart + input->pos : istart;
     char* const ostart = (char*)output->dst;
-    char* const oend = ostart + output->size;
-    char* op = ostart + output->pos;
+    char* const oend = output->size ? ostart + output->size : ostart;
+    char* op = output->pos ? ostart + output->pos : ostart;
     U32 someMoreWork = 1;
 
     /* check expectations */
@@ -3698,7 +3699,7 @@ static size_t ZSTD_compressStream_generi
                                         zcs->inBuff + zcs->inBuffPos, toLoad,
                                         ip, iend-ip);
                 zcs->inBuffPos += loaded;
-                ip += loaded;
+                if (loaded) ip += loaded;
                 if ( (flushMode == ZSTD_e_continue)
                   && (zcs->inBuffPos < zcs->inBuffTarget) ) {
                     /* not enough input to fill full block : stop here */
@@ -3758,7 +3759,7 @@ static size_t ZSTD_compressStream_generi
                             zcs->outBuff + zcs->outBuffFlushedSize, toFlush);
                 DEBUGLOG(5, "toFlush: %u into %u ==> flushed: %u",
                             (unsigned)toFlush, (unsigned)(oend-op), (unsigned)flushed);
-                op += flushed;
+                if (flushed) op += flushed;
                 zcs->outBuffFlushedSize += flushed;
                 if (toFlush!=flushed) {
                     /* flush not fully completed, presumably because dst is too small */
diff --git a/google3/third_party/zstdlib/compress/zstd_double_fast.c b/google3/third_party/zstdlib/compress/zstd_double_fast.c
--- a/google3/third_party/zstdlib/compress/zstd_double_fast.c
+++ b/google3/third_party/zstdlib/compress/zstd_double_fast.c
@@ -96,7 +96,7 @@ size_t ZSTD_compressBlock_doubleFast_gen
                                      dictCParams->hashLog : hBitsL;
     const U32 dictHBitsS           = dictMode == ZSTD_dictMatchState ?
                                      dictCParams->chainLog : hBitsS;
-    const U32 dictAndPrefixLength  = (U32)(ip - prefixLowest + dictEnd - dictStart);
+    const U32 dictAndPrefixLength  = (U32)((ip - prefixLowest) + (dictEnd - dictStart));
 
     DEBUGLOG(5, "ZSTD_compressBlock_doubleFast_generic");
 
diff --git a/google3/third_party/zstdlib/compress/zstd_lazy.c b/google3/third_party/zstdlib/compress/zstd_lazy.c
--- a/google3/third_party/zstdlib/compress/zstd_lazy.c
+++ b/google3/third_party/zstdlib/compress/zstd_lazy.c
@@ -660,7 +660,7 @@ ZSTD_compressBlock_lazy_generic(
     const U32 dictIndexDelta       = dictMode == ZSTD_dictMatchState ?
                                      prefixLowestIndex - (U32)(dictEnd - dictBase) :
                                      0;
-    const U32 dictAndPrefixLength = (U32)(ip - prefixLowest + dictEnd - dictLowest);
+    const U32 dictAndPrefixLength = (U32)((ip - prefixLowest) + (dictEnd - dictLowest));
 
     /* init */
     ip += (dictAndPrefixLength == 0);

Could you fix that please?

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions