Skip to content

Commit 49877d1

Browse files
committed
Fix IO safety in unix.rs
1 parent a9900f3 commit 49877d1

File tree

5 files changed

+128
-55
lines changed

5 files changed

+128
-55
lines changed

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,8 @@ harness = false
4343
[[test]]
4444
name = "helper"
4545
path = "tests/helper.rs"
46+
47+
[[test]]
48+
name = "spawn-after-drop"
49+
path = "tests/spawn-after-drop.rs"
50+
harness = false

src/unix.rs

+73-53
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ pub struct Acquired {
4747

4848
impl Client {
4949
pub fn new(mut limit: usize) -> io::Result<Client> {
50-
let client = unsafe { Client::mk()? };
50+
let client = Client::mk()?;
5151

5252
// I don't think the character written here matters, but I could be
5353
// wrong!
5454
const BUFFER: [u8; 128] = [b'|'; 128];
5555

5656
let mut write = &client.write;
5757

58-
set_nonblocking(write.as_raw_fd(), true)?;
58+
set_nonblocking(write.as_fd(), true)?;
5959

6060
while limit > 0 {
6161
let n = limit.min(BUFFER.len());
@@ -64,39 +64,52 @@ impl Client {
6464
limit -= n;
6565
}
6666

67-
set_nonblocking(write.as_raw_fd(), false)?;
67+
set_nonblocking(write.as_fd(), false)?;
6868

6969
Ok(client)
7070
}
7171

72-
unsafe fn mk() -> io::Result<Client> {
73-
let mut pipes = [0; 2];
74-
72+
fn mk() -> io::Result<Client> {
7573
// Attempt atomically-create-with-cloexec if we can on Linux,
7674
// detected by using the `syscall` function in `libc` to try to work
7775
// with as many kernels/glibc implementations as possible.
7876
#[cfg(target_os = "linux")]
7977
{
8078
static PIPE2_AVAILABLE: AtomicBool = AtomicBool::new(true);
8179
if PIPE2_AVAILABLE.load(Ordering::SeqCst) {
82-
match libc::syscall(libc::SYS_pipe2, pipes.as_mut_ptr(), libc::O_CLOEXEC) {
83-
-1 => {
84-
let err = io::Error::last_os_error();
85-
if err.raw_os_error() == Some(libc::ENOSYS) {
86-
PIPE2_AVAILABLE.store(false, Ordering::SeqCst);
87-
} else {
88-
return Err(err);
80+
unsafe {
81+
let mut pipes = [0; 2];
82+
match libc::syscall(libc::SYS_pipe2, pipes.as_mut_ptr(), libc::O_CLOEXEC) {
83+
-1 => {
84+
let err = io::Error::last_os_error();
85+
if err.raw_os_error() == Some(libc::ENOSYS) {
86+
PIPE2_AVAILABLE.store(false, Ordering::SeqCst);
87+
} else {
88+
return Err(err);
89+
}
90+
}
91+
_ => {
92+
return Ok(Client::from_fds(
93+
OwnedFd::from_raw_fd(pipes[0]),
94+
OwnedFd::from_raw_fd(pipes[1]),
95+
))
8996
}
9097
}
91-
_ => return Ok(Client::from_fds(pipes[0], pipes[1])),
9298
}
9399
}
94100
}
95101

96-
cvt(libc::pipe(pipes.as_mut_ptr()))?;
97-
drop(set_cloexec(pipes[0], true));
98-
drop(set_cloexec(pipes[1], true));
99-
Ok(Client::from_fds(pipes[0], pipes[1]))
102+
let (read, write) = unsafe {
103+
let mut pipes = [0; 2];
104+
cvt(libc::pipe(pipes.as_mut_ptr()))?;
105+
(
106+
OwnedFd::from_raw_fd(pipes[0]),
107+
OwnedFd::from_raw_fd(pipes[1]),
108+
)
109+
};
110+
drop(set_cloexec(read.as_fd(), true));
111+
drop(set_cloexec(write.as_fd(), true));
112+
Ok(Client::from_fds(read, write))
100113
}
101114

102115
pub(crate) unsafe fn open(s: &str, check_pipe: bool) -> Result<Client, FromEnvErrorInner> {
@@ -211,18 +224,21 @@ impl Client {
211224
}
212225

213226
Ok(Some(Client {
214-
read: clone_fd_and_set_cloexec(read)?,
215-
write: clone_fd_and_set_cloexec(write)?,
227+
read: clone_fd_and_set_cloexec(BorrowedFd::borrow_raw(read))?,
228+
write: clone_fd_and_set_cloexec(BorrowedFd::borrow_raw(write))?,
216229
creation_arg,
217230
is_non_blocking: None,
218231
}))
219232
}
220233

221-
unsafe fn from_fds(read: c_int, write: c_int) -> Client {
234+
fn from_fds(read: OwnedFd, write: OwnedFd) -> Client {
222235
Client {
223-
read: File::from_raw_fd(read),
224-
write: File::from_raw_fd(write),
225-
creation_arg: ClientCreationArg::Fds { read, write },
236+
creation_arg: ClientCreationArg::Fds {
237+
read: read.as_raw_fd(),
238+
write: write.as_raw_fd(),
239+
},
240+
read: read.into(),
241+
write: write.into(),
226242
is_non_blocking: None,
227243
}
228244
}
@@ -304,7 +320,7 @@ impl Client {
304320

305321
if let Some(is_non_blocking) = self.is_non_blocking.as_ref() {
306322
if !is_non_blocking.load(Ordering::Relaxed) {
307-
set_nonblocking(fifo.as_raw_fd(), true)?;
323+
set_nonblocking(fifo.as_fd(), true)?;
308324
is_non_blocking.store(true, Ordering::Relaxed);
309325
}
310326
} else {
@@ -357,24 +373,30 @@ impl Client {
357373
Ok(unsafe { len.assume_init() } as usize)
358374
}
359375

360-
pub fn configure(&self, cmd: &mut Command) {
361-
if matches!(self.creation_arg, ClientCreationArg::Fifo { .. }) {
362-
// We `File::open`ed it when inheriting from environment,
363-
// so no need to set cloexec for fifo.
364-
return;
365-
}
366-
// Here we basically just want to say that in the child process
367-
// we'll configure the read/write file descriptors to *not* be
368-
// cloexec, so they're inherited across the exec and specified as
369-
// integers through `string_arg` above.
370-
let read = self.read.as_raw_fd();
371-
let write = self.write.as_raw_fd();
372-
unsafe {
373-
cmd.pre_exec(move || {
374-
set_cloexec(read, false)?;
375-
set_cloexec(write, false)?;
376-
Ok(())
377-
});
376+
pub fn configure(self: &Arc<Self>, cmd: &mut Command) {
377+
match self.creation_arg {
378+
ClientCreationArg::Fifo { .. } => {
379+
// We `File::open`ed it when inheriting from environment,
380+
// so no need to set cloexec for fifo.
381+
}
382+
ClientCreationArg::Fds { read, write } => {
383+
// Here we basically just want to say that in the child process
384+
// we'll configure the read/write file descriptors to *not* be
385+
// cloexec, so they're inherited across the exec and specified as
386+
// integers through `string_arg` above.
387+
unsafe {
388+
// Keep a reference to the jobserver alive in the closure so that
389+
// the pipe FDs aren't closed, otherwise `set_cloexec` might end up
390+
// targetting a completely unrelated file descriptor.
391+
let arc = self.clone();
392+
cmd.pre_exec(move || {
393+
let _ = &arc;
394+
set_cloexec(BorrowedFd::borrow_raw(read), false)?;
395+
set_cloexec(BorrowedFd::borrow_raw(write), false)?;
396+
Ok(())
397+
});
398+
}
399+
}
378400
}
379401
}
380402
}
@@ -515,34 +537,32 @@ unsafe fn fd_check(fd: c_int, check_pipe: bool) -> Result<(), FromEnvErrorInner>
515537
}
516538
}
517539

518-
fn clone_fd_and_set_cloexec(fd: c_int) -> Result<File, FromEnvErrorInner> {
519-
// Safety: fd is a valid fd dand it remains open until returns
520-
unsafe { BorrowedFd::borrow_raw(fd) }
521-
.try_clone_to_owned()
540+
fn clone_fd_and_set_cloexec(fd: BorrowedFd<'_>) -> Result<File, FromEnvErrorInner> {
541+
fd.try_clone_to_owned()
522542
.map(File::from)
523-
.map_err(|err| FromEnvErrorInner::CannotOpenFd(fd, err))
543+
.map_err(|err| FromEnvErrorInner::CannotOpenFd(fd.as_raw_fd(), err))
524544
}
525545

526-
fn set_cloexec(fd: c_int, set: bool) -> io::Result<()> {
546+
fn set_cloexec(fd: BorrowedFd<'_>, set: bool) -> io::Result<()> {
527547
unsafe {
528-
let previous = cvt(libc::fcntl(fd, libc::F_GETFD))?;
548+
let previous = cvt(libc::fcntl(fd.as_raw_fd(), libc::F_GETFD))?;
529549
let new = if set {
530550
previous | libc::FD_CLOEXEC
531551
} else {
532552
previous & !libc::FD_CLOEXEC
533553
};
534554
if new != previous {
535-
cvt(libc::fcntl(fd, libc::F_SETFD, new))?;
555+
cvt(libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, new))?;
536556
}
537557
Ok(())
538558
}
539559
}
540560

541-
fn set_nonblocking(fd: c_int, set: bool) -> io::Result<()> {
561+
fn set_nonblocking(fd: BorrowedFd<'_>, set: bool) -> io::Result<()> {
542562
let status_flag = if set { libc::O_NONBLOCK } else { 0 };
543563

544564
unsafe {
545-
cvt(libc::fcntl(fd, libc::F_SETFL, status_flag))?;
565+
cvt(libc::fcntl(fd.as_raw_fd(), libc::F_SETFL, status_flag))?;
546566
}
547567

548568
Ok(())

src/wasm.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl Client {
7575
Ok(*lock)
7676
}
7777

78-
pub fn configure(&self, _cmd: &mut Command) {
78+
pub fn configure(self: &Arc<Self>, _cmd: &mut Command) {
7979
unreachable!();
8080
}
8181
}

src/windows.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ impl Client {
214214
}
215215
}
216216

217-
pub fn configure(&self, _cmd: &mut Command) {
217+
pub fn configure(self: &Arc<Self>, _cmd: &mut Command) {
218218
// nothing to do here, we gave the name of our semaphore to the
219219
// child above
220220
}

tests/spawn-after-drop.rs

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use std::{env, process::Command};
2+
3+
use jobserver::{Client, FromEnvErrorKind};
4+
5+
macro_rules! t {
6+
($e:expr) => {
7+
match $e {
8+
Ok(e) => e,
9+
Err(e) => panic!("{} failed with {}", stringify!($e), e),
10+
}
11+
};
12+
}
13+
14+
fn main() {
15+
match env::args().skip(1).next().unwrap_or_default().as_str() {
16+
"" => {
17+
let me = t!(env::current_exe());
18+
let mut cmd = Command::new(me);
19+
let client = t!(Client::new(1));
20+
client.configure(&mut cmd);
21+
drop(client);
22+
let status = t!(cmd.arg("from_env").status());
23+
assert!(status.success());
24+
}
25+
"from_env" => {
26+
let me = t!(env::current_exe());
27+
let mut cmd = Command::new(me);
28+
let client = unsafe { t!(match Client::from_env_ext(true).client {
29+
// Its ok for a dropped jobservers path to no longer exist (e.g. on Windows).
30+
Err(e) if matches!(e.kind(), FromEnvErrorKind::CannotOpenPath) => return,
31+
res => res,
32+
}) };
33+
client.configure(&mut cmd);
34+
drop(client);
35+
let status = t!(cmd.arg("use_it").status());
36+
assert!(status.success());
37+
}
38+
"use_it" => {
39+
let client = unsafe { t!(match Client::from_env_ext(true).client {
40+
// See above.
41+
Err(e) if matches!(e.kind(), FromEnvErrorKind::CannotOpenPath) => return,
42+
res => res,
43+
}) };
44+
t!(client.acquire());
45+
}
46+
_ => unreachable!(),
47+
}
48+
}

0 commit comments

Comments
 (0)