十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深入解析 PaddlePaddle paddle.trace 堆缓冲区溢出漏洞(PDSA-2023-003 / CVE-2023-38671):从 PoC 触发路径到 InferMeta 边界校验修复

深入解析 PaddlePaddle paddle.trace 堆缓冲区溢出漏洞(PDSA-2023-003 / CVE-2023-38671):从 PoC 触发路径到 InferMeta 边界校验修复 深入解析 PaddlePaddle paddle.trace 堆缓冲区溢出漏洞PDSA-2023-003 / CVE-2023-38671从 PoC 触发路径到 InferMeta 边界校验修复【免费下载链接】PaddlePArallel Distributed Deep LEarning: Machine Learning Framework from Industrial Practice 『飞桨』核心框架深度学习机器学习高性能单机、分布式训练和跨平台部署项目地址: https://gitcode.com/GitHub_Trending/pa/Paddle导读本文围绕 PaddlePaddle 官方安全通告 PDSA-2023-003 展开完整剖析paddle.trace算子中一个已被修复的堆缓冲区溢出Heap Buffer Overflow漏洞CVE-2023-38671包括官方 PoC 的触发条件、trace从 Python API 到 C Kernel 的参数流转路径、修复方案落点在 TraceInferMeta 中的边界校验逻辑以及 PaddlePaddle 的安全模型与漏洞上报流程。读完后你可以理解越界 axis 参数为何能导致内存破坏、如何在当前代码库中验证该漏洞的修复状态以及如何在生产环境中安全使用此类张量 API。1. 漏洞概述项目内容通告编号PDSA-2023-003CVE 编号CVE-2023-38671漏洞类型堆缓冲区溢出Heap buffer overflow受影响 APIpaddle.trace修复版本PaddlePaddle 2.5.0官方通告声明该修复将包含在 2.5.0 中修复提交为12549dfe3e87a4c30f852d2eca81d7f67c8daa87报告者Tong LiuShanghaiTech University上海科技大学该漏洞的官方定义非常明确paddle.trace存在堆缓冲区溢出攻击者通过构造非法的axis1/axis2参数值即可触发。按照 PaddlePaddle 在 SECURITY.md 中对漏洞的判定标准——如果恶意输入能触发内存破坏memory corruption或非干净退出non-clean exit这类缺陷即被视为安全问题——paddle.trace在越界参数下导致的堆溢出显然满足这一标准属于需要立即修复的高优先级代码安全问题。值得强调的是这与参数不合法时抛出异常拒绝执行是两回事。官方安全指南中明确指出对于意外的参数和行为PaddlePaddle 会通过抛出 Python 异常或 C 返回错误状态进行检查此时虽然仍可能导致拒绝服务但退出是干净的因此不算安全漏洞。paddle.trace的严重性恰恰在于非法参数没有被拦截而是继续进入了底层内存计算路径最终造成堆内存破坏。2. 官方 PoC触发条件逐行拆解官方通告给出的最小复现脚本如下完整继承自 pdsa-2023-003.mdimport paddle import numpy as np from paddle import trace x paddle.to_tensor(np.random.uniform(-10, 10, [2, 2, 2]).astype(np.float64)) offset paddle.to_tensor(np.random.uniform(-10, 10, []).astype(np.int32)) axis1 paddle.to_tensor(np.random.uniform(-6666666, -2, []).astype(np.int32)) axis2 paddle.to_tensor(np.random.uniform(-6666666, -2, []).astype(np.int32)) trace(x, offset, axis1, axis2)对照 paddle.trace 的 Python 接口定义可以拆解出三个关键的异常输入def trace( x: Tensor, offset: int 0, axis1: int 0, axis2: int 1, name: str | None None, ) - Tensor:参数类型异常offset、axis1、axis2的声明类型都是int但 PoC 传入的是 0 维paddle.Tensorint32。动态图模式下框架会将标量张量转换为 Python 标量继续执行因此类型异常本身不是根因但反映了该 API 入口对输入约束的依赖。数值范围异常真正的触发点axis1、axis2取值范围是[-6666666, -2]内的随机 int32即远超合法范围的巨大负数。对一个 3 维输入张量而言合法的 axis 取值区间是[-3, 2]即[-len(shape), len(shape)-1]。-6666666这样的值在归一化dim ndim后会得到一个绝对值巨大的非法维度索引。输入张量本身完全合法x是[2, 2, 2]的 float64 张量数据值也在正常范围内。这说明不需要构造恶意数据仅靠越界 axis 参数就能触发内存破坏攻击面非常小、触发成本极低。一个容易混淆的语义边界需要特别说明paddle.trace的文档明确写道——如果 offset 超出 axis1、axis2 所指示的输入 shape 范围将返回 0见 math.py 文档字符串。也就是说越界的offset是合法语义返回零值而越界的axis1/axis2是非法输入应被拒绝。漏洞的本质正是后者未被拦截非法值被传递到了底层维度计算中。3. 参数流转路径从 Python 调用到 C 内存操作理解漏洞机理需要看清trace的参数在框架中经历的完整链路。当前代码库中该链路分为两条分支见 math.py 实现if in_dynamic_or_pir_mode(): return _C_ops.trace(x, offset, axis1, axis2) else: __check_input(x, offset, axis1, axis2) # ... LayerHelper append_op(typetrace, attrs{offset:..., axis1:..., axis2:...})动态图 / PIR 模式直接透传给_C_ops.tracePython 层不做任何数值范围检查校验责任完全下沉到 C 侧即 InferMeta 层。静态图模式先在 Python 层执行__check_input对 axis 做assert范围校验再以attrs形式写入算子描述符。而__check_input的校验逻辑math.py对 axis 的处理方式是axis1_ axis1 if axis1 0 else len(input_shape) axis1 # ... assert (0 axis1_) and (axis1_ len(input_shape)), ( fThe argument axis1 is out of range (expected to be in range of f[{-(len(input_shape))}, {len(input_shape) - 1}], but got {axis1}). )即负数 axis 先做ndim axis归一化再要求归一化结果落在[0, ndim)内且要求axis1 ! axis2。对于 PoC 中的-6666666归一化后仍为巨大的负数该校验会失败。关键在于PoC 走的是动态图分支脚本未创建Program上下文默认动态图Python 侧的__check_input根本不会执行参数原封不动进入 C 层。在存在漏洞的版本中C 侧对 axis 缺少等价的边界校验非法值继续参与输出维度的推导与对角线计算最终导致越界的堆内存读写——这就是堆缓冲区溢出的成因链。4. 修复落点TraceInferMeta 中的边界校验当前代码库中trace的输出形状推导函数 TraceInferMeta 对输入做了完整的防御性校验与通告所述已修复的状态一致void TraceInferMeta( const MetaTensor x, int offset, int axis1, int axis2, MetaTensor* out) { int dim1 axis1; int dim2 axis2; auto x_dims x.dims(); int dim1_ dim1 0 ? x_dims.size() dim1 : dim1; int dim2_ dim2 0 ? x_dims.size() dim2 : dim2; PADDLE_ENFORCE_GE(x_dims.size(), 2, common::errors::OutOfRange( Input(x)s dim is out of range (expected at least 2, but got %ld)., x_dims.size())); PADDLE_ENFORCE_LT(dim1_, x_dims.size(), common::errors::OutOfRange( axis1 is out of range (expected to be in range of [%ld, %ld], but got %ld)., ...)); PADDLE_ENFORCE_GE(dim1_, 0, common::errors::OutOfRange( axis1 is out of range (expected to be in range of [%ld, %ld], but got %ld)., ...)); PADDLE_ENFORCE_LT(dim2_, x_dims.size(), common::errors::OutOfRange(...)); PADDLE_ENFORCE_GE(dim2_, 0, common::errors::OutOfRange(...)); PADDLE_ENFORCE_NE(dim1_, dim2_, common::errors::InvalidArgument( The dimensions should not be identical %ld vs %ld., dim1, dim2)); // ... 随后才基于 dim1_/dim2_ 推导输出 shape }逐条校验覆盖了三类非法输入校验项拒绝的输入错误类型x_dims.size() 2少于 2 维的输入张量OutOfRange0 dim1_ ndim且0 dim2_ ndim越界的 axis1 / axis2正是 PoC 的-6666666OutOfRangedim1_ ! dim2_axis1 与 axis2 指向同一轴InvalidArgument这意味着把 PoC 中的axis1 -6666666放入当前代码库会在 InferMeta 阶段被PADDLE_ENFORCE_GE(dim1_, 0, ...)干净地拦截并抛出OutOfRange异常——即从内存破坏降级为预期内的错误处理符合官方安全模型对非漏洞行为clean exit的定义。由于 InferMeta 在 Kernel 执行之前被调用非法值不再有机会流入后续的内存分配与计算路径这正是修复能够阻断堆溢出的根本原因。5. 修复后的内核实现TraceKernel 与 DiagonalInferMeta 通过之后实际的计算由 CPU TraceKernel 完成GPU 侧有对应的 trace_kernel.cutemplate typename T, typename Context void TraceKernel(const Context dev_ctx, const DenseTensor x, int offset, int axis1, int axis2, DenseTensor* out) { auto* out_data dev_ctx.template AllocT(out); if (out out-numel() 0) { return; } const DenseTensor diag funcs::DiagonalT, Context(dev_ctx, x, offset, axis1, axis2); if (diag.numel() 0) { auto x EigenMatrixT::Reshape(diag, diag.dims().size() - 1); auto output EigenVectorT::Flatten(*out); auto reduce_dim Eigen::arrayint, 1({1}); output.device(*dev_ctx.eigen_device()) x.sum(reduce_dim); out-Resize(out-dims()); } else { std::fill(out_data, out_data out-numel(), static_castT(0)); } }可以看到内核本身并不做 axis 合法性判断它假定Diagonal拿到的 axis 一定合法直接基于其构造对角线张量并做 Eigen 归约求和只有对角线元素数为 0 时对应offset 越界返回 0的合法语义才用std::fill写零填充。这一内核不做防御、校验前置到 InferMeta的分层设计也从侧面印证了一旦 InferMeta 的边界校验缺位Diagonal中的维度/偏移计算就是漏洞利用的直接载体。内核通过PD_REGISTER_KERNEL注册了 float、double、int、int64_t、float16、complex64、complex128 等多种 dtype 的 CPU 版本说明该 API 的暴露面较广校验缺失的影响也随之放大。6. PaddlePaddle 的安全模型与漏洞上报PDSA-2023-003 通告末尾引导读者参阅 SECURITY.md 了解安全模型。结合该文档可以把本漏洞放在 PaddlePaddle 的整体安全体系里理解漏洞判定标准。官方将模型执行任意计算读写文件、网络通信导致的内存耗尽、死锁等视为框架的固有能力而非漏洞除非超出相关操作的本意同时明确只有恶意输入触发内存破坏或非干净退出才定性为安全漏洞。paddle.trace堆溢出正落在后者范畴内。漏洞上报流程。安全团队鼓励负责任披露responsible disclosure要求报告者提交漏洞详情与可复现的 PoC本通告中完整保留了报告者的 PoC 即为范例、攻击场景与攻击者可达成的目标、漏洞是否已公开、报告者姓名与所属机构。修复发布后官方会在安全通告中公开漏洞细节与报告者署名本例为上海科技大学的 Tong Liu也可以选择匿名。自动化漏洞发现工具。官方提到已开源部分代码安全工具含动态的算子 fuzzer 样本和静态的 CodeQL 查询样本供安全研究者编写自己的 fuzzer 或 QL 查询测试更多 PaddlePaddle 模块。像paddle.trace这类Python 入口宽松 C 深层计算的算子正是参数越界类 fuzzer 的典型目标。运行不受信任模型的通用建议。SECURITY.md 还特别提醒paddle.load隐式使用 pickle恶意构造的模型文件可能导致任意代码执行因此加载不可信模型应置于沙箱中paddle.distributed的 RPC 等分布式能力不做加密与鉴权仅应部署在可信网络内。7. 防护与升级建议基于以上分析给开发者和使用者的实操建议如下升级到包含修复的版本。官方通告声明修复包含在PaddlePaddle 2.5.0中修复提交12549dfe3e87a4c30f852d2eca81d7f67c8daa87。如果生产环境仍在使用 2.5.0 之前的版本且存在参数来源于外部输入的paddle.trace调用路径应优先升级。在调用侧收紧参数类型与取值。即便在修复后的版本中也建议对axis1/axis2显式校验例如确保-(ndim) axis ndim且axis1 ! axis2并确认传入的是 Pythonint而非张量——这与 PoC 的输入形态形成对照。合法取值示例见 API 文档math.py 示例paddle.trace(case2, offset1, axis11, axis22)。把越界 offset 与越界 axis 区分开。offset越界是合法语义返回 0业务代码可以依赖axis1/axis2越界是非法输入修复后的框架会抛出OutOfRange错误业务侧应将其视为输入校验失败而非数值异常处理。遵循沙箱与最小信任原则。若你的服务会把用户可控的脚本/模型参数传入张量 API例如动态构图、自定义层拼装应参照 SECURITY.md 的建议将执行环境沙箱化并关注 security/advisory 目录下的后续通告。8. 小结PDSA-2023-003 是一个典型的API 参数边界校验缺失类漏洞paddle.trace的 Python 动态图分支将axis1/axis2原样透传给 C 运行时越界的巨大负数 axis 未经拦截即参与对角线维度推导最终造成堆缓冲区溢出。修复方案将校验前置到 TraceInferMeta用PADDLE_ENFORCE系列断言把非法输入在 Kernel 执行前干净地拒绝掉与 PaddlePaddle 官方非法输入应 clean exit 而非内存破坏的安全模型完全对齐。对使用者而言升级到 2.5.0、在调用侧收紧 axis 参数、并将不受信任的执行隔离在沙箱中是应对此类问题的完整防线。【免费下载链接】PaddlePArallel Distributed Deep LEarning: Machine Learning Framework from Industrial Practice 『飞桨』核心框架深度学习机器学习高性能单机、分布式训练和跨平台部署项目地址: https://gitcode.com/GitHub_Trending/pa/Paddle创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表