已关闭
[Bug-Report|缺陷反馈]: aclnnIsInf 尾块按 32 字节整块搬运导致 GM 越界读写(numel=1 越界写 31 字节,同形 IsNegInf 无此问题) #3925
liuhanwen8创建于  9 天前关闭于  4 天前
liuhanwen8
liuhanwen8
9 天前 创建

现象

aclnnIsInf 尾块按 32 字节整块搬运,越过 out 的实际大小,造成 GM 越界写/读;同形 aclnnIsNegInf 无此问题。

  • numel=1(out 为 BOOL 共 1 字节)逻辑区外被改写 31 字节;numel=7 / 15 分别 25 / 17 字节(恰好写到 32 字节边界)。
  • mssanitizer memcheck 在同一 kernel 报 illegal read of size 28 / illegal write of size 31,与上面字节数吻合。

期望

out 只允许写 numel 字节(aclTensor 的逻辑大小);尾块按实际元素数搬运,不得整块 32B 读写。

环境

  • 昇腾硬件型号:Ascend 910B2(soc=Ascend910B2)
  • CANN 版本:9.2.0;ops-math 仓 master 提交 584b2ea6
  • 操作系统:Ubuntu 22.04 / g++ 11.4

复现步骤

  1. 取 ops-math 仓 math/is_inf/op_api/aclnn_is_inf.h 与 math/is_neg_inf/op_api/aclnn_isneginf.h;库用 CANN 的 libopapi.so / libnnopbase.so / libascendcl.so。
  2. 按下面命令构建 repro:out 缓冲分配 64 字节并整体预填 0xAA,aclTensor 逻辑大小仍是 numel 字节。
  3. 运行 ./repro 0,程序统计 [numel, 64) 内被改写的字节数。

复现脚本

g++ -std=c++17 -O1 repro.cpp -o repro \
  -I$ASCEND_HOME/include -I$ASCEND_HOME/aarch64-linux/include/aclnnop \
  -I<ops-math>/math/is_inf/op_api -I<ops-math>/math/is_neg_inf/op_api \
  -I<ops-math>/common/inc/external -I<ops-math>/third_party/opbase/include \
  -L$ASCEND_HOME/lib64 -lopapi -lnnopbase -lascendcl
LD_LIBRARY_PATH=$ASCEND_HOME/lib64 ./repro 0
// aclnnIsInf 越界写探针:out 逻辑大小 n 字节,缓冲分配 64B 并预填 0xAA
#include <acl/acl.h>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include "aclnn_is_inf.h"
#include "aclnn_isneginf.h"

static void probe(const char* name, bool isInf, int64_t n)
{
    const size_t BUF = 64;
    void *xDev = nullptr, *oDev = nullptr;
    aclrtMalloc(&xDev, n * sizeof(float), ACL_MEM_MALLOC_HUGE_FIRST);
    aclrtMalloc(&oDev, BUF, ACL_MEM_MALLOC_HUGE_FIRST);
    std::vector<float> hx(n, 1.0f);
    aclrtMemcpy(xDev, n * sizeof(float), hx.data(), n * sizeof(float), ACL_MEMCPY_HOST_TO_DEVICE);
    std::vector<unsigned char> canary(BUF, 0xAA);
    aclrtMemcpy(oDev, BUF, canary.data(), BUF, ACL_MEMCPY_HOST_TO_DEVICE);

    std::vector<int64_t> d{n}, s{1};
    aclTensor* x = aclCreateTensor(d.data(), 1, ACL_FLOAT, s.data(), 0, ACL_FORMAT_ND,
                                   d.data(), 1, xDev);
    aclTensor* out = aclCreateTensor(d.data(), 1, ACL_BOOL, s.data(), 0, ACL_FORMAT_ND,
                                     d.data(), 1, oDev);
    aclrtStream st = nullptr;
    aclrtCreateStream(&st);
    uint64_t ws = 0;
    aclOpExecutor* ex = nullptr;
    aclnnStatus r1 = isInf ? aclnnIsInfGetWorkspaceSize(x, out, &ws, &ex)
                           : aclnnIsNegInfGetWorkspaceSize(x, out, &ws, &ex);
    int r2 = -1;
    if (r1 == ACL_SUCCESS) {
        void* wsp = nullptr;
        if (ws > 0) { aclrtMalloc(&wsp, ws, ACL_MEM_MALLOC_HUGE_FIRST); }
        r2 = static_cast<int>(isInf ? aclnnIsInf(wsp, ws, ex, st) : aclnnIsNegInf(wsp, ws, ex, st));
        aclrtSynchronizeStream(st);
        if (wsp != nullptr) { aclrtFree(wsp); }
    }
    std::vector<unsigned char> back(BUF, 0);
    aclrtMemcpy(back.data(), BUF, oDev, BUF, ACL_MEMCPY_DEVICE_TO_HOST);
    int outside = 0;
    for (size_t i = static_cast<size_t>(n); i < BUF; ++i) { if (back[i] != 0xAA) { ++outside; } }
    printf("%-9s numel=%-3lld ret1=%d ret2=%d | 越界改写字节数=%d\n",
           name, static_cast<long long>(n), static_cast<int>(r1), r2, outside);
    fflush(stdout);
    aclDestroyTensor(x); aclDestroyTensor(out);
    aclrtFree(xDev); aclrtFree(oDev);
    aclrtDestroyStream(st);
}

