You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fd - PipeWrite would take the ownership of this fd.
(sic)
The method is allegedly safe, but the argument, however, is a RawFd. It's not possible for PipeWrite to take ownership of a RawFd, and remain a safe method: taking ownership of a RawFd is an inherently unsafe operation, as can be seen in std's documentation for converting a RawFd into an OwnedFd. This is because you, the caller, must maintain the invariant that you yourself have the ownership to give, as it isn't (yet) tracked by the type system. Were it safe, you could simply convert one RawFd into two OwnedFds, each believing itself to be the owner of the same FD. The same problem exists here.
Under the hood, PipeWrite does not convert the FD to an OwnedFd; rather, it just keeps the RawFd, but the "take the ownership" does happen; that is visible in, e.g., PipeFd::drop:
(PipeFd is a bit of common-code; both PipeWrite & PipeRead end up holding a PipeFd.)
Given this, it's easy to whip up an example that triggers UB:
use std::fs::File;use std::io::Write;#[tokio::main]asyncfnmain(){let(_pipe_read, pipe_write) = nix::unistd::pipe().expect("failed to create pipe");let p1 = tokio_pipe::PipeWrite::from_raw_fd_checked(pipe_write).expect("the first call to from_raw_fd_checked failed, which is unexpected");let p2 = tokio_pipe::PipeWrite::from_raw_fd_checked(pipe_write).expect("the second call to from_raw_fd_checked failed, which is should happen, but doesn't");drop(p1);println!("Somehow we're here");// Open a second file to cause the FD to get recycled:letmut f = File::create("a-test-file").expect("failed to open file");// UB really starts here:// We drop a FD we don't actually own:drop(p2);// This write should succeed, but won't:
f.write_all(b"Hello, world").expect("failed to write to file unexpectedly!");}
This program should fail at let p2 =, as it isn't logically possible for both to own the same FD. However, neither fail, so we end up with both "owning" the same FD. The drop(p1) closes the pipe, setting up p2 to drop a completely random FD. The drop(p2) call here ends up closing the FD owned by the File, and the write fails with "Bad file descriptor".
In a larger program this would be rather hard to debug; writes could easily end up in unintended places as FDs are recycled, or reads could occur unexpectedly on FDs owned by other objects. UB.
I wonder if a better design here would be for PipeWrite & PipeRead to be, e.g., PipeWrite<T: AsRawFd>; one could then pass an OwnedFd (in which case PipeWrite et al. would own it) or a RawFd (and it would not); the concern of safely managing the ownership of the raw FD is essentially put onto the caller. This is, of course, a breaking change.
The text was updated successfully, but these errors were encountered:
The documentation is enough to see it:
(sic)
The method is allegedly safe, but the argument, however, is a
RawFd
. It's not possible forPipeWrite
to take ownership of aRawFd
, and remain a safe method: taking ownership of aRawFd
is an inherently unsafe operation, as can be seen instd
's documentation for converting aRawFd
into anOwnedFd
. This is because you, the caller, must maintain the invariant that you yourself have the ownership to give, as it isn't (yet) tracked by the type system. Were it safe, you could simply convert oneRawFd
into twoOwnedFd
s, each believing itself to be the owner of the same FD. The same problem exists here.Under the hood,
PipeWrite
does not convert the FD to anOwnedFd
; rather, it just keeps theRawFd
, but the "take the ownership" does happen; that is visible in, e.g.,PipeFd::drop
:(
PipeFd
is a bit of common-code; bothPipeWrite
&PipeRead
end up holding aPipeFd
.)Given this, it's easy to whip up an example that triggers UB:
Cargo.toml
Cargo.lock
This program should fail at
let p2 =
, as it isn't logically possible for both to own the same FD. However, neither fail, so we end up with both "owning" the same FD. Thedrop(p1)
closes the pipe, setting upp2
to drop a completely random FD. Thedrop(p2)
call here ends up closing the FD owned by theFile
, and the write fails with "Bad file descriptor".In a larger program this would be rather hard to debug; writes could easily end up in unintended places as FDs are recycled, or reads could occur unexpectedly on FDs owned by other objects. UB.
I wonder if a better design here would be for
PipeWrite
&PipeRead
to be, e.g.,PipeWrite<T: AsRawFd>
; one could then pass anOwnedFd
(in which casePipeWrite
et al. would own it) or aRawFd
(and it would not); the concern of safely managing the ownership of the raw FD is essentially put onto the caller. This is, of course, a breaking change.The text was updated successfully, but these errors were encountered: