Skip to content

Commit 358fe9f

Browse files
authored
refactor: redefine ForkptyResult to avoid uninited master field (#2315)
* refactor: redefine ForkptyResult to avoid uninited master field * docs: safety
1 parent a97cc87 commit 358fe9f

File tree

3 files changed

+55
-28
lines changed

3 files changed

+55
-28
lines changed

changelog/2315.changed.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Change the `ForkptyResult` type to the following repr so that the uninitialized
2+
`master` field won't be accessed in the child process:
3+
4+
```rs
5+
pub enum ForkptyResult {
6+
Parent {
7+
child: Pid,
8+
master: OwnedFd,
9+
},
10+
Child,
11+
}
12+
```

src/pty.rs

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ use std::os::unix::prelude::*;
1212
use crate::errno::Errno;
1313
#[cfg(not(target_os = "aix"))]
1414
use crate::sys::termios::Termios;
15-
#[cfg(feature = "process")]
16-
use crate::unistd::ForkResult;
1715
#[cfg(all(feature = "process", not(target_os = "aix")))]
1816
use crate::unistd::Pid;
1917
use crate::{fcntl, unistd, Result};
@@ -31,15 +29,19 @@ pub struct OpenptyResult {
3129

3230
feature! {
3331
#![feature = "process"]
34-
/// Representation of a master with a forked pty
35-
///
36-
/// This is returned by [`forkpty`].
32+
/// A successful result of [`forkpty()`].
3733
#[derive(Debug)]
38-
pub struct ForkptyResult {
39-
/// The master port in a virtual pty pair
40-
pub master: OwnedFd,
41-
/// Metadata about forked process
42-
pub fork_result: ForkResult,
34+
pub enum ForkptyResult {
35+
/// This is the parent process of the underlying fork.
36+
Parent {
37+
/// The PID of the fork's child process
38+
child: Pid,
39+
/// A file descriptor referring to master side of the pseudoterminal of
40+
/// the child process.
41+
master: OwnedFd,
42+
},
43+
/// This is the child process of the underlying fork.
44+
Child,
4345
}
4446
}
4547

@@ -300,9 +302,7 @@ pub fn openpty<
300302

301303
feature! {
302304
#![feature = "process"]
303-
/// Create a new pseudoterminal, returning the master file descriptor and forked pid.
304-
/// in `ForkptyResult`
305-
/// (see [`forkpty`](https://man7.org/linux/man-pages/man3/forkpty.3.html)).
305+
/// Create a new process operating in a pseudoterminal.
306306
///
307307
/// If `winsize` is not `None`, the window size of the slave will be set to
308308
/// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's
@@ -319,6 +319,11 @@ feature! {
319319
/// special care must be taken to only invoke code you can control and audit.
320320
///
321321
/// [async-signal-safe]: https://man7.org/linux/man-pages/man7/signal-safety.7.html
322+
///
323+
/// # Reference
324+
///
325+
/// * [FreeBSD](https://man.freebsd.org/cgi/man.cgi?query=forkpty)
326+
/// * [Linux](https://man7.org/linux/man-pages/man3/forkpty.3.html)
322327
#[cfg(not(target_os = "aix"))]
323328
pub unsafe fn forkpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(
324329
winsize: T,
@@ -343,14 +348,23 @@ pub unsafe fn forkpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b T
343348

344349
let res = unsafe { libc::forkpty(master.as_mut_ptr(), ptr::null_mut(), term, win) };
345350

346-
let fork_result = Errno::result(res).map(|res| match res {
347-
0 => ForkResult::Child,
348-
res => ForkResult::Parent { child: Pid::from_raw(res) },
349-
})?;
351+
let success_ret = Errno::result(res)?;
352+
let forkpty_result = match success_ret {
353+
// In the child process
354+
0 => ForkptyResult::Child,
355+
// In the parent process
356+
child_pid => {
357+
// SAFETY:
358+
// 1. The master buffer is guaranteed to be initialized in the parent process
359+
// 2. OwnedFd::from_raw_fd won't panic as the fd is a valid file descriptor
360+
let master = unsafe { OwnedFd::from_raw_fd( master.assume_init() ) };
361+
ForkptyResult::Parent {
362+
master,
363+
child: Pid::from_raw(child_pid),
364+
}
365+
}
366+
};
350367

351-
Ok(ForkptyResult {
352-
master: unsafe { OwnedFd::from_raw_fd( master.assume_init() ) },
353-
fork_result,
354-
})
368+
Ok(forkpty_result)
355369
}
356370
}

test/test_pty.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use nix::fcntl::{open, OFlag};
88
use nix::pty::*;
99
use nix::sys::stat;
1010
use nix::sys::termios::*;
11+
use nix::sys::wait::WaitStatus;
1112
use nix::unistd::{pause, write};
1213

1314
/// Test equivalence of `ptsname` and `ptsname_r`
@@ -247,29 +248,29 @@ fn test_openpty_with_termios() {
247248
fn test_forkpty() {
248249
use nix::sys::signal::*;
249250
use nix::sys::wait::wait;
250-
use nix::unistd::ForkResult::*;
251251
// forkpty calls openpty which uses ptname(3) internally.
252252
let _m0 = crate::PTSNAME_MTX.lock();
253253
// forkpty spawns a child process
254254
let _m1 = crate::FORK_MTX.lock();
255255

256256
let string = "naninani\n";
257257
let echoed_string = "naninani\r\n";
258-
let pty = unsafe { forkpty(None, None).unwrap() };
259-
match pty.fork_result {
260-
Child => {
258+
let res = unsafe { forkpty(None, None).unwrap() };
259+
match res {
260+
ForkptyResult::Child => {
261261
write(stdout(), string.as_bytes()).unwrap();
262262
pause(); // we need the child to stay alive until the parent calls read
263263
unsafe {
264264
_exit(0);
265265
}
266266
}
267-
Parent { child } => {
267+
ForkptyResult::Parent { child, master } => {
268268
let mut buf = [0u8; 10];
269269
assert!(child.as_raw() > 0);
270-
crate::read_exact(&pty.master, &mut buf);
270+
crate::read_exact(&master, &mut buf);
271271
kill(child, SIGTERM).unwrap();
272-
wait().unwrap(); // keep other tests using generic wait from getting our child
272+
let status = wait().unwrap(); // keep other tests using generic wait from getting our child
273+
assert_eq!(status, WaitStatus::Signaled(child, SIGTERM, false));
273274
assert_eq!(&buf, echoed_string.as_bytes());
274275
}
275276
}

0 commit comments

Comments
 (0)