Skip to content

Commit 90b1c17

Browse files
Merge #918
918: Fix passing multiple file descriptors / control messages via sendmsg r=asomers a=jonas-schievink Fixes #464 Closes #874 because it's incorporated here Closes #756 because it adds the test from that issue (with fixes) Co-authored-by: alecmocatta <[email protected]>
2 parents e0577cc + 5d6dc26 commit 90b1c17

File tree

3 files changed

+126
-34
lines changed

3 files changed

+126
-34
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1414
### Fixed
1515
- Made `preadv` take immutable slice of IoVec.
1616
([#914](https://github.com/nix-rust/nix/pull/914))
17+
- Fixed passing multiple file descriptors over Unix Sockets.
18+
([#918](https://github.com/nix-rust/nix/pull/918))
1719

1820
### Removed
1921

src/sys/socket/mod.rs

Lines changed: 79 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -287,18 +287,37 @@ impl fmt::Debug for Ipv6MembershipRequest {
287287
}
288288
}
289289

290-
/// Copy the in-memory representation of src into the byte slice dst,
291-
/// updating the slice to point to the remainder of dst only. Unsafe
292-
/// because it exposes all bytes in src, which may be UB if some of them
293-
/// are uninitialized (including padding).
294-
unsafe fn copy_bytes<'a, 'b, T: ?Sized>(src: &T, dst: &'a mut &'b mut [u8]) {
290+
/// Copy the in-memory representation of `src` into the byte slice `dst`.
291+
///
292+
/// Returns the remainder of `dst`.
293+
///
294+
/// Panics when `dst` is too small for `src` (more precisely, panics if
295+
/// `mem::size_of_val(src) >= dst.len()`).
296+
///
297+
/// Unsafe because it transmutes `src` to raw bytes, which is only safe for some
298+
/// types `T`. Refer to the [Rustonomicon] for details.
299+
///
300+
/// [Rustonomicon]: https://doc.rust-lang.org/nomicon/transmutes.html
301+
unsafe fn copy_bytes<'a, T: ?Sized>(src: &T, dst: &'a mut [u8]) -> &'a mut [u8] {
295302
let srclen = mem::size_of_val(src);
296-
let mut tmpdst = &mut [][..];
297-
mem::swap(&mut tmpdst, dst);
298-
let (target, mut remainder) = tmpdst.split_at_mut(srclen);
299-
// Safe because the mutable borrow of dst guarantees that src does not alias it.
300-
ptr::copy_nonoverlapping(src as *const T as *const u8, target.as_mut_ptr(), srclen);
301-
mem::swap(dst, &mut remainder);
303+
ptr::copy_nonoverlapping(
304+
src as *const T as *const u8,
305+
dst[..srclen].as_mut_ptr(),
306+
srclen
307+
);
308+
309+
&mut dst[srclen..]
310+
}
311+
312+
/// Fills `dst` with `len` zero bytes and returns the remainder of the slice.
313+
///
314+
/// Panics when `len >= dst.len()`.
315+
fn pad_bytes(len: usize, dst: &mut [u8]) -> &mut [u8] {
316+
for pad in &mut dst[..len] {
317+
*pad = 0;
318+
}
319+
320+
&mut dst[len..]
302321
}
303322

304323
cfg_if! {
@@ -434,6 +453,11 @@ pub enum ControlMessage<'a> {
434453
///
435454
/// See the description in the "Ancillary messages" section of the
436455
/// [unix(7) man page](http://man7.org/linux/man-pages/man7/unix.7.html).
456+
///
457+
/// Using multiple `ScmRights` messages for a single `sendmsg` call isn't recommended since it
458+
/// causes platform-dependent behaviour: It might swallow all but the first `ScmRights` message
459+
/// or fail with `EINVAL`. Instead, you can put all fds to be passed into a single `ScmRights`
460+
/// message.
437461
ScmRights(&'a [RawFd]),
438462
/// A message of type `SCM_TIMESTAMP`, containing the time the
439463
/// packet was received by the kernel.
@@ -545,7 +569,7 @@ impl<'a> ControlMessage<'a> {
545569

546570
// Unsafe: start and end of buffer must be cmsg_align'd. Updates
547571
// the provided slice; panics if the buffer is too small.
548-
unsafe fn encode_into<'b>(&self, buf: &mut &'b mut [u8]) {
572+
unsafe fn encode_into(&self, buf: &mut [u8]) {
549573
match *self {
550574
ControlMessage::ScmRights(fds) => {
551575
let cmsg = cmsghdr {
@@ -554,17 +578,16 @@ impl<'a> ControlMessage<'a> {
554578
cmsg_type: libc::SCM_RIGHTS,
555579
..mem::uninitialized()
556580
};
557-
copy_bytes(&cmsg, buf);
581+
let buf = copy_bytes(&cmsg, buf);
558582

559583
let padlen = cmsg_align(mem::size_of_val(&cmsg)) -
560584
mem::size_of_val(&cmsg);
585+
let buf = pad_bytes(padlen, buf);
561586

562-
let mut tmpbuf = &mut [][..];
563-
mem::swap(&mut tmpbuf, buf);
564-
let (_padding, mut remainder) = tmpbuf.split_at_mut(padlen);
565-
mem::swap(buf, &mut remainder);
587+
let buf = copy_bytes(fds, buf);
566588

567-
copy_bytes(fds, buf);
589+
let padlen = self.space() - self.len();
590+
pad_bytes(padlen, buf);
568591
},
569592
ControlMessage::ScmTimestamp(t) => {
570593
let cmsg = cmsghdr {
@@ -573,21 +596,28 @@ impl<'a> ControlMessage<'a> {
573596
cmsg_type: libc::SCM_TIMESTAMP,
574597
..mem::uninitialized()
575598
};
576-
copy_bytes(&cmsg, buf);
599+
let buf = copy_bytes(&cmsg, buf);
577600

578601
let padlen = cmsg_align(mem::size_of_val(&cmsg)) -
579602
mem::size_of_val(&cmsg);
603+
let buf = pad_bytes(padlen, buf);
580604

581-
let mut tmpbuf = &mut [][..];
582-
mem::swap(&mut tmpbuf, buf);
583-
let (_padding, mut remainder) = tmpbuf.split_at_mut(padlen);
584-
mem::swap(buf, &mut remainder);
605+
let buf = copy_bytes(t, buf);
585606

586-
copy_bytes(t, buf);
607+
let padlen = self.space() - self.len();
608+
pad_bytes(padlen, buf);
587609
},
588610
ControlMessage::Unknown(UnknownCmsg(orig_cmsg, bytes)) => {
589-
copy_bytes(orig_cmsg, buf);
590-
copy_bytes(bytes, buf);
611+
let buf = copy_bytes(orig_cmsg, buf);
612+
613+
let padlen = cmsg_align(mem::size_of_val(&orig_cmsg)) -
614+
mem::size_of_val(&orig_cmsg);
615+
let buf = pad_bytes(padlen, buf);
616+
617+
let buf = copy_bytes(bytes, buf);
618+
619+
let padlen = self.space() - self.len();
620+
pad_bytes(padlen, buf);
591621
}
592622
}
593623
}
@@ -600,23 +630,25 @@ impl<'a> ControlMessage<'a> {
600630
///
601631
/// Allocates if cmsgs is nonempty.
602632
pub fn sendmsg<'a>(fd: RawFd, iov: &[IoVec<&'a [u8]>], cmsgs: &[ControlMessage<'a>], flags: MsgFlags, addr: Option<&'a SockAddr>) -> Result<usize> {
603-
let mut len = 0;
604633
let mut capacity = 0;
605634
for cmsg in cmsgs {
606-
len += cmsg.len();
607635
capacity += cmsg.space();
608636
}
609637
// Note that the resulting vector claims to have length == capacity,
610638
// so it's presently uninitialized.
611639
let mut cmsg_buffer = unsafe {
612-
let mut vec = Vec::<u8>::with_capacity(len);
613-
vec.set_len(len);
640+
let mut vec = Vec::<u8>::with_capacity(capacity);
641+
vec.set_len(capacity);
614642
vec
615643
};
616644
{
617-
let mut ptr = &mut cmsg_buffer[..];
645+
let mut ofs = 0;
618646
for cmsg in cmsgs {
619-
unsafe { cmsg.encode_into(&mut ptr) };
647+
let mut ptr = &mut cmsg_buffer[ofs..];
648+
unsafe {
649+
cmsg.encode_into(ptr);
650+
}
651+
ofs += cmsg.space();
620652
}
621653
}
622654

@@ -669,10 +701,23 @@ pub fn recvmsg<'a, T>(fd: RawFd, iov: &[IoVec<&mut [u8]>], cmsg_buffer: Option<&
669701
};
670702
let ret = unsafe { libc::recvmsg(fd, &mut mhdr, flags.bits()) };
671703

704+
let cmsg_buffer = if msg_controllen > 0 {
705+
// got control message(s)
706+
debug_assert!(!mhdr.msg_control.is_null());
707+
unsafe {
708+
// Safe: The pointer is not null and the length is correct as part of `recvmsg`s
709+
// contract.
710+
slice::from_raw_parts(mhdr.msg_control as *const u8,
711+
mhdr.msg_controllen as usize)
712+
}
713+
} else {
714+
// No control message, create an empty buffer to avoid creating a slice from a null pointer
715+
&[]
716+
};
717+
672718
Ok(unsafe { RecvMsg {
673719
bytes: try!(Errno::result(ret)) as usize,
674-
cmsg_buffer: slice::from_raw_parts(mhdr.msg_control as *const u8,
675-
mhdr.msg_controllen as usize),
720+
cmsg_buffer,
676721
address: sockaddr_storage_to_addr(&address,
677722
mhdr.msg_namelen as usize).ok(),
678723
flags: MsgFlags::from_bits_truncate(mhdr.msg_flags),

test/sys/test_socket.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,51 @@ pub fn test_scm_rights() {
167167
close(w).unwrap();
168168
}
169169

170+
/// Tests that passing multiple fds using a single `ControlMessage` works.
171+
#[test]
172+
fn test_scm_rights_single_cmsg_multiple_fds() {
173+
use std::os::unix::net::UnixDatagram;
174+
use std::os::unix::io::{RawFd, AsRawFd};
175+
use std::thread;
176+
use nix::sys::socket::{CmsgSpace, ControlMessage, MsgFlags, sendmsg, recvmsg};
177+
use nix::sys::uio::IoVec;
178+
use libc;
179+
180+
let (send, receive) = UnixDatagram::pair().unwrap();
181+
let thread = thread::spawn(move || {
182+
let mut buf = [0u8; 8];
183+
let iovec = [IoVec::from_mut_slice(&mut buf)];
184+
let mut space = CmsgSpace::<[RawFd; 2]>::new();
185+
let msg = recvmsg(
186+
receive.as_raw_fd(),
187+
&iovec,
188+
Some(&mut space),
189+
MsgFlags::empty()
190+
).unwrap();
191+
assert!(!msg.flags.intersects(MsgFlags::MSG_TRUNC | MsgFlags::MSG_CTRUNC));
192+
193+
let mut cmsgs = msg.cmsgs();
194+
match cmsgs.next() {
195+
Some(ControlMessage::ScmRights(fds)) => {
196+
assert_eq!(fds.len(), 2,
197+
"unexpected fd count (expected 2 fds, got {})",
198+
fds.len());
199+
},
200+
_ => panic!(),
201+
}
202+
assert!(cmsgs.next().is_none(), "unexpected control msg");
203+
204+
assert_eq!(iovec[0].as_slice(), [1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8]);
205+
});
206+
207+
let slice = [1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, 8u8];
208+
let iov = [IoVec::from_slice(&slice)];
209+
let fds = [libc::STDIN_FILENO, libc::STDOUT_FILENO]; // pass stdin and stdout
210+
let cmsg = [ControlMessage::ScmRights(&fds)];
211+
sendmsg(send.as_raw_fd(), &iov, &cmsg, MsgFlags::empty(), None).unwrap();
212+
thread.join().unwrap();
213+
}
214+
170215
// Verify `sendmsg` builds a valid `msghdr` when passing an empty
171216
// `cmsgs` argument. This should result in a msghdr with a nullptr
172217
// msg_control field and a msg_controllen of 0 when calling into the

0 commit comments

Comments
 (0)