-
Notifications
You must be signed in to change notification settings - Fork 112
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 two buffer overruns reachable from safe code #231
Conversation
Issue 1: When `zstd::stream::raw::NoOp::run` computes the length of the slice that it should copy, it does not take `output.pos()` into account. Instead it can write up to offset `output.dst.capacity() + output.pos()`, if the input is long enough. So even someone who uses NoOp exactly as it's intended to be used may trigger a buffer overrun. The minimal fix for this issue is to use `output.dst.capacity() - output.pos()` instead, in the call to `min`. Issue 2: zstd_safe::OutBuffer is documented as having the invariant that "pos <= dst.capacity()". However, it can't reliably enforce that invariant. Because `dst` is a public field, its capacity can be changed without updating `pos`. For example, if `dst` is a `Vec`, someone could resize it to be smaller than `pos` and then call `shrink_to_fit` on it. Additionally, it's possible to entirely replace one `Vec` with another, which may have a smaller capacity. In either case, the invariant would be violated without using any `unsafe` blocks. As a result I believe the zstd_safe::CCtx/DCtx methods which use OutBuffer are currently vulnerable to buffer overruns in the underlying C library even if the caller uses only safe Rust. In addition: `zstd::stream::raw::NoOp::run` contains the only `unsafe` blocks in the `zstd` crate. The first of the three `unsafe` blocks there is only safe if OutBuffer's invariant is preserved. Since its invariant can't currently be assumed even if the callers use only safe code, this is a potential buffer overrun as well. I've audited all uses of the private `OutBuffer::pos` field and I believe they're safe, so all possibly-unsafe accesses pass through the public `fn pos()` accessor. Therefore I've added an assertion there to check that the invariant still holds. I believe this method is called infrequently and not performance-critical.
Hi, and thanks for the investigation!
Arf indeed, thanks for the fix!
Whoops indeed. I think we probably don't need this field to be public. We only need to access I pushed this proposed change in 3cb6088.
I'm afraid assertions are entirely skipped in release mode, so this would still be vulnerable (but detectable in debug if the user is lucky). |
Nice, I think making Thank you for merging this quickly! Do you have a schedule in mind for making a release containing these fixes? |
Ah, good to know! Not sure where this misconception came from.
Still wondering if I can sneakily not bump the version, as it hopefully affects very few real use cases. In particular most users of the zstd crate should be unaffected by this technically breaking change. |
I've been thinking about this a bit. Yeah, it would be nice to let cargo automatically pick up the new version, but you'd know better than I do whether making My suggestion would be to do a patch release with only my Whichever approach you pick, I think it'll also help to file a RustSec advisory so that people are more likely to notice the new version. In addition, once there's a release I can record a passing audit for, I'll also add a |
Changes: * Bump zstd-safe to 7.0.0. * Fix potential buffer overflow (#231). * Some doc improvement.
When I tried to audit our previous exemption for zstd, I found two buffer overruns which were reachable from safe Rust, although not reachable from Wasmtime. I got them fixed upstream but didn't update our cargo-vet audits to reflect the issue with the older versions. Alex updated our dependencies to pull in the fixed versions in bytecodealliance#7870, and this PR notes for the benefit of anyone importing the Bytecode Alliance audit set that older versions should not be used. See gyscos/zstd-rs#231
When I tried to audit our previous exemption for zstd, I found two buffer overruns which were reachable from safe Rust, although not reachable from Wasmtime. I got them fixed upstream but didn't update our cargo-vet audits to reflect the issue with the older versions. Alex updated our dependencies to pull in the fixed versions in bytecodealliance#7870, and this PR notes for the benefit of anyone importing the Bytecode Alliance audit set that older versions should not be used. See gyscos/zstd-rs#231
I was auditing the zstd crate's usage of unsafe blocks as part of a larger effort to audit the transitive dependencies of Wasmtime. I haven't audited zstd_safe in detail, but I believe the following two issues are the only ones which impact the few
unsafe
blocks in the zstd crate. Hopefully I'll have time later to go look at zstd_safe in depth as well.Issue 1:
When
zstd::stream::raw::NoOp::run
computes the length of the slice that it should copy, it does not takeoutput.pos()
into account. Instead it can write up to offsetoutput.dst.capacity() + output.pos()
, if the input is long enough. So even someone who uses NoOp exactly as it's intended to be used may trigger a buffer overrun.The minimal fix for this issue is to use
output.dst.capacity() - output.pos()
instead, in the call tomin
.Issue 2:
zstd_safe::OutBuffer is documented as having the invariant that "pos <= dst.capacity()". However, it can't reliably enforce that invariant. Because
dst
is a public field, its capacity can be changed without updatingpos
. For example, ifdst
is aVec
, someone could resize it to be smaller thanpos
and then callshrink_to_fit
on it. Additionally, it's possible to entirely replace oneVec
with another, which may have a smaller capacity. In either case, the invariant would be violated without using anyunsafe
blocks.As a result I believe the zstd_safe::CCtx/DCtx methods which use OutBuffer are currently vulnerable to buffer overruns in the underlying C library even if the caller uses only safe Rust.
In addition:
zstd::stream::raw::NoOp::run
contains the onlyunsafe
blocks in thezstd
crate. The first of the threeunsafe
blocks there is only safe if OutBuffer's invariant is preserved. Since its invariant can't currently be assumed even if the callers use only safe code, this is a potential buffer overrun as well.I've audited all uses of the private
OutBuffer::pos
field and I believe they're safe, so all possibly-unsafe accesses pass through the publicfn pos()
accessor. Therefore I've added an assertion there to check that the invariant still holds. I believe this method is called infrequently and not performance-critical.