int main(int argc, char** argv)
{
    int dev = (argc > 1) ? atoi(argv[1]) : 0;
    aclInit(nullptr);
    aclrtSetDevice(dev);
    probe("IsInf", true, 1);
    probe("IsInf", true, 7);
    probe("IsNegInf", false, 1);
    probe("IsNegInf", false, 7);
    aclrtResetDevice(dev);
    aclFinalize();
    return 0;
}

实测输出:

IsInf     numel=1   ret1=0 ret2=0 | 越界改写字节数=31
IsInf     numel=7   ret1=0 ret2=0 | 越界改写字节数=25
IsNegInf  numel=1   ret1=0 ret2=0 | 越界改写字节数=0
IsNegInf  numel=7   ret1=0 ret2=0 | 越界改写字节数=0

影响

整图执行时 out 只有 numel 字节,多写的字节落在相邻 tensor / workspace 上:numel 不是 32 整数倍的用例会静默踩坏其它张量,表现为随机精度异常或后段 coredump,报错点与真正出错的算子不一致。memcheck 在自动化路径两轮均复现,同形 IsNegInf 两轮全绿。

建议

math/is_inf/op_kernel/is_inf.h 的 ComputePerCore() 尾块里,输出侧 actualOutCount = CeilAlign(actualOutCount, DATA_BLOCK) 把搬运长度抬到 32 字节,输入侧 dataCount = CeilAlign(tailDataCount, dataBlock) 同样按块上取整。建议尾块改用 DataCopyPad 或按 tailDataCount 的真实字节搬运,去掉这两处 DATA_BLOCK 对齐,并补 numel=1/7/15 的 ST + memcheck。

likedislike
liuhanwen8liuhanwen8
9 天前 添加了label:bug-report
陈思
陈思成员
9 天前 评论:

您好,感谢反馈!按您报告的 cann/ops-math@584b2ea6cbaeba684a51a74296d82d4d50ab3b0f 核查,IsInf 尾块实现确实把输入、输出的 GM 搬运长度向 32 字节边界扩展,能够解释您给出的尾部字节数。

问题分析

这里输入按 int16_t 单元访问,输出按 uint8_t 单元访问,CopyInX/CopyOut直接把传入长度交给 DataCopy。以一个 FLOAT 元素为例:有效输入是 4 字节,对应 2 个 int16 单元;尾块上取整为 16 个 int16 单元后读取 32 字节,超过有效输入 28 字节。输出的有效 BOOL 长度为 1 字节,却再次上取整为 32 字节,超过有效输出 31 字节。numel=7/15 时输出多搬运 25/17 字节,也与报告一致。

需要分清有效长度与分配长度:您当前探针给输出实际分配了 64 字节,哨兵变化直接证明的是逻辑输出区外被写;超过真实分配边界的访问则对应您报告的 memcheck 结果。复验还应结合实际缓冲映射和分配边界确认,不能因为探针预留了空间就忽略整图复用或相邻有效区域的风险。IsNegInf 在这组对照中没有同样改写,是有用的定位线索,但不代表两个接口共用同一尾块实现。

处理方向

一个可行方案是把 GM 的真实尾部读写长度与 UB 的对齐计算长度分开:输入只搬入有效字节,在 UB 内完成必要填充和向量对齐;输出只搬回真实 BOOL 元素数。可评估使用支持精确尾长的搬运方式,如适用的 DataCopyPad,并核对输入、输出两侧长度单位。仅删除两处上取整、仍沿用普通整块 DataCopy,可能无法完成未对齐尾部的正确搬运。

验证可覆盖 numel=1/7/15/31/32/33、多核最后尾块以及不同输入精度,同时检查数值、哨兵区和 memcheck。当前提交中未定位到 IsInf 公开第一段的完整包装实现,API 输出与内核缓冲的对应仍需结合目标库核实;以上为内核及 tiling 源码核对,尚未独立设备实测。我们会继续确认尾块搬运的有效范围及修正方式。

likedislike
陈思陈思成员
9 天前 将 chen_0715 设为负责人
陈思陈思成员
9 天前 添加了label:pending-confirmation
陈思陈思成员
8 天前 删除了label:pending-confirmation
Cchenyifeng成员
5 天前 将 sakuraqqz 设为负责人
Cchenyifeng成员
5 天前 移除了负责人 chen_0715
CANN-robotCANN-robot成员
4 天前 关闭了 issue
CANN-robotCANN-robot成员
4 天前 添加了label:resolved