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. |
asomers
left a comment
There was a problem hiding this comment.
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?
| name = "test-ptymaster-drop" | ||
| path = "test/test_ptymaster_drop.rs" | ||
|
|
||
| [[test]] |
There was a problem hiding this comment.
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.
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.
|
|
||
| /// 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.
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];| 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.
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?
| /// 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.
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.
| 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.
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..]| } | ||
| }, | ||
| Err(_) => { | ||
| panic!(); |
There was a problem hiding this comment.
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.
| 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()) { |
|
|
||
| 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.
This should be libc::STDIN_FILENO and libc::STDOUT_FILENO.
|
|
||
| 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.
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? |
asomers
left a comment
There was a problem hiding this comment.
Getting close now. I only have cosmetic comments at this point. Also, we'll need a squash before we're done.
| /// | ||
| /// 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.
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 droping the contents of dst. Please update the comment.
There was a problem hiding this comment.
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.
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.
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.
Much nicer than the previous version. I like.
|
|
||
| let mut cmsgs = msg.cmsgs(); | ||
| match cmsgs.next() { | ||
| Some(ControlMessage::ScmRights(fds)) => assert_eq!(fds.len(), 2, "unexpected fd count (expected 2 fds)"), |
|
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)