Skip to content

Commit 3cbb4c3

Browse files
authored
Clarify safety invariants in SocketAddrArg. (#1325)
In making another safety review of the `SocketAddrArg` code, I found it too subtle to have `MMsgHdr::new_with_addr` depending on knowing that `with_msghdr` happens to avoid using a temporary in the case `SocketAddrAny`. Change it to avoid doing this, add more comments, and change `with_msghdr`'s argument to a reference, to prevent it from being accidentally used in this way. And change `with_noaddr_msghdr` to `noaddr_msghdr` and make it just return a `msghdr`, because it never needs to construct any temporaries, so it avoids this issue. Also, make `SocketAddrArg::with_sockaddr` and `with_recv_msghdr` unsafe, as the provided function will need to contain an `unsafe` block which will need to be able to trust it's being called correctly. And add safety comments for the callers and impls of these functions and traits.
1 parent 871dee3 commit 3cbb4c3

13 files changed

Lines changed: 428 additions & 309 deletions

File tree

src/backend/libc/conv.rs

Lines changed: 0 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -180,82 +180,3 @@ pub(super) fn ret_send_recv(len: isize) -> io::Result<usize> {
180180
pub(super) fn ret_send_recv(len: i32) -> io::Result<usize> {
181181
ret_usize(len as isize)
182182
}
183-
184-
/// Convert the value to the `msg_iovlen` field of a `msghdr` struct.
185-
#[cfg(all(
186-
not(any(windows, target_os = "espidf", target_os = "wasi")),
187-
any(
188-
target_os = "android",
189-
all(
190-
target_os = "linux",
191-
not(target_env = "musl"),
192-
not(all(target_env = "uclibc", any(target_arch = "arm", target_arch = "mips")))
193-
)
194-
)
195-
))]
196-
#[inline]
197-
pub(super) fn msg_iov_len(len: usize) -> c::size_t {
198-
len
199-
}
200-
201-
/// Convert the value to the `msg_iovlen` field of a `msghdr` struct.
202-
#[cfg(all(
203-
not(any(
204-
windows,
205-
target_os = "espidf",
206-
target_os = "redox",
207-
target_os = "vita",
208-
target_os = "wasi"
209-
)),
210-
not(any(
211-
target_os = "android",
212-
all(
213-
target_os = "linux",
214-
not(target_env = "musl"),
215-
not(all(target_env = "uclibc", any(target_arch = "arm", target_arch = "mips")))
216-
)
217-
))
218-
))]
219-
#[inline]
220-
pub(crate) fn msg_iov_len(len: usize) -> c::c_int {
221-
len.try_into().unwrap_or(c::c_int::MAX)
222-
}
223-
224-
/// Convert the value to a `socklen_t`.
225-
#[cfg(any(
226-
bsd,
227-
solarish,
228-
target_env = "musl",
229-
target_os = "aix",
230-
target_os = "emscripten",
231-
target_os = "fuchsia",
232-
target_os = "haiku",
233-
target_os = "hurd",
234-
target_os = "nto",
235-
))]
236-
#[inline]
237-
pub(crate) fn msg_control_len(len: usize) -> c::socklen_t {
238-
len.try_into().unwrap_or(c::socklen_t::MAX)
239-
}
240-
241-
/// Convert the value to a `size_t`.
242-
#[cfg(not(any(
243-
bsd,
244-
solarish,
245-
windows,
246-
target_env = "musl",
247-
target_os = "aix",
248-
target_os = "emscripten",
249-
target_os = "espidf",
250-
target_os = "fuchsia",
251-
target_os = "haiku",
252-
target_os = "hurd",
253-
target_os = "nto",
254-
target_os = "redox",
255-
target_os = "vita",
256-
target_os = "wasi",
257-
)))]
258-
#[inline]
259-
pub(crate) fn msg_control_len(len: usize) -> c::size_t {
260-
len
261-
}

