Skip to content

Commit dad1bde

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 aa8f0fd commit dad1bde

File tree

2 files changed

+129
-88
lines changed

2 files changed

+129
-88
lines changed

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

+27-88
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_common::process::{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,
@@ -101,14 +99,6 @@ pub struct Command {
10199
pgroup: Option<pid_t>,
102100
}
103101

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

172162
impl Command {
173-
#[cfg(not(target_os = "linux"))]
174163
pub fn new(program: &OsStr) -> Command {
175164
let mut saw_nul = false;
176165
let program_kind = ProgramKind::new(program.as_ref());
177166
let program = os2c(program, &mut saw_nul);
167+
let mut args = CStringArray::with_capacity(1);
168+
args.push(program.clone());
178169
Command {
179-
argv: Argv(vec![program.as_ptr(), ptr::null()]),
180-
args: vec![program.clone()],
181170
program,
182-
program_kind,
171+
args,
183172
env: Default::default(),
184-
cwd: None,
185-
uid: None,
186-
gid: None,
187-
saw_nul,
188-
closures: Vec::new(),
189-
groups: None,
190-
stdin: None,
191-
stdout: None,
192-
stderr: None,
193-
pgroup: None,
194-
}
195-
}
196-
197-
#[cfg(target_os = "linux")]
198-
pub fn new(program: &OsStr) -> Command {
199-
let mut saw_nul = false;
200-
let program_kind = ProgramKind::new(program.as_ref());
201-
let program = os2c(program, &mut saw_nul);
202-
Command {
203-
argv: Argv(vec![program.as_ptr(), ptr::null()]),
204-
args: vec![program.clone()],
205-
program,
206173
program_kind,
207-
env: Default::default(),
208174
cwd: None,
209175
uid: None,
210176
gid: None,
@@ -214,6 +180,7 @@ impl Command {
214180
stdin: None,
215181
stdout: None,
216182
stderr: None,
183+
#[cfg(target_os = "linux")]
217184
create_pidfd: false,
218185
pgroup: None,
219186
}
@@ -222,20 +189,11 @@ impl Command {
222189
pub fn set_arg_0(&mut self, arg: &OsStr) {
223190
// Set a new arg0
224191
let arg = os2c(arg, &mut self.saw_nul);
225-
debug_assert!(self.argv.0.len() > 1);
226-
self.argv.0[0] = arg.as_ptr();
227-
self.args[0] = arg;
192+
self.args.write(0, arg);
228193
}
229194

230195
pub fn arg(&mut self, arg: &OsStr) {
231-
// Overwrite the trailing null pointer in `argv` and then add a new null
232-
// pointer.
233196
let arg = os2c(arg, &mut self.saw_nul);
234-
self.argv.0[self.args.len()] = arg.as_ptr();
235-
self.argv.0.push(ptr::null());
236-
237-
// Also make sure we keep track of the owned value to schedule a
238-
// destructor for this memory.
239197
self.args.push(arg);
240198
}
241199

@@ -286,6 +244,8 @@ impl Command {
286244

287245
pub fn get_args(&self) -> CommandArgs<'_> {
288246
let mut iter = self.args.iter();
247+
// argv[0] contains the program name, but we are only interested in the
248+
// arguments so skip it.
289249
iter.next();
290250
CommandArgs { iter }
291251
}
@@ -298,12 +258,12 @@ impl Command {
298258
self.cwd.as_ref().map(|cs| Path::new(OsStr::from_bytes(cs.as_bytes())))
299259
}
300260

301-
pub fn get_argv(&self) -> &Vec<*const c_char> {
302-
&self.argv.0
261+
pub fn get_argv(&self) -> &CStringArray {
262+
&self.args
303263
}
304264

305265
pub fn get_program_cstr(&self) -> &CStr {
306-
&*self.program
266+
&self.program
307267
}
308268

309269
#[allow(dead_code)]
@@ -392,32 +352,6 @@ fn os2c(s: &OsStr, saw_nul: &mut bool) -> CString {
392352
})
393353
}
394354

395-
// Helper type to manage ownership of the strings within a C-style array.
396-
pub struct CStringArray {
397-
items: Vec<CString>,
398-
ptrs: Vec<*const c_char>,
399-
}
400-
401-
impl CStringArray {
402-
pub fn with_capacity(capacity: usize) -> Self {
403-
let mut result = CStringArray {
404-
items: Vec::with_capacity(capacity),
405-
ptrs: Vec::with_capacity(capacity + 1),
406-
};
407-
result.ptrs.push(ptr::null());
408-
result
409-
}
410-
pub fn push(&mut self, item: CString) {
411-
let l = self.ptrs.len();
412-
self.ptrs[l - 1] = item.as_ptr();
413-
self.ptrs.push(ptr::null());
414-
self.items.push(item);
415-
}
416-
pub fn as_ptr(&self) -> *const *const c_char {
417-
self.ptrs.as_ptr()
418-
}
419-
}
420-
421355
fn construct_envp(env: BTreeMap<OsString, OsString>, saw_nul: &mut bool) -> CStringArray {
422356
let mut result = CStringArray::with_capacity(env.len());
423357
for (mut k, v) in env {
@@ -606,14 +540,16 @@ impl fmt::Debug for Command {
606540
write!(f, "{}={value:?} ", key.to_string_lossy())?;
607541
}
608542
}
609-
if self.program != self.args[0] {
543+
544+
if *self.program != self.args[0] {
610545
write!(f, "[{:?}] ", self.program)?;
611546
}
612-
write!(f, "{:?}", self.args[0])?;
547+
write!(f, "{:?}", &self.args[0])?;
613548

614-
for arg in &self.args[1..] {
549+
for arg in self.get_args() {
615550
write!(f, " {:?}", arg)?;
616551
}
552+
617553
Ok(())
618554
}
619555
}
@@ -645,14 +581,16 @@ impl From<u8> for ExitCode {
645581
}
646582

647583
pub struct CommandArgs<'a> {
648-
iter: crate::slice::Iter<'a, CString>,
584+
iter: CStringIter<'a>,
649585
}
650586

651587
impl<'a> Iterator for CommandArgs<'a> {
652588
type Item = &'a OsStr;
589+
653590
fn next(&mut self) -> Option<&'a OsStr> {
654-
self.iter.next().map(|cs| OsStr::from_bytes(cs.as_bytes()))
591+
self.iter.next().map(|cs| OsStr::from_bytes(cs.to_bytes()))
655592
}
593+
656594
fn size_hint(&self) -> (usize, Option<usize>) {
657595
self.iter.size_hint()
658596
}
@@ -662,6 +600,7 @@ impl<'a> ExactSizeIterator for CommandArgs<'a> {
662600
fn len(&self) -> usize {
663601
self.iter.len()
664602
}
603+
665604
fn is_empty(&self) -> bool {
666605
self.iter.is_empty()
667606
}
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)