Skip to content

Commit c66c8e6

Browse files
authored
Rollup merge of #138896 - joboet:process_noalias, r=Noratrieb
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.
2 parents e88e854 + 89a90d6 commit c66c8e6

File tree

2 files changed

+142
-89
lines changed

2 files changed

+142
-89
lines changed

library/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: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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+
// SAFETY:
29+
// `CStringArray` owns all of its strings, and they were all transformed
30+
// into pointers using `CString::into_raw`. Also, this is not the null
31+
// pointer since the indexing above would have failed.
32+
drop(unsafe { CString::from_raw(old.cast_mut()) });
33+
}
34+
35+
/// Push an additional string to the array.
36+
pub fn push(&mut self, item: CString) {
37+
let argc = self.ptrs.len() - 1;
38+
// Replace the null pointer at the end of the array...
39+
self.ptrs[argc] = item.into_raw();
40+
// ... and recreate it to restore the data structure invariant.
41+
self.ptrs.push(ptr::null());
42+
}
43+
44+
/// Returns a pointer to the C-string array managed by this type.
45+
pub fn as_ptr(&self) -> *const *const c_char {
46+
self.ptrs.as_ptr()
47+
}
48+
49+
/// Returns an iterator over all `CStr`s contained in this array.
50+
pub fn iter(&self) -> CStringIter<'_> {
51+
CStringIter { iter: self.ptrs[..self.ptrs.len() - 1].iter() }
52+
}
53+
}
54+
55+
impl Index<usize> for CStringArray {
56+
type Output = CStr;
57+
fn index(&self, index: usize) -> &CStr {
58+
let ptr = self.ptrs[..self.ptrs.len() - 1][index];
59+
// SAFETY:
60+
// `CStringArray` owns all of its strings. Also, this is not the null
61+
// pointer since the indexing above would have failed.
62+
unsafe { CStr::from_ptr(ptr) }
63+
}
64+
}
65+
66+
impl fmt::Debug for CStringArray {
67+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68+
f.debug_list().entries(self.iter()).finish()
69+
}
70+
}
71+
72+
// SAFETY: `CStringArray` is basically just a `Vec<CString>`
73+
unsafe impl Send for CStringArray {}
74+
// SAFETY: `CStringArray` is basically just a `Vec<CString>`
75+
unsafe impl Sync for CStringArray {}
76+
77+
impl Drop for CStringArray {
78+
fn drop(&mut self) {
79+
// SAFETY:
80+
// `CStringArray` owns all of its strings, and they were all transformed
81+
// into pointers using `CString::into_raw`.
82+
self.ptrs[..self.ptrs.len() - 1]
83+
.iter()
84+
.for_each(|&p| drop(unsafe { CString::from_raw(p.cast_mut()) }))
85+
}
86+
}
87+
88+
/// An iterator over all `CStr`s contained in a `CStringArray`.
89+
#[derive(Clone)]
90+
pub struct CStringIter<'a> {
91+
iter: crate::slice::Iter<'a, *const c_char>,
92+
}
93+
94+
impl<'a> Iterator for CStringIter<'a> {
95+
type Item = &'a CStr;
96+
fn next(&mut self) -> Option<&'a CStr> {
97+
// SAFETY:
98+
// `CStringArray` owns all of its strings. Also, this is not the null
99+
// pointer since the last element is excluded when creating `iter`.
100+
self.iter.next().map(|&p| unsafe { CStr::from_ptr(p) })
101+
}
102+
103+
fn size_hint(&self) -> (usize, Option<usize>) {
104+
self.iter.size_hint()
105+
}
106+
}
107+
108+
impl<'a> ExactSizeIterator for CStringIter<'a> {
109+
fn len(&self) -> usize {
110+
self.iter.len()
111+
}
112+
fn is_empty(&self) -> bool {
113+
self.iter.is_empty()
114+
}
115+
}

0 commit comments

Comments
 (0)