src/backend/libc/net/addr.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ impl SocketAddrStorage {
253253

254254
/// Return the `sa_family` of this socket address.
255255
pub fn family(&self) -> AddressFamily {
256+
// SAFETY: `self.0` is a `sockaddr_storage` so it has enough space.
256257
unsafe {
257258
AddressFamily::from_raw(crate::backend::net::read_sockaddr::read_sa_family(
258259
crate::utils::as_ptr(&self.0).cast::<c::sockaddr>(),
@@ -263,6 +264,7 @@ impl SocketAddrStorage {
263264
/// Clear the `sa_family` of this socket address to
264265
/// `AddressFamily::UNSPEC`.
265266
pub fn clear_family(&mut self) {
267+
// SAFETY: `self.0` is a `sockaddr_storage` so it has enough space.
266268
unsafe {
267269
crate::backend::net::read_sockaddr::initialize_family_to_unspec(
268270
crate::utils::as_mut_ptr(&mut self.0).cast::<c::sockaddr>(),

src/backend/libc/net/msghdr.rs

Lines changed: 131 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,124 @@
44
//! the message headers may reference stack-local data.
55
66
use crate::backend::c;
7-
use crate::backend::conv::{msg_control_len, msg_iov_len};
87

98
use crate::io::{self, IoSlice, IoSliceMut};
109
use crate::net::SocketAddrBuf;
1110
use crate::net::{addr::SocketAddrArg, RecvAncillaryBuffer, SendAncillaryBuffer};
1211

1312
use core::mem::zeroed;
1413

14+
/// Convert the value to the `msg_iovlen` field of a `msghdr` struct.
15+
#[cfg(all(
16+
not(any(windows, target_os = "espidf", target_os = "wasi")),
17+
any(
18+
target_os = "android",
19+
all(
20+
target_os = "linux",
21+
not(target_env = "musl"),
22+
not(all(target_env = "uclibc", any(target_arch = "arm", target_arch = "mips")))
23+
)
24+
)
25+
))]
26+
#[inline]
27+
fn msg_iov_len(len: usize) -> c::size_t {
28+
len
29+
}
30+
31+
/// Convert the value to the `msg_iovlen` field of a `msghdr` struct.
32+
#[cfg(all(
33+
not(any(
34+
windows,
35+
target_os = "espidf",
36+
target_os = "redox",
37+
target_os = "vita",
38+
target_os = "wasi"
39+
)),
40+
not(any(
41+
target_os = "android",
42+
all(
43+
target_os = "linux",
44+
not(target_env = "musl"),
45+
not(all(target_env = "uclibc", any(target_arch = "arm", target_arch = "mips")))
46+
)
47+
))
48+
))]
49+
#[inline]
50+
fn msg_iov_len(len: usize) -> c::c_int {
51+
len.try_into().unwrap_or(c::c_int::MAX)
52+
}
53+
54+
/// Convert the value to a `socklen_t`.
55+
#[cfg(any(
56+
bsd,
57+
solarish,
58+
target_env = "musl",
59+
target_os = "aix",
60+
target_os = "emscripten",
61+
target_os = "fuchsia",
62+
target_os = "haiku",
63+
target_os = "hurd",
64+
target_os = "nto",
65+
))]
66+
#[inline]
67+
fn msg_control_len(len: usize) -> c::socklen_t {
68+
len.try_into().unwrap_or(c::socklen_t::MAX)
69+
}
70+
71+
/// Convert the value to a `size_t`.
72+
#[cfg(not(any(
73+
bsd,
74+
solarish,
75+
windows,
76+
target_env = "musl",
77+
target_os = "aix",
78+
target_os = "emscripten",
79+
target_os = "espidf",
80+
target_os = "fuchsia",
81+
target_os = "haiku",
82+
target_os = "hurd",
83+
target_os = "nto",
84+
target_os = "redox",
85+
target_os = "vita",
86+
target_os = "wasi",
87+
)))]
88+
#[inline]
89+
fn msg_control_len(len: usize) -> c::size_t {
90+
len
91+
}
92+
1593
/// Create a message header intended to receive a datagram.
16-
pub(crate) fn with_recv_msghdr<R>(
94+
///
95+
/// # Safety
96+
///
97+
/// If `f` dereferences the pointers in the `msghdr`, it must do so only within
98+
/// the bounds indicated by the associated lengths in the `msghdr`.
99+
///
100+
/// And, if `f` returns `Ok`, it must have updated the `msg_controllen` field
101+
/// of the `msghdr` to indicate how many bytes it initialized.
102+
pub(crate) unsafe fn with_recv_msghdr<R>(
17103
name: &mut SocketAddrBuf,
18104
iov: &mut [IoSliceMut<'_>],
19105
control: &mut RecvAncillaryBuffer<'_>,
20106
f: impl FnOnce(&mut c::msghdr) -> io::Result<R>,
21107
) -> io::Result<R> {
22108
control.clear();
23109

24-
let mut msghdr = {
25-
let mut h = zero_msghdr();
26-
h.msg_name = name.storage.as_mut_ptr().cast();
27-
h.msg_namelen = name.len;
28-
h.msg_iov = iov.as_mut_ptr().cast();
29-
h.msg_iovlen = msg_iov_len(iov.len());
30-
h.msg_control = control.as_control_ptr().cast();
31-
h.msg_controllen = msg_control_len(control.control_len());
32-
h
33-
};
110+
let mut msghdr = zero_msghdr();
111+
msghdr.msg_name = name.storage.as_mut_ptr().cast();
112+
msghdr.msg_namelen = name.len;
113+
msghdr.msg_iov = iov.as_mut_ptr().cast();
114+
msghdr.msg_iovlen = msg_iov_len(iov.len());
115+
msghdr.msg_control = control.as_control_ptr().cast();
116+
msghdr.msg_controllen = msg_control_len(control.control_len());
34117

35118
let res = f(&mut msghdr);
36119

37120
// Reset the control length.
38121
if res.is_ok() {
39-
unsafe {
40-
control.set_control_len(msghdr.msg_controllen.try_into().unwrap_or(usize::MAX));
41-
}
122+
// SAFETY: `f` returned `Ok`, so our safety condition requires `f` to
123+
// have initialized `msg_controllen` bytes.
124+
control.set_control_len(msghdr.msg_controllen as usize);
42125
}
43126

44127
name.len = msghdr.msg_namelen;
@@ -47,39 +130,49 @@ pub(crate) fn with_recv_msghdr<R>(
47130
}
48131

49132
/// Create a message header intended to send without an address.
50-
pub(crate) fn with_noaddr_msghdr<R>(
133+
///
134+
/// The returned `msghdr` will contain raw pointers to the memory
135+
/// referenced by `iov` and `control`.
136+
pub(crate) fn noaddr_msghdr(
51137
iov: &[IoSlice<'_>],
52138
control: &mut SendAncillaryBuffer<'_, '_, '_>,
53-
f: impl FnOnce(c::msghdr) -> R,
54-
) -> R {
55-
f({
56-
let mut h = zero_msghdr();
57-
h.msg_iov = iov.as_ptr() as _;
58-
h.msg_iovlen = msg_iov_len(iov.len());
59-
h.msg_control = control.as_control_ptr().cast();
60-
h.msg_controllen = msg_control_len(control.control_len());
61-
h
62-
})
139+
) -> c::msghdr {
140+
let mut h = zero_msghdr();
141+
h.msg_iov = iov.as_ptr() as _;
142+
h.msg_iovlen = msg_iov_len(iov.len());
143+
h.msg_control = control.as_control_ptr().cast();
144+
h.msg_controllen = msg_control_len(control.control_len());
145+
h
63146
}
64147

65148
/// Create a message header intended to send with the specified address.
66-
pub(crate) fn with_msghdr<R>(
149+
///
150+
/// This creates a `c::msghdr` and calls a function `f` on it. The `msghdr`'s
151+
/// raw pointers may point to temporaries, so this function should avoid
152+
/// storing the pointers anywhere that would outlive the function call.
153+
///
154+
/// # Safety
155+
///
156+
/// If `f` dereferences the pointers in the `msghdr`, it must do so only within
157+
/// the bounds indicated by the associated lengths in the `msghdr`.
158+
pub(crate) unsafe fn with_msghdr<R>(
67159
addr: &impl SocketAddrArg,
68160
iov: &[IoSlice<'_>],
69161
control: &mut SendAncillaryBuffer<'_, '_, '_>,
70-
f: impl FnOnce(c::msghdr) -> R,
162+
f: impl FnOnce(&c::msghdr) -> R,
71163
) -> R {
72164
addr.with_sockaddr(|addr_ptr, addr_len| {
73-
f({
74-
let mut h = zero_msghdr();
75-
h.msg_name = addr_ptr as *mut _;
76-
h.msg_namelen = addr_len as _;
77-
h.msg_iov = iov.as_ptr() as _;
78-
h.msg_iovlen = msg_iov_len(iov.len());
79-
h.msg_control = control.as_control_ptr().cast();
80-
h.msg_controllen = msg_control_len(control.control_len());
81-
h
82-
})
165+
let mut h = zero_msghdr();
166+
h.msg_name = addr_ptr as *mut _;
167+
h.msg_namelen = addr_len as _;
168+
h.msg_iov = iov.as_ptr() as _;
169+
h.msg_iovlen = msg_iov_len(iov.len());
170+
h.msg_control = control.as_control_ptr().cast();
171+
h.msg_controllen = msg_control_len(control.control_len());
172+
// Pass a reference to the `c::msghdr` instead of passing it by value
173+
// because it may contain pointers to temporary objects that won't
174+
// live beyond the call to `with_sockaddr`.
175+
f(&h)
83176
})
84177
}
85178

src/backend/libc/net/read_sockaddr.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ unsafe fn read_sun_path0(storage: *const c::sockaddr) -> u8 {
112112
///
113113
/// # Safety
114114
///
115-
/// `storage` must point to a valid socket address returned from the OS.
115+
/// `storage` must point to a least an initialized `sockaddr_header`.
116116
#[inline]
117117
pub(crate) unsafe fn sockaddr_nonempty(storage: *const c::sockaddr, len: SocketAddrLen) -> bool {
118118
if len == 0 {
@@ -137,6 +137,10 @@ pub(crate) unsafe fn sockaddr_nonempty(storage: *const c::sockaddr, len: SocketA
137137

138138
/// Set the `sa_family` field of a socket address to `AF_UNSPEC`, so that we
139139
/// can test for `AF_UNSPEC` to test whether it was stored to.
140+
///
141+
/// # Safety
142+
///
143+
/// `storage` must point to a least an initialized `sockaddr_header`.
140144
pub(crate) unsafe fn initialize_family_to_unspec(storage: *mut c::sockaddr) {
141145
(*storage.cast::<sockaddr_header>()).sa_family = c::AF_UNSPEC as _;
142146
}

0 commit comments

Comments
 (0)