Skip to content

LangChain numexpr.evaluate 执行 LLM 生成代码

一句话

据 NVD,LangChain 0.0.245 的数学链路可让 LLM 生成内容经 numexpr.evaluate 进入代码执行 sink,属于 ToLO-Exec

怎么读这个案例

这个案例适合用来理解”换一个执行器不等于风险消失”。

numexpr.evaluate 不是 Python 内置 eval,但它仍然会解释执行表达式。只要表达式来自 LLM 输出,就仍要问:

  • 表达式语言能做什么?
  • 上下文里有哪些对象?
  • 版本是否修复了已知问题?

它和 CVE-2023-29374 LLMMathChain 的关系是: LLMMathChain 修复时采用了 numexpr,但 numexpr 自己历史上也有 RCE bug。这个案例就是 numexpr 一次安全问题被映射回 LangChain 的 ToLO 路径

影响组件

影响组件是 LangChain 的数学表达式执行能力及其可选依赖 numexpr

来源affected_versionsfixed_in
NVDlangchain 0.0.245
GHSAlangchain <0.0.308,numexpr <2.8.5
LangChain PRlangchain 0.0.308,把 numexpr 依赖下限提升到 ^2.8.6
numexpr issue #442涉及 ^USER_FUNCTIONS$ 等被滥用接口

LangChain 修复 PR 把 numexpr 依赖下限提高到 2.8.6,因此本站暂记为 pending(fixed_in 表述差异)。

ToLO 路径

内容
SourceS_LLM^framework — LLMMathChain 生成的表达式文本
Transform框架把模型输出整理为待求值表达式
Sinknumexpr.evaluate(expr, ...)(ToLO-Exec,但 sink 不是 Python eval)
Guard 缺失缺少与代码执行匹配的 C_SAFE^allowlistC_SAFE^capability 或隔离执行边界

这里的 sink 与普通 exec 不同,但 ToLO 判断相同:LLM 输出决定了求值引擎要解释的表达式。若表达式语言、函数集合或上下文对象可被滥用,就落入执行边界风险

教学骨架

import numexpr
# 修复前:LLMMathChain 调用
expr = llm_output_text # ← S_LLM^framework
# numexpr 可识别一些函数(sin, cos, exp 等)和算术运算。
# 历史 CVE 涉及 numexpr 内部 ^USER_FUNCTIONS^ 解析处理某些畸形 payload,
# 在特定版本下可能跨过表达式边界访问 Python 上下文。
result = numexpr.evaluate(expr)

⚠ 这里不展开具体 PoC payload。重点是:numexpr 不等于安全求值器,要看版本

修复方式

据 LangChain PR #11302 和 commit 801ddb12856c32a1c2d2f50e4c6638c57894d0c7,上游在 v0.0.308 将可选依赖 numexpr 的最低版本从 ^2.8.4 提高到 ^2.8.6,依赖新版输入收紧来降低执行风险

具体来说:

  • 不是 LangChain 自己加了 AST 校验。
  • 不是 LangChain 改用其他求值器。
  • 而是 升级 numexpr 依赖版本,让 numexpr 本身的新版 fix 生效。

修复属于哪一类 C_SAFE?

这种修复很难直接归入 C_SAFE 五类。它最接近:

  • 依赖版本约束 —— 不在 C_SAFE 五类里,而是一种间接修复
  • 静态分析里不容易识别这种修复为 sanitizer,除非规则同时理解依赖版本和库语义

更彻底的修复应当是:

  • 把 numexpr 包装在 C_SAFE^capability(进程级 sandbox)中,即使 numexpr 出新 bug,也限制后果范围。
  • 给 numexpr 显式传 global_dict={}, local_dict={...}(LLMMathChain 修复 PR #2943 已经这样做)。

对 ToLO 分类的启发

该案例支持 ToLO-Exec:问题不在传统用户输入直接到 eval,而在 LLM 输出被框架当作可信中间结果后进入执行 sink。它不需要扩展 taxonomy,但提示数学/代码解释器类工具需要能力门控或白名单,而不只是类型检查。

它也提醒案例复盘要区分”sink API 名称”和”sink 语义”:

  • numexpr.evaluate 不是 Python 内置 eval
  • 但它仍然是解释执行 LLM 生成表达式的边界

更广泛地说:任何对外承诺”安全求值”的库都要看版本和实现细节。它们不是自动 C_SAFE^safe-codec:

安全级别注意事项
ast.literal_eval只接字面量,不接表达式;DoS 可能(超大嵌套)
numexpr.evaluate表达式引擎;历史 CVE;需 global_dict={}
sympy.sympify低-中默认支持很多函数,可能解析时执行;需 evaluate=False 等约束
RestrictedPython低-中多次绕过 CVE
eval永远不要在 untrusted 上下文用

学习者要带走什么

  1. sink 的名字不一定叫 eval,只要它解释执行 LLM 生成内容,就要按执行边界看。
  2. 依赖升级可以修复已知风险,但静态分析里不容易把版本约束识别为 sanitizer。
  3. 表达式求值器适合配合变量 allowlist、函数 allowlist 和最小权限上下文
  4. CodeQL 等工具检测 ToLO-Exec 时,sink 集合应包含所有”解释执行”风格的 API,而不只是 eval / exec 字面调用。

读完检查

为什么这个案例仍归为 ToLO-Exec,而不是新增一个 ToLO-Numexpr?

→ 因为 taxonomy 按 sink 语义分组,numexpr.evaluate 的核心语义仍是解释执行表达式。开新子类需要新的 sink 语义和新的 sanitizer 适配,这里都没有。

公开来源

下一步阅读