已关闭
【runtime】TPRT-003: CQ 报告接收接口缓冲区越界写入 #821
佳祺创建于  24 天前关闭于  20 小时前
佳祺
佳祺
24 天前 创建

【runtime】TPRT-003: CQ 报告接收接口缓冲区越界写入

基本信息

属性
漏洞 ID TPRT-003
类型 buffer-over-write (CWE-787)
严重性 High
置信度 75 (LIKELY)
文件 src/tprt/feature/src/tprt_cqhandle.cc
函数 TprtCqHandle::TprtCqHandleGetCqe
行号 72

漏洞描述

TprtCqHandleGetCqe 函数将内部完成队列(CQ)中的 CQE 报告条目写入用户提供的缓冲区 cqeAddr。循环写入的数量由 min(队列可用条目数, cqeInfo->cqeNum) 决定。

核心问题cqeNum 完全由用户控制,API 仅验证其非零(cqeNum != 0)和缓冲区指针非空(cqeAddr != nullptr),但从未验证 cqeAddr 指向的缓冲区实际大小是否能容纳 cqeNumTprtCqeReport_t 结构体

攻击者可声明一个较大的 cqeNum(如 1024),同时提供一个远小于所需空间的缓冲区。当内部队列中存在足够多的待报告 CQE 时,函数将向缓冲区写入超出其边界的数据,造成堆或栈缓冲区溢出。

漏洞代码

文件: src/tprt/feature/src/tprt_cqhandle.cc,第 60-78 行

void TprtCqHandle::TprtCqHandleGetCqe(TprtReportCqeInfo_t* cqeInfo)
{
    uint16_t cqTail = cqTail_.load();
    uint16_t cqHead = cqHead_.load();
    if (cqTail == cqHead) {
        cqeInfo->reportCqeNum = 0U;
        return;
    }
    const uint32_t queueDepth = TprtManage::Instance()->TprtGetSqMaxDepth();
    uint32_t reportCqeNum = 0U;
    const std::lock_guard<std::mutex> lock(cqQueueLock_);
    while ((cqHead != cqTail) && (reportCqeNum < cqeInfo->cqeNum)) {
        // 漏洞点:未验证 cqeAddr 缓冲区是否能容纳 reportCqeNum+1 个元素
        (TprtPtrToPtr<TprtCqeReport_t*>(cqeInfo->cqeAddr))[reportCqeNum] = cqQueue_[cqHead];
        ++reportCqeNum;
        cqHead = (cqHead + 1U) % queueDepth;
    }
    cqeInfo->reportCqeNum = reportCqeNum;
    cqHead_.store(cqHead);
}

数据流分析

用户应用
  │
  ├─ 构造 TprtReportCqeInfo_t:
  │    ├─ cqeAddr = 用户分配的缓冲区指针(大小未知)
  │    ├─ cqeNum  = 用户声明的期望 CQE 数量(可任意设置)
  │    └─ type    = TPRT_QUERY_CQ_INFO
  │
  ▼
TprtCqReportRecv(devId, cqeInfo)          [tprt_api.cc:167]
  │
  ├─ 检查: cqeInfo != nullptr              ✓ 通过
  ├─ 检查: cqeInfo->cqeNum != 0            ✓ 通过(但不检查上限)
  ├─ 检查: cqeInfo->cqeAddr != nullptr     ✓ 通过(但不检查缓冲区大小)
  ├─ 检查: cqeInfo->type == TPRT_QUERY_CQ_INFO  ✓ 通过
  ├─ 检查: devId 有效                       ✓ 通过
  ├─ 检查: cqId 对应 cqHandle 存在          ✓ 通过
  │
  ▼
TprtCqHandleGetCqe(cqeInfo)               [tprt_cqhandle.cc:60]
  │
  ├─ 循环条件: cqHead != cqTail && reportCqeNum < cqeInfo->cqeNum
  │
  ▼
越界写入: cqeAddr[reportCqeNum] = cqQueue_[cqHead]   ← 漏洞点
  │  reportCqeNum 可增长至 min(队列可用数, cqeNum)
  │  若 cqeNum > 缓冲区实际容量 / sizeof(TprtCqeReport_t),则越界

攻击场景

前置条件

  1. 攻击者能调用 TprtCqReportRecv 公开 API
  2. 内部 CQ 队列中存在待报告的 CQE 条目(通过先前提交的任务产生)

攻击步骤

  1. 攻击者分配一个较小的缓冲区,例如仅能容纳 2 个 TprtCqeReport_t(约 28 字节)
  2. 构造 TprtReportCqeInfo_t,设置 cqeAddr 指向该小缓冲区,cqeNum = 1024
  3. 调用 TprtCqReportRecv
  4. 若内部队列有 N 个待报告 CQE(N > 2),函数将写入 N 个 CQE 条目,超出缓冲区边界 (N-2) × 14 字节

影响

  • 最大越界写入量: 内部队列最大深度为 SQCQ_MAX_DEPTH = 1024,每个 TprtCqeReport_t 为 14 字节(packed),最大可越界写入约 14,308 字节
  • 写入内容: 内部 CQE 报告数据(taskSn, errorCode, errorType, sqeType, sqId, sqHead),非攻击者直接可控内容
  • 潜在利用: 虽然写入内容非直接可控,但越界写入可覆盖相邻内存中的对象指针、返回地址或关键数据结构,可能导致代码执行

