Skip to content

Commit 49b43ed

Browse files
committed
Fix IO safety in unix.rs
1 parent a9900f3 commit 49b43ed

File tree

5 files changed

+117
-57
lines changed

5 files changed

+117
-57
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

+69-55
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,51 @@ 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);
81-
if PIPE2_AVAILABLE.load(Ordering::SeqCst) {
82-
match libc::syscall(libc::SYS_pipe2, pipes.as_mut_ptr(), libc::O_CLOEXEC) {
79+
if PIPE2_AVAILABLE.load(Ordering::Relaxed) {
80+
let mut pipes = [0; 2];
81+
match unsafe { libc::syscall(libc::SYS_pipe2, pipes.as_mut_ptr(), libc::O_CLOEXEC) }
82+
{
8383
-1 => {
8484
let err = io::Error::last_os_error();
8585
if err.raw_os_error() == Some(libc::ENOSYS) {
86-
PIPE2_AVAILABLE.store(false, Ordering::SeqCst);
86+
PIPE2_AVAILABLE.store(false, Ordering::Relaxed);
8787
} else {
8888
return Err(err);
8989
}
9090
}
91-
_ => return Ok(Client::from_fds(pipes[0], pipes[1])),
91+
_ => unsafe {
92+
return Ok(Client::from_fds(
93+
OwnedFd::from_raw_fd(pipes[0]),
94+
OwnedFd::from_raw_fd(pipes[1]),
95+
));
96+
},
9297
}
9398
}
9499
}
95100

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]))
101+
let (read, write) = unsafe {
102+
let mut pipes = [0; 2];
103+
cvt(libc::pipe(pipes.as_mut_ptr()))?;
104+
(
105+
OwnedFd::from_raw_fd(pipes[0]),
106+
OwnedFd::from_raw_fd(pipes[1]),
107+
)
108+
};
109+
set_cloexec(read.as_fd(), true)?;
110+
set_cloexec(write.as_fd(), true)?;
111+
Ok(Client::from_fds(read, write))
100112
}
101113

102114
pub(crate) unsafe fn open(s: &str, check_pipe: bool) -> Result<Client, FromEnvErrorInner> {
@@ -211,18 +223,21 @@ impl Client {
211223
}
212224

213225
Ok(Some(Client {
214-
read: clone_fd_and_set_cloexec(read)?,
215-
write: clone_fd_and_set_cloexec(write)?,
226+
read: clone_fd_and_set_cloexec(BorrowedFd::borrow_raw(read))?,
227+
write: clone_fd_and_set_cloexec(BorrowedFd::borrow_raw(write))?,
216228
creation_arg,
217229
is_non_blocking: None,
218230
}))
219231
}
220232

221-
unsafe fn from_fds(read: c_int, write: c_int) -> Client {
233+
fn from_fds(read: OwnedFd, write: OwnedFd) -> Client {
222234
Client {
223-
read: File::from_raw_fd(read),
224-
write: File::from_raw_fd(write),
225-
creation_arg: ClientCreationArg::Fds { read, write },
235+
creation_arg: ClientCreationArg::Fds {
236+
read: read.as_raw_fd(),
237+
write: write.as_raw_fd(),
238+
},
239+
read: read.into(),
240+
write: write.into(),
226241
is_non_blocking: None,
227242
}
228243
}
@@ -304,7 +319,7 @@ impl Client {
304319

305320
if let Some(is_non_blocking) = self.is_non_blocking.as_ref() {
306321
if !is_non_blocking.load(Ordering::Relaxed) {
307-
set_nonblocking(fifo.as_raw_fd(), true)?;
322+
set_nonblocking(fifo.as_fd(), true)?;
308323
is_non_blocking.store(true, Ordering::Relaxed);
309324
}
310325
} else {
@@ -357,24 +372,30 @@ impl Client {
357372
Ok(unsafe { len.assume_init() } as usize)
358373
}
359374

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-
});
375+
pub fn configure(self: &Arc<Self>, cmd: &mut Command) {
376+
match self.creation_arg {
377+
ClientCreationArg::Fifo { .. } => {
378+
// We `File::open`ed it when inheriting from environment,
379+
// so no need to set cloexec for fifo.
380+
}
381+
ClientCreationArg::Fds { read, write } => {
382+
// Here we basically just want to say that in the child process
383+
// we'll configure the read/write file descriptors to *not* be
384+
// cloexec, so they're inherited across the exec and specified as
385+
// integers through `string_arg` above.
386+
unsafe {
387+
// Keep a reference to the jobserver alive in the closure so that
388+
// the pipe FDs aren't closed, otherwise `set_cloexec` might end up
389+
// targetting a completely unrelated file descriptor.
390+
let arc = self.clone();
391+
cmd.pre_exec(move || {
392+
let _ = &arc;
393+
set_cloexec(BorrowedFd::borrow_raw(read), false)?;
394+
set_cloexec(BorrowedFd::borrow_raw(write), false)?;
395+
Ok(())
396+
});
397+
}
398+
}
378399
}
379400
}
380401
}
@@ -515,34 +536,32 @@ unsafe fn fd_check(fd: c_int, check_pipe: bool) -> Result<(), FromEnvErrorInner>
515536
}
516537
}
517538

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()
539+
fn clone_fd_and_set_cloexec(fd: BorrowedFd<'_>) -> Result<File, FromEnvErrorInner> {
540+
fd.try_clone_to_owned()
522541
.map(File::from)
523-
.map_err(|err| FromEnvErrorInner::CannotOpenFd(fd, err))
542+
.map_err(|err| FromEnvErrorInner::CannotOpenFd(fd.as_raw_fd(), err))
524543
}
525544

526-
fn set_cloexec(fd: c_int, set: bool) -> io::Result<()> {
545+
fn set_cloexec(fd: BorrowedFd<'_>, set: bool) -> io::Result<()> {
527546
unsafe {
528-
let previous = cvt(libc::fcntl(fd, libc::F_GETFD))?;
547+
let previous = cvt(libc::fcntl(fd.as_raw_fd(), libc::F_GETFD))?;
529548
let new = if set {
530549
previous | libc::FD_CLOEXEC
531550
} else {
532551
previous & !libc::FD_CLOEXEC
533552
};
534553
if new != previous {
535-
cvt(libc::fcntl(fd, libc::F_SETFD, new))?;
554+
cvt(libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, new))?;
536555
}
537556
Ok(())
538557
}
539558
}
540559

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

544563
unsafe {
545-
cvt(libc::fcntl(fd, libc::F_SETFL, status_flag))?;
564+
cvt(libc::fcntl(fd.as_raw_fd(), libc::F_SETFL, status_flag))?;
546565
}
547566

548567
Ok(())
@@ -570,12 +589,7 @@ mod test {
570589

571590
use crate::{test::run_named_fifo_try_acquire_tests, Client};
572591

573-
use std::{
574-
fs::File,
575-
io::{self, Write},
576-
os::unix::io::AsRawFd,
577-
sync::Arc,
578-
};
592+
use std::{fs::File, io::Write, os::unix::io::AsRawFd, sync::Arc};
579593

580594
fn from_imp_client(imp: ClientImp) -> Client {
581595
Client {

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

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

0 commit comments

Comments
 (0)