Skip to content

Commit ca025d8

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 2a9bacf commit ca025d8

File tree

7 files changed

+127
-33
lines changed

7 files changed

+127
-33
lines changed

library/std/src/io/mod.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,6 +1626,64 @@ 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+
crate::hint::cold_path();
1642+
for (i, buf) in bufs.iter().enumerate() {
1643+
if !buf.is_empty() {
1644+
// Take all buffers after the first non-empty buffer,
1645+
// clamped to `n`.
1646+
let len = cmp::min(bufs.len() - i, n);
1647+
break 'slices &bufs[i..i + len];
1648+
}
1649+
}
1650+
// POSIX requires at least one buffer for writev.
1651+
// https://pubs.opengroup.org/onlinepubs/9799919799/functions/writev.html
1652+
break 'slices empty;
1653+
}
1654+
bufs
1655+
}
1656+
}
1657+
1658+
/// Limits a slice of buffers to at most `n` buffers and ensures that it has at
1659+
/// least one buffer, even if empty.
1660+
///
1661+
/// When the slice contains over `n` buffers, ensure that at least one non-empty
1662+
/// buffer is in the truncated slice, if there is one.
1663+
#[allow(unused_macros)] // Not used on all platforms
1664+
pub(crate) macro limit_slices_mut($bufs:expr, $n:expr) {
1665+
'slices: {
1666+
let bufs: &mut [IoSliceMut<'_>] = $bufs;
1667+
let n: usize = $n;
1668+
super let empty = &mut [IoSliceMut::new(&mut [])];
1669+
if bufs.len() > n || bufs.is_empty() {
1670+
crate::hint::cold_path();
1671+
for (i, buf) in bufs.iter().enumerate() {
1672+
if !buf.is_empty() {
1673+
// Take all buffers after the first non-empty buffer,
1674+
// clamped to `n`.
1675+
let len = cmp::min(bufs.len() - i, n);
1676+
break 'slices &mut bufs[i..i + len];
1677+
}
1678+
}
1679+
// POSIX requires at least one buffer for readv.
1680+
// https://pubs.opengroup.org/onlinepubs/9799919799/functions/readv.html
1681+
break 'slices empty;
1682+
}
1683+
bufs
1684+
}
1685+
}
1686+
16291687
/// A trait for objects which are byte-oriented sinks.
16301688
///
16311689
/// Implementors of the `Write` trait are sometimes called 'writers'.

library/std/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@
280280
#![feature(cfg_target_thread_local)]
281281
#![feature(cfi_encoding)]
282282
#![feature(char_max_len)]
283+
#![feature(cold_path)]
283284
#![feature(const_trait_impl)]
284285
#![feature(core_float_math)]
285286
#![feature(decl_macro)]
@@ -318,6 +319,7 @@
318319
#![feature(staged_api)]
319320
#![feature(stmt_expr_attributes)]
320321
#![feature(strict_provenance_lints)]
322+
#![feature(super_let)]
321323
#![feature(thread_local)]
322324
#![feature(try_blocks)]
323325
#![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
@@ -126,11 +126,12 @@ impl FileDesc {
126126
target_os = "nuttx"
127127
)))]
128128
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
129+
let bufs = io::limit_slices_mut!(bufs, max_iov());
129130
let ret = cvt(unsafe {
130131
libc::readv(
131132
self.as_raw_fd(),
132133
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
133-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
134+
bufs.len() as libc::c_int,
134135
)
135136
})?;
136137
Ok(ret as usize)
@@ -221,11 +222,12 @@ impl FileDesc {
221222
target_os = "openbsd", // OpenBSD 2.7
222223
))]
223224
pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
225+
let bufs = io::limit_slices_mut!(bufs, max_iov());
224226
let ret = cvt(unsafe {
225227
libc::preadv(
226228
self.as_raw_fd(),
227229
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
228-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
230+
bufs.len() as libc::c_int,
229231
offset as _,
230232
)
231233
})?;
@@ -267,11 +269,12 @@ impl FileDesc {
267269
) -> isize;
268270
);
269271

