Skip to content

Commit ac71cd2

Browse files
committed
std: fix aliasing bug in UNIX process implementation
`CStringArray` contained both `CString`s and their pointers. Unfortunately, since `CString` uses `Box`, moving the `CString`s into the `Vec` can (under stacked borrows) invalidate the pointer to the string, meaning the resulting `Vec<*const c_char>` was, from an opsem perspective, unusable. This PR removes removes the `Vec<CString>` from `CStringArray`, instead recreating the `CString`/`CStr` from the pointers when necessary. Also,`CStringArray` is now used for the process args as well, the old implementation was suffering from the same kind of bug.
1 parent 10ed7f5 commit ac71cd2

File tree

2 files changed

+129
-89
lines changed

2 files changed

+129
-89
lines changed

std/src/sys/process/unix/common.rs

Lines changed: 27 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#[cfg(all(test, not(target_os = "emscripten")))]
22
mod tests;
33

4-
use libc::{EXIT_FAILURE, EXIT_SUCCESS, c_char, c_int, gid_t, pid_t, uid_t};
4+
use libc::{EXIT_FAILURE, EXIT_SUCCESS, c_int, gid_t, pid_t, uid_t};
55

6+
pub use self::cstring_array::CStringArray;
7+
use self::cstring_array::CStringIter;
68
use crate::collections::BTreeMap;
79
use crate::ffi::{CStr, CString, OsStr, OsString};
810
use crate::os::unix::prelude::*;
@@ -14,7 +16,9 @@ use crate::sys::fs::OpenOptions;
1416
use crate::sys::pipe::{self, AnonPipe};
1517
use crate::sys::process::env::{CommandEnv, CommandEnvs};
1618
use crate::sys_common::{FromInner, IntoInner};
17-
use crate::{fmt, io, ptr};
19+
use crate::{fmt, io};
20+
21+
mod cstring_array;
1822

