Prhub

#45118 [Security] Add timeout guard for regex compilation in structured outp…

原始 PR 作者 jperezdealgaba 合并时间 2026-06-13 17:52 文件变更 5 提交数 6 评论 7 代码增减 +126 / -4

执行摘要

为正则编译添加超时防护,防止 ReDoS 攻击

正则编译中的 ReDoS 漏洞(GHSA-rwxx-mrjm-wc2m)允许攻击者通过嵌套量词(如 (a+)+b)触发指数级 DFA 状态爆炸,导致 worker 线程永久挂起。本 PR 通过为编译添加可配置超时来防御此类攻击。

该 PR 修复了安全漏洞,实现经过多轮 review,生命周期管理正确,测试覆盖完整,建议合并。值得关注的设计决策包括线程池手动生命周期管理和函数签名优化。

讨论亮点
  • 线程池生命周期:depthfirst-app[bot] 指出初始实现使用 ThreadPoolExecutor 上下文管理器导致阻塞;作者手动管理,在超时路径调用 shutdown(wait=False, cancel_futures=True)
  • 函数签名:hmellor 建议将 fn 类型从 Callable[[], _T] 改为 Callable[[str], _T],简化调用点;作者采纳并更新。
  • 冗余异常处理:hmellor 指出 validate_xgrammar_grammar 中的 except ValueError: raise 多余;作者删除。
  • 测试准确性:hmellor 评论某测试“doesn't test what it says it does”,作者未直接回应,但 PR 最终合并。

实现拆解

  1. 环境变量声明:在 vllm/envs.pyEnvVars 类和运行时解析块中分别定义 VLLM_REGEX_COMPILATION_TIMEOUT_S: int = 5,同时添加 os.getenv 读取逻辑,值为 0 表示禁用超时。
  2. 工具函数实现:在 vllm/v1/structured_output/utils.py 中新增 compile_regex_with_timeout。该函数接收一个编译可执行体 fnpattern,创建一个 ThreadPoolExecutor(max_workers=1) 并提交任务,调用 future.result(timeout=timeout)。若超时,则取消 future 并立即关闭线程池(shutdown(wait=False, cancel_futures=True)),抛出 ValueError;否则正常关闭并返回结果。它不依赖上下文管理器,确保超时路径不会被阻塞。
  3. xgrammar 后端接入:修改 vllm/v1/structured_output/backend_xgrammar.py,在 compile_grammar 方法的 REGEX 分支中将 self.compiler.compile_regex(grammar_spec) 替换为 compile_regex_with_timeout(self.compiler.compile_regex, grammar_spec);在 validate_xgrammar_grammar 函数中将 xgr.Grammar.from_regex(so_params.regex) 替换为 compile_regex_with_timeout(xgr.Grammar.from_regex, so_params.regex),并移除冗余的 except ValueError: raise 分支。
  4. outlines 后端接入:修改 vllm/v1/structured_output/backend_outlines.py_compile_index 方法,将 oc.Index(regex_string, vocabulary.inner) 放置在超时包装内:compile_regex_with_timeout(lambda pat: oc.Index(pat, vocabulary.inner), regex_string)
  5. 单元测试:新增 tests/v1/structured_output/test_regex_compilation_timeout.py,包含 5 个测试用例:正常编译成功、超时引发 ValueError、超时禁用时正常返回、编译本身异常传播、错误消息中包含 pattern 前 200 字符。使用 patch 修改环境变量以控制超时时间。
文件 模块 状态 重要度
vllm/v1/structured_output/utils.py 结构化输出 modified 7.37
vllm/v1/structured_output/backend_xgrammar.py 结构化输出 modified 5.77
vllm/v1/structured_output/backend_outlines.py 结构化输出 modified 5.19
vllm/envs.py 环境配置 modified 5.27
tests/v1/structured_output/test_regex_compilation_timeout.py 测试 added 6.99

关键符号

compile_regex_with_timeout XgrammarBackend.compile_grammar XgrammarBackend.validate_xgrammar_grammar OutlinesBackend._compile_index

关键源码片段

vllm/v1/structured_output/utils.py core-logic

核心工具函数 compile_regex_with_timeout 的实现所在,是超时保护的核心组件。

# compile_regex_with_timeout 是正则编译超时保护的核心函数。
# 它接收编译函数 fn 和 pattern,通过 ThreadPoolExecutor 在子线程中执行,
# 并在超过 VLLM_REGEX_COMPILATION_TIMEOUT_S 秒后抛出 ValueError。_T = TypeVar("_T")def compile_regex_with_timeout(fn: Callable[[str], _T], pattern: str) -> _T:
    """Run a regex compilation callable with a timeout."""
    timeout = envs.VLLM_REGEX_COMPILATION_TIMEOUT_S # 从环境变量读取,单位秒
    if timeout <= 0:
        return fn(pattern) # 超时禁用,直接编译
​
    executor = ThreadPoolExecutor(max_workers=1)
    future = executor.submit(fn, pattern)
    try:
        result = future.result(timeout=timeout)
    except TimeoutError:
        # 超时后取消任务并立即关闭线程池,避免阻塞
        future.cancel()
        executor.shutdown(wait=False, cancel_futures=True)
        raise ValueError(
            f"Regex compilation timed out after {timeout}s. "
            "The pattern may be too complex or contain constructs that "
            "cause exponential state-space explosion (e.g. nested "
            f"quantifiers). Pattern: {pattern[:200]}"
        ) from None
    else:
        # 正常完成,关闭线程池
        executor.shutdown(wait=False)
        return result

评论区精华

ThreadPoolExecutor 上下文管理器阻塞超时 正确性

depthfirst-app[bot] 指出原始实现使用 ThreadPoolExecutor 作为上下文管理器,__exit__ 会阻塞等待线程完成,导致超时失效。作者在后续提交中手动管理 executor 生命周期,确保超时后立即关闭。

结论:通过手动管理 executor,在 TimeoutError 时调用 cancel 和 shutdown(wait=False) 解决。 · 已解决

compile_regex_with_timeout 函数签名优化 设计

hmellor 建议将 fn 类型从 Callable[[], _T] 改为 Callable[[str], _T],让调用方更简洁。作者采纳并更新所有调用点。

结论:签名修改,调用点简化。 · 已解决

移除 validate_xgrammar_grammar 中冗余的 except ValueError style

hmellor 指出 except ValueError: raise 块多余,因为外层 except Exception 已捕获所有异常。作者移除了该分支。

结论:代码简化。 · 已解决

测试表达不够精确 测试

hmellor 评论某个测试 'doesn't test what it says it does',但未指明具体用例。作者未直接回复,但最终 PR 合并,测试套件通过。

结论:未完全澄清,但 PR 已合并,测试套件通过。 · 已解决

风险与影响

性能风险:每次编译创建单线程临时线程池,开销可忽略。误杀风险:默认5秒对绝大多数合法正则足够,但复杂 pattern 可能超时,用户可设超时为0禁用。线程泄漏:实现已覆盖超时和正常完成两种路径的 shutdown,无显著泄漏。影响范围:仅影响结构化输出开启正则 regex 的请求,其他请求不受影响。

用户透明受益于该安全保护,无需配置。系统稳定性提升,减少因恶意请求导致的 hang。团队新增少量需维护的代码,但测试覆盖完整。

线程生命周期管理 默认超时配置 安全边界

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论