272+
let bufs = io::limit_slices_mut!(bufs, max_iov());
270273
let ret = cvt(unsafe {
271274
preadv(
272275
self.as_raw_fd(),
273276
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
274-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
277+
bufs.len() as libc::c_int,
275278
offset as _,
276279
)
277280
})?;
@@ -291,11 +294,12 @@ impl FileDesc {
291294

292295
match preadv64.get() {
293296
Some(preadv) => {
297+
let bufs = io::limit_slices_mut!(bufs, max_iov());
294298
let ret = cvt(unsafe {
295299
preadv(
296300
self.as_raw_fd(),
297301
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
298-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
302+
bufs.len() as libc::c_int,
299303
offset as _,
300304
)
301305
})?;
@@ -327,11 +331,12 @@ impl FileDesc {
327331

328332
match preadv.get() {
329333
Some(preadv) => {
334+
let bufs = io::limit_slices_mut!(bufs, max_iov());
330335
let ret = cvt(unsafe {
331336
preadv(
332337
self.as_raw_fd(),
333338
bufs.as_mut_ptr() as *mut libc::iovec as *const libc::iovec,
334-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
339+
bufs.len() as libc::c_int,
335340
offset as _,
336341
)
337342
})?;
@@ -359,11 +364,12 @@ impl FileDesc {
359364
target_os = "nuttx"
360365
)))]
361366
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
367+
let bufs = io::limit_slices!(bufs, max_iov());
362368
let ret = cvt(unsafe {
363369
libc::writev(
364370
self.as_raw_fd(),
365371
bufs.as_ptr() as *const libc::iovec,
366-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
372+
bufs.len() as libc::c_int,
367373
)
368374
})?;
369375
Ok(ret as usize)
@@ -427,11 +433,12 @@ impl FileDesc {
427433
target_os = "openbsd", // OpenBSD 2.7
428434
))]
429435
pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
436+
let bufs = io::limit_slices!(bufs, max_iov());
430437
let ret = cvt(unsafe {
431438
libc::pwritev(
432439
self.as_raw_fd(),
433440
bufs.as_ptr() as *const libc::iovec,
434-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
441+
bufs.len() as libc::c_int,
435442
offset as _,
436443
)
437444
})?;
@@ -473,11 +480,12 @@ impl FileDesc {
473480
) -> isize;
474481
);
475482

483+
let bufs = io::limit_slices!(bufs, max_iov());
476484
let ret = cvt(unsafe {
477485
pwritev(
478486
self.as_raw_fd(),
479487
bufs.as_ptr() as *const libc::iovec,
480-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
488+
bufs.len() as libc::c_int,
481489
offset as _,
482490
)
483491
})?;
@@ -497,11 +505,12 @@ impl FileDesc {
497505

498506
match pwritev64.get() {
499507
Some(pwritev) => {
508+
let bufs = io::limit_slices!(bufs, max_iov());
500509
let ret = cvt(unsafe {
501510
pwritev(
502511
self.as_raw_fd(),
503512
bufs.as_ptr() as *const libc::iovec,
504-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
513+
bufs.len() as libc::c_int,
505514
offset as _,
506515
)
507516
})?;
@@ -533,11 +542,12 @@ impl FileDesc {
533542

534543
match pwritev.get() {
535544
Some(pwritev) => {
545+
let bufs = io::limit_slices!(bufs, max_iov());
536546
let ret = cvt(unsafe {
537547
pwritev(
538548
self.as_raw_fd(),
539549
bufs.as_ptr() as *const libc::iovec,
540-
cmp::min(bufs.len(), max_iov()) as libc::c_int,
550+
bufs.len() as libc::c_int,
541551
offset as _,
542552
)
543553
})?;
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
}

0 commit comments

Comments
 (0)