1923
cfg_if::cfg_if! {
2024
if #[cfg(target_os = "fuchsia")] {
@@ -77,13 +81,7 @@ cfg_if::cfg_if! {
7781

7882
pub struct Command {
7983
program: CString,
80-
args: Vec<CString>,
81-
/// Exactly what will be passed to `execvp`.
82-
///
83-
/// First element is a pointer to `program`, followed by pointers to
84-
/// `args`, followed by a `null`. Be careful when modifying `program` or
85-
/// `args` to properly update this as well.
86-
argv: Argv,
84+
args: CStringArray,
8785
env: CommandEnv,
8886

8987
program_kind: ProgramKind,
@@ -102,14 +100,6 @@ pub struct Command {
102100
pgroup: Option<pid_t>,
103101
}
104102

105-
// Create a new type for argv, so that we can make it `Send` and `Sync`
106-
struct Argv(Vec<*const c_char>);
107-
108-
// It is safe to make `Argv` `Send` and `Sync`, because it contains
109-
// pointers to memory owned by `Command.args`
110-
unsafe impl Send for Argv {}
111-
unsafe impl Sync for Argv {}
112-
113103
// passed back to std::process with the pipes connected to the child, if any
114104
// were requested
115105
pub struct StdioPipes {
@@ -171,42 +161,17 @@ impl ProgramKind {
171161
}
172162

173163
impl Command {
174-
#[cfg(not(target_os = "linux"))]
175164
pub fn new(program: &OsStr) -> Command {
176165
let mut saw_nul = false;
177166
let program_kind = ProgramKind::new(program.as_ref());
178167
let program = os2c(program, &mut saw_nul);
168+
let mut args = CStringArray::with_capacity(1);
169+
args.push(program.clone());
179170
Command {
180-
argv: Argv(vec![program.as_ptr(), ptr::null()]),
181-
args: vec![program.clone()],
182171
program,
183-
program_kind,
172+
args,
184173
env: Default::default(),
185-
cwd: None,
186-
chroot: None,
187-
uid: None,
188-
gid: None,
189-
saw_nul,
190-
closures: Vec::new(),
191-
groups: None,
192-
stdin: None,
193-
stdout: None,
194-
stderr: None,
195-
pgroup: None,
196-
}
197-
}
198-
199-
#[cfg(target_os = "linux")]
200-
pub fn new(program: &OsStr) -> Command {
201-
let mut saw_nul = false;
202-
let program_kind = ProgramKind::new(program.as_ref());
203-
let program = os2c(program, &mut saw_nul);
204-
Command {
205-
argv: Argv(vec![program.as_ptr(), ptr::null()]),
206-
args: vec![program.clone()],
207-
program,
208174
program_kind,
209-
env: Default::default(),
210175
cwd: None,
211176
chroot: None,
212177
uid: None,
@@ -217,6 +182,7 @@ impl Command {
217182
stdin: None,
218183
stdout: None,
219184
stderr: None,
185+
#[cfg(target_os = "linux")]
220186
create_pidfd: false,
221187
pgroup: None,
222188
}
@@ -225,20 +191,11 @@ impl Command {
225191
pub fn set_arg_0(&mut self, arg: &OsStr) {
226192
// Set a new arg0
227193
let arg = os2c(arg, &mut self.saw_nul);
228-
debug_assert!(self.argv.0.len() > 1);
229-
self.argv.0[0] = arg.as_ptr();
230-
self.args[0] = arg;
194+
self.args.write(0, arg);
231195
}
232196

233197
pub fn arg(&mut self, arg: &OsStr) {
234-
// Overwrite the trailing null pointer in `argv` and then add a new null
235-
// pointer.
236198
let arg = os2c(arg, &mut self.saw_nul);
237-
self.argv.0[self.args.len()] = arg.as_ptr();
238-
self.argv.0.push(ptr::null());
239-
240-
// Also make sure we keep track of the owned value to schedule a
241-
// destructor for this memory.
242199
self.args.push(arg);
243200
}
244201

@@ -295,6 +252,8 @@ impl Command {
295252

296253
pub fn get_args(&self) -> CommandArgs<'_> {
297254
let mut iter = self.args.iter();
255+
// argv[0] contains the program name, but we are only interested in the
256+
// arguments so skip it.
298257
iter.next();
299258
CommandArgs { iter }
300259
}
@@ -307,12 +266,12 @@ impl Command {
307266
self.cwd.as_ref().map(|cs| Path::new(OsStr::from_bytes(cs.as_bytes())))
308267
}
309268

310-
pub fn get_argv(&self) -> &Vec<*const c_char> {
311-
&self.argv.0
269+
pub fn get_argv(&self) -> &CStringArray {
270+
&self.args
312271
}
313272

314273
pub fn get_program_cstr(&self) -> &CStr {
315-
&*self.program
274+
&self.program
316275
}
317276

318277
#[allow(dead_code)]
@@ -405,32 +364,6 @@ fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString {
405364
})
406365
}
407366

408-
// Helper type to manage ownership of the strings within a C-style array.
409-
pub struct CStringArray {
410-
items: Vec<CString>,
411-
ptrs: Vec<*const c_char>,
412-
}
413-
414-
impl CStringArray {
415-
pub fn with_capacity(capacity: usize) -> Self {
416-
let mut result = CStringArray {
417-
items: Vec::with_capacity(capacity),
418-
ptrs: Vec::with_capacity(capacity + 1),
419-
};
420-
result.ptrs.push(ptr::null());
421-
result
422-
}
423-
pub fn push(&mut self, item: CString) {
424-
let l = self.ptrs.len();
425-
self.ptrs[l - 1] = item.as_ptr();
426-
self.ptrs.push(ptr::null());
427-
self.items.push(item);
428-
}
429-
pub fn as_ptr(&self) -> *const *const c_char {
430-
self.ptrs.as_ptr()
431-
}
432-
}
433-
434367
fn construct_envp(env: BTreeMap<OsString, OsString>, saw_nul: &mut bool) -> CStringArray {
435368
let mut result = CStringArray::with_capacity(env.len());
436369
for (mut k, v) in env {
@@ -619,14 +552,16 @@ impl fmt::Debug for Command {
619552
write!(f, "{}={value:?} ", key.to_string_lossy())?;
620553
}
621554
}
622-
if self.program != self.args[0] {
555+
556+
if *self.program != self.args[0] {
623557
write!(f, "[{:?}] ", self.program)?;
624558
}
625-
write!(f, "{:?}", self.args[0])?;
559+
write!(f, "{:?}", &self.args[0])?;
626560

627-
for arg in &self.args[1..] {
561+
for arg in self.get_args() {
628562
write!(f, " {:?}", arg)?;
629563
}
564+
630565
Ok(())
631566
}
632567
}
@@ -658,14 +593,16 @@ impl From<u8> for ExitCode {
658593
}
659594

660595
pub struct CommandArgs<'a> {
661-
iter: crate::slice::Iter<'a, CString>,
596+
iter: CStringIter<'a>,
662597
}
663598

664599
impl<'a> Iterator for CommandArgs<'a> {
665600
type Item = &'a OsStr;
601+
666602
fn next(&mut self) -> Option<&'a OsStr> {
667-
self.iter.next().map(|cs| OsStr::from_bytes(cs.as_bytes()))
603+
self.iter.next().map(|cs| OsStr::from_bytes(cs.to_bytes()))
668604
}
605+
669606
fn size_hint(&self) -> (usize, Option<usize>) {
670607
self.iter.size_hint()
671608
}
@@ -675,6 +612,7 @@ impl<'a> ExactSizeIterator for CommandArgs<'a> {
675612
fn len(&self) -> usize {
676613
self.iter.len()
677614
}
615+
678616
fn is_empty(&self) -> bool {
679617
self.iter.is_empty()
680618
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use crate::ffi::{CStr, CString, c_char};
2+
use crate::ops::Index;
3+
use crate::{fmt, mem, ptr};
4+
5+
/// Helper type to manage ownership of the strings within a C-style array.
6+
///
7+
/// This type manages an array of C-string pointers terminated by a null
8+
/// pointer. The pointer to the array (as returned by `as_ptr`) can be used as
9+
/// a value of `argv` or `environ`.
10+
pub struct CStringArray {
11+
ptrs: Vec<*const c_char>,
12+
}
13+
14+
impl CStringArray {
15+
/// Creates a new `CStringArray` with enough capacity to hold `capacity`
16+
/// strings.
17+
pub fn with_capacity(capacity: usize) -> Self {
18+
let mut result = CStringArray { ptrs: Vec::with_capacity(capacity + 1) };
19+
result.ptrs.push(ptr::null());
20+
result
21+
}
22+
23+
/// Replace the string at position `index`.
24+
pub fn write(&mut self, index: usize, item: CString) {
25+
let argc = self.ptrs.len() - 1;
26+
let ptr = &mut self.ptrs[..argc][index];
27+
let old = mem::replace(ptr, item.into_raw());
28+
drop(unsafe { CString::from_raw(old.cast_mut()) });
29+
}
30+
31+
/// Push an additional string to the array.
32+
pub fn push(&mut self, item: CString) {
33+
let argc = self.ptrs.len() - 1;
34+
// Replace the null pointer at the end of the array...
35+
self.ptrs[argc] = item.into_raw();
36+
// ... and recreate it to restore the data structure invariant.
37+
self.ptrs.push(ptr::null());
38+
}
39+
40+
/// Returns a pointer to the C-string array managed by this type.
41+
pub fn as_ptr(&self) -> *const *const c_char {
42+
self.ptrs.as_ptr()
43+
}
44+
45+
/// Returns an iterator over all `CStr`s contained in this array.
46+
pub fn iter(&self) -> CStringIter<'_> {
47+
CStringIter { iter: self.ptrs[..self.ptrs.len() - 1].iter() }
48+
}
49+
}
50+
51+
impl Index<usize> for CStringArray {
52+
type Output = CStr;
53+
fn index(&self, index: usize) -> &CStr {
54+
let ptr = self.ptrs[..self.ptrs.len() - 1][index];
55+
unsafe { CStr::from_ptr(ptr) }
56+
}
57+
}
58+
59+
impl fmt::Debug for CStringArray {
60+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61+
f.debug_list().entries(self.iter()).finish()
62+
}
63+
}
64+
65+
// SAFETY: `CStringArray` is basically just a `Vec<CString>`
66+
unsafe impl Send for CStringArray {}
67+
// SAFETY: `CStringArray` is basically just a `Vec<CString>`
68+
unsafe impl Sync for CStringArray {}
69+
70+
impl Drop for CStringArray {
71+
fn drop(&mut self) {
72+
self.ptrs[..self.ptrs.len() - 1]
73+
.iter()
74+
.for_each(|&p| drop(unsafe { CString::from_raw(p.cast_mut()) }))
75+
}
76+
}
77+
78+
/// An iterator over all `CStr`s contained in a `CStringArray`.
79+
#[derive(Clone)]
80+
pub struct CStringIter<'a> {
81+
iter: crate::slice::Iter<'a, *const c_char>,
82+
}
83+
84+
impl<'a> Iterator for CStringIter<'a> {
85+
type Item = &'a CStr;
86+
fn next(&mut self) -> Option<&'a CStr> {
87+
self.iter.next().map(|&p| unsafe { CStr::from_ptr(p) })
88+
}
89+
90+
fn size_hint(&self) -> (usize, Option<usize>) {
91+
self.iter.size_hint()
92+
}
93+
}
94+
95+
impl<'a> ExactSizeIterator for CStringIter<'a> {
96+
fn len(&self) -> usize {
97+
self.iter.len()
98+
}
99+
fn is_empty(&self) -> bool {
100+
self.iter.is_empty()
101+
}
102+
}

0 commit comments

Comments
 (0)