Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix usan runtime errors #983

Closed
wants to merge 1 commit into from
Closed

Fix usan runtime errors #983

wants to merge 1 commit into from

Conversation

t-mat
Copy link
Contributor

@t-mat t-mat commented May 27, 2021

This is a follow up PR of #982.
This PR fixes make usan runtime errors.

Majority of usan runtime errors are two types.

(1) Applying pointer arithmetic to NULL pointer. (undefined behaviour)

- const BYTE* p = x + 1;            // usan runtime error when `x` is NULL.
+ const BYTE* p = (const BYTE*) ((uptrval) x + 1);

(2) Result of pointer arithmetic is overflowed. (undefined behaviour)

- const BYTE* p = x - 0x4000;      // usan runtime error when `x` is less than 0x4000.
+ const BYTE* p = (const BYTE*) ((uptrval) x - 0x4000);

Patch for LZ4_resetStreamHC_fast() is just a bit cumbersome. For this line

LZ4_streamHCPtr->internal_donotuse.end -= (uptrval) LZ4_streamHCPtr->internal_donotuse.base;

usan claims

applying non-zero offset to non-null pointer 0xXXXXXXXXXXXXXXXX produced null pointer

So, we need to cast both of pointers to uptrval and cast to const LZ4_byte*.

LZ4_streamHCPtr->internal_donotuse.end =
    (const LZ4_byte*) (  (uptrval) LZ4_streamHCPtr->internal_donotuse.end
                       - (uptrval) LZ4_streamHCPtr->internal_donotuse.base  );

Majority of usan runtime errors are two types.

(1) Applying pointer arithmetic to NULL pointer.  (undefined behaviour)

```
- const BYTE* p = x + 1;            // usan runtime error when `x` is NULL.
+ const BYTE* p = (const BYTE*) ((uptrval) x + 1);
```

(2) Result of pointer arithmetic is overflowed.  (undefined behaviour)

```
- const BYTE* p = x - 0x4000;      // usan runtime error when `x` is less than 0x4000.
+ const BYTE* p = (const BYTE*) ((uptrval) x - 0x4000);
```

Patch for `LZ4_resetStreamHC_fast()` is just a bit cumbersome.  For this line

```
LZ4_streamHCPtr->internal_donotuse.end -= (uptrval) LZ4_streamHCPtr->internal_donotuse.base;
```

`usan` claims

> applying non-zero offset to non-null pointer 0xXXXXXXXXXXXXXXXX produced null pointer

So, we need to cast both of pointers to `uptrval` and cast to `const LZ4_byte*`.

```
LZ4_streamHCPtr->internal_donotuse.end =
    (const LZ4_byte*) (  (uptrval) LZ4_streamHCPtr->internal_donotuse.end
                       - (uptrval) LZ4_streamHCPtr->internal_donotuse.base  );
```
@t-mat
Copy link
Contributor Author

t-mat commented May 27, 2021

FYI, here's an actual make usan log

@Cyan4973
Copy link
Member

I'm a bit embarrassed by the proposed fix
because it negatively impacts readability (and possibly portability a bit)
just for the purpose of pleasing a test tool.
It feels like an incorrect priority.

I'm fine with the end objective to try to stay clear from UB.
But I don't make it an all-in priority if the cost is worse maintenance (or worse performance).

Rather, let's try to improve both UB and readability, whenever it makes sense.
For other cases, which are too hard to find a solution for on short term,
I'm fine with silencing the associated warning, at least temporarily.
Most of the situations flagged are not dangerous, since the result of the flagged pointer arithmetic is not used afterwards.

@@ -134,12 +134,14 @@ static int g_debuglog_enable = 1;
typedef uint32_t U32;
typedef int32_t S32;
typedef uint64_t U64;
typedef uintptr_t uptrval;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately uintptr_t is not guaranteed to be present in <stdint.h>.
This could lead to portability issues for platforms that don't support it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. In the standard, it's optional type. But I've copied this definition from lz4.c for consistency. And uptrval has been used in lz4.c for long time.
Therefore, if <stdint.h> doesn't have uintptr_t, they can't compile entire lz4 code at all. Also if I change this line, we must change counterpart in lz4.c.

But I don't say this is the best definition. So can we leave this in this PR, and can we create other PR to address uptrval issue?

Although possible solution may be

