中文 | English
附录 H:安全差异分析
对真实 ascendc-to-rs 转译批次中 998 对 CANN 8.5 内核(AscendC C++ 与 ascend-rs Rust 对比)的分析,与 附录 G 使用同一语料库。
对于每个内核,我们识别 C++ 版本中存在的内存安全漏洞类别,以及 Rust 转译如何阻止这些漏洞。下面列出的六个类别是 AscendC 编程模型的结构性属性,它们在所有算子类别中统一适用。
范围说明 —— 两级保真度。 998 个内核中,247 个为 Transpiled(主体携带 C++ 计算 intrinsic),751 个为 Registered(主体为 identity stub;签名和 ABI 为真)。§H.1 / §H.2 的安全类别统计分析的是 C++ 源文件 —— 即用户手写算子会引入的风险。“Rust 防护“一列指的是
ascend-rsAPI 的结构性属性(类型化指针、无FreeTensor、带隐式屏障的复合 intrinsic):这些属性适用于 任何 经转译器路由的内核,无论其当前主体为 Transpiled 还是 Registered,因为它们是生成 ABI 和引入 API 面的属性 —— 而非主体内容的属性。
H.1 安全类别汇总
| # | 安全类别 | C++ 风险 | Rust 防护 | 受影响内核数 |
|---|---|---|---|---|
| 1 | 类型混淆 | GM_ADDR 类型擦除 | 类型化指针签名(*const T) | 983/998(98%) |
| 2 | 缓冲区溢出 | GetValue(i)/SetValue(i, v),i >= count | 不透明缓冲区 ID + 显式 count 参数 | 9/998(1%) |
| 3 | 释放后使用 | FreeTensor() 留下失效句柄 | ascend-rs API 中无 FreeTensor 操作 | 3/998(0.3%) |
| 4 | 缺失同步 | DMA→计算之间缺少 pipe_barrier() | kernel_ops 组合算子内部包含屏障 | 793/998(79%) |
| 5 | 重复释放 | FreeTensor() 对同一句柄调用两次 | ascend-rs API 中无 FreeTensor 操作 | 3/998(0.3%) |
| 6 | 整数溢出 | u32 算术:blockIdx * perBlockLen | wrapping_mul 显式化溢出语义 | 785/998(78%) |
H.2 类别细分
下表计数按真实 ascendc-to-rs 批次的类别缩放(见 附录 G §G.1)。类型混淆、缺失同步、整数溢出是结构性的 —— 几乎影响所有内核。缓冲区溢出 / UAF / 重复释放很少见,主要集中在维护显式 LocalTensor 生命周期的算子(主要是 ops_nn 和 ops_transformer)中。
| 类别 | 总计 | C1: 类型 | C2: 越界 | C3: UAF | C4: 同步 | C5: 重复释放 | C6: 溢出 |
|---|---|---|---|---|---|---|---|
| ops_cv | 41 | 41 | 0 | 0 | 33 | 0 | 32 |
| ops_legacy | 343 | 343 | 0 | 0 | 273 | 0 | 270 |
| ops_math | 155 | 155 | 0 | 0 | 123 | 0 | 121 |
| ops_nn | 306 | 301 | 6 | 3 | 243 | 3 | 240 |
| ops_oam | 3 | 3 | 0 | 0 | 2 | 0 | 2 |
| ops_transformer | 150 | 140 | 3 | 0 | 119 | 0 | 120 |
| 总计 | 998 | 983 | 9 | 3 | 793 | 3 | 785 |
H.3 反例输入
对于每个安全类别,给出一个能在 C++ 中触发该漏洞、但在 Rust 中被拦截或阻止的反例输入。示例内核均取自真实 ascendc-to-rs 批次。
证据范围说明。 若所举示例内核当前为 Registered identity stub(见附录 G),则所引用的
blockIdx * perBlockLen/FreeTensor/GetValue代码模式位于 原始 C++ 源文件cann_kernels/<kernel>/<kernel>.cpp,而非当前的.rs主体。Rust 的防护机制是结构性的(类型化指针、API 表面、复合 intrinsic),当转译器未来在主体中完成下降时它将继续生效。
类别 1:类型混淆
触发方式: 向 f32 内核传入 f16 数据
C++ 行为: 静默数据损坏(将 f16 位模式当成 f32 解释)
Rust 行为: 编译期类型错误(*const u16 ≠ *const f32)
示例内核: ops_legacy__fast_gelu、ops_math__cos_apt、ops_nn__gelu_apt
证据: 全部在内核边界使用 GM_ADDR(类型擦除 uint8_t*);转译器将其替换为由 MLIR 元素类型推导出的类型化指针。
类别 2:缓冲区溢出
触发方式: count = buffer_size + 1
C++ 行为: 越界 SRAM 读写(未定义行为)
Rust 行为: 缓冲区 ID 抽象阻止原始索引;显式 count 参数流经类型化 ascend_* API
示例内核: ops_legacy__drop_out_v3、ops_nn__masked_scatter_apt(以及相关的 ops_math__drop_out_* / ops_legacy__scatter_nd_* 变体)
证据: 使用 GetValue(未检查索引)+ 对 LocalTensor 的数组索引。
类别 3:释放后使用
触发方式: 先释放缓冲区,再通过失效句柄读取
C++ 行为: 读取已释放的 SRAM(垃圾数据)
Rust 行为: 不存在 free API —— 缓冲区生命周期由运行时管理
示例内核: C++ 主体中调用 FreeTensor() 的三个 drop_out_* 变体(ops_legacy__drop_out_v3、ops_math__drop_out_v3、ops_legacy__drop_out_do_mask)
证据: 调用 FreeTensor();对应的 Rust 句柄仍然有效,因为 ascend-rs 没有 FreeTensor 操作。
类别 4:缺失同步
触发方式: 移除 load 与 compute 之间的屏障
C++ 行为: 读到陈旧 / 部分 DMA 数据(非确定)
Rust 行为: 总是发射 ascend_pipe_barrier()
示例内核: ops_legacy__foreach_add_list_inplace、ops_legacy__log_softmax_v2_apt、ops_transformer__attention_update_apt
证据: 这些内核在 C++ 主体中有两次显式 pipe_barrier 调用 —— 任一遗漏都会造成数据竞争。ascend-rs 组合算子无条件插入它们。
类别 5:重复释放
触发方式: 对同一个 LocalTensor 调用两次 FreeTensor
C++ 行为: 破坏队列的 free-list(未定义行为)
Rust 行为: 不存在 free API —— double-free 无法表达
示例内核: 与 C3 相同的三个 drop_out_* 变体
证据: C++ dropout 内核中反复调用 FreeTensor;转译后的 Rust 根本没有对应操作。
类别 6:整数溢出
触发方式: blockIdx = 1048576, perBlockLen = 4096 → 回绕到 0
C++ 行为: 静默回绕到 0,产生错误的内存偏移
Rust 行为: wrapping_mul(4096) → 0(显式,debug 模式 panic)
示例内核: 任何跨 block 分块的内核,例如 ops_transformer__flash_attention_score、ops_nn__batch_norm_v3、ops_legacy__foreach_add_list_inplace
证据: 使用 blockIdx * perBlockLen 并以 uint32_t 进行偏移计算。
H.4 解读
主要漏洞类别是 C1:类型混淆(98% 的内核)。这是 AscendC C++ API 的结构性属性:所有内核入口都以 GM_ADDR(= uint8_t*)接收张量指针,在内核边界擦除了所有元素类型信息。host 张量 dtype 与内核假设 dtype 的任何不匹配都会产生静默数据损坏,没有任何运行时诊断。
在 ascend-rs 中,内核入口使用类型化 Rust 指针(f16/bf16 是 *const u16、f32 是 *const f32 等)。dtype 不匹配在编译期就是类型错误,在内核被编译或运行前即被捕获。
C4:缺失同步 影响 79% 的内核。AscendC 编程模型要求在 DMA 操作与后续向量计算之间手动调用 pipe_barrier()。遗漏会产生非确定性的错误结果且无诊断。ascend-rs kernel_ops 组合算子(例如 ascend_vec_add_f16)始终包含必要的屏障 —— 它们无法被意外遗漏。
C6:整数溢出 影响 78% 的内核。C++ 中的 block 索引算术(blockIdx * perBlockLen)使用 uint32_t,在 2³² 处静默回绕而无诊断。Rust 的 wrapping_mul 将回绕行为显式化,并在 debug 构建中触发 panic。
H.5 逐内核详情
全量逐内核安全报告(所有 998 个真实批次内核)作为机器生成的配套文件维护:仓库中的 blog/appendix_safety_report.md。文件列出每个内核的安全类别成员(C1–C6)及用于识别每个类别的证据。