Closed
[Bug-Report|缺陷反馈]: aclnnAddN 空 tensorList(Size()==0)时 (*tensors)[0] 先于空列表早退分支被解引用,宿主 SIGSEGV #3558
liuhanwen8Add on  28 days agoClosed at  11 days ago
liuhanwen8
liuhanwen8
28 days ago Create

Describe the current behavior / 问题描述 (Mandatory / 必填)

aclnnAddNGetWorkspaceSize 传入空 tensorList(指针非空、Size()==0)时,在 CheckParams 内就通过 (*tensors)[0] 访问了空容器,早于入口的空列表早退分支,宿主侧空/越界解引用导致进程崩溃(SIGSEGV)。

math/add_n/op_api/aclnn_add_n.cpp(当前 master):

// CheckParams(入口 L133 调用)
103: static aclnnStatus CheckParams(const aclTensorList* tensors, const aclTensor* out)
...
108:     OP_CHECK_DTYPE_NOT_SAME((*tensors)[0], out, return false);   // 首次 [0] 越界
117:     CHECK_RET(CheckShape(tensors, out), ACLNN_ERR_PARAM_INVALID);
119:     if (IsPrivateFormat((*tensors)[0]->GetStorageFormat())) {      // 第三处 [0]

// CheckShape 内
85:      auto shape = (*tensors)[0]->GetViewShape();                    // 第二处 [0]

// 入口:空列表早退在 CheckParams 之后,被架空
133:     auto ret = CheckParams(tensors, out);
134:     CHECK_RET(ret == ACLNN_SUCCESS, ret);
136:     if (tensors->Size() == 0) {          // 设计上的空列表处理,但已不可达
137:         *workspaceSize = 0;
138:         uniqueExecutor.ReleaseTo(executor);
139:         return ACLNN_SUCCESS;
140:     }

要点:

  • CheckNotNull(L45 OP_CHECK_NULL(tensors))对"指针非空但 Size()==0"判空通过,逐元素循环 0 次亦通过;
  • aclTensorList 的 operator[] 无边界检查,空容器 [0] 为越界访问;
  • L136-140 的空列表早退分支表明「空列表」是设计预期内的合法输入(按空输入处理),但它位于 CheckParams 之后,对 L108/L85/L119 的访问不可达 —— 属"先解引用、后判断"。

Environment Information / 环境信息 (Mandatory / 必填)

  • 昇腾硬件型号:Atlas 800T A2 / Ascend 910B2
  • CANN 版本:9.2.0-beta.2
  • Python 版本:3.10

Steps to reproduce the issue / 重现步骤 (Mandatory / 必填)

构造空 tensorList 后直调第一段接口(aclCreateTensorList(arr, 0) 返回的列表指针非空且 Size()==0):

#!/usr/bin/env python3
import ctypes as C, sys, os
LIB = "<CANN>/aarch64-linux/lib64"
cl = C.CDLL(f"{LIB}/libascendcl.so", mode=C.RTLD_GLOBAL)
op = C.CDLL(f"{LIB}/libopapi.so", mode=C.RTLD_GLOBAL)
cl.aclInit.argtypes=[C.c_void_p]; cl.aclrtSetDevice.argtypes=[C.c_int32]
cl.aclrtCreateStream.argtypes=[C.POINTER(C.c_void_p)]
cl.aclrtMalloc.argtypes=[C.POINTER(C.c_void_p), C.c_size_t, C.c_int32]
op.aclCreateTensor.argtypes=[C.POINTER(C.c_int64), C.c_uint64, C.c_int32, C.POINTER(C.c_int64),
                             C.c_int64, C.c_int32, C.POINTER(C.c_int64), C.c_uint64, C.c_void_p]
op.aclCreateTensor.restype=C.c_void_p
op.aclCreateTensorList.argtypes=[C.POINTER(C.c_void_p), C.c_uint64]
op.aclCreateTensorList.restype=C.c_void_p
P=C.c_void_p
cl.aclInit(None); cl.aclrtSetDevice(int(os.environ.get("DEV","1")))
st=P(); cl.aclrtCreateStream(C.byref(st))
def mk(dims):
    n=len(dims); dd=(C.c_int64*max(n,1))(*dims); ss=(C.c_int64*max(n,1))(*([1]*n))
    p=P(); cl.aclrtMalloc(C.byref(p),256,0)
    return op.aclCreateTensor(dd,n,0,ss,0,2,dd,n,p)     # FLOAT/ND
G=op.aclnnAddNGetWorkspaceSize; G.restype=C.c_int
G.argtypes=[P,P,C.POINTER(C.c_uint64),C.POINTER(P)]
case = sys.argv[1] if len(sys.argv)>1 else "empty"
tl = op.aclCreateTensorList((P*1)(), 0) if case=="empty" else op.aclCreateTensorList((P*1)(mk([4])), 1)
print("tensorList =", tl, flush=True)
ws=C.c_uint64(0); ex=P()
r=G(tl, mk([4]), C.byref(ws), C.byref(ex))
print("ret =", r, flush=True)

运行:source <CANN>/set_env.sh && python3 repro.py empty / python3 repro.py control

Describe the expected behavior / 预期结果 (Mandatory / 必填)

空 tensorList 应走到设计好的早退分支(*workspaceSize = 0 + 返回 ACLNN_SUCCESS,空图);或按契约返回明确错误码。不应发生进程崩溃。

实测(Atlas 800T A2 / CANN 9.2.0-beta.2,每例独立进程):

用例 tensorList 返回
空列表(有效数组指针 + size=0) 指针非空、Size()==0 rc=139 SIGSEGV
空列表(nullptr 数组 + size=0) 指针非空、Size()==0 rc=139 SIGSEGV
对照(1 个合法元素) 指针非空、Size()==1 rc=0 SUCCESS(ws=0)

核心转储已确认(code=11,SIGSEGV)。

Special notes for this issue/备注 (Optional / 选填)

  • 查重:未检索到同型 issue(aclnnAddN 相关命中均为 FusedMulAddN/文档/ST 用例,与本缺陷不同)。建议关联 #3554(aclnnCat / aclnnChunkCat 空 TensorList 零退分支返回 ACLNN_SUCCESS 未写 *workspaceSize)——同属"空 tensorList / 零退分支"区域但缺陷不同(#3554 为出参契约漏写,本单为 [0] 越界先于早退),非重复。
  • 修复建议:把入口 L136 的空列表早退分支前移到 CheckParams 之前;或在 CheckParams L108、CheckShape L85、L119 之前加 if (tensors->Size() == 0) { return true; } 短路。
  • 建议同时补"aclnn × tensorList 空列表"探针用例。
likedislike
liuhanwen8liuhanwen8
28 days ago Added label: bug-report
陈思
陈思Collaborators
27 days ago Comments:

您好,感谢反馈!

问题分析

核查 cann/ops-math master 8f1aae7e1927 的 AddN 实现,CheckNotNull 对非空的空列表对象会通过,随后 CheckParams 就在 dtype 比较中读取 (*tensors)[0],而入口的 Size()==0 处理位于 CheckParams 之后;形状与格式检查也依赖首元素。这个顺序确实存在空列表越界访问,能解释您提供的宿主 SIGSEGV 现象。

处理方向

可将不依赖列表元素的检查与非空列表检查拆开:先保留列表/out 指针等基础检查,再处理空列表,只有确认长度非零后才读取首元素。不能把 Size() 判断直接移到列表指针校验之前;CheckParams 返回的是 aclnnStatus,也不能用 return true 作为成功码。

当前文档明确写了“支持空 TensorList,此时输出为空 Tensor”,但 out 一栏又写“不支持空 Tensor”,两处说明存在冲突。修复时需要同步统一这一契约,避免只是消除崩溃,却对非空 out 无条件成功返回而不写输出。可用空列表、空指针、单元素正常列表以及空/非空 out 组合确认返回行为。我们会继续跟踪。

likedislike
Yyue-maCollaborators
27 days ago Assign VoyageZhou as responsible person
fitZepHYrfitZepHYrCollaborators
12 days ago Associated pull request
fitZepHYrfitZepHYrCollaborators
12 days ago Deleted associated pull request: fix(add_n): 补齐输入dtype一致性校验并修复空TensorList段错误(issue #3652)
fitZepHYrfitZepHYrCollaborators
12 days ago Associated pull request
CANN-robotCANN-robotCollaborators
11 days ago Close issue
CANN-robotCANN-robotCollaborators
11 days ago Added label: resolved
fitZepHYrfitZepHYrCollaborators
11 days ago Deleted associated pull request: fix(add_n): 补齐输入dtype一致性校验并修复空TensorList段错误(issue #3652)