-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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 oss-fuzz case 55714 #3476
fix oss-fuzz case 55714 #3476
Conversation
lib/legacy/zstd_v03.c
Outdated
@@ -2711,7 +2711,7 @@ static size_t ZSTD_execSequence(BYTE* op, | |||
if (litEnd > litLimit) return ERROR(corruption_detected); /* overRead beyond lit buffer */ | |||
|
|||
/* copy Literals */ | |||
ZSTD_wildcopy(op, *litPtr, sequence.litLength); /* note : oLitEnd <= oend-8 : no risk of overwrite beyond oend */ | |||
ZSTD_memmove(op, *litPtr, sequence.litLength); /* note : used to be wildCopy, changed to fix a bug in 32-bit mode (oss-fuzz case 55714) */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this fix is sufficient. We're supposed to have 8 extra bytes in the litBuffer, so this should be valid as long as litEnd <= litLimit
.
I think the problem we're running into is that the limit checks should be re-written to avoid overflow. We don't care about speed, so we should do the obviously correct thing.
The offset check also looks janky, so we should clean that up as well.
E.g.
size_t const seqLength = sequence.litLength + sequence.matchLength;
if (seqLength > (size_t)(oend - op)) return ERROR(dstSize_tooSmall);
if (sequence.litLength > (size_t)(litLimit -*litPtr)) return ERROR(corruption_detected);
/* Now we know we don't have any overflow in literal / match lengths, can use the pointer check for oend_8*/
if (oLitEnd > oend_8) return ERROR(dstSize_tooSmall);
if (sequence.offset > (U32)(oLitEnd - base)) return ERROR(corruption_detected);
This bug is present in every version of the legacy decoder, so we'll have to fix every one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few small changes left. And I think we also need to add the same checks to zstd_v07.c
if (endMatch > oend) return ERROR(dstSize_tooSmall); /* overwrite beyond dst buffer */ | ||
if (litEnd > litLimit) return ERROR(corruption_detected); | ||
if (sequence.matchLength > (size_t)(*litPtr-op)) return ERROR(dstSize_tooSmall); /* overwrite literal segment */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that we need to keep this check, because the literals are put in the output buffer, but oend
isn't reduced.
lib/legacy/zstd_v04.c
Outdated
if (sequence.litLength > (size_t)(litLimit - *litPtr)) return ERROR(corruption_detected); | ||
/* Now we know there are no overflow in literal nor match lengths, can use pointer checks */ | ||
if (oLitEnd > oend_8) return ERROR(dstSize_tooSmall); | ||
if (sequence.offset > (U32)(oLitEnd - base)) return ERROR(corruption_detected); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we have extDict starting in v0.4. But the offset check below looks correct, so we can just delete this starting in this version.
if (sequence.offset > (U32)(oLitEnd - base)) return ERROR(corruption_detected); |
lib/legacy/zstd_v05.c
Outdated
if (sequence.litLength > (size_t)(litLimit - *litPtr)) return ERROR(corruption_detected); | ||
/* Now we know there are no overflow in literal nor match lengths, can use pointer checks */ | ||
if (oLitEnd > oend_8) return ERROR(dstSize_tooSmall); | ||
if (sequence.offset > (U32)(oLitEnd - base)) return ERROR(corruption_detected); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (sequence.offset > (U32)(oLitEnd - base)) return ERROR(corruption_detected); |
lib/legacy/zstd_v06.c
Outdated
if (sequence.litLength > (size_t)(litLimit - *litPtr)) return ERROR(corruption_detected); | ||
/* Now we know there are no overflow in literal nor match lengths, can use pointer checks */ | ||
if (oLitEnd > oend_8) return ERROR(dstSize_tooSmall); | ||
if (sequence.offset > (U32)(oLitEnd - base)) return ERROR(corruption_detected); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (sequence.offset > (U32)(oLitEnd - base)) return ERROR(corruption_detected); |
@Cyan4973 the version compatibility test did catch the bug in |
impacts legacy decoder v0.3 in 32-bit mode
in case it would be applicable here too.
slightly different constraints on end of buffer conditions
in case it would be applicable for this legacy version too.
in case it would be applicable for this version too
in case it would applicable for this version
Below With this modified benchmark module (not published here), I could verify that the fixes for legacy decoders |
which uses a different technique to store literals, and therefore must check for potential overwrites.
@Cyan4973 do we also need to fix |
Otherwise the PR looks good! |
Well, it's not clear if that would be useful. |
@Cyan4973 I see the same potential 32-bit pointer overflows in v0.7: Lines 3555 to 3556 in df21ace
We've built all of our OSS-Fuzz fuzzers with all legacy versions since 2019. But we haven't run any 32-bit fuzzers, where this issue could occur, until just now. |
There is definitely a pointer overflow bug in 32-bit mode. We have a similar fix in dev, but I guess we didn't apply it to all of our legacy decoders: zstd/lib/decompress/zstd_decompress_block.c Line 991 in df21ace
The bug in v0.3 is occurring because of a pointer overflow. It happens that v0.3 allows very large literal lengths (in the fuzzed example it is I'm also wondering if we need to guard against the end of the literals buffer being within 128KB of the end of the address space in dev. We can consider that in a separate PR though. |
Versions
|
OK, checks of |
This issue, discovered by oss-fuzz, can only happen with the following conditions :
that is to say, this scenario is very unlikely to be reproducible in the wild.
Fixing it nonetheless for completeness.
The fix is trivial, though it's likely going to cost some performance. But at this stage, it's fair to say we don't care about the performance of the deprecated legacy decoder v0.3 anymore. There shouldn't be any data left using this old short lived legacy format.