Skip to content

Commit 8658673

Browse files
committed
Ensure non-empty buffers for vectored I/O
`readv` and `writev` have platform-specific constraints on the number of buffers allowed. For example, on Unix, when the number of buffers passed is zero or over the `IOV_MAX` limit, `EINVAL` is returned. Other platforms have similar constraints. Currently, this is only partially handled in `read_vectored` and `write_vectored` implementations. They truncate the length of the `bufs` slice to `IOV_MAX`, so the slice never exceeds the maximum. However, if the only non-empty buffers are in `bufs[IOV_MAX..]`, the functions will return `Ok(0)`, erroneously signaling EOF. Additionally, the non-zero minimum is not handled.
1 parent dddd7ab commit 8658673

File tree

7 files changed

+122
-33
lines changed

7 files changed

+122
-33
lines changed

library/std/src/io/mod.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,6 +1626,60 @@ impl<'a> Deref for IoSlice<'a> {
16261626
}
16271627
}
16281628

1629+
/// Limits a slice of buffers to at most `n` buffers and ensures that it has at
1630+
/// least one buffer, even if empty.
1631+
///
1632+
/// When the slice contains over `n` buffers, ensure that at least one non-empty
1633+
/// buffer is in the truncated slice, if there is one.
1634+
#[allow(unused_macros)] // Not used on all platforms
1635+
pub(crate) macro limit_slices($bufs:expr, $n:expr) {
1636+
'slices: {
1637+
let bufs: &[IoSlice<'_>] = $bufs;
1638+
let n: usize = $n;
1639+
super let empty = &[IoSlice::new(&[])];
1640+
// if bufs.len() > n || bufs.is_empty()
1641+
if core::intrinsics::unlikely(bufs.len().wrapping_sub(1) >= n) {
1642+
for (i, buf) in bufs.iter().enumerate() {
1643+
if !buf.is_empty() {
1644+
let len = cmp::min(bufs.len() - i, n);
1645+
break 'slices &bufs[i..i + len];
1646+
}
1647+
}
1648+
// POSIX requires at least one buffer for writev.
1649+
// https://pubs.opengroup.org/onlinepubs/9799919799/functions/writev.html
1650+
break 'slices empty;
1651+
}
1652+
bufs
1653+
}
1654+
}
1655+
1656+
/// Limits a slice of buffers to at most `n` buffers and ensures that it has at
1657+
/// least one buffer, even if empty.
1658+
///
1659+
/// When the slice contains over `n` buffers, ensure that at least one non-empty
1660+
/// buffer is in the truncated slice, if there is one.
1661+
#[allow(unused_macros)] // Not used on all platforms
1662+
pub(crate) macro limit_slices_mut($bufs:expr, $n:expr) {
1663+
'slices: {
1664+
let bufs: &mut [IoSliceMut<'_>] = $bufs;
1665+
let n: usize = $n;
1666+
super let empty = &mut [IoSliceMut::new(&mut [])];
1667+
// if bufs.len() > n || bufs.is_empty()
1668+
if core::intrinsics::unlikely(bufs.len().wrapping_sub(1) >= n) {
1669+
for (i, buf) in bufs.iter().enumerate() {
1670+
if !buf.is_empty() {
1671+
let len = cmp::min(bufs.len() - i, n);
1672+
break 'slices &mut bufs[i..i + len];
1673+
}
1674+
}
1675+
// POSIX requires at least one buffer for readv.
1676+
// https://pubs.opengroup.org/onlinepubs/9799919799/functions/readv.html
1677+
break 'slices empty;
1678+
}
1679+
bufs
1680+
}
1681+
}
1682+
16291683
/// A trait for objects which are byte-oriented sinks.
16301684
///
16311685
/// Implementors of the `Write` trait are sometimes called 'writers'.

library/std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@
315315
#![feature(staged_api)]
316316
#![feature(stmt_expr_attributes)]
317317
#![feature(strict_provenance_lints)]
318+
#![feature(super_let)]
318319
#![feature(thread_local)]
319320
#![feature(try_blocks)]
320321
#![feature(try_trait_v2)]

library/std/src/sys/fd/hermit.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#![unstable(reason = "not public", issue = "none", feature = "fd")]
22