/* From n1256 (C99), "7.18.2 Limits of specified-width integer types"
> 1. The following object-like macros specify the minimum and
>    maximum limits of the types declared in <stdint.h>.
> ...
> - maximum value of pointer-holding unsigned integer type
>   UINTPTR_MAX

They say "the types declared in <stdint.h>".  So if uintptr_t is
not declared in <stdint.h>, they must not have a UINTPTR_MAX. */

#if defined(UINTPTR_MAX) /* UINTPTR_MAX is optional macro constant */
  typedef uintptr_t uptrval;
#else
  typedef uintmax_t uptrval; /* choose the available "best" type */
#endif

void in_some_function() {
    LZ4_STATIC_ASSERT(sizeof(uptrval) >= sizeof(void*));
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arf, I thought that I had removed that definition a long time ago already.
I might have confused with zstd.
Anyway, that's still a goal : we should get rid of uintptr_t.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I'm also realized that lz4frame.c is the best candidate to start "get rid of uintptr_t" movement since it didn't have uptrval 😉

@@ -886,8 +886,8 @@ LZ4_FORCE_INLINE int LZ4_compress_generic_validated(
/* the dictCtx currentOffset is indexed on the start of the dictionary,
* while a dictionary in the current context precedes the currentOffset */
const BYTE* dictBase = !dictionary ? NULL : (dictDirective == usingDictCtx) ?
dictionary + dictSize - dictCtx->currentOffset :
dictionary + dictSize - startIndex;
(const BYTE*) ((uptrval) dictionary + dictSize - dictCtx->currentOffset) :
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is strange.
We just checked !dictionary just before,
so by that point, we are certain than dictionary != NULL,
hence it shouldn't be necessary to fudge the calculation through uptrval.

Copy link
Contributor Author

@t-mat t-mat May 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this line, usan doesn't report about pointer arithmetic for NULL.

It indicates a pointer index expression overflow.

../lib/lz4.c:890:51: runtime error: pointer index expression with base 0x000000ed9910 overflowed to 0xfffffffffffe5156
../lib/lz4.c:890:51: runtime error: pointer index expression with base 0x000001d08480 overflowed to 0xffffffffffff8480
../lib/lz4.c:890:51: runtime error: pointer index expression with base 0x0000022ca440 overflowed to 0xfffffffffffca440

For instance, usan reports this error with the following variables:

dictionary=0x1d0e480, dictSize=65536, startIndex=30539776

  dictionary + dictSize - startIndex
= 0x1d0e480 + 65536 - 30539776 
= -7040

edit : put all errors for investigation

@@ -865,7 +865,7 @@ LZ4_FORCE_INLINE int LZ4_compress_generic_validated(
const BYTE* ip = (const BYTE*) source;

U32 const startIndex = cctx->currentOffset;
const BYTE* base = (const BYTE*) source - startIndex;
const BYTE* base = (const BYTE*) ((uptrval) source - startIndex);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation states that, by this point, we should have source != NULL.

Copy link
Contributor Author

@t-mat t-mat May 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usan reports an pointer arithmetic overflow.

../lib/lz4.c:868:45: runtime error: pointer index expression with base 0x000000001000 overflowed to 0xffffffffffffac2f
../lib/lz4.c:868:45: runtime error: pointer index expression with base 0x000000ed9910 overflowed to 0xfffffffffffe5156
../lib/lz4.c:868:45: runtime error: pointer index expression with base 0x000001cd3400 overflowed to 0xffffffffffff3400

edit : put all errors for future investigation.

@@ -978,17 +978,17 @@ LZ4_FORCE_INLINE int LZ4_compress_generic_validated(
matchIndex += dictDelta; /* make dictCtx index comparable with current context */
lowLimit = dictionary;
} else {
match = base + matchIndex;
match = (const BYTE*) ((uptrval) base + matchIndex);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should rather ensure that, whenever this code is triggered, we have a guarantee that base != NULL.
asserting this condition would be fine.

@Cyan4973
Copy link
Member

Some of these runtime UB warnings seem to uncover root causes that should not happen.
I feel we should use this opportunity to take care of the root causes.
Hiding the UB behind a complex cast equivalent seems like a worse outcome to me.

@t-mat
Copy link
Contributor Author

t-mat commented May 28, 2021

I'm really sorry. But these errors which you've commented are not caused by NULL pointer. It's a pointer overflow.
I had have to append an actual make usan error for every single line of change. Again, I apologize about my laziness.

edit: I'll write instance of all usan errors later.

@Cyan4973
Copy link
Member

Indeed, pointer overflow is a different type of UB.
Problem is, the code relies on the assumption of a flat address space.
This issue will be much more difficult to eliminate, especially without impacting performance.

I would recommend to ignore it for the time being.
For reference, in zstd, we use -fsanitize-recover=pointer-overflow for ubsan

@@ -1554,7 +1556,7 @@ size_t LZ4F_decompress(LZ4F_dctx* dctx,
} }

srcPtr += sizeToCopy;
dstPtr += sizeToCopy;
dstPtr = (BYTE*) ((uptrval) dstPtr + sizeToCopy);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usan-log:../lib/lz4frame.c:1557:24: runtime error: applying zero offset to null pointer

Since we have if (dstPtr == NULL) in the previous block, this should be fixed as the following

                if (dstPtr == NULL) {
                    sizeToCopy = 0;
                } else {
                    ...
                    if (dctx->frameInfo.blockMode == LZ4F_blockLinked) {
                       ...
-               }   }
+                   }
+                   dstPtr += sizeToCopy;
+               }

                srcPtr += sizeToCopy;
-               dstPtr += sizeToCopy; /* [usan] ../lib/lz4frame.c:1557:24: runtime error: applying zero offset to null pointer */

lib/lz4frame.c Show resolved Hide resolved
lowLimit = (const BYTE*)source;
}
} else if (dictDirective==usingExtDict) {
if (matchIndex < startIndex) {
DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex);
assert(startIndex - matchIndex >= MINMATCH);
match = dictBase + matchIndex;
match = (const BYTE*) ((uptrval)dictBase + matchIndex);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

../lib/lz4.c:988:42: runtime error: applying non-zero offset 2096600 to null pointer
../lib/lz4.c:988:42: runtime error: pointer index expression with base 0xfffffffffffca440 overflowed to 0x0000022c9b93
../lib/lz4.c:988:42: runtime error: pointer index expression with base 0xffffffffffff8480 overflowed to 0x000001d05682

lowLimit = dictionary;
} else {
match = base + matchIndex;
match = (const BYTE*) ((uptrval) base + matchIndex);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

../lib/lz4.c:991:38: runtime error: pointer index expression with base 0xffffffffffff3400 overflowed to 0x000001cd3490
../lib/lz4.c:991:38: runtime error: pointer index expression with base 0xffffffffffffac2f overflowed to 0x000000001092

@t-mat
Copy link
Contributor Author

t-mat commented May 28, 2021

Since my inline comments for PR looks like spam, I'd like to just put a entire make usan log here. Fell free to tell me that you prefer to see inline comment. If so, I'll split and put this log to correspond line.

`make usan` (lib/lz4.c)
../lib/lz4.c:868:45: runtime error: pointer index expression with base 0x000000001000 overflowed to 0xffffffffffffac2f
../lib/lz4.c:868:45: runtime error: pointer index expression with base 0x000000ed9910 overflowed to 0xfffffffffffe5156
../lib/lz4.c:868:45: runtime error: pointer index expression with base 0x000001cd3400 overflowed to 0xffffffffffff3400

../lib/lz4.c:890:51: runtime error: pointer index expression with base 0x000000ed9910 overflowed to 0xfffffffffffe5156
../lib/lz4.c:890:51: runtime error: pointer index expression with base 0x000001d08480 overflowed to 0xffffffffffff8480
../lib/lz4.c:890:51: runtime error: pointer index expression with base 0x0000022ca440 overflowed to 0xfffffffffffca440

../lib/lz4.c:988:42: runtime error: applying non-zero offset 2096600 to null pointer
../lib/lz4.c:988:42: runtime error: pointer index expression with base 0xfffffffffffca440 overflowed to 0x0000022c9b93
../lib/lz4.c:988:42: runtime error: pointer index expression with base 0xffffffffffff8480 overflowed to 0x000001d05682

../lib/lz4.c:991:38: runtime error: pointer index expression with base 0xffffffffffff3400 overflowed to 0x000001cd3490
../lib/lz4.c:991:38: runtime error: pointer index expression with base 0xffffffffffffac2f overflowed to 0x000000001092

../lib/lz4.c:995:34: runtime error: pointer index expression with base 0xfffffffffffe5156 overflowed to 0x000000ed2093

../lib/lz4.c:1176:38: runtime error: pointer index expression with base 0xfffffffffffca440 overflowed to 0x0000022c9aeb
../lib/lz4.c:1176:38: runtime error: pointer index expression with base 0xffffffffffff8480 overflowed to 0x000001d06ca8

../lib/lz4.c:1179:34: runtime error: pointer index expression with base 0xffffffffffff3400 overflowed to 0x000001cd344c
../lib/lz4.c:1179:34: runtime error: pointer index expression with base 0xffffffffffffac2f overflowed to 0x000000001899

../lib/lz4.c:1183:30: runtime error: pointer index expression with base 0xfffffffffffe5156 overflowed to 0x000000ed34b1

../lib/lz4.c:1671:58: runtime error: applying zero offset to null pointer

../lib/lz4.c:2289:49: runtime error: applying zero offset to null pointer
`make usan` lib/lz4hc.c
../lib/lz4hc.c:109:23: runtime error: pointer index expression with base 0x000000001000 overflowed to 0xffffffffffff1000
../lib/lz4hc.c:109:23: runtime error: pointer index expression with base 0x000000e6d6a0 overflowed to 0xfffffffffffce25f

../lib/lz4hc.c:111:27: runtime error: pointer index expression with base 0x000000001000 overflowed to 0xffffffffffff1000
../lib/lz4hc.c:111:27: runtime error: pointer index expression with base 0x000000e6d6a0 overflowed to 0xfffffffffffce25f

../lib/lz4hc.c:127:41: runtime error: pointer index expression with base 0xffffffffffde3410 overflowed to 0x000001df3404
../lib/lz4hc.c:127:41: runtime error: pointer index expression with base 0xfffffffffffce25f overflowed to 0x000000e6d6a0
../lib/lz4hc.c:127:41: runtime error: pointer index expression with base 0xffffffffffff1000 overflowed to 0x000000001000

../lib/lz4hc.c:255:43: runtime error: pointer index expression with base 0xfffffffffffce25f overflowed to 0x000000e6d6a0
../lib/lz4hc.c:255:43: runtime error: pointer index expression with base 0xffffffffffff1000 overflowed to 0x000000001000

../lib/lz4hc.c:281:47: runtime error: pointer index expression with base 0xfffffffffffce25f overflowed to 0x000000e6d75b
../lib/lz4hc.c:281:47: runtime error: pointer index expression with base 0xffffffffffff1000 overflowed to 0x000000001066

../lib/lz4hc.c:296:51: runtime error: pointer index expression with base 0xffffffffffde3410 overflowed to 0x000001df0839

../lib/lz4hc.c:298:56: runtime error: pointer index expression with base 0xffffffffffde3410 overflowed to 0x000001de3410

../lib/lz4hc.c:309:38: runtime error: pointer index expression with base 0xfffffffffffeac2f overflowed to 0x000000000375

../lib/lz4hc.c:1037:52: runtime error: applying non-zero offset to non-null pointer 0x7ffff43a77c0 produced null pointer
../lib/lz4hc.c:1037:52: runtime error: subtraction of unsigned offset from 0x000000e6fb2b overflowed to 0x000000ea18cc

../lib/lz4hc.c:1092:37: runtime error: pointer index expression with base 0xffffffffffde3410 overflowed to 0x000001de3410

../lib/lz4hc.c:1099:29: runtime error: pointer index expression with base 0x000000001000 overflowed to 0xfffffffffffeac2f

../lib/lz4hc.c:1133:56: runtime error: pointer index expression with base 0xfffffffffff27f75 overflowed to 0x000000ec9910
../lib/lz4hc.c:1133:56: runtime error: pointer index expression with base 0xffffffffffde3410 overflowed to 0x000001de3410
../lib/lz4hc.c:1133:56: runtime error: pointer index expression with base 0xffffffffffff1000 overflowed to 0x000000001000

../lib/lz4hc.c:1134:56: runtime error: pointer index expression with base 0xffffffffffde3410 overflowed to 0x000001df3410
../lib/lz4hc.c:1134:56: runtime error: pointer index expression with base 0xfffffffffff27f75 overflowed to 0x000000ec9910
../lib/lz4hc.c:1134:56: runtime error: pointer index expression with base 0xffffffffffff1000 overflowed to 0x000000001000

../lib/lz4hc.c:1167:68: runtime error: applying zero offset to null pointer
../lib/lz4hc.c:1167:68: runtime error: pointer index expression with base 0xfffffffffff27f75 overflowed to 0x000000ec9910

../lib/lz4hc.c:1177:50: runtime error: applying zero offset to null pointer

../lib/lz4hc.c:1178:42: runtime error: pointer index expression with base 0x000001df3410 overflowed to 0xffffffffffde3410
../lib/lz4hc.c:1178:42: runtime error: applying zero offset to null pointer
../lib/lz4hc.c:1178:42: runtime error: pointer index expression with base 0x000000ed9910 overflowed to 0xfffffffffff1490c

../lib/lz4hc.c:1372:46: runtime error: pointer index expression with base 0x000000001000 overflowed to 0xffffffffffffbadb

../lib/lz4hc.c:1550:78: runtime error: pointer index expression with base 0x00000000123c overflowed to 0xffffffffffffe3a0

../lib/lz4hc.c:1552:33: runtime error: pointer index expression with base 0x00000000172e overflowed to 0xfffffffffffff127

@Cyan4973
Copy link
Member

Thanks @t-mat ,
your log report is pretty clear.

@t-mat
Copy link
Contributor Author

t-mat commented May 29, 2021

It's not an error of make usan, but it may be addressed during reviewing process about ubsan.

When I run the following test

CC=clang-10 CXX=clang++-10 make -C tests clean test-lz4c32

I've seen the following assertion.

---- test huge files compression/decompression ----
...
./datagen -g4500MB | ../programs/lz4 -v3BD | ../programs/lz4 -qt
*** LZ4 command line interface 32-bits v1.9.3, by Yann Collet ***
...
Read : 2040 MB   ==> 45.27%   lz4: ../lib/lz4hc.c:282: int LZ4HC_InsertAndGetWiderMatch(LZ4HC_CCtx_internal *, 
const BYTE *const, const BYTE *const, const BYTE *const, int, const BYTE **, const BYTE **, const int, const 
int, const int, const dictCtx_directive, const HCfavor_e)
: Assertion `matchPtr >= lowPrefixPtr' failed.
Aborted (core dumped)

Here's a full test log.

I also confirmed same assertion with all recent versions of clang from clang-3.5 to clang-12.

Please let me know if this is not a valid test. I'm working to include this test scenario into GH-Actions CI.

@t-mat
Copy link
Contributor Author

t-mat commented May 29, 2021

gcc-11 reports another error for x86 build. It isn't related to make usan though.

$ CC=gcc-11 CXX=g++-11 make -C tests clean test-lz4c32

./datagen -g4500MB | ../programs/lz4 -v3BD | ../programs/lz4 -qt
*** LZ4 command line interface 32-bits v1.9.3, by Yann Collet ***
...
Read : 2040 MB   ==> 45.26%   lz4: ../lib/lz4hc.c:148: LZ4HC_countBack: Assertion `(size_t)(match - mMin) < (1U<<31)' failed.

Here's a full log.

@Cyan4973
Copy link
Member

gcc-11 reports another error for x86 build. It isn't related to make usan though.

I presume it's related to #991 ?

@Cyan4973
Copy link
Member

closing, as I believe that using uptrval is not the right way to deal with these usan warnings

@Cyan4973 Cyan4973 closed this May 30, 2021
@t-mat
Copy link
Contributor Author

t-mat commented May 30, 2021

gcc-11 reports another error for x86 build. It isn't related to make usan though.
I presume it's related to #991 ?

I also think so. But it seems they're (edit: GH-Actions team is) pulling out GCC11 from the repository.

They will remove it from ubuntu-18.04 and ubuntu-20.04.

I apologize again for making many confusions by random comments and hasty introduction of gcc-11 without confirmation. Sorry.

@Cyan4973
Copy link
Member

No pb @t-mat , you are trying to make a great CI system, and finding many little details in the process.
It's normal and healthy !

@t-mat
Copy link
Contributor Author

t-mat commented May 31, 2021

I presume it's related to #991 ?

I build gcc-11 (gcc 11.1.0) from source, and observe same assertion. So added it to #991.

Assertion is:

 lz4: ../lib/lz4hc.c:148: LZ4HC_countBack: Assertion `(size_t)(match - mMin) < (1U<<31)' failed.

@t-mat t-mat deleted the fix-usan-errors branch August 12, 2022 04:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants