-
Notifications
You must be signed in to change notification settings - Fork 105
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
Ref<core::cell::Ref<[u8]>, _>::into_ref
is unsound
#716
Comments
How exciting. (I came here via #679, and I have not analysed the unsoundness in detail. Taking what is written here as true:) I would recommend filing a RUSTSEC advisory and not yanking affected versions. Possibly telling you things you already knowAlso you should probably do an underlying process root cause analysis. The technical analysis above is a concrete explanation of the programming mistake. But programming mistakes are inevitable because we are human. When mistakes can lead to significant problems, it is usually best to have safeguards (procedural, or engineered) that try to catch or mitigate those errors. Your goals presumably include avoid deploying vulnerable code; with the choice of Rust as a language, that refines to a sub-goal of not deploying unsound code. Deployment of this mistake represents a failure of your process for preventing unsoundness, and could be seen as a near-miss for deployment of a vulnerability. (I don't feel qualified from reading the above to say how near a miss it is.) Or to put it another way, you might want to reconsider your approach towards assurance of memory-unsafe code including unsafe Rust. Ways you might prevent deployment of mistakes might include machine verification or checking, elimination of unneeded unsafe APIs, adoption of defensive coding styles, semiformal coding practices or proofs, or (weakest) informally-structured manual human review or audit. |
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.7.31.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.7.31.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.6.6.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.5.2.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.5.2.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.4.1.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.3.2.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.2.9.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.2.9.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.3.2.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.6.6.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.5.2.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.4.1.
For more information: - google/zerocopy#716 - GHSA-3mv5-343c-w2qg
For more information: - google/zerocopy#716 - GHSA-3mv5-343c-w2qg
This update was provoked by a flaw in the zerocopy dependency. - google/zerocopy#716
[Unsoundness issue in 0.7.30 and below](google/zerocopy#716)
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.8.0-alpha.2.
This commit implements the fix for #716 which will be released as a new version in version trains 0.2, 0.3, 0.4, 0.5, 0.6, and 0.7. See #716 for a description of the soundness hole and an explanation of why this fix is chosen. Unfortunately, due to dtolnay/trybuild#241, there is no way for us to write a UI test that will detect a failure post-monomorphization, which is when the code implemented in this change is designed to fail. I have manually verified that unsound uses of these APIs now fail to compile. Release 0.8.0-alpha.2.
All affected version trains (including 0.8.0-alpha) have now been fixed and all affected versions have been yanked. We still want to follow up with a more ergonomic and natural API (#758), but that can be considered follow-up work. Thus, I'm considering this done and closing this issue. |
Add `IntoByteSlice` and `IntoByteSliceMut` traits, which extend `ByteSlice` and `ByteSliceMut`, adding `Into<&[u8]>` and `Into<&mut [u8]>` bounds respectively. Use these new traits as the bounds in the `Ref` methods `into_ref`, `into_mut`, `into_slice`, and `into_mut_slice`. This allows us to remove the post-monomorphization error which was originally added to patch the soundness hole in #716 in a backwards-compatible way. Closes #758
Add `IntoByteSlice` and `IntoByteSliceMut` traits, which extend `ByteSlice` and `ByteSliceMut`, adding `Into<&[u8]>` and `Into<&mut [u8]>` bounds respectively. Use these new traits as the bounds in the `Ref` methods `into_ref`, `into_mut`, `into_slice`, and `into_mut_slice`. This allows us to remove the post-monomorphization error which was originally added to patch the soundness hole in #716 in a backwards-compatible way. Closes #758
Add `IntoByteSlice` and `IntoByteSliceMut` traits, which extend `ByteSlice` and `ByteSliceMut`, adding `Into<&[u8]>` and `Into<&mut [u8]>` bounds respectively. Use these new traits as the bounds in the `Ref` methods `into_ref`, `into_mut`, `into_slice`, and `into_mut_slice`. This allows us to remove the post-monomorphization error which was originally added to patch the soundness hole in #716 in a backwards-compatible way. Closes #758
For brevity, this issue will refer to
core::cell::Ref
asCoreRef
andcore::cell::RefMut
asCoreRefMut
.Overview
Ref<B, T>
'sinto_ref
,into_mut
,into_slice
, andinto_mut_slice
methods are unsound whenB
isCoreRef<[u8]>
orCoreRefMut<[u8]>
.Progress
ByteSlice
do not permit this unsoundnessProof of concept
The following (100% safe) code exhibits undefined behavior according to Miri (playground link):
Miri output:
The following playground examples demonstrate the same UB using the other types and methods:
CoreRefMut
andRef::into_mut
CoreRef
andRef::into_slice
CoreRefMut
andRef::into_mut_slice
Explanation
This explanation is in terms of
CoreRef
and theRef::into_ref
method, but the same explanation holds ofCoreRefMut
andRef::into_mut
,Ref::into_slice
, andRef::into_mut_slice
.The relevant bits of the code are:
zerocopy/src/lib.rs
Lines 4787 to 4805 in a8572da
zerocopy/src/lib.rs
Lines 4924 to 4947 in a8572da
zerocopy/src/lib.rs
Lines 5332 to 5339 in a8572da
Quick explanation
We'll start with a concise explanation which will paper over the unsoundness, and hopefully help to demonstrate how a bug like this could have made its way in.
Ref::deref_helper
is unsafe, and allows the caller to provide an arbitrary lifetime,'a
, which is not necessarily the same as the lifetime of its&self
parameter. It returns a reference,&'a T
, of that arbitrary lifetime. In exchange, it requires the caller to guarantee that "the lifetime'a
is valid for this reference."Ref::into_ref
is only available when the buffer type,B
, is valid for'a
(ie,B: 'a
). Thus, it can soundly callderef_helper
internally - theB: 'a
bound ensures that the returned reference won't outlive the buffer.Bug
The bug comes from the fact that
B: 'a
is an insufficient guarantee. In particular, it guarantees that the buffer type satisfies'a
, but what that means differs by type. When I authored this code, I was probably implicitly thinking in terms of&'a [u8]
, where the'a
means that the underlying memory is considered immutably borrowed for the lifetime'a
- it can neither be mutated nor dropped during'a
.Unfortunately,
CoreRef<[u8]>: 'a
only requires that the underlyingRefCell
lives for at least'a
. [1] TheCoreRef
itself may be dropped sooner than this and still satisfyCoreRef<[u8]>: 'a
. Of course, if we think specifically in terms ofRefCell
, it makes sense that this has to be the behavior -RefCell
's whole purpose is to allow mutation which is tracked dynamically at runtime rather than statically at compile time. By dropping theCoreRef
, we're decrementing theRefCell
's refcount and allowing the underlying memory to be mutated again.Another perspective on why this is problematic is to trace the function call stack and figure out why this code compiles at all. We start with
Ref::into_ref
, which takesself
by value. As a consequence, it dropsself
at the end of the method scope. We might reasonably ask how this is able to compile given that the returned reference outlives that scope.As we already saw,
Ref::deref_helper
takes a&self
of any lifetime and returns a&'a T
of the caller-chosen lifetime'a
. Internally, it accomplishes this by calling<B as ByteSlice>::as_ptr
.as_ptr
is defined like so:zerocopy/src/lib.rs
Lines 5332 to 5339 in a8572da
This involves an implicit call to
Deref::deref
, so we can desugar like so:Note that
Deref<Target = [u8]>::deref
has the signaturefn deref<'a>(&'a self) -> &'a [u8]
, and so the returnedbytes
has the same lifetime as theself
argument toas_ptr
. Bubbling up the call stack, that means that it has the same lifetime asself
inRef::into_ref
, which is only valid until the end ofinto_ref
's method scope.[1] In particular, the
RefCell<T>
methodborrow
has the signaturefn borrow<'a>(&'a self) -> CoreRef<'a, T>
- the'a
serves to ensure that the returnedCoreRef
doesn't outlive the underlyingRefCell
.History and affected versions
This commit from December, 2019 is as far back as our Git history goes, when zerocopy's code was migrated from elsewhere in Fuchsia's codebase (this was before zerocopy had been moved to GitHub, when it still lived in Fuchia's source tree).
From a manually search of docs.rs, it seems that the earliest affected version is 0.2.2.
Ref::into_ref
exists here in 0.2.2, but does not exist in 0.2.1.Impact
It is difficult to estimate the usage of these methods in the wild, but in my personal experience, I've never seen anyone use the
Ref<B, T>
type withB = CoreRef<[u8]>
orB = CoreRefMut<[u8]>
. An affected user would need to use this setup and also call theinto_ref
,into_mut
,into_slice
, orinto_mut_slice
methods.Mitigation plan
Thankfully, our Git history goes back far enough that we have source code for every affected minor version (ie, 0.2.X and onwards). Looking at the history up until the release of 0.3.0, we can see that the most recent commit we have from 0.2.X is 19ac2a7, and this is confirmed by the
Cargo.toml.crates-io
file. Thus, we should be able to backport a fix to every affected minor version. Depending on how the discussion in #679 concludes, we may also decide to yank some or all affected versions after publishing a fix to each affected minor version.Fix
TL;DR: We will add a check that causes code which exercises this UB to fail compilation after monomorphization.
What fix we implement on existing versions is significantly more constrained. No matter what we do, any fix must technically be a breaking change, as code which could previously compile and exhibit UB at runtime should either no longer compile or should panic at runtime.
We considered a few options, and evaluated them across a few axes:
Ref<B, T>
'sB
parameter is generic, but in practiceB
is never set toCoreRef
orCoreRefMut
. This code should continue to compile.CoreRef
orCoreRefMut
in combination withRef
and not make use ofinto_ref
,into_mut
,into_slice
, orinto_mut_slice
. This code should continue to compile.CoreRef
orCoreRefMut
in combination withRef
and make use ofinto_ref
,into_mut
,into_slice
, orinto_mut_slice
, which may exhibit UB at runtime. This code should no longer compile.We considered these options:
into_ref
,into_mut
,into_slice
, orinto_mut_slice
on an extra trait bound on theB
type parameter - importantly, a trait bound not satisfied byCoreRef
orCoreRefMut
.into_ref
,into_mut
,into_slice
, orinto_mut_slice
currently requireB: ByteSlice
, we could remove the impl ofByteSlice
forCoreRef
andCoreRefMut
.into_ref
,into_mut
,into_slice
, orinto_mut_slice
are called whenB
is set toCoreRef
orCoreRefMut
, they panic before exhibiting UB.into_ref
,into_mut
,into_slice
, orinto_mut_slice
are called whenB
is set toCoreRef
orCoreRefMut
, compilation is failed after monomorphization has already happened.Only the last option satisfies all our criteria, and so we will implement it. For completeness, here is each option evaluated against each criterion:
into_xxx
compile whenB
is generic?CoreRef
/CoreRefMut
compile when not usinginto_xxx
?B
ByteSlice
impl fromCoreRef
/CoreRefMut
The text was updated successfully, but these errors were encountered: