-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
tokio::io::ReadHalf<T>::unsplit
can violate Pin
contract when T: !Unpin
#5372
Comments
Thanks for the report. For context, @zachs18 initially reported this via our security process. We decided that it was not a security issue and asked for the report to be filed as an issue. The reasoning is that the specific set of conditions needed to trigger an issue (a We will address the issue here and backport fixes to the LTS branches. |
Fixed in 1.18.6 (LTS), 1.20.4 (LTS), and 1.24.2 (latest). |
Version
From what I gather on docs.rs,
tokio::io::ReadHalf::unsplit
was introduced in tokio0.1.12
, so all versions since0.1.12
are probably affected, but the example is tested with tokio1.24.1
.Requires the
io-util
feature.Platform
Not platform-specific.
Background
tokio::io::split
allows splitting aT: AsyncRead + AsyncWrite
into separateReadHalf<T>: AsyncRead
andWriteHalf<T>: AsyncWrite
halves, each of which can be used separately. This is done by havingReadHalf<T>
'spoll_read
method callT
'spoll_read
method, andWriteHalf<T>
'spoll_write
callT
'spoll_write
method (with appropriate mutual exclusion). Note that both of these methods takeself: Pin<&mut Self>
, which is a guarantee that theT
will not be moved before it is dropped (unlessT: Unpin
) (https://doc.rust-lang.org/std/pin/index.html#drop-guarantee). This is fine in absence ofunsplit
, since as a safety comment says (https://github.com/tokio-rs/tokio/blob/master/tokio/src/io/split.rs#L154), theT
is stored in anArc
that is shared between theReadhalf<T>
andWriteHalf<T>
, and which is not deallocated or moved from until both theReadHalf
andWriteHalf
are dropped (and thus theT
is dropped).Description
However, in the presence of
ReadHalf::unsplit
, this safety comment is incorrect: theT
is not pinned, since theT
may be moved out of the sharedArc
by callingReadHalf::unsplit
(https://github.com/tokio-rs/tokio/blob/master/tokio/src/io/split.rs#L82), possibly after aPin<&mut T>
has been created by callingReadHalf::poll_read
orWriteHalf::poll_write
, violating thePin
contract (ifT: !Unpin
).A simple (but breaking-change) fix would be to add a
where T: Unpin
bound toReadHalf::unsplit
. (and perhaps to hold aPin<Arc<Inner<T>>>
instead of anArc<Inner<T>>
inReadHalf
andWriteHalf
to communicate the requirement).Note that there are not (to my knowledge) any concrete types in
tokio
by itself which areAsyncRead + AsyncWrite + !Unpin
(File, TcpStream, and UnixStream are allUnpin
, and the adapters all areUnpin
if their underlying type isUnpin
).Following is (what I think is) an (admittedly somewhat dubious) minimal example of undefined behaviour (use-after-free) resulting from this violation of the
Pin
contract. Here is a playground link to the same example.Example code
Cargo.toml
:src/main.rs
In
async fn example_1
,BadRW
allows writing to deallocated memory. Inasync fn example_2
,BadRW
allows writing to a different object than the one that was pinned (if the allocator re-uses the address of a particular previous allocation).Following are the output of
cargo run
andMIRIFLAGS=-Zmiri-backtrace=full cargo +nightly miri run
on my computer (only the first example gets run under miri, because it aborts. The second example isn't interesting under miri, since miri doesn't re-use the previous allocation anyway).`cargo run` output
miri output
The text was updated successfully, but these errors were encountered: