Skip to content

Conversation

@uran0sH
Copy link
Contributor

@uran0sH uran0sH commented Jul 11, 2025

Summary of the PR

Make sock_ctrl_msg work on unix. Split to these commits:

  1. Use pipefd to replace eventfd in tests
  2. Update libc version
  3. Replace CMSG_*! macros with libc equivalent
  4. Move sock_ctrl_msg.rs to the unix folder
  5. sock_ctrl_msg: fix raw_recvmsg() on macOS

Requirements

Before submitting your PR, please make sure you addressed the following
requirements:

  • [ x ] All commits in this PR have Signed-Off-By trailers (with
    git commit -s), and the commit message has max 60 characters for the
    summary and max 75 characters for each description line.
  • [ x ] All added/changed functionality has a corresponding unit/integration
    test.
  • [ x ] All added/changed public-facing functionality has entries in the "Upcoming
    Release" section of CHANGELOG.md (if no such section exists, please create one).
  • [ x ] Any newly added unsafe code is properly documented.

@uran0sH
Copy link
Contributor Author

uran0sH commented Jul 11, 2025

@stefano-garzarella
Copy link
Member

@uran0sH please check the CI

@uran0sH uran0sH force-pushed the sock_ctrl_msg branch 3 times, most recently from 2784a19 to 82a42b6 Compare July 14, 2025 15:40
@uran0sH
Copy link
Contributor Author

uran0sH commented Jul 15, 2025

@uran0sH please check the CI

Fixed

Copy link
Member

@stefano-garzarella stefano-garzarella left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of macos can we refer to unix in the changelog/pr title/comments/etc.

@uran0sH
Copy link
Contributor Author

uran0sH commented Jul 16, 2025

Instead of macos can we refer to unix in the changelog/pr title/comments/etc.

unix is better, but I don't test it on other unix's systems

@stefano-garzarella
Copy link
Member

Instead of macos can we refer to unix in the changelog/pr title/comments/etc.

unix is better, but I don't test it on other unix's systems

We can do later (e.g. in a simple freebsd vm), but yeah, please use unix for now

@uran0sH
Copy link
Contributor Author

uran0sH commented Jul 16, 2025

Because I am not sure about this commit 7d8683c, I think we should discuss here. Below it's what i found.
linux:

CMSG_SPACE = (CMSG_ALIGN(length as usize) + CMSG_ALIGN(mem::size_of::<cmsghdr>())) as c_uint
CMSG_ALIGN = (len + mem::size_of::<usize>() - 1) & !(mem::size_of::<usize>() - 1)

mac

CMSG_SPACE = (__DARWIN_ALIGN32(mem::size_of::<cmsghdr>()) + __DARWIN_ALIGN32(length as usize)) as c_uint
const fn __DARWIN_ALIGN32(p: usize) -> usize {
    const __DARWIN_ALIGNBYTES32: usize = mem::size_of::<u32>() - 1;
    (p + __DARWIN_ALIGNBYTES32) & !__DARWIN_ALIGNBYTES32
}

The difference is the value of align: linux uses the size of usize, and macos uses the size of u32. This is also for CMSG_LEN.

@stefano-garzarella
Copy link
Member

Because I am not sure about this commit 7d8683c, I think we should discuss here. Below it's what i found. linux:

CMSG_SPACE = (CMSG_ALIGN(length as usize) + CMSG_ALIGN(mem::size_of::<cmsghdr>())) as c_uint
CMSG_ALIGN = (len + mem::size_of::<usize>() - 1) & !(mem::size_of::<usize>() - 1)

mac

CMSG_SPACE = (__DARWIN_ALIGN32(mem::size_of::<cmsghdr>()) + __DARWIN_ALIGN32(length as usize)) as c_uint
const fn __DARWIN_ALIGN32(p: usize) -> usize {
    const __DARWIN_ALIGNBYTES32: usize = mem::size_of::<u32>() - 1;
    (p + __DARWIN_ALIGNBYTES32) & !__DARWIN_ALIGNBYTES32
}

The difference is the value of align: linux uses the size of usize, and macos uses the size of u32. This is also for CMSG_LEN.

What are your concerns about that patch?
(maybe it's better to open the discussion, exactly on the line where the change is)

@roypat
Copy link
Member

roypat commented Jul 17, 2025

@uran0sH could you clarify what exactly test failure you were seeing with that gets fixed by 7d8683c? I had @ShadowCurse run the tests without that commit on his mac, and they still passed 🤔

@uran0sH
Copy link
Contributor Author

uran0sH commented Jul 17, 2025

@uran0sH could you clarify what exactly test failure you were seeing with that gets fixed by 7d8683c? I had @ShadowCurse run the tests without that commit on his mac, and they still passed 🤔

Sorry for replying so late. It doesn't fail every time. There is a probability that it will fail the test.
How to reproduce:

cargo test --package vmm-sys-util --lib -- unix::sock_ctrl_msg::tests --show-output --nocapture

Run this command multiple times. When it fails, it reports:

cmsg len: 28, CMSG_LEN(0): 12
close fd: 13
close fd: 14
close fd: 0
close fd: 0


test unix::sock_ctrl_msg::tests::send_more_recv_less1 ... cmsg len: 28, CMSG_LEN: 12
close fd: 12
ok
close fd: 0
close fd: 0
close fd: 0


fatal runtime error: IO Safety violation: owned file descriptor already closed

(I print which fd will be closed)
Run on Linux:

cmsg len: 24, CMSG_LEN(0): 16
close fd: 13
close fd: 14

cmsg len: 24, CMSG_LEN(0): 16
close fd: 11
close fd: 12

It can always pass on linux.
It's so weired. Running send_more_recv_less2 or send_more_recv_less1 alone seems always pass. I feel so confused.

@uran0sH uran0sH force-pushed the sock_ctrl_msg branch 2 times, most recently from c429fa6 to eaeed1b Compare July 17, 2025 17:12
@uran0sH uran0sH changed the title Make sock_ctrl_msg work on macos Make sock_ctrl_msg work on unix Jul 17, 2025
@uran0sH
Copy link
Contributor Author

uran0sH commented Jul 17, 2025

Instead of macos can we refer to unix in the changelog/pr title/comments/etc.

unix is better, but I don't test it on other unix's systems

We can do later (e.g. in a simple freebsd vm), but yeah, please use unix for now

Changed

@stefano-garzarella
Copy link
Member

It can always pass on linux. It's so weired. Running send_more_recv_less2 or send_more_recv_less1 alone seems always pass. I feel so confused.

Can be a race between the 2 tests?

@stefano-garzarella
Copy link
Member

cmsg len: 28, CMSG_LEN(0): 12
close fd: 13
close fd: 14
close fd: 0
close fd: 0

test unix::sock_ctrl_msg::tests::send_more_recv_less1 ... cmsg len: 28, CMSG_LEN: 12
close fd: 12
ok
close fd: 0
close fd: 0
close fd: 0

I think the strange thing here is have 0 as fd to close (multiple times), which is really strange.
@uran0sH When the test works, do you also have 0 as fd to close?

@stefano-garzarella
Copy link
Member

cmsg len: 28, CMSG_LEN(0): 12
close fd: 13
close fd: 14
close fd: 0
close fd: 0
test unix::sock_ctrl_msg::tests::send_more_recv_less1 ... cmsg len: 28, CMSG_LEN: 12
close fd: 12
ok
close fd: 0
close fd: 0
close fd: 0

I think the strange thing here is have 0 as fd to close (multiple times), which is really strange. @uran0sH When the test works, do you also have 0 as fd to close?

In Linux I see that MSG_CTRUNC is set, so from your output it seems that the msg is also truncated, but the flag not set, or there is an error on checking that flag.

@roypat
Copy link
Member

roypat commented Jul 18, 2025

Mhh, the IO violation then probably comes not from this function, but rather from the Rust "runtime" shutting down and dropping its Stdin object, which causes fd 0 to get closed. Which does bring up the interesting point that really, this function closing fds actually seems all sorts of wrong to me. At the very least, it should absolutely never try to close fds 0,1 and 2, because the Rust runtime definitely manages them, and closing them via libc::close() will always result in a io safety violation. But then, really, there's no way for this function to know whether some FD it received is actually managed by a File object elsewhere. This function should probably just returnVec<RawFd>, to avoid that entire problem (well, technically its unsafe so we could make the safety invariant "socket must not have more than in_fds.len() fds available", but that's not satisfiable for any caller). It still doesn't really solve the issue of macos receiving fd 0 a bunch of times though :/

@stefano-garzarella
Copy link
Member

Mhh, the IO violation then probably comes not from this function, but rather from the Rust "runtime" shutting down and dropping its Stdin object, which causes fd 0 to get closed. Which does bring up the interesting point that really, this function closing fds actually seems all sorts of wrong to me. At the very least, it should absolutely never try to close fds 0,1 and 2, because the Rust runtime definitely manages them, and closing them via libc::close() will always result in a io safety violation. But then, really, there's no way for this function to know whether some FD it received is actually managed by a File object elsewhere. This function should probably just returnVec<RawFd>, to avoid that entire problem (well, technically its unsafe so we could make the safety invariant "socket must not have more than in_fds.len() fds available", but that's not satisfiable for any caller). It still doesn't really solve the issue of macos receiving fd 0 a bunch of times though :/

Agree, but we should fix in another PR, see #245 (comment) for quick fix for this PR.

@stefano-garzarella
Copy link
Member

@uran0sH please revert 6a4b213 and apply the following patch:

From f0bccfe6479937f18d231be613794d01c4f15d6f Mon Sep 17 00:00:00 2001
From: Stefano Garzarella <sgarzare@redhat.com>
Date: Fri, 18 Jul 2025 11:36:24 +0200
Subject: [PATCH] sock_ctrl_msg: fix raw_recvmsg() on macOS

From: Stefano Garzarella <sgarzare@redhat.com>

Limit cmsg_len to the size of cmsg_buffer, since it seems that in macOS,
when `MSG_CTRUNC` is set, `cmsg.cmsg_len` is not updated with the
current size, but left with the size of the sender (this is not
happening on Linux).

Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>
---
 src/unix/sock_ctrl_msg.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/unix/sock_ctrl_msg.rs b/src/unix/sock_ctrl_msg.rs