3-
use crate::cmp;
43
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read, SeekFrom};
54
use crate::os::hermit::hermit_abi;
65
use crate::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
@@ -39,11 +38,12 @@ impl FileDesc {
3938
}
4039

4140
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
41+
let bufs = io::limit_slices_mut!(bufs, max_iov());
4242
let ret = cvt(unsafe {
4343
hermit_abi::readv(
4444
self.as_raw_fd(),
4545
bufs.as_mut_ptr() as *mut hermit_abi::iovec as *const hermit_abi::iovec,
46-
cmp::min(bufs.len(), max_iov()),
46+
bufs.len(),
4747
)
4848
})?;
4949
Ok(ret as usize)
@@ -66,11 +66,12 @@ impl FileDesc {
6666
}
6767

6868
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
69+
let bufs = io::limit_slices!(bufs, max_iov());
6970
let ret = cvt(unsafe {
7071
hermit_abi::writev(
7172
self.as_raw_fd(),
7273
bufs.as_ptr() as *const hermit_abi::iovec,
73-
cmp::min(bufs.len(), max_iov()),
74+
bufs.len(),
7475
)
7576
})?;
7677
Ok(ret as usize)

library/std/src/sys/fd/unix.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,12 @@ impl FileDesc {
111111
target_os = "nuttx"
112112
)))]
113113
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
114+
let bufs = io::limit_slices_mut!(bufs, max_iov());
114115
let ret = cvt(unsafe {
115116
libc::readv(
116117
self.as_raw_fd(),
117118
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
118-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
119+
bufs.len() as libc::c_int,
119120
)
120121
})?;
121122
Ok(ret as usize)
@@ -201,11 +202,12 @@ impl FileDesc {
201202
target_os = "openbsd", // OpenBSD 2.7
202203
))]
203204
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
205+
let bufs = io::limit_slices_mut!(bufs, max_iov());
204206
let ret = cvt(unsafe {
205207
libc::preadv(
206208
self.as_raw_fd(),
207209
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
208-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
210+
bufs.len() as libc::c_int,
209211
offset as _,
210212
)
211213
})?;
@@ -247,11 +249,12 @@ impl FileDesc {
247249
) -> isize;
248250
);
249251

252+
let bufs = io::limit_slices_mut!(bufs, max_iov());
250253
let ret = cvt(unsafe {
251254
preadv(
252255
self.as_raw_fd(),
253256
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
254-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
257+
bufs.len() as libc::c_int,
255258
offset as _,
256259
)
257260
})?;
@@ -271,11 +274,12 @@ impl FileDesc {
271274

272275
match preadv64.get() {
273276
Some(preadv) => {
277+
let bufs = io::limit_slices_mut!(bufs, max_iov());
274278
let ret = cvt(unsafe {
275279
preadv(
276280
self.as_raw_fd(),
277281
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
278-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
282+
bufs.len() as libc::c_int,
279283
offset as _,
280284
)
281285
})?;
@@ -307,11 +311,12 @@ impl FileDesc {
307311

308312
match preadv.get() {
309313
Some(preadv) => {
314+
let bufs = io::limit_slices_mut!(bufs, max_iov());
310315
let ret = cvt(unsafe {
311316
preadv(
312317
self.as_raw_fd(),
313318
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
314-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
319+
bufs.len() as libc::c_int,
315320
offset as _,
316321
)
317322
})?;
@@ -339,11 +344,12 @@ impl FileDesc {
339344
target_os = "nuttx"
340345
)))]
341346
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
347+
let bufs = io::limit_slices!(bufs, max_iov());
342348
let ret = cvt(unsafe {
343349
libc::writev(
344350
self.as_raw_fd(),
345351
bufs.as_ptr() as *const libc::iovec,
346-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
352+
bufs.len() as libc::c_int,
347353
)
348354
})?;
349355
Ok(ret as usize)
@@ -408,11 +414,12 @@ impl FileDesc {
408414
target_os = "openbsd", // OpenBSD 2.7
409415
))]
410416
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
417+
let bufs = io::limit_slices!(bufs, max_iov());
411418
let ret = cvt(unsafe {
412419
libc::pwritev(
413420
self.as_raw_fd(),
414421
bufs.as_ptr() as *const libc::iovec,
415-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
422+
bufs.len() as libc::c_int,
416423
offset as _,
417424
)
418425
})?;
@@ -454,11 +461,12 @@ impl FileDesc {
454461
) -> isize;
455462
);
456463

