飞桨安全公告 PDSA-2023-002 深度解读:paddle.flip 空指针解引用漏洞(CVE-2023-38670)分析与修复
【免费下载链接】PaddlePArallel Distributed Deep LEarning: Machine Learning Framework from Industrial Practice (『飞桨』核心框架,深度学习&机器学习高性能单机、分布式训练和跨平台部署)项目地址: https://gitcode.com/GitHub_Trending/pa/Paddle
本文以飞桨(PaddlePaddle)官方安全公告 security/advisory/pdsa-2023-002.md 为核心骨架,结合当前仓库中
paddle.flip的 API 定义、CPU/GPU/XPU 底层 Kernel 实现与回归测试,深入剖析该漏洞的触发路径、根因与修复方案。读者读完本文后,将能够复现漏洞场景、理解空指针解引用的成因,并掌握升级修复版本与编写安全调用代码的完整实践方法。
一、漏洞概览:PDSA-2023-002 与 CVE-2023-38670
PDSA-2023-002 是飞桨官方发布的安全公告之一,对应 CVE 编号CVE-2023-38670,公告原文位于 security/advisory/pdsa-2023-002.md(另有中文版 security/advisory/pdsa-2023-002_cn.md)。核心信息如下:
| 条目 | 内容 |
|---|---|
| 公告编号 | PDSA-2023-002 |
| CVE 编号 | CVE-2023-38670 |
| 漏洞类型 | 空指针解引用(Null pointer dereference) |
| 受影响 API | paddle.flip |
| 危害表现 | 进程崩溃(segfault) |
| 修复提交 | ed96baeed19b4e11b6cbc2dcc6776245ba5fab13 |
| 修复版本 | 飞桨 2.5.0(修复包含于该版本) |
| 漏洞报告者 | Tong Liu(上海科技大学,ShanghaiTech University) |
公告明确指出:paddle.flip在特定非法输入下会因空指针解引用(nullptr dereference)导致程序段错误(segfault)崩溃。由于飞桨安全公告体系中"补丁将包含在 2.5.0 版本当中"意味着该修复随 2.5.0 发布,因此飞桨 2.5.0 之前的版本均在受影响范围内,建议相关用户尽快评估并升级。
飞桨完整的安全模型、漏洞报告流程与联系渠道,参见仓库根目录的 SECURITY.md 与 SECURITY_cn.md。历史漏洞公告统一收录于 security/advisory 目录,本文所属的 PDSA-2023 系列覆盖了 2023 年披露的多个安全问题。
二、漏洞对象 paddle.flip:功能、签名与正常用法
要理解漏洞,先要认识paddle.flip这个算子本身。
2.1 API 定义与参数语义
paddle.flip用于沿指定轴反转(翻转)一个 n 维张量的元素顺序,其官方 Python 层定义位于 python/paddle/_paddle_docs.py,签名如下:
def flip(x: Tensor, axis: Sequence[int] | int, name: str | None = None) -> Tensor| 参数 | 类型 | 语义 |
|---|---|---|
x | Tensor | 输入张量,形状为[N_1, N_2, ..., N_k],支持 float32、float64、int32、int64、bool 等数据类型,别名input |
axis | list / tuple / int | 需要翻转的轴,支持负数索引(从末尾倒数),别名dims |
name | str,可选 | 算子名称,默认None,用于调试与图分析 |
正常用例如下(官方 docstring 中的可运行示例):
>>> import paddle >>> image_shape = (3, 2, 2) >>> img = paddle.arange(image_shape[0] * image_shape[1] * image_shape[2]).reshape(image_shape) >>> tmp = paddle.flip(img, [0, 1]) >>> print(tmp) Tensor(shape=[3, 2, 2], dtype=int64, place=Place(cpu), stop_gradient=True, [[[10, 11], [8 , 9 ]], [[6 , 7 ], [4 , 5 ]], [[3 , 2 ], [0 , 1 ]]]) >>> out = paddle.flip(tmp, -1) >>> print(out) Tensor(shape=[3, 2, 2], dtype=int64, place=Place(cpu), stop_gradient=True, [[[11, 10], [9 , 8 ]], [[7 , 6 ], [5 , 4 ]], [[3 , 2 ], [1 , 0 ]]])可以看到,axis既可以是列表[0, 1](同时翻转多个轴),也可以是负数-1(翻转最后一维),返回张量与输入数据类型一致。
2.2 底层 Kernel 实现链路
paddle.flip的 Python 层最终会映射到 PHI(Paddle High-level IR)算子体系中名为flip的 Kernel。当前仓库中该算子实现了多后端版本:
- CPU Kernel:paddle/phi/kernels/cpu/flip_kernel.cc
- GPU Kernel:paddle/phi/kernels/gpu/flip_kernel.cu
- XPU Kernel:paddle/phi/kernels/xpu/flip_kernel.cc
- 头文件声明:paddle/phi/kernels/flip_kernel.h
以 CPU 实现为例,核心翻转逻辑如下:
template <typename T, typename Context> void FlipKernel(const Context& dev_ctx, const DenseTensor& x, const std::vector<int>& axis, DenseTensor* out) { auto x_dims = x.dims(); const int total_dims = x_dims.size(); std::bitset<dim_bitset_size> dim_bitset; for (auto& item : axis) { auto dim = item; if (item < 0) { dim += total_dims; // 负数轴归一化到 [0, total_dims) } dim_bitset[dim] = true; } auto x_strides = common::stride(x_dims); auto numel = x.numel(); const T* x_data = x.data<T>(); T* out_data = dev_ctx.template Alloc<T>(out); if (out->numel() == 0) { return; } // 对每个输出元素,根据 dim_bitset 计算其在输入中的翻转后偏移 dst_offset for (int64_t i = 0; i < numel; ++i) { // ... 将线性索引 i 按 strides 分解,命中翻转轴时取 (x_dims[d] - 1 - cur_indices) out_data[i] = x_data[dst_offset]; } }关键点在于:
axis在 C++ 层以std::vector<int>形式接收,且 Kernel 内部直接假设axis是一个合法的整数向量;- 负数轴通过
dim += total_dims归一化后,直接作为std::bitset<64>的下标使用; - Kernel 通过
x.data<T>()获取输入数据指针后立即解引用(x_data[dst_offset]),全程没有对非法axis值或异常输入形态做防御性校验。
该 Kernel 注册支持的数据类型包括float、double、int32_t、int64_t、bool、complex64、complex128(见 paddle/phi/kernels/cpu/flip_kernel.cc)。此外,flip还具备 TensorRT 推理转换支持(见 paddle/fluid/inference/tensorrt/convert/flip_op.cc),且要求动态 shape 模式(见 paddle/fluid/inference/tensorrt/op_teller.cc)。
三、漏洞细节:PoC 逐步拆解
公告给出的完整 PoC 如下:
import paddle import numpy as np from paddle import flip x = (paddle.to_tensor(np.random.uniform(-10, 10, [1, 2, 3]).astype(np.int64)),) axis = paddle.to_tensor( np.random.uniform(-2147483648, 2147483647, [3, 3]).astype(np.int32) ) flip(x, axis)逐行分析这个 PoC,可以发现它刻意违反paddle.flip的合法输入约定:
x是一个只含一个张量的元组(tuple),而不是直接传入的 Tensor。官方签名的第一个参数类型是Tensor,这里却传入了(paddle.to_tensor(...),)这种包装形态;axis是一个形状为[3, 3]的 int32 Tensor,而官方签名要求axis必须是int或list/tuple。这里传入了形状完全不匹配的张量,且数值随机取遍 int32 全范围(-2147483648到2147483647),其中包含了大量远超张量维度的非法轴值(如负数、大于等于total_dims的数值)。
当这些非法输入穿透 Python 层进入底层算子执行路径后,由于算子内部缺少对输入形态与axis取值范围的校验,最终在解引用空指针(nullptr)时触发段错误,进程直接崩溃——这正是公告描述的"segfaults with a nullptr dereference"。
四、根因分析:从源码看校验缺失
结合当前仓库源码,可以从两个层面理解漏洞根因:
4.1 Python 层:输入约定依赖用户自觉
从 python/paddle/_paddle_docs.py 的 API 定义看,flip的 docstring 只通过类型注解axis: Sequence[int] | int声明了合法形态,文档也明确写出axis应为list|tuple|int。但类型注解不构成运行时强制校验——当用户传入Tensor形态的axis或元组形态的x时,Python 层没有抛出清晰的参数错误,而是将这种异常形态继续向下传递。
4.2 C++ 层:Kernel 对合法输入的无条件信任
从 paddle/phi/kernels/cpu/flip_kernel.cc 的实现可以看出,Kernel 直接假设:
axis已经是合法整数向量,dim_bitset[dim] = true时不做越界检查;x.data<T>()返回的指针必然有效,紧接着的out_data[i] = x_data[dst_offset]直接解引用。
当上层传递了错误形态的参数(如把 Tensor 当作 axis 传入,或在内部转换阶段得到无效指针),Kernel 便会在未加防御的情况下执行解引用,从而命中空指针,表现为不可捕获的 segfault。从源码结构看,这类问题属于**算子输入校验缺失(missing input validation)**导致的不安全解引用,也是 C/C++ 原生扩展中常见的崩溃类安全缺陷。
五、修复方案与缓解措施
5.1 官方修复
飞桨官方已在提交ed96baeed19b4e11b6cbc2dcc6776245ba5fab13中修复该问题,修复随飞桨 2.5.0 版本发布。修复方向即为对flip的输入参数增加严格校验:拒绝不符合Tensor + (int|list|tuple)约定的调用形态,并在进入底层 Kernel 前对axis取值进行合法性检查,从而杜绝空指针解引用的发生路径。
5.2 升级建议
- 确认当前版本:通过
python -c "import paddle; print(paddle.__version__)"查看安装版本; - 升级到修复版本:若版本低于 2.5.0,建议升级至包含修复的版本,例如
pip install -U paddlepaddle(CPU 版)或对应平台的 GPU 安装包; - 评估暴露面:如果业务代码中存在将外部输入直接传入
paddle.flip(尤其是将axis参数交由模型配置、用户请求数据驱动)的场景,应优先排查并在升级前做好临时防护。
5.3 临时规避写法(升级前)
在无法立即升级的情况下,务必保证paddle.flip的调用符合 API 约定:
import paddle x = paddle.to_tensor([[1, 2, 3], [4, 5, 6]], dtype='int64') # ✅ 正确:axis 使用 int 或 list/tuple out = paddle.flip(x, axis=[0, 1]) out = paddle.flip(x, axis=-1) # ❌ 错误:axis 传 Tensor、x 传 tuple 等非法形态应禁止 # bad = paddle.flip((x,), axis=paddle.to_tensor([0]))这条防御性约束同样适用于项目中的其他飞桨算子调用:对来自外部输入的任何张量形状、轴参数,先做类型与取值范围校验,再进入框架 API。
六、仓库中的回归测试与验证
当前仓库保留了flip算子的完整测试资产,可作为理解正常行为与验证修复的参考:
- API 级测试:test/legacy_test/test_flip.py —— 覆盖静态图(
paddle.static)与动态图两种执行模式,验证paddle.flip、output.flip(0)等方法调用形式与翻转结果的正确性(如输入[[1,2,3],[4,5,6]]沿轴 0、-1 翻转的输出比对); - IPU 后端测试:test/ipu/test_flip_op_ipu.py —— 验证 IPU 硬件上的算子一致性;
- TensorRT 推理测试:test/ir/inference/test_trt_convert_flip.py —— 验证
flip经 TensorRT 转换后的推理正确性。
从测试组织方式可以推断,飞桨对算子的验证以"正常合法输入的正确性"为主;而 PDSA-2023-002 这类安全公告补充的,正是针对非法输入形态的鲁棒性防线——这也是深度学习框架安全性建设中"输入校验"与"崩溃隔离"的核心命题。
七、总结
PDSA-2023-002(CVE-2023-38670)揭示了飞桨paddle.flip算子在非法输入下因空指针解引用导致进程崩溃的问题:
- 攻击面:任何能够向
paddle.flip注入非约定输入(Tensor 形态的axis、元组形态的x、越界轴值)的代码路径; - 影响:进程崩溃(DoS 性质),修复包含于飞桨 2.5.0;
- 防御:升级至修复版本,并在应用层严格遵守 API 参数约定、对输入做前置校验。
对于使用飞桨进行推理服务部署或对输入可控性要求较高的场景,建议持续关注 security/advisory 目录下的历史公告,并参照 SECURITY_cn.md 建立漏洞上报与版本跟踪机制,将框架安全纳入常规运维流程。
【免费下载链接】PaddlePArallel Distributed Deep LEarning: Machine Learning Framework from Industrial Practice (『飞桨』核心框架,深度学习&机器学习高性能单机、分布式训练和跨平台部署)项目地址: https://gitcode.com/GitHub_Trending/pa/Paddle
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考