Undefined behavior in `ZSTD_decompress` for certain inputs
- Dominant language
- C
- Stars
- 27.9k
- Forks
- 2.6k
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 8
Description
**Describe the bug**
When compiled with clang and `-fsanitize=undefined`, `ZSTD_decompress` will hit undefined behavior with certain inputs:
```
/decompress/zstd_decompress_block.c:1696:45: runtime error: applying non-zero offset 79823856 to null pointer
```
**To Reproduce**
1. Inputs that trigger the UB: [zstd-non-zero-offset-inputs.zip](https://github.com/facebook/zstd/files/10728643/zstd-non-zero-offset-inputs.zip) (found when fuzzing a Zig implementation of a zstd decompressor)
2. Compile zstd with clang via `make lib CFLAGS="-fsanitize=undefined -fPIC"`
3. Compile examples with `make LDFLAGS="-fsanitize=undefined"`
4. Run one of the inputs through `simple_decompression`, e.g. `./simple_decompression 'id:000032,sig:06,src:000673,time:3423388,execs:797038,op:havoc,rep:2'`
Example of the output with `DEBUGLEVEL=10`
```
.//decompress/zstd_decompress.c: ZSTD_getFrameHeader_advanced: minInputSize = 5, srcSize = 39
.//decompress/zstd_decompress.c: ZSTD_decompressMultiFrame
.//decompress/zstd_decompress.c: reading magic number FD2FB528 (expecting FD2FB528)
.//decompress/zstd_decompress.c: ZSTD_decompressFrame (srcSize:39)
.//decompress/zstd_decompress.c: ZSTD_getFrameHeader_advanced: minInputSize = 5, srcSize = 9
.//decompress/zstd_decompress_block.c: ZSTD_decompressBlock_internal (size : 20)
.//decompress/zstd_decompress_block.c: ZSTD_decodeLiteralsBlock
.//decompress/zstd_decompress_block.c: ZSTD_decodeLiteralsBlock : cSize=1, nbLiterals=0
.//decompress/zstd_decompress_block.c: ZSTD_decodeSeqHeaders
.//decompress/zstd_decompress_block.c: ZSTD_getLongOffsetsShare: (tableLog=5)
.//decompress/zstd_decompress_block.c: ZSTD_decompressSequencesLong
.//decompress/zstd_decompress_block.c: ZSTD_initFseState : val=5 using 5 bits
.//decompress/zstd_decompress_block.c: ZSTD_initFseState : val=24 using 5 bits
.//decompress/zstd_decompress_block.c: ZSTD_initFseState : val=0 using 0 bits
.//decompress/zstd_decompress_block.c: seq: litL=0, matchL=3, offset=8
/decompress/zstd_decompress_block.c:1696:45: runtime error: applying non-zero offset 94193544721696 to null pointer
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /decompress/zstd_decompress_block.c:1696:45 in
.//decompress/zstd_decompress_block.c: seq: litL=2, matchL=3, offset=1
.//decompress/zstd_decompress_block.c: seq: litL=0, matchL=3, offset=4
.//decompress/zstd_decompress_block.c: seq: litL=0, matchL=3, offset=8
.//decompress/zstd_decompress_block.c: seq: litL=0, matchL=3, offset=2
.//decompress/zstd_decompress_block.c: seq: litL=0, matchL=3, offset=4
.//decompress/zstd_decompress_block.c: seq: litL=0, matchL=3, offset=8
.//decompress/zstd_decompress_block.c: seq: litL=0, matchL=3, offset=2
.//decompress/zstd_decompress_block.c: seq: litL=2, matchL=3, offset=8
.//decompress/zstd_decompress_block.c:884: ERROR!: check sequenceLength > (size_t)(oend - op) failed, returning ERROR(dstSize_tooSmall): last match must fit within dstBuffer
simple_decompression.c:40 CHECK(!ZSTD_isError(err)) failed: Destination buffer is too small
```
**Expected behavior**
No UB
**Desktop:**
- OS: Linux
- Compiler clang
**Additional context**
UB does not happen with the `streaming_decompression` example for these inputs.
Related issues: https://github.com/facebook/zstd/issues/2110, https://github.com/facebook/zstd/issues/3236
Contributor guide
Assessment
This issue has not been assessed yet.