464+
let bufs = io::limit_slices!(bufs, max_iov());
457465
let ret = cvt(unsafe {
458466
pwritev(
459467
self.as_raw_fd(),
460468
bufs.as_ptr() as *const libc::iovec,
461-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
469+
bufs.len() as libc::c_int,
462470
offset as _,
463471
)
464472
})?;
@@ -478,11 +486,12 @@ impl FileDesc {
478486

479487
match pwritev64.get() {
480488
Some(pwritev) => {
489+
let bufs = io::limit_slices!(bufs, max_iov());
481490
let ret = cvt(unsafe {
482491
pwritev(
483492
self.as_raw_fd(),
484493
bufs.as_ptr() as *const libc::iovec,
485-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
494+
bufs.len() as libc::c_int,
486495
offset as _,
487496
)
488497
})?;
@@ -514,11 +523,12 @@ impl FileDesc {
514523

515524
match pwritev.get() {
516525
Some(pwritev) => {
526+
let bufs = io::limit_slices!(bufs, max_iov());
517527
let ret = cvt(unsafe {
518528
pwritev(
519529
self.as_raw_fd(),
520530
bufs.as_ptr() as *const libc::iovec,
521-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
531+
bufs.len() as libc::c_int,
522532
offset as _,
523533
)
524534
})?;
Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
11
use core::mem::ManuallyDrop;
22

3-
use super::FileDesc;
3+
use super::{FileDesc, max_iov};
44
use crate::io::IoSlice;
55
use crate::os::unix::io::FromRawFd;
66

77
#[test]
88
fn limit_vector_count() {
9+
const IOV_MAX: usize = max_iov();
10+
11+
let stdout = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(1) });
12+
let mut bufs = vec![IoSlice::new(&[]); IOV_MAX * 2 + 1];
13+
assert_eq!(stdout.write_vectored(&bufs).unwrap(), 0);
14+
15+
// The slice of buffers is truncated to IOV_MAX buffers. However, since the
16+
// first IOV_MAX buffers are all empty, it is sliced starting at the first
17+
// non-empty buffer to avoid erroneously returning Ok(0). In this case, that
18+
// starts with the b"hello" buffer and ends just before the b"world!"
19+
// buffer.
20+
bufs[IOV_MAX] = IoSlice::new(b"hello");
21+
bufs[IOV_MAX * 2] = IoSlice::new(b"world!");
22+
assert_eq!(stdout.write_vectored(&bufs).unwrap(), b"hello".len())
23+
}
24+
25+
#[test]
26+
fn empty_vector() {
27+
let stdin = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(0) });
28+
assert_eq!(stdin.read_vectored(&mut []).unwrap(), 0);
29+
930
let stdout = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(1) });
10-
let bufs = (0..1500).map(|_| IoSlice::new(&[])).collect::<Vec<_>>();
11-
assert!(stdout.write_vectored(&bufs).is_ok());
31+
assert_eq!(stdout.write_vectored(&[]).unwrap(), 0);
1232
}

library/std/src/sys/net/connection/socket/solid.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::os::solid::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, Owne
99
use crate::sys::abi;
1010
use crate::sys_common::{FromInner, IntoInner};
1111
use crate::time::Duration;
12-
use crate::{cmp, mem, ptr, str};
12+
use crate::{mem, ptr, str};
1313

1414
pub(super) mod netc {
1515
pub use crate::sys::abi::sockets::*;
@@ -223,12 +223,9 @@ impl Socket {
223223
}
224224

225225
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
226+
let bufs = io::limit_slices_mut!(bufs, max_iov());
226227
let ret = cvt(unsafe {
227-
netc::readv(
228-
self.as_raw_fd(),
229-
bufs.as_ptr() as *const netc::iovec,
230-
cmp::min(bufs.len(), max_iov()) as c_int,
231-
)
228+
netc::readv(self.as_raw_fd(), bufs.as_ptr() as *const netc::iovec, bufs.len() as c_int)
232229
})?;
233230
Ok(ret as usize)
234231
}
@@ -268,12 +265,9 @@ impl Socket {
268265
}
269266

