已关闭
【runtime】TPRT-002: 源缓冲区大小未校验导致越界读 #822
佳祺创建于 26 天前关闭于 2 天前
ykl999
26 天前 评论:
26 天前 评论:
/assign @Reyn52166


26 天前 将 Reyn52166 设为负责人
ykl999
26 天前 评论:
26 天前 评论:
你好该问题已收到,针对 源缓冲区大小未校验导致越界读这个问题,我们将根据issue提出代码检查下


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


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


2 天前 issue状态由 待办的 改变为 已完成
2 天前 关闭了 issue
2 天前 添加了label:resolved
【runtime】TPRT-002: 源缓冲区大小未校验导致越界读
基本信息
src/tprt/feature/src/tprt_sqhandle.ccSqPushTask(第69-96行)漏洞描述
tprt_sqhandle.cc中的SqPushTask函数根据用户提供的sqeNum值,从用户提供的sqeAddr缓冲区循环拷贝 SQE 条目到内部队列sqQueue_。整个路径上仅校验了内部队列容量是否充足(IsQueueFull),从未验证sqeAddr指向的源缓冲区是否实际包含sqeNum个TprtSqe_t元素。若攻击者声明较大的sqeNum但提供较小的源缓冲区,将触发越界读,泄漏的内存数据被写入内部队列并可能被后续执行。漏洞代码
// tprt_sqhandle.cc:69-96 uint32_t TprtSqHandle::SqPushTask(const uint8_t* sqeAddr, const uint32_t sqeNum) { const uint32_t depth = TprtManage::Instance()->TprtGetSqMaxDepth(); const std::lock_guard<std::mutex> lock(sqQueueLock_); uint16_t sqHead = sqHead_.load(); uint16_t sqTail = sqTail_.load(); bool queueFull = TprtManage::Instance()->IsQueueFull(sqHead, sqTail, sqeNum); if (queueFull) { // ... 仅检查内部队列是否满 ... return TPRT_SQ_QUEUE_FULL; } uint32_t copySqeNum = 0U; while (copySqeNum != sqeNum) { // ❌ 将 sqeAddr 强制转换为 TprtSqe_t* 并按索引读取 // ❌ 未验证 sqeAddr 缓冲区是否有 sqeNum 个元素 sqQueue_[sqTail] = (TprtPtrToPtr<const TprtSqe_t*>(sqeAddr))[copySqeNum]; PrintTprtSqe(&sqQueue_[sqTail]); ++copySqeNum; sqTail = (sqTail + 1U) % depth; } sqTail_.store(sqTail); return TPRT_SUCCESS; }TprtPtrToPtr的实现(tprt_base.hpp:95-99)——纯reinterpret_cast,无安全检查:template <typename TO, typename TI> inline TO TprtPtrToPtr(const TI ptr) { return reinterpret_cast<TO>(ptr); }完整数据流分析
攻击路径
关键数据结构
// tprt_type.h:34-38 — 用户提交的发送信息结构体 typedef struct tagTprtTaskSendInfo_t { uint8_t* sqeAddr; // 用户控制的 SQE 缓冲区地址 uint32_t sqId; // 提交队列 ID uint32_t sqeNum; // 声称的 SQE 数量 ← 攻击者控制 } TprtTaskSendInfo_t; // tprt_sqe_cqe.h:73-76 — SQE 结构体(packed,约 66 字节) union TprtSqe_t { TprtStarsCommonSqe_t commonSqe; // header(10B) + commandCustom[14](56B) = 66B TprtStarsAicpuSqe aicpuSqe; // header(10B) + addrs + timeout + res = 66B };每个
TprtSqe_t约 66 字节(#pragma pack(1)),若sqeNum=100但缓冲区仅含 1 个元素,越界读范围为(100-1) × 66 = 6534字节。API 入口点验证缺陷分析
TprtSqPushTask(tprt_api.cc:119-149)执行的验证:sendInfo != nullptrsendInfo->sqeNum < depthdevId有效性sendInfo->sqId有效性sendInfo->sqeAddr != nullptrsendInfo->sqeAddr缓冲区大小 ≥sqeNum × sizeof(TprtSqe_t)sendInfo->sqeNum > 0SqPushTask内部验证:IsQueueFull)sqeAddr空指针检查攻击面分析
入口点
TprtSqPushTask声明于src/tprt/inc/external/tprt_api.h,以extern "C"导出,是公开 C API:// tprt_api.h:21 uint32_t TprtSqPushTask(const uint32_t devId, const TprtTaskSendInfo_t* sendInfo);攻击前提
libtprt.so)TprtDeviceOpen和TprtSqCqCreate创建了设备和 SQ/CQTprtTaskSendInfo_t:sqeAddr= 指向一个仅含 N 个TprtSqe_t的缓冲区sqeNum= M,其中 M > N 且 M < depth(最大 1024)sqId= 已创建的合法 SQ ID攻击示例
// 攻击者代码 TprtSqe_t smallBuf[1]; // 仅分配 1 个 SQE 的空间(~66 字节) memset(smallBuf, 0, sizeof(smallBuf)); TprtTaskSendInfo_t sendInfo = { .sqeAddr = (uint8_t*)smallBuf, .sqId = 0, .sqeNum = 100 // 声称有 100 个 SQE,实际只有 1 个 }; // 将触发 99 × 66 = 6534 字节的越界读 TprtSqPushTask(devId, &sendInfo);影响评估
直接影响
sqQueue_内部队列中,可能包含敏感数据(密钥、指针、其他进程数据)sqeAddr为 NULL 或越界读触及未映射页面,导致进程崩溃(段错误)链式利用(与 TPRT-001 组合)
越界读泄漏的数据若恰好包含可解释为
startPcAddr和argsAddr的值,将被SqExeTask→TprtExeAicpuTask当作函数指针执行(参见 TPRT-001),形成信息泄露 → 任意代码执行的链式攻击:修复建议
方案一:在 API 入口增加源缓冲区大小验证(推荐)
在
TprtSqPushTask中增加对sqeAddr的验证:uint32_t TprtSqPushTask(const uint32_t devId, const TprtTaskSendInfo_t* sendInfo) { const uint32_t depth = cce::tprt::TprtManage::Instance()->TprtGetSqMaxDepth(); if ((sendInfo == nullptr) || (sendInfo->sqeNum == 0U) || (sendInfo->sqeNum >= depth)) { TPRT_LOG(TPRT_LOG_ERROR, "input is invalid device_id[%u].", devId); return TPRT_INPUT_INVALID; } // 新增:验证 sqeAddr 非空 if (sendInfo->sqeAddr == nullptr) { TPRT_LOG(TPRT_LOG_ERROR, "sqeAddr is null, device_id[%u].", devId); return TPRT_INPUT_INVALID; } // ... 后续逻辑不变 ... }方案二:扩展 API 接口增加缓冲区大小参数(根本修复)
修改
TprtTaskSendInfo_t结构体,增加缓冲区大小字段:typedef struct tagTprtTaskSendInfo_t { uint8_t* sqeAddr; uint32_t sqId; uint32_t sqeNum; uint32_t sqeBufSize; // 新增:缓冲区实际字节大小 } TprtTaskSendInfo_t;在
SqPushTask中验证:uint32_t TprtSqHandle::SqPushTask(const uint8_t* sqeAddr, const uint32_t sqeNum, const uint32_t sqeBufSize) { if (sqeAddr == nullptr || sqeNum == 0U) { return TPRT_INPUT_INVALID; } const uint32_t requiredSize = sqeNum * static_cast<uint32_t>(sizeof(TprtSqe_t)); if (sqeBufSize < requiredSize) { TPRT_LOG(TPRT_LOG_ERROR, "sqeBufSize[%u] < required[%u] (sqeNum=%u, sizeof(TprtSqe_t)=%zu)", sqeBufSize, requiredSize, sqeNum, sizeof(TprtSqe_t)); return TPRT_INPUT_INVALID; } // ... 后续拷贝逻辑不变 ... }方案三:防御性拷贝(缓解措施)
若无法修改 API,可在
SqPushTask中使用安全拷贝函数,在读取每个 SQE 前验证源地址可读性:while (copySqeNum != sqeNum) { const TprtSqe_t* src = TprtPtrToPtr<const TprtSqe_t*>(sqeAddr) + copySqeNum; // 使用平台相关的安全内存读取检查 if (!IsMemoryReadable(src, sizeof(TprtSqe_t))) { TPRT_LOG(TPRT_LOG_ERROR, "sqeAddr read failed at index %u", copySqeNum); return TPRT_INPUT_INVALID; } sqQueue_[sqTail] = *src; ++copySqeNum; sqTail = (sqTail + 1U) % depth; }验证方法
TprtTaskSendInfo_t,设置sqeNum大于sqeAddr实际元素数,使用 AddressSanitizer 编译运行,验证是否触发 OOB read 报告sqeAddr = NULL,sqeNum = 1,验证是否被正确拒绝sqeNum = 0,验证不会进入异常路径sqeNum和sqeAddr进行随机组合测试,监控越界访问