jq 1.8.2 一次修了 16 个 CVE:把不可信 JSON 喂给 jq 的代价
jq 在流水线里的实际位置
curl ... | jq、日志按行切片、校验用户上传的 JSON,这些用法中 jq 解析的往往是外部输入。这类组件的缺陷不只表现为"输出错了",还可能造成崩溃、越界读写和拒绝服务,而且发生在 set -e 的脚本中间时,失败点很难定位。
2026-06-20 发布的 jq 1.8.2 一次性修了 16 个 CVE,外加两个 GHSA 编号问题,还有一批未编号的加固。下面按触发方式分组,同类问题合并说明。
栈溢出与递归深度
共同点:数据结构的深度由输入决定,而递归实现没有深度上限。
- CVE-2026-33947:给
jv_setpath/jv_getpath/jv_delpaths的路径深度加限制 - CVE-2026-40612:
contains的包含检查加深度限制 - CVE-2026-43896:递归对象 merge 加深度限制
- CVE-2026-44777:检测循环模块 import
- CVE-2026-47770:深层结构相等比较与比较递归加防护
- 未编号项:释放深层嵌套值时避免栈溢出
触发这些缺陷不需要恶意构造,一个几万层嵌套的数组就足够让 jq 发生段错误。contains 和 == 这类看似无害的运算符尤其容易被忽略,因为调用方通常只把它们当成判断语句。
内存与溢出
- CVE-2026-32316:
jvp_string_append与jvp_string_copy_replace_bad堆缓冲区溢出 - CVE-2026-39979:
jv_parse_sized()越界读 - CVE-2026-49839:raw file 加载时堆溢出
- GHSA-hj52-j2c9-r8r4:
tokenadd有符号整数溢出(同类的 CVE-2026-41257 是stack_reallocate的符号溢出)
这类问题在 ASan 构建下是稳定可复现的崩溃,在普通构建里可能表现为静默的内存破坏。--rawfile 这条值得单独记:它读的不是 JSON,而是整份原始文件,长度完全由被读文件决定,所以不要把不可信路径交给它。
解析语义
- CVE-2026-33948:JSON parser 的 NUL 截断
- CVE-2026-41256:用
-f加载的程序文件 NUL 截断 - CVE-2026-43894:拒绝长于
DEC_MAX_DIGITS(999999999)的数字字面量 - CVE-2026-43895:模块 import 路径中嵌入 NUL 字节被拒
- CVE-2026-39956:
_strindices缺少运行时类型检查 - CVE-2026-54679:收紧字符串长度边界,
implode传播非法 jv - GHSA-gf4g-95wj-4q4r:
args2obj()数组参数路径的 use-after-free
NUL 截断的后果是校验位置看到的内容和处理位置看到的内容不一致:解析器读到 \0 就当字符串结束,后面的字节被丢掉。对做白名单/黑名单校验的脚本,这等于让校验结果失真。
DoS
- CVE-2026-40164:随机化 hash seed,缓解哈希碰撞型 DoS
- 函数参数个数与函数定义数量加上限,避免 SEGV
另外,1.8.2 预分配了字符串解析用的 tokenbuf,修了若干内存泄漏与双重释放。这些没有 CVE 编号,但对常驻服务来说比一次崩溃更隐蔽。
停在 1.8.0 并不等于安全
2025-07 的 1.8.1 修了 CVE-2025-49014(f_strftime 堆 use-after-free),但把 1.8.0 引入的 reduce/foreach 状态变量改动回退了,原因是严重的性能回退。也就是说 1.8.0 有已知漏洞,1.8.1 在不同行为上有一次退让,1.8.2 才是把这一批安全修复叠上去的版本。
可以立刻做的几件事
升级到 1.8.2,然后在流水线入口限制输入规模与耗时:
curl -s "$API" | head -c 10485760 | jq -r '.data[]'
timeout 10 jq -e '.ok' input.json
给 jq 进程加栈和虚拟内存上限,把爆栈变成可预期的失败而不是整机抖动:
( ulimit -s 8192; ulimit -v 1048576; jq . untrusted.json )
需要限制嵌套深度时,先量一遍再决定是否放行(paths 在大文件上有开销):
jq -e '[paths] | map(length) | max