270267
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
268+
let bufs = io::limit_slices!(bufs, max_iov());
271269
let ret = cvt(unsafe {
272-
netc::writev(
273-
self.as_raw_fd(),
274-
bufs.as_ptr() as *const netc::iovec,
275-
cmp::min(bufs.len(), max_iov()) as c_int,
276-
)
270+
netc::writev(self.as_raw_fd(), bufs.as_ptr() as *const netc::iovec, bufs.len() as c_int)
277271
})?;
278272
Ok(ret as usize)
279273
}

library/std/src/sys/net/connection/socket/windows.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,6 @@ impl Socket {
299299
}
300300

301301
fn recv_with_flags(&self, mut buf: BorrowedCursor<'_>, flags: c_int) -> io::Result<()> {
302-
// On unix when a socket is shut down all further reads return 0, so we
303-
// do the same on windows to map a shut down socket to returning EOF.
304302
let length = cmp::min(buf.capacity(), i32::MAX as usize) as i32;
305303
let result =
306304
unsafe { c::recv(self.as_raw(), buf.as_mut().as_mut_ptr() as *mut _, length, flags) };
@@ -309,6 +307,9 @@ impl Socket {
309307
c::SOCKET_ERROR => {
310308
let error = unsafe { c::WSAGetLastError() };
311309

310+
// On Unix when a socket is shut down, all further reads return
311+
// 0, so we do the same on Windows to map a shut down socket to
312+
// returning EOF.
312313
if error == c::WSAESHUTDOWN {
313314
Ok(())
314315
} else {
@@ -333,8 +334,9 @@ impl Socket {
333334
}
334335

335336
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
336-
// On unix when a socket is shut down all further reads return 0, so we
337-
// do the same on windows to map a shut down socket to returning EOF.
337+
// WSARecv requires at least one buffer.
338+
let bufs = if bufs.is_empty() { &mut [IoSliceMut::new(&mut [])] } else { bufs };
339+
338340
let length = cmp::min(bufs.len(), u32::MAX as usize) as u32;
339341
let mut nread = 0;
340342
let mut flags = 0;
@@ -355,6 +357,9 @@ impl Socket {
355357
_ => {
356358
let error = unsafe { c::WSAGetLastError() };
357359

360+
// On Unix when a socket is shut down, all further reads return
361+
// 0, so we do the same on Windows to map a shut down socket to
362+
// returning EOF.
358363
if error == c::WSAESHUTDOWN {
359364
Ok(0)
360365
} else {
@@ -384,8 +389,6 @@ impl Socket {
384389
let mut addrlen = size_of_val(&storage) as netc::socklen_t;
385390
let length = cmp::min(buf.len(), <wrlen_t>::MAX as usize) as wrlen_t;
386391

387-
// On unix when a socket is shut down all further reads return 0, so we
388-
// do the same on windows to map a shut down socket to returning EOF.
389392
let result = unsafe {
390393
c::recvfrom(
391394
self.as_raw(),
@@ -401,6 +404,9 @@ impl Socket {
401404
c::SOCKET_ERROR => {
402405
let error = unsafe { c::WSAGetLastError() };
403406

407+
// On Unix when a socket is shut down, all further reads return
408+
// 0, so we do the same on Windows to map a shut down socket to
409+
// returning EOF.
404410
if error == c::WSAESHUTDOWN {
405411
Ok((0, unsafe { socket_addr_from_c(&storage, addrlen as usize)? }))
406412
} else {
@@ -420,6 +426,9 @@ impl Socket {
420426
}
421427

422428
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
429+
// WSASend requires at least one buffer.
430+
let bufs = if bufs.is_empty() { &[IoSlice::new(&[])] } else { bufs };
431+
423432
let length = cmp::min(bufs.len(), u32::MAX as usize) as u32;
424433
let mut nwritten = 0;
425434
let result = unsafe {

0 commit comments

Comments
 (0)