“启动一个外部命令”最容易写出安全与卡死的问题。这篇用真实的子进程量出:拼接字符串的注入后果、管道缓冲到底多大、环境变量与 PATH 的真实行为,以及各种启动失败的信息有多容易混淆。
拼接进 sh -c 的字符串:输出 hi;标记文件 PWNED_shell 被创建 = true
作为单独的参数传给 echo:输出 hi; touch <data>/PWNED_args;标记文件 PWNED_args 被创建 = false
必须用 shell 时,把输入放在位置参数里(sh -c 'echo "$1"' sh <输入>):输出 hi; touch <data>/PWNED_args2;标记文件 PWNED_args2 被创建 = false
Runtime.exec(String) 按空白切分、不识别引号:echo 'a b' "c d" 的输出是 'a b' "c d"
ProcessBuilder("echo", "a b", "c d") 的输出是 a b c d
echo * 不经过 shell 时不展开:*;经过 sh -c 时被展开成工作目录里的文件名:one.txt PWNED_shell子进程向 stdout 写 1000 字节、Java 不读:1.5 s 内正常退出 子进程向 stdout 写 16384 字节、Java 不读:1.5 s 内正常退出 子进程向 stdout 写 20000 字节、Java 不读:1.5 s 内正常退出 子进程向 stdout 写 65536 字节、Java 不读:1.5 s 内正常退出 子进程向 stdout 写 70000 字节、Java 不读:1.5 s 后仍然没退出(写满管道被阻塞) 子进程向 stdout 写 1000000 字节、Java 不读:1.5 s 后仍然没退出(写满管道被阻塞) redirectOutput(DISCARD):写 1,000,000 字节,退出 = true redirectOutput(文件):退出 = true,文件大小 1000000 只读 stdout、子进程大量写 stderr(1,000,000 字节):1.5 s 后仍未退出——stderr 管道写满 redirectErrorStream(true) 并读完合并后的流:读到 1000005 字节,退出码 0 redirectErrorStream(true) 的合并结果:out1 | err1 | out2 | err2
子进程默认继承环境:printenv PATH 的前 30 个字符 = /opt/homebrew/opt/openjdk@21/b(与 Java 进程相同:true) 子进程环境的 PATH 里把假的 uname 所在目录放在最前面:pb.start() 运行的是 Darwin(可执行文件的查找用的是 Java 进程自己的 PATH);而用 sh -c uname(由子进程里的 shell 按子进程的 PATH 查找)运行的是 FAKE-UNAME environment().clear() 之后 printenv 输出 0 个字符、退出码 0(即使子进程环境里没有 PATH,start() 也找到了 printenv) environment().put / remove:MY=1 HOME=(未设置)
命令不存在:IOException:Cannot run program "no-such-command-xyz": Exec failed, error: 2 (No such file or directory) 命令存在但工作目录不存在:IOException:Cannot run program "echo" (in directory "<data>/missing-dir"): Exec failed, error: 2 (No such file or directory) 文件存在但没有执行权限:IOException:Cannot run program "<data>/noexec.sh": Exec failed, error: 13 (Permission denied) 命令是一个目录:IOException:Cannot run program "<data>": Exec failed, error: 13 (Permission denied) 参数里含 NUL 字符:IOException:invalid null character in command 空命令字符串:IOException:Cannot run program "": Exec failed, error: 2 (No such file or directory) 命令列表为空:ArrayIndexOutOfBoundsException:Index 0 out of bounds for length 0 3 MB 的参数:IOException:Cannot run program "echo": Exec failed, error: 7 (Argument list too long) 退出码:sh -c 'exit 3' 的 waitFor = 3