现有缓解措施

检查项 位置 效果
cqeInfo != nullptr tprt_api.cc:169 防止空指针解引用
cqeInfo->cqeNum != 0 tprt_api.cc:169 防止零请求(但不限制上限)
cqeInfo->cqeAddr != nullptr tprt_api.cc:169 防止空缓冲区指针
cqHead != cqTail 循环条件 tprt_cqhandle.cc:71 写入量受限于队列实际可用条目数

缺失的关键检查: 无 cqeNum 上限验证,无缓冲区大小验证。

置信度评分明细

{
  "id": "TPRT-003",
  "confidence": 75,
  "status": "LIKELY",
  "veto_applied": false,
  "scoring_details": {
    "base": 30,
    "reachability": 30,
    "controllability": 25,
    "mitigations": -10,
    "context": 0,
    "cross_file": 0
  }
}
维度 评分 理由
基础分 30 默认基础分
可达性 +30 TprtCqReportRecvextern "C" 公开 API,用户应用可直接调用
数据可控性 +25 cqeAddr(目标地址)和 cqeNum(写入数量)均由用户完全控制
缓解措施 -10 存在空指针检查,但缺少缓冲区大小验证和 cqeNum 上限检查
上下文 0 对外暴露的公开 API 接口
跨文件 0 调用链完整:TprtCqReportRecvTprtCqHandleGetCqe,无中间安全检查

修复建议

TprtCqReportRecv(API 入口层)添加 cqeNum 上限验证:

// tprt_api.cc - TprtCqReportRecv 函数中,在现有检查之后添加:
if (cqeInfo->cqeNum > SQCQ_MAX_DEPTH) {
    TPRT_LOG(TPRT_LOG_ERROR, "cqeNum[%u] exceeds max depth[%u], device_id=%u.",
             cqeInfo->cqeNum, SQCQ_MAX_DEPTH, devId);
    return TPRT_INPUT_INVALID;
}

此修复将 cqeNum 限制在队列最大深度范围内。由于调用方按约定应分配至少 cqeNum * sizeof(TprtCqeReport_t) 大小的缓冲区,限制 cqeNum 上限可确保写入量不会超过合理预期。

注意: 从根本上讲,C API 无法从裸指针推断缓冲区实际大小。最安全的做法是在 API 文档中明确要求调用方保证 cqeAddr 缓冲区大小 ≥ cqeNum * sizeof(TprtCqeReport_t),并配合 cqeNum 上限检查作为纵深防御。

likedislike
ykl999
ykl999成员
24 天前 评论:

/assign @Reyn52166

likedislike
CANN-robotCANN-robot成员
24 天前 将 Reyn52166 设为负责人
ykl999
ykl999成员
24 天前 评论:

你好,该安全问题已收到,我们排查一下TprtCqHandleGetCqe 相关代码

likedislike
wangtao成员
24 天前 评论:

@syaunfang
当前问题产生的前提是TprtCqReportRecv属于api,并且接受外部入参。
事实上,tprt_api.h里面声明的接口,并非外部api,头文件不会对外发布,so文件也只被runtime内部代码调用,在当前的调用路径上
XpuDriver::LogicCqReportV2 ->TprtCqReportRecv->TprtCqHandleGetCqe
LogicCqReportV2的内存report 和reportCnt是匹配的,并且内存足够存放。所以不存在代码问题。

likedislike
wangtao成员
24 天前 评论:

@sunnana_004434229 针对这些问题,可能产生了误解,需将tprt_api.h改名或增加说明,为内部api,非外部可见。

likedislike
Lleihuan1成员
23 天前 将 guo-yanjun 设为负责人
guo-yanjun成员
11 天前 评论:

@syaunfang 感谢反馈。针对该问题,我们已完成初步分析并形成统一整改方案:计划将 libxpu_tprt.so 调整为仅构建期使用的静态库,内部链接到 Runtime v100/v200,同时隐藏 Tprt*、cce::tprt::*等动态符号,并清理相关安装及打包清单,确保交付包不再包含 libxpu_tprt.so 或 libxpu_tprt.a。
该方案将收口 TprtCqReportRecv、TprtSqPushTask 等内部接口通过动态链接或 dlsym 被外部直接调用的路径,并保留现有 Runtime 内部调用链。后续还将补充必要的输入健壮性检查,并完成动态依赖、符号表、安装包、功能回归及升级回滚验证。目前正在修改代码并进行依赖和兼容性确认,有进一步进展会及时同步。

likedislike
guo-yanjun成员
20 小时前 评论:

@syaunfang 您好,TPRT 内部接口暴露问题已完成整改和验证,相关修改已提交代码仓,如果您后续有新的进展或诉求,欢迎随时重新开启此 Issue 或提交新的 Issue,我们会继续为您跟进。

likedislike
Gguo-yanjun成员
20 小时前 issue状态由 待办的 改变为 已完成
Gguo-yanjun成员
20 小时前 关闭了 issue
CANN-robotCANN-robot成员
20 小时前 添加了label:resolved