工具的 probe FILE TOKEN 先验证合成输入,再用当前 $^X 启动固定的 Perl 子脚本,传入一个 1–24 字节可打印 ASCII token。调用 probe demo.tsv 'alpha;echo BAD' 时,子进程得到的是完整 $ARGV[0];实际 STDOUT arg=alpha;echo BAD\n,STDERR probe note\n,退出 0。没有第二个 echo 命令执行。这个断言验证此处的参数列表调用,不等于证明任意字符串拼装的 shell 命令安全。

1
2
open3(undef, $child_stdout, $child_stderr,
$program, '-e', $child_code, '--', $token);

-- 将 token 留在 Perl 脚本的参数区;CLI 不接受用户指定 $program 或 $child_code,这两个值分别是 $^X 与固定源码。反例输入含实际换行,CLI 在创建子进程前以 probe requires one ASCII token of at most 24 bytes\n 拒绝(退出 2,STDOUT 空)。限定 token 不仅是输入控制,也使本实验的输出和诊断保持很短:当前 run_probe 顺序读取 stdout 再读 stderr,若随意换成会写大量双向输出的外部程序,管道可能填满并相互等待;不能把它介绍成通用进程执行器。

练习:若把 alpha;echo BAD 改成 x y,空格会分裂成两个参数吗?在此参数列表调用中不会;本机三通道正反例见 examples/perl-language/evidence/26-31.md,参考 Perl 5.38.2 pod/perlipc.pod 与核心模块 IPC::Open3 1.22 的文档。

程序路径、参数和子进程结果

参数列表调用有三个独立对象:可执行文件路径、传给解释器的选项、传给脚本的数据。把它们合成一条 shell 字符串,会让空格、分号和重定向符参与另一层解析。仅将 token 加引号也不足以证明所有拼接安全,因为程序路径和其它参数同样可能包含特殊字符。

受控实验将程序固定为当前 Perl,把代码固定为源码中的短脚本。含空格的 token 作为一个参数传入,分号按普通字节保留。t/26-31-objects-and-process.t 同时测试不存在的解释器路径:这属于启动失败,不会获得一个已经正常启动的子进程退出 7。后者使用固定 fail token,子进程先写 child failed 到 STDERR 再退出,父进程能同时记录 exit、signal 和两条输出通道。

waitpid 成功以后立即读取 $?。先执行其它可能更新 $? 的操作再拆位,会把状态来源弄错。本课程保存 exit = $? >> 8、signal = $? & 127;信号终止不是应用 exit 15,外部 Python 观测 SIGTERM 的 returncode 为 -15。不同 API 的表示法须按各自文档解释,不能只比较十进制数是否相等。

当前 Runner 保留了明确限制:输出由固定短脚本生成,stdout 与 stderr 都很短,顺序读取才适用于这个实验。将它扩展为不受控进程时,需要同时排空两个通道、限制累计输出并设计超时和进程回收,不能只扩大 token 长度。新的 replay 使用临时文件承接受控子进程输出,避免累计 TAP 原文填满管道。

[PATTERN] 对外部程序使用明确 argv,先验证启动,再等待并记录完整状态,最后才解释 stdout。成功的文本不能覆盖非零状态,空 stdout 也不能自动判失败。

练习:子进程 stdout 空、stderr 非空、exit 7,父进程能否当成合法空查询?不能。查询的空结果是本工具业务接口的成功值;子进程已经给出失败状态,须按进程故障处理。含空格路径和元字符的测试只能证明实际参数边界,不能证明任意 shell 字符串拼装。

来源:Perl 5.38.2 IPC::Open3、perlipc、waitpid。