mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-10 01:51:32 -10:00
fix(execpolicy): keep cmd apostrophes from hiding later commands
cmd.exe treats apostrophes as literal characters, so its /C or /K payload can execute an ampersand-separated command inside apostrophes. The shared POSIX-oriented scanner previously grouped that text and missed the later invocation. Normalize literal apostrophes only in the explicit cmd payload before the existing bounded nested scan. Keep ordinary quoted arguments and other shell payloads on their existing path. Extend the existing wrapper case table; no new test function or code comment. Refs #6963. Follow-up review on contributor PR #6929, retained as its own PR. macOS: CODEWHALE_DEV_NEXTEST=0 CARGO_BUILD_JOBS=1 scripts/dev-test.sh execpolicy command_safety test result: ok. 89 passed; 0 failed; 0 ignored; 0 measured; 147 filtered out; finished in 0.01s Focused all-target Clippy, fmt and git diff --check passed. The first fmt check requested only match-arm formatting, applied before commit. Reused unchanged web/npm gate: 1312 passed; 0 failed; 7 skipped; check:web passed. Windows 11 ARM64: a harmless cmd /D /S /C echo probe emitted the second-stage marker between literal apostrophes. This proves shell semantics only; the new Rust classifier has not yet run on Windows. Hosted Windows CI is required. The prior PR head's Windows job passed 18299 tests, including owner_crash_reentry; that receipt does not qualify this changed head. No native-app, package or Windows 10 1607 acceptance is claimed.
This commit is contained in:
1 parent
0b5042a443
commit
454b607bdf
1 file changed
+15
-11
@@ -2092,26 +2092,29 @@ pub fn is_literal_rm_invocation(command: &str) -> bool {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Whether `words[index]` is the script body a shell was asked to run — the
|
||||
/// Extract the script body a shell was asked to run — the
|
||||
/// argument after `sh -c`, `bash -lc`, and friends.
|
||||
///
|
||||
/// Only that position is a nested command line. An ordinary quoted argument
|
||||
/// that merely contains an operator (`echo 'x & rm -rf /etc'`) is data, and
|
||||
/// re-parsing it invents commands the shell never runs.
|
||||
fn is_shell_script_argument(words: &[String], index: usize) -> bool {
|
||||
words[..index].iter().enumerate().any(|(start, word)| {
|
||||
fn nested_shell_script(words: &[String], index: usize) -> Option<String> {
|
||||
words[..index].iter().enumerate().find_map(|(start, word)| {
|
||||
let name = command_word(word);
|
||||
if SHELL_WRAPPERS.contains(&name.as_str()) {
|
||||
return shell_command_payload(&words[start + 1..])
|
||||
.is_some_and(|payload| std::ptr::eq(payload, &words[index]));
|
||||
.filter(|payload| std::ptr::eq(*payload, &words[index]))
|
||||
.cloned();
|
||||
}
|
||||
let flag = words[index - 1].to_ascii_lowercase();
|
||||
match name.as_str() {
|
||||
"cmd" => matches!(flag.as_str(), "/c" | "/k"),
|
||||
"powershell" | "pwsh" => {
|
||||
flag == "-c" || flag == "-command" || flag == "-commandwithargs"
|
||||
"cmd" if matches!(flag.as_str(), "/c" | "/k") => Some(words[index].replace('\'', "")),
|
||||
"powershell" | "pwsh"
|
||||
if flag == "-c" || flag == "-command" || flag == "-commandwithargs" =>
|
||||
{
|
||||
Some(words[index].clone())
|
||||
}
|
||||
_ => false,
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2143,9 +2146,9 @@ pub fn command_invocations(command: &str) -> Option<Vec<Vec<String>>> {
|
||||
// is an argument — `echo 'x & rm -rf /etc'` names no `rm`, and
|
||||
// re-parsing it both invented one and failed readable commands
|
||||
// closed once a `&` made the word reproduce itself.
|
||||
if is_shell_script_argument(&words, index)
|
||||
&& word.contains(|ch: char| ch.is_whitespace() || matches!(ch, ';' | '&' | '|'))
|
||||
&& !collect(word, depth + 1, out)
|
||||
if word.contains(|ch: char| ch.is_whitespace() || matches!(ch, ';' | '&' | '|'))
|
||||
&& let Some(script) = nested_shell_script(&words, index)
|
||||
&& !collect(&script, depth + 1, out)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -2984,6 +2987,7 @@ mod tests {
|
||||
"bash -c -e 'rm -rf /etc'",
|
||||
"sh -c 'true&rm -rf /etc'",
|
||||
"cmd /C \"echo x&rm -rf /etc\"",
|
||||
"cmd /C \"echo 'x & rm -rf /etc & echo '\"",
|
||||
"powershell -Command 'echo x; rm -rf /etc'",
|
||||
"\\rm -rf /etc",
|
||||
"/usr/bin/rm -rf /etc",
|
||||
|
||||
Reference in new issue
Block a user