Skip to content

Commit f6cedbc

Browse files
committed
Correct Windows argument handling
Previously the command line string would have been incorrectly constructed if argv[0] contained a doublequote (`"`) or ended in a trailing backslash (`\`). This is a very rare edge case because, by convention, argv[0] is the path to the application and Windows file names cannot contain doublequotes. Fixes #1881
1 parent 6cf851f commit f6cedbc

File tree

1 file changed

+75
-10
lines changed

1 file changed

+75
-10
lines changed

src/eval.rs

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use std::convert::TryFrom;
44
use std::ffi::OsStr;
5+
use std::iter;
56

67
use log::info;
78

@@ -202,17 +203,8 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
202203
// Store command line as UTF-16 for Windows `GetCommandLineW`.
203204
{
204205
// Construct a command string with all the aguments.
205-
let mut cmd = String::new();
206-
for arg in config.args.iter() {
207-
if !cmd.is_empty() {
208-
cmd.push(' ');
209-
}
210-
cmd.push_str(&*shell_escape::windows::escape(arg.as_str().into()));
211-
}
212-
// Don't forget `0` terminator.
213-
cmd.push(std::char::from_u32(0).unwrap());
206+
let cmd_utf16: Vec<u16> = args_to_utf16_command_string(config.args.iter());
214207

215-
let cmd_utf16: Vec<u16> = cmd.encode_utf16().collect();
216208
let cmd_type = tcx.mk_array(tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap());
217209
let cmd_place =
218210
ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Machine.into())?;
@@ -353,3 +345,76 @@ pub fn eval_entry<'tcx>(
353345
Err(e) => report_error(&ecx, e),
354346
}
355347
}
348+
349+
/// Turns an array of arguments into a Windows command line string.
350+
///
351+
/// The string will be UTF-16 encoded and NUL terminated.
352+
///
353+
/// Panics if the zeroth argument contains the `"` character because doublequotes
354+
/// in argv[0] cannot be encoded using the standard command line parsing rules.
355+
fn args_to_utf16_command_string<I, T>(mut args: I) -> Vec<u16>
356+
where
357+
I: Iterator<Item = T>,
358+
T: AsRef<str>,
359+
{
360+
// Parse argv[0]. Slashes aren't escaped. Literal double quotes are not allowed.
361+
let mut cmd = if let Some(arg0) = args.next() {
362+
let arg0 = arg0.as_ref();
363+
if arg0.is_empty() {
364+
"\"\"".into()
365+
} else if arg0.contains('"') {
366+
panic!("argv[0] cannot contain a doublequote (\") character");
367+
} else {
368+
// Always surround argv[0] with quotes.
369+
let mut s = String::new();
370+
s.push('"');
371+
s.push_str(arg0);
372+
s.push('"');
373+
s
374+
}
375+
} else {
376+
return vec![0];
377+
};
378+
379+
// Build the other arguments.
380+
for arg in args {
381+
let arg = arg.as_ref();
382+
cmd.push(' ');
383+
if arg.is_empty() {
384+
cmd.push_str("\"\"");
385+
} else if !arg.bytes().any(|c| matches!(c, b'"' | b'\t' | b' ')) {
386+
cmd.push_str(arg);
387+
} else {
388+
cmd.push('"');
389+
let mut chars = arg.chars().peekable();
390+
loop {
391+
let mut nslashes = 0;
392+
while let Some(&'\\') = chars.peek() {
393+
chars.next();
394+
nslashes += 1;
395+
}
396+
397+
match chars.next() {
398+
Some('"') => {
399+
cmd.extend(iter::repeat('\\').take(nslashes * 2 + 1));
400+
cmd.push('"');
401+
}
402+
Some(c) => {
403+
cmd.extend(iter::repeat('\\').take(nslashes));
404+
cmd.push(c);
405+
}
406+
None => {
407+
cmd.extend(iter::repeat('\\').take(nslashes * 2));
408+
break;
409+
}
410+
}
411+
}
412+
cmd.push('"');
413+
}
414+
}
415+
416+
if cmd.contains('\0') {
417+
panic!("interior null in command line arguments");
418+
}
419+
cmd.encode_utf16().chain(iter::once(0)).collect()
420+
}

0 commit comments

Comments
 (0)