-
Notifications
You must be signed in to change notification settings - Fork 682
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 passing multiple file descriptors / control messages via sendmsg #918
Conversation
Current CI failures:
|
Every single CI job that failed passes another test that also passes 2 fds, but only using a single control message. Seems like you're not supposed to use more than 1 I've deleted the broken test and added a note. |
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 good so far! Does Nix also have a bug with sending two dissimilar control messages in a single sendmsg
call, and if so does this change fix it?
Cargo.toml
Outdated
@@ -50,3 +50,8 @@ harness = false | |||
[[test]] | |||
name = "test-ptymaster-drop" | |||
path = "test/test_ptymaster_drop.rs" | |||
|
|||
[[test]] |
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.
Why did you need to use a separate test program? If this program is meant to be interactive, and provides no additional test coverage over the other unit test that you wrote, then it should go in the examples directory, not the tests directory.
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.
Turns out the test I added in a recent commit essentially does the same thing (pass 2 fds using 1 cmsg), so I'll remove this one.
Adding examples to nix is a very good idea, but I guess it should be done as a more coordinated approach rather than in this PR.
src/sys/socket/mod.rs
Outdated
@@ -301,6 +301,18 @@ unsafe fn copy_bytes<'a, 'b, T: ?Sized>(src: &T, dst: &'a mut &'b mut [u8]) { | |||
mem::swap(dst, &mut remainder); | |||
} | |||
|
|||
/// Pad byte slice dst with `len` zeroes, and update the slice to point to | |||
/// the remainder. | |||
fn pad_bytes(len: usize, dst: &mut &mut [u8]) { |
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.
Return arguments is very C-ish. Could you return a reference to the remainder instead? Like this?
fn pad_bytes(len: usize, sl: &'a mut [u8]) -> &'a mut [u8];
src/sys/socket/mod.rs
Outdated
fn pad_bytes(len: usize, dst: &mut &mut [u8]) { | ||
let mut tmpbuf = &mut [][..]; | ||
mem::swap(&mut tmpbuf, dst); | ||
let (padding, mut remainder) = tmpbuf.split_at_mut(len); |
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.
Seems like you're padding the left half of the slice, which is not what I though from the function description. Could you please make that more clear?
src/sys/socket/mod.rs
Outdated
@@ -434,6 +446,10 @@ pub enum ControlMessage<'a> { | |||
/// | |||
/// See the description in the "Ancillary messages" section of the | |||
/// [unix(7) man page](http://man7.org/linux/man-pages/man7/unix.7.html). | |||
/// | |||
/// Do not use more than one `ScmRights` message for a single `sendmsg` call - Depending on the |
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.
This is a pretty strong statement. If we're going to lecture our users about portability best-practices, then we should at least describe it as a recommendation, not a requirement.
src/sys/socket/mod.rs
Outdated
mem::swap(&mut tmpbuf, buf); | ||
let (_padding, mut remainder) = tmpbuf.split_at_mut(padlen); | ||
mem::swap(buf, &mut remainder); | ||
pad_bytes(padlen, buf); |
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.
It looks like the caller doesn't care that encode_into
returns the new buffer reference by argument. So we can simplify this function by making buf
a &mut [u8]
, then simplifying pad_bytes
into something more like:
for i in 0..padlen {
buf[i] = 0;
}
buf = buf[padlen..]
test/test_pass_fds.rs
Outdated
} | ||
}, | ||
Err(_) => { | ||
panic!(); |
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.
These two panic messages are indistinguishable since you don't provide descriptions for them. And if the error case is a simple panic
, you may as well just use unwrap
instead of if let
.
test/test_pass_fds.rs
Outdated
let mut buf = [0u8; 8]; | ||
let iovec = [IoVec::from_mut_slice(&mut buf)]; | ||
let mut space = CmsgSpace::<[RawFd; 2]>::new(); | ||
let (paneid, fds) = match recvmsg(receive.as_raw_fd(), &iovec, Some(&mut space), MsgFlags::empty()) { |
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.
What is a "paneid" ?
test/test_pass_fds.rs
Outdated
|
||
let slice = [1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8]; | ||
let iov = [IoVec::from_slice(&slice)]; | ||
let arr = [0, 1]; // Pass stdin and stdout |
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.
This should be libc::STDIN_FILENO
and libc::STDOUT_FILENO
.
test/sys/test_socket.rs
Outdated
|
||
let slice = [1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8]; | ||
let iov = [IoVec::from_slice(&slice)]; | ||
let fds = [0, 1]; // pass stdin and stdout |
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.
should be libc::STDIN_FILENO and libc::STDOUT_FILENO.
This should fix that, but I haven't added a test since there's no convenient |
Why not use |
Attempting to send a |
@asomers Anything left to do here? |
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.
Getting close now. I only have cosmetic comments at this point. Also, we'll need a squash before we're done.
src/sys/socket/mod.rs
Outdated
/// | ||
/// Panics when `dst` is too small for `src` (panics if `mem::size_of_val(src) >= dst.len()`). | ||
/// | ||
/// Unsafe because it exposes all bytes in `src`, which may be UB if some of them are uninitialized |
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.
It's not actually unsafe to read an uninitialized slice, because slices are always supposed to be initialized. The unsafe part is creating the uninitialized slice in the first place. However, it is unsafe to call copy_nonoverlapping
without verifying that the slices don't overlap, without verifying that T
implements Copy
, and without drop
ing the contents of dst
. Please update the comment.
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.
The problem is that this is reading the bytes of an arbitrary T
, including its padding. If reading the bytes from a T
is safe, then so is this function. It doesn't create any uninitialized memory or slices.
AFAIK, LLVM considers reads of padding bytes undef
, which would be unsafe, but I don't know if rustc makes it safe in some way (eg. by providing its own padding members that read as junk instead of undef
).
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.
So the unsafe part is that it's effectively transmuting the T
. Transmutation is unsafe regardless of padding issues.
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 transmutation to just bytes is unsafe except when padding is involved (as there's no way to read data that isn't valid as a u8
), but I've clarified what's going on here
/// Fills `dst` with `len` zero bytes and returns the remainder of the slice. | ||
/// | ||
/// Panics when `len >= dst.len()`. | ||
fn pad_bytes(len: usize, dst: &mut [u8]) -> &mut [u8] { |
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.
Much nicer than the previous version. I like.
test/sys/test_socket.rs
Outdated
|
||
let mut cmsgs = msg.cmsgs(); | ||
match cmsgs.next() { | ||
Some(ControlMessage::ScmRights(fds)) => assert_eq!(fds.len(), 2, "unexpected fd count (expected 2 fds)"), |
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.
80 chars per line, please.
bors r+ |
Build failed |
That doesn't look good - but also spurious |
bors retry |
923: Fix control message *decoding* and add support for `ScmCredentials` r=asomers a=jonas-schievink While #918 fixed the *encoding*, the *decoding* done by the `CmsgIterator` still remained broken when multiple messages are received. However, since nix didn't support any control message that could reliably be sent twice in one `sendmsg` call, this couldn't be tested properly. This PR addresses this by adding `SCM_CREDENTIALS` support and testing all of this by passing both an `SCM_CREDENTIALS` and a `SCM_RIGHTS` message at the same time. I've also verified that the resulting encoding is the same as for roughly equivalent C code. Co-authored-by: Jonas Schievink <[email protected]>
923: Fix control message *decoding* and add support for `ScmCredentials` r=asomers a=jonas-schievink While #918 fixed the *encoding*, the *decoding* done by the `CmsgIterator` still remained broken when multiple messages are received. However, since nix didn't support any control message that could reliably be sent twice in one `sendmsg` call, this couldn't be tested properly. This PR addresses this by adding `SCM_CREDENTIALS` support and testing all of this by passing both an `SCM_CREDENTIALS` and a `SCM_RIGHTS` message at the same time. I've also verified that the resulting encoding is the same as for roughly equivalent C code. Co-authored-by: Jonas Schievink <[email protected]>
Fixes #464
Closes #874 because it's incorporated here
Closes #756 because it adds the test from that issue (with fixes)