FlawOpen

flawopen.com/Teardowns/cve-2024-33066-qualcomm-wlan-log-redirection

● CVE-2024-33066 · CVSS 9.8 · 严重
安全研究 · FlawOpen

CVE-2024-33066:高通 WLAN Resource Manager 日志重定向内存损坏

CVE-2024-33066 会在高通 WLAN Resource Manager 将日志重定向到调用方指定路径时造成内存损坏。高通于 2024 年 10 月发布修复。

💡 通俗易懂的原理解析 (ELI5)

想象一家酒店的前台,任何人打电话来都可以说:“以后把住客登记簿的副本寄到这个地址。”前台把对方口述的内容抄在一张小卡片上,哪怕字写出了卡片、写到了桌面上,然后照着上面写的地方寄出副本,连经理的保险柜也不例外。修复办法是只接受一个简短的标签,用来指明档案室里的某个抽屉。

核心概念与专有名词

WLAN Resource Manager
高通 CVE-2024-33066 公告中提到的 Wi-Fi 组件。它运行在高通的网络平台上,这些平台用于路由器、Mesh 系统和无线接入点。
IPQ / QCN 芯片组
高通的 Wi-Fi 网络片上系统系列。受影响列表包括 IPQ5332、IPQ8074A、IPQ9574、QCN9000 以及 Immersive Home 平台,并非手机芯片。
CWE-20
输入校验不当:软件信任输入,却没有检查它是否具备代码所依赖的属性,例如最大长度或允许的目录。
相对目录打开
使用 openat() 基于一个目录描述符、以不含 '/' 的纯文件名打开文件,这样结果就不可能落在该目录之外。
CVSS 3.1 向量
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H:可经网络访问、复杂度低、无需权限和用户交互,对三项属性的影响都很高(评分 9.8)。

框架映射

  • 本指南所述弱点的 CWE 参考: CWE-20

根本原因剖析 (Root Cause)

高通的公告只说明 WLAN Resource Manager 在“while redirecting log file to any file location with any file name”时发生内存损坏(CWE-20)。随日志重定向请求提供的目标,在使用前既没有检查长度,也没有检查指向哪里。下面的代码模拟了这一输入校验缺口,仅用于示意,并非高通源代码。

攻击执行流程分解

步骤 1

1. 收到日志重定向请求

WLAN Resource Manager 接受把日志输出发送到别处的请求。CVSS 向量(AV:N/PR:N/UI:N)将该缺陷评定为无需权限和用户交互即可经网络触达。

步骤 2

2. 接受任意位置和任意文件名

根据高通的描述,重定向可以指向任意位置、使用任意文件名:请求中的目标既没有做长度检查,也没有检查它指向哪里。

步骤 3

3. 未经检查的输入破坏内存

在下面的模型中,比固定路径缓冲区更长的名称会使其溢出(strcpy),而且任何路径(包括配置文件)都可以被打开写入。高通没有公布其代码中发生的是哪一种情况。

步骤 4

4. 影响与现状

CVSS 3.1 将对机密性、完整性和可用性的影响都评为高(9.8)。高通已在 2024 年 10 月的公告中把修复交付给设备厂商;CISA 的分级记录显示没有已知的利用。

源代码对比:漏洞与安全实现

这是该漏洞类别的示意模型,并非高通代码。加固后的处理函数在使用输入前先用 strnlen 限定长度,只接受来自固定字符集的短文件名(不含 '/',也不能以 '.' 开头),并用 openat() 相对于启动时选定的日志目录打开它,同时加上 O_NOFOLLOW,防止预先放置的符号链接改变写入目标。调用方可以选择文件名,但永远不能选择位置;过长的输入会被拒绝,而不是被复制。

存在漏洞的实现
#include <fcntl.h>
#include <string.h>

#define LOG_PATH_MAX 64

struct rm_log_cfg {
    char path[LOG_PATH_MAX];
    int fd;
};

/* 处理“重定向日志”请求;`requested` 来自请求正文。 */
int rm_set_log_destination(struct rm_log_cfg *cfg, const char *requested)
{
    strcpy(cfg->path, requested);   /* 未检查长度:过长的输入会越过 path[] 的边界 */
    cfg->fd = open(cfg->path, O_WRONLY | O_CREAT | O_APPEND, 0644);  /* 任意位置 */
    return cfg->fd < 0 ? -1 : 0;
}
加固后的安全修复
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#define LOG_NAME_MAX 32

struct rm_log_cfg {
    int dir_fd;                /* 固定的日志目录,在启动时打开一次 */
    char name[LOG_NAME_MAX];
    int fd;
};

static int valid_log_name(const char *name, size_t len)
{
    if (len == 0 || len >= LOG_NAME_MAX || name[0] == '.')
        return 0;
    for (size_t i = 0; i < len; i++) {
        char c = name[i];
        int ok = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
                 c == '-' || c == '_' || c == '.';
        if (!ok)
            return 0;          /* 拒绝 '/',因此名称无法离开 dir_fd */
    }
    return 1;
}

int rm_set_log_destination(struct rm_log_cfg *cfg, const char *requested)
{
    size_t len = strnlen(requested, LOG_NAME_MAX);
    if (!valid_log_name(requested, len))
        return -EINVAL;

    int fd = openat(cfg->dir_fd, requested,
                    O_WRONLY | O_CREAT | O_APPEND | O_NOFOLLOW | O_CLOEXEC, 0600);
    if (fd < 0)
        return -errno;

    memcpy(cfg->name, requested, len + 1);
    if (cfg->fd >= 0)
        close(cfg->fd);
    cfg->fd = fd;
    return 0;
}

工程与系统安全加固清单

参考来源