index af1cebf..f2021f3 100644
--- a/src/unix/sock_ctrl_msg.rs
+++ b/src/unix/sock_ctrl_msg.rs
@@ -224,8 +224,8 @@ unsafe fn raw_recvmsg(
         // read.
         let cmsg = (cmsg_ptr as *mut cmsghdr).read_unaligned();
         if cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS {
-            let fds_count: usize =
-                (cmsg.cmsg_len as usize - CMSG_LEN(0) as usize) / size_of::<RawFd>();
+            let cmsg_len: usize = std::cmp::min(cmsg.cmsg_len as usize, cmsg_capacity);
+            let fds_count: usize = (cmsg_len - CMSG_LEN(0) as usize) / size_of::<c_int>();
             // The sender can transmit more data than we can buffer. If a message is too long to
             // fit in the supplied buffer, excess bytes may be discarded depending on the type of
             // socket the message is received from.
-- 
2.50.1

@roypat
Copy link
Member

roypat commented Jul 18, 2025

Mhh, the IO violation then probably comes not from this function, but rather from the Rust "runtime" shutting down and dropping its Stdin object, which causes fd 0 to get closed. Which does bring up the interesting point that really, this function closing fds actually seems all sorts of wrong to me. At the very least, it should absolutely never try to close fds 0,1 and 2, because the Rust runtime definitely manages them, and closing them via libc::close() will always result in a io safety violation. But then, really, there's no way for this function to know whether some FD it received is actually managed by a File object elsewhere. This function should probably just returnVec<RawFd>, to avoid that entire problem (well, technically its unsafe so we could make the safety invariant "socket must not have more than in_fds.len() fds available", but that's not satisfiable for any caller). It still doesn't really solve the issue of macos receiving fd 0 a bunch of times though :/

Agree, but we should fix in another PR, see #245 (comment) for quick fix for this PR.

Yeah, fair enough, I'll submit something after this is merged, there's a few other things I'd like to have a look at anyway :)

@stefano-garzarella
Copy link
Member

@uran0sH okay, about this PR, the next steps are:

If we fix those, I think we are ready to merge, so @roypat can rebase the #246 on top if this

Eventfd is linux-specific. In order to support unix,
we need to replace it with pipefd.

Signed-off-by: Wenyu Huang <huangwenyuu@outlook.com>
@uran0sH
Copy link
Contributor Author

uran0sH commented Jul 19, 2025

@uran0sH okay, about this PR, the next steps are:

If we fix those, I think we are ready to merge, so @roypat can rebase the #246 on top if this

Fixed these.

@stefano-garzarella
Copy link
Member

@uran0sH great thanks! The final result LGTM, but please move the commit where to update the libc version before the commit where you use CMSG_ macros, otherwise, if in the future we will bisect this crate, we can have a build issue.

uran0sH and others added 5 commits July 21, 2025 11:04
Update libc version to fix:
```
error[E0015]: cannot call non-const function `CMSG_SPACE` in constants
  --> src/unix/sock_ctrl_msg.rs:73:14
   |
73 |     unsafe { CMSG_SPACE(size_of::<RawFd>() as u32 * 32) as usize };
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: calls in constants are limited to constant functions, tuple structs and tuple variants
```

Signed-off-by: Wenyu Huang <huangwenyuu@outlook.com>
libc also provides functions with the same functionality
and supports multiple platforms, so we can replace
CMSG_*! with libc equivalent. The test `buffer_len` doesn't need,
if we use libc.

Signed-off-by: Wenyu Huang <huangwenyuu@outlook.com>
Make some modifications to sock_ctl_msg to support Unix systems.
Including adjusting the size of cmsg_len for different platforms.

Signed-off-by: Wenyu Huang <huangwenyuu@outlook.com>
Limit cmsg_len to the size of cmsg_buffer, since it seems that in macOS,
when `MSG_CTRUNC` is set, `cmsg.cmsg_len` is not updated with the
current size, but left with the size of the sender (this is not
happening on Linux).

Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>
Signed-off-by: Wenyu Huang <huangwenyuu@outlook.com>
Signed-off-by: Wenyu Huang <huangwenyuu@outlook.com>
@uran0sH
Copy link
Contributor Author

uran0sH commented Jul 21, 2025

@uran0sH great thanks! The final result LGTM, but please move the commit where to update the libc version before the commit where you use CMSG_ macros, otherwise, if in the future we will bisect this crate, we can have a build issue.

done

@roypat roypat merged commit e2869cd into rust-vmm:main Jul 21, 2025
2 checks passed
@germag
Copy link

germag commented Jul 21, 2025

Mhh, the IO violation then probably comes not from this function, but rather from the Rust "runtime" shutting down and dropping its Stdin object, which causes fd 0 to get closed. Which does bring up the interesting point that really, this function closing fds actually seems all sorts of wrong to me. At the very least, it should absolutely never try to close fds 0,1 and 2, because the Rust runtime definitely manages them, and closing them via libc::close() will always result in a io safety violation. But then, really, there's no way for this function to know whether some FD it received is actually managed by a File object elsewhere. This function should probably just returnVec<RawFd>, to avoid that entire problem (well, technically its unsafe so we could make the safety invariant "socket must not have more than in_fds.len() fds available", but that's not satisfiable for any caller). It still doesn't really solve the issue of macos receiving fd 0 a bunch of times though :/

Sorry I'm a bit late, I wanted to comment about this last week, I also think it is incorrect to close FDs in that function, if the process reaches its NOFILE limit the received FDs are already closed, so close() will fire the io safety violation (since 1.80 [0]), or worse closing a reused FD.

[0] rust-lang/rust#124210

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants