Skip to content

Commit d6c7230

Browse files
committed
std: Set CLOEXEC for all fds opened on unix
This commit starts to set the CLOEXEC flag for all files and sockets opened by the standard library by default on all unix platforms. There are a few points of note in this commit: * The implementation is not 100% satisfactory in the face of threads. File descriptors only have the `F_CLOEXEC` flag set *after* they are opened, allowing for a fork/exec to happen in the middle and leak the descriptor. Some platforms do support atomically opening a descriptor while setting the `CLOEXEC` flag, and it is left as a future extension to bind these apis as it is unclear how to do so nicely at this time. * The implementation does not offer a method of opting into the old behavior of not setting `CLOEXEC`. This will possibly be added in the future through extensions on `OpenOptions`, for example. * This change does not yet audit any Windows APIs to see if the handles are inherited by default by accident. This is a breaking change for users who call `fork` or `exec` outside of the standard library itself and expect file descriptors to be inherted. All file descriptors created by the standard library will no longer be inherited. [breaking-change]
1 parent 88fc543 commit d6c7230

File tree

6 files changed

+125
-30
lines changed

6 files changed

+125
-30
lines changed

src/libstd/sys/unix/c.rs

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,39 +26,35 @@ use libc;
2626
target_os = "dragonfly",
2727
target_os = "bitrig",
2828
target_os = "openbsd"))]
29-
pub const FIONBIO: libc::c_ulong = 0x8004667e;
30-
#[cfg(any(all(target_os = "linux",
31-
any(target_arch = "x86",
32-
target_arch = "x86_64",
33-
target_arch = "arm",
34-
target_arch = "aarch64")),
35-
target_os = "android"))]
36-
pub const FIONBIO: libc::c_ulong = 0x5421;
37-
#[cfg(all(target_os = "linux",
38-
any(target_arch = "mips",
39-
target_arch = "mipsel",
40-
target_arch = "powerpc")))]
41-
pub const FIONBIO: libc::c_ulong = 0x667e;
42-
43-
#[cfg(any(target_os = "macos",
44-
target_os = "ios",
45-
target_os = "freebsd",
46-
target_os = "dragonfly",
47-
target_os = "bitrig",
48-
target_os = "openbsd"))]
49-
pub const FIOCLEX: libc::c_ulong = 0x20006601;
29+
mod consts {
30+
use libc;
31+
pub const FIONBIO: libc::c_ulong = 0x8004667e;
32+
pub const FIOCLEX: libc::c_ulong = 0x20006601;
33+
pub const FIONCLEX: libc::c_ulong = 0x20006602;
34+
}
5035
#[cfg(any(all(target_os = "linux",
5136
any(target_arch = "x86",
5237
target_arch = "x86_64",
5338
target_arch = "arm",
5439
target_arch = "aarch64")),
5540
target_os = "android"))]
56-
pub const FIOCLEX: libc::c_ulong = 0x5451;
41+
mod consts {
42+
use libc;
43+
pub const FIONBIO: libc::c_ulong = 0x5421;
44+
pub const FIOCLEX: libc::c_ulong = 0x5451;
45+
pub const FIONCLEX: libc::c_ulong = 0x5450;
46+
}
5747
#[cfg(all(target_os = "linux",
5848
any(target_arch = "mips",
5949
target_arch = "mipsel",
6050
target_arch = "powerpc")))]
61-
pub const FIOCLEX: libc::c_ulong = 0x6601;
51+
mod consts {
52+
use libc;
53+
pub const FIONBIO: libc::c_ulong = 0x667e;
54+
pub const FIOCLEX: libc::c_ulong = 0x6601;
55+
pub const FIONCLEX: libc::c_ulong = 0x6600;
56+
}
57+
pub use self::consts::*;
6258

6359
#[cfg(any(target_os = "macos",
6460
target_os = "ios",

src/libstd/sys/unix/fd.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use core::prelude::*;
1313
use io;
1414
use libc::{self, c_int, size_t, c_void};
1515
use mem;
16+
use sys::c;
1617
use sys::cvt;
1718
use sys_common::AsInner;
1819

@@ -51,6 +52,20 @@ impl FileDesc {
5152
}));
5253
Ok(ret as usize)
5354
}
55+
56+
pub fn set_cloexec(&self) {
57+
unsafe {
58+
let ret = c::ioctl(self.fd, c::FIOCLEX);
59+
debug_assert_eq!(ret, 0);
60+
}
61+
}
62+
63+
pub fn unset_cloexec(&self) {
64+
unsafe {
65+
let ret = c::ioctl(self.fd, c::FIONCLEX);
66+
debug_assert_eq!(ret, 0);
67+
}
68+
}
5469
}
5570

5671
impl AsInner<c_int> for FileDesc {

src/libstd/sys/unix/fs2.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,9 @@ impl File {
215215
let fd = try!(cvt_r(|| unsafe {
216216
libc::open(path.as_ptr(), flags, opts.mode)
217217
}));
218-
Ok(File(FileDesc::new(fd)))
218+
let fd = FileDesc::new(fd);
219+
fd.set_cloexec();
220+
Ok(File(fd))
219221
}
220222

221223
pub fn file_attr(&self) -> io::Result<FileAttr> {

src/libstd/sys/unix/net.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ impl Socket {
4747
};
4848
unsafe {
4949
let fd = try!(cvt(libc::socket(fam, ty, 0)));
50-
Ok(Socket(FileDesc::new(fd)))
50+
let fd = FileDesc::new(fd);
51+
fd.set_cloexec();
52+
Ok(Socket(fd))
5153
}
5254
}
5355

@@ -56,13 +58,16 @@ impl Socket {
5658
let fd = try!(cvt_r(|| unsafe {
5759
libc::accept(self.0.raw(), storage, len)
5860
}));
59-
Ok(Socket(FileDesc::new(fd)))
61+
let fd = FileDesc::new(fd);
62+
fd.set_cloexec();
63+
Ok(Socket(fd))
6064
}
6165

6266
pub fn duplicate(&self) -> io::Result<Socket> {
63-
cvt(unsafe { libc::dup(self.0.raw()) }).map(|fd| {
64-
Socket(FileDesc::new(fd))
65-
})
67+
let fd = try!(cvt(unsafe { libc::dup(self.0.raw()) }));
68+
let fd = FileDesc::new(fd);
69+
fd.set_cloexec();
70+
Ok(Socket(fd))
6671
}
6772

6873
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {

src/libstd/sys/unix/pipe2.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ pub unsafe fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> {
3232

3333
impl AnonPipe {
3434
pub fn from_fd(fd: libc::c_int) -> AnonPipe {
35-
AnonPipe(FileDesc::new(fd))
35+
let fd = FileDesc::new(fd);
36+
fd.set_cloexec();
37+
AnonPipe(fd)
3638
}
3739

3840
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {

src/test/run-pass/fds-are-cloexec.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// ignore-windows
12+
13+
#![feature(libc)]
14+
15+
extern crate libc;
16+
17+
use std::env;
18+
use std::fs::{self, File};
19+
use std::io;
20+
use std::net::{TcpListener, TcpStream, UdpSocket};
21+
use std::os::unix::prelude::*;
22+
use std::process::Command;
23+
use std::thread;
24+
25+
fn main() {
26+
let args = env::args().collect::<Vec<_>>();
27+
if args.len() == 1 {
28+
parent()
29+
} else {
30+
child(&args)
31+
}
32+
}
33+
34+
fn parent() {
35+
let file = File::open("Makefile").unwrap();
36+
let _dir = fs::read_dir("/").unwrap();
37+
let tcp1 = TcpListener::bind("127.0.0.1:0").unwrap();
38+
assert_eq!(tcp1.as_raw_fd(), file.as_raw_fd() + 2);
39+
let tcp2 = tcp1.try_clone().unwrap();
40+
let addr = tcp1.local_addr().unwrap();
41+
let t = thread::scoped(|| TcpStream::connect(addr).unwrap());
42+
let tcp3 = tcp1.accept().unwrap().0;
43+
let tcp4 = t.join();
44+
let tcp5 = tcp3.try_clone().unwrap();
45+
let tcp6 = tcp4.try_clone().unwrap();
46+
let udp1 = UdpSocket::bind("127.0.0.1:0").unwrap();
47+
let udp2 = udp1.try_clone().unwrap();
48+
49+
let status = Command::new(env::args().next().unwrap())
50+
.arg(file.as_raw_fd().to_string())
51+
.arg((file.as_raw_fd() + 1).to_string())
52+
.arg(tcp1.as_raw_fd().to_string())
53+
.arg(tcp2.as_raw_fd().to_string())
54+
.arg(tcp3.as_raw_fd().to_string())
55+
.arg(tcp4.as_raw_fd().to_string())
56+
.arg(tcp5.as_raw_fd().to_string())
57+
.arg(tcp6.as_raw_fd().to_string())
58+
.arg(udp1.as_raw_fd().to_string())
59+
.arg(udp2.as_raw_fd().to_string())
60+
.status()
61+
.unwrap();
62+
assert!(status.success());
63+
}
64+
65+
fn child(args: &[String]) {
66+
let mut b = [0u8; 2];
67+
for arg in &args[1..] {
68+
let fd: libc::c_int = arg.parse().unwrap();
69+
unsafe {
70+
assert_eq!(libc::read(fd, b.as_mut_ptr() as *mut _, 2), -1);
71+
assert_eq!(io::Error::last_os_error().raw_os_error(),
72+
Some(libc::EBADF));
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)