Skip to content

Commit 191e319

Browse files
committed
[WIP] size limit for command line
1 parent 095829a commit 191e319

File tree

2 files changed

+148
-56
lines changed

2 files changed

+148
-56
lines changed

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

+46-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::sys_common::process::CommandEnv;
1313
#[cfg(not(target_os = "fuchsia"))]
1414
use crate::sys::fs::OpenOptions;
1515

16-
use libc::{c_char, c_int, gid_t, uid_t, EXIT_FAILURE, EXIT_SUCCESS};
16+
use libc::{c_char, c_int, gid_t, strlen, uid_t, EXIT_FAILURE, EXIT_SUCCESS};
1717

1818
cfg_if::cfg_if! {
1919
if #[cfg(target_os = "fuchsia")] {
@@ -75,6 +75,7 @@ pub struct Command {
7575
args: Vec<CString>,
7676
argv: Argv,
7777
env: CommandEnv,
78+
arg_max: Option<isize>,
7879

7980
cwd: Option<CString>,
8081
uid: Option<uid_t>,
@@ -137,6 +138,7 @@ impl Command {
137138
args: vec![program.clone()],
138139
program,
139140
env: Default::default(),
141+
arg_max: Default::default(),
140142
cwd: None,
141143
uid: None,
142144
gid: None,
@@ -202,6 +204,49 @@ impl Command {
202204
self.gid
203205
}
204206

207+
pub fn get_size(&mut self) -> io::Result<usize> {
208+
use crate::mem;
209+
let argv = self.argv.0;
210+
let argv_size: usize = argv.iter().map(|x| unsafe { strlen(*x) + 1 }).sum::<usize>()
211+
+ (argv.len() + 1) * mem::size_of::<*const u8>();
212+
213+
// Envp size calculation is approximate.
214+
let env = self.env.capture();
215+
let env_size: usize = env
216+
.iter()
217+
.map(|(k, v)| unsafe {
218+
os2c(k.as_ref(), &mut self.saw_nul).to_bytes().len()
219+
+ os2c(v.as_ref(), &mut self.saw_nul).to_bytes().len()
220+
+ 2
221+
})
222+
.sum::<usize>()
223+
+ (env.len() + 1) * mem::size_of::<*const u8>();
224+
225+
Ok(argv_size + env_size)
226+
}
227+
228+
pub fn check_size(&mut self, refresh: bool) -> io::Result<bool> {
229+
use crate::sys;
230+
use core::convert::TryInto;
231+
if refresh || self.arg_max.is_none() {
232+
let (limit, errno) = unsafe {
233+
let old_errno = sys::os::errno();
234+
sys::os::set_errno(0);
235+
let limit = libc::sysconf(libc::_SC_ARG_MAX);
236+
let errno = sys::os::errno();
237+
sys::os::set_errno(old_errno);
238+
(limit, errno)
239+
};
240+
241+
if errno != 0 {
242+
return Err(io::Error::from_raw_os_error(errno));
243+
} else {
244+
self.arg_max = limit.try_into().ok();
245+
}
246+
}
247+
Ok(self.arg_max.unwrap() < 0 || self.get_size()? < (self.arg_max.unwrap() as usize))
248+
}
249+
205250
pub fn get_closures(&mut self) -> &mut Vec<Box<dyn FnMut() -> io::Result<()> + Send + Sync>> {
206251
&mut self.closures
207252
}

library/std/src/sys/windows/process.rs

+102-55
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ fn ensure_no_nuls<T: AsRef<OsStr>>(str: T) -> io::Result<T> {
6565
}
6666
}
6767

68+
// 32768 minus NUL plus starting space in our implementation
69+
const CMDLINE_MAX: usize = 32768;
6870
pub struct Command {
6971
program: OsString,
7072
args: Vec<OsString>,
@@ -75,6 +77,8 @@ pub struct Command {
7577
stdin: Option<Stdio>,
7678
stdout: Option<Stdio>,
7779
stderr: Option<Stdio>,
80+
cmdline: Vec<u16>,
81+
cmdline_error: Option<io::Error>,
7882
}
7983

8084
pub enum Stdio {
@@ -106,11 +110,32 @@ impl Command {
106110
stdin: None,
107111
stdout: None,
108112
stderr: None,
113+
cmdline: Vec::new(),
114+
cmdline_error: None,
109115
}
110116
}
111117

118+
pub fn maybe_arg(&mut self, arg: &OsStr) -> io::Result<()> {
119+
self.args.push(arg.to_os_string());
120+
self.cmdline.push(' ' as u16);
121+
let result = append_arg(&mut cmd, arg, false);
122+
if result.is_err() {
123+
self.cmdline.truncate(self.cmdline.len() - 1);
124+
result
125+
} else if self.cmdline.size() >= CMDLINE_MAX {
126+
// Roll back oversized
127+
self.cmdline.truncate(self.cmdline.len() - 1 - result.unwrap());
128+
Err(io::Error::new(ErrorKind::InvalidInput, "oversized cmdline"))
129+
}
130+
Ok()
131+
}
112132
pub fn arg(&mut self, arg: &OsStr) {
113-
self.args.push(arg.to_os_string())
133+
if self.cmdline_error.is_none() {
134+
let result = self.maybe_arg(self, arg);
135+
if result.is_err() {
136+
self.cmdline_error = Some(result.expect_err());
137+
}
138+
}
114139
}
115140
pub fn env_mut(&mut self) -> &mut CommandEnv {
116141
&mut self.env
@@ -136,34 +161,45 @@ impl Command {
136161
default: Stdio,
137162
needs_stdin: bool,
138163
) -> io::Result<(Process, StdioPipes)> {
164+
if self.cmdline_error.is_some() {
165+
return self.cmdline_error.unwrap();
166+
}
167+
139168
let maybe_env = self.env.capture_if_changed();
140169
// To have the spawning semantics of unix/windows stay the same, we need
141170
// to read the *child's* PATH if one is provided. See #15149 for more
142171
// details.
143-
let program = maybe_env.as_ref().and_then(|env| {
144-
if let Some(v) = env.get(OsStr::new("PATH")) {
145-
// Split the value and test each path to see if the
146-
// program exists.
147-
for path in split_paths(&v) {
148-
let path = path
149-
.join(self.program.to_str().unwrap())
150-
.with_extension(env::consts::EXE_EXTENSION);
151-
if fs::metadata(&path).is_ok() {
152-
return Some(path.into_os_string());
172+
let program = maybe_env
173+
.as_ref()
174+
.and_then(|env| {
175+
if let Some(v) = env.get(OsStr::new("PATH")) {
176+
// Split the value and test each path to see if the
177+
// program exists.
178+
for path in split_paths(&v) {
179+
let path = path
180+
.join(self.program.to_str().unwrap())
181+
.with_extension(env::consts::EXE_EXTENSION);
182+
if fs::metadata(&path).is_ok() {
183+
return Some(path.into_os_string());
184+
}
153185
}
154186
}
155-
}
156-
None
157-
});
187+
None
188+
})
189+
.as_ref()
190+
.unwrap_or(&self.program);
191+
192+
// Prepare and terminate the application name and the cmdline
193+
// XXX: this won't work for 16-bit, might be preferable to do a extend_from_slice
194+
let program_str: Vec<u16> = Vec::new();
195+
append_arg(&mut program_str, program, true)?;
196+
program_str.push(0);
197+
self.cmdline.push(0);
158198

159199
let mut si = zeroed_startupinfo();
160200
si.cb = mem::size_of::<c::STARTUPINFO>() as c::DWORD;
161201
si.dwFlags = c::STARTF_USESTDHANDLES;
162202

163-
let program = program.as_ref().unwrap_or(&self.program);
164-
let mut cmd_str = make_command_line(program, &self.args)?;
165-
cmd_str.push(0); // add null terminator
166-
167203
// stolen from the libuv code.
168204
let mut flags = self.flags | c::CREATE_UNICODE_ENVIRONMENT;
169205
if self.detach {
@@ -201,8 +237,8 @@ impl Command {
201237

202238
unsafe {
203239
cvt(c::CreateProcessW(
204-
ptr::null(),
205-
cmd_str.as_mut_ptr(),
240+
program_str.as_mut_ptr(),
241+
self.cmdline.as_mut_ptr().offset(1), // Skip the starting space
206242
ptr::null_mut(),
207243
ptr::null_mut(),
208244
c::TRUE,
@@ -221,6 +257,14 @@ impl Command {
221257

222258
Ok((Process { handle: Handle::new(pi.hProcess) }, pipes))
223259
}
260+
261+
pub fn get_size(&mut self) -> io::Result<usize> {
262+
let (_, cmd_str) = self.prepare_command_line()?;
263+
Ok(cmd_str.len())
264+
}
265+
pub fn check_size(&mut self, _refresh: bool) -> io::Result<bool> {
266+
Ok(self.get_size()? < 32767)
267+
}
224268
}
225269

226270
impl fmt::Debug for Command {
@@ -445,6 +489,44 @@ fn zeroed_process_information() -> c::PROCESS_INFORMATION {
445489
}
446490
}
447491

492+
fn append_arg(cmd: &mut Vec<u16>, arg: &OsStr, force_quotes: bool) -> io::Result<usize> {
493+
let mut addsize: usize = 0;
494+
// If an argument has 0 characters then we need to quote it to ensure
495+
// that it actually gets passed through on the command line or otherwise
496+
// it will be dropped entirely when parsed on the other end.
497+
ensure_no_nuls(arg)?;
498+
let arg_bytes = &arg.as_inner().inner.as_inner();
499+
let quote =
500+
force_quotes || arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t') || arg_bytes.is_empty();
501+
if quote {
502+
cmd.push('"' as u16);
503+
addsize += 1;
504+
}
505+
506+
let mut backslashes: usize = 0;
507+
for x in arg.encode_wide() {
508+
if x == '\\' as u16 {
509+
backslashes += 1;
510+
} else {
511+
if x == '"' as u16 {
512+
// Add n+1 backslashes to total 2n+1 before internal '"'.
513+
cmd.extend((0..=backslashes).map(|_| '\\' as u16));
514+
addsize += backslashes + 1;
515+
}
516+
backslashes = 0;
517+
}
518+
cmd.push(x);
519+
}
520+
521+
if quote {
522+
// Add n backslashes to total 2n before ending '"'.
523+
cmd.extend((0..backslashes).map(|_| '\\' as u16));
524+
cmd.push('"' as u16);
525+
addsize += backslashes + 1;
526+
}
527+
Ok(addsize)
528+
}
529+
448530
// Produces a wide string *without terminating null*; returns an error if
449531
// `prog` or any of the `args` contain a nul.
450532
fn make_command_line(prog: &OsStr, args: &[OsString]) -> io::Result<Vec<u16>> {
@@ -459,41 +541,6 @@ fn make_command_line(prog: &OsStr, args: &[OsString]) -> io::Result<Vec<u16>> {
459541
append_arg(&mut cmd, arg, false)?;
460542
}
461543
return Ok(cmd);
462-
463-
fn append_arg(cmd: &mut Vec<u16>, arg: &OsStr, force_quotes: bool) -> io::Result<()> {
464-
// If an argument has 0 characters then we need to quote it to ensure
465-
// that it actually gets passed through on the command line or otherwise
466-
// it will be dropped entirely when parsed on the other end.
467-
ensure_no_nuls(arg)?;
468-
let arg_bytes = &arg.as_inner().inner.as_inner();
469-
let quote = force_quotes
470-
|| arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t')
471-
|| arg_bytes.is_empty();
472-
if quote {
473-
cmd.push('"' as u16);
474-
}
475-
476-
let mut backslashes: usize = 0;
477-
for x in arg.encode_wide() {
478-
if x == '\\' as u16 {
479-
backslashes += 1;
480-
} else {
481-
if x == '"' as u16 {
482-
// Add n+1 backslashes to total 2n+1 before internal '"'.
483-
cmd.extend((0..=backslashes).map(|_| '\\' as u16));
484-
}
485-
backslashes = 0;
486-
}
487-
cmd.push(x);
488-
}
489-
490-
if quote {
491-
// Add n backslashes to total 2n before ending '"'.
492-
cmd.extend((0..backslashes).map(|_| '\\' as u16));
493-
cmd.push('"' as u16);
494-
}
495-
Ok(())
496-
}
497544
}
498545

499546
fn make_envp(maybe_env: Option<BTreeMap<EnvKey, OsString>>) -> io::Result<(*mut c_void, Vec<u16>)> {

0 commit comments

Comments
 (0)