ksmbd模糊测试改进与漏洞挖掘深度解析

本文详细介绍了针对Linux内核ksmbd模块的模糊测试改进方法,包括状态管理、协议规范实现和多策略模糊测试技术。通过syzkaller框架发现了23个安全漏洞,涵盖UAF、OOB读写等类型,并分享了具体的技术实现细节和漏洞发现过程。

引言

这是对先前发布文章的后续研究。我们最初的研究发现了几个无需认证的漏洞,但仅触及了攻击表面的浅层。即使在修补代码绕过认证后,大多数有趣的操作仍需与最初忽略的处理程序和状态进行交互。本文第二部分将解释如何扩大覆盖范围并应用不同的模糊测试策略来识别更多漏洞。

配置相关的攻击面

某些功能需要额外的配置选项。我们尝试启用许多可用功能以最大化暴露的攻击面。这帮助我们触发了在最小化配置示例中被禁用的代码路径。但为简化设置,我们未考虑Kerberos支持或RDMA等特性,这些可作为后续改进目标。

扩展攻击面的功能

(仅oplocks默认启用)

  • G = 仅全局范围

  • S = 每共享配置,也可全局设置为默认值

  • 持久句柄 (G)

  • 机会锁 (S)

  • 服务器多通道支持 (G)

  • SMB2租约 (G)

  • VFS对象 (S)

从代码视角,除smb2pdu.c外,还涉及以下源文件:

  • ndr.c – SMB结构中使用的NDR编码/解码
  • oplock.c – 机会锁请求和中断处理
  • smbacl.c – SMB ACL解析和执行
  • vfs.c – 虚拟文件系统操作接口
  • vfs_cache.c – 文件和目录查找的缓存层

模糊测试器改进

SMB3在执行大多数操作前需要有效的会话建立,其认证流程多步骤且需要正确排序。实现有效的Kerberos认证对于模糊测试不切实际。

如第一部分所述,我们修补了NTLMv2认证以便与资源交互。我们还显式允许访客账户,并指定map to guest = bad user以便在凭证无效时回退到"guest"。在报告CVE-2024-50285后,信用限制变得更严格,因此我们也移除了该限制以避免速率限制。

当我们用更大的语料库重启syzkaller时,几分钟后所有剩余候选都被拒绝。调查发现是由于默认max connections = 128,我们不得不将其增加到最大值65536。未更改其他限制。

状态管理

SMB交互是有状态的,依赖于会话、TreeID和FileID。模糊测试需要模拟有效转换如smb2_create ⇢ smb2_ioctl ⇢ smb2_close。当我们发起smb2_tree_connectsmb2_sess_setupsmb2_create等操作时,在伪系统调用中手动解析响应以提取资源标识符并在后续调用中重用。我们的测试工具被编程为每个伪系统调用发送多条消息。

资源解析示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 处理响应(不包含+4B PDU长度)
void process_buffer(int msg_no, const char *buffer, size_t received) {
  // ... 省略 ...
  
  // 提取SMB2命令
  uint16_t cmd_rsp = u16((const uint8_t *)(buffer + CMD_OFFSET));
  debug("Response command: 0x%04x\n", cmd_rsp);

  switch (cmd_rsp) {
    case SMB2_TREE_CONNECT:
      if (received >= TREE_ID_OFFSET + sizeof(uint32_t)) {
        tree_id = u32((const uint8_t *)(buffer + TREE_ID_OFFSET));
        debug("Obtained tree_id: 0x%x\n", tree_id);
      }
      break;
    // ... 其他案例 ...
  }
}

另一个待解决问题是ksmbd依赖全局状态(内存池或会话表),这使得模糊测试确定性降低。我们尝试启用实验性reset_acc_state功能来重置累积状态,但显著降低了模糊测试速度。我们决定不过多关注可重现性,因为每个漏洞通常出现在数十甚至数百个测试用例中。其余情况我们使用如下所述的聚焦模糊测试。

协议规范

我们基于官方SMB协议规范实现测试工具,为所有支持的SMB命令实现语法。微软通过其开放规范计划发布SMB和其他协议的详细技术文档。

例如,SMB2 IOCTL请求的线格式如下所示:

![SMB2 IOCTL请求格式]

我们手动将此规范重写为语法,使测试工具能自动构建有效的SMB2 IOCTL请求:

1
2
3
4
5
smb2_ioctl_req {
        Header_Prefix           SMB2Header_Prefix
        Command                 const[0xb, int16]
        // ... 其他字段 ...
} [packed]

我们在翻译过程中对照源代码进行最终检查以识别和验证可能的不匹配。

模糊测试策略

由于好奇仅使用默认syzkaller配置和从头生成的语料库可能遗漏的漏洞,我们探索了不同的模糊测试方法。

FocusAreas

有时我们触发了无法重现的漏洞,从崩溃日志中无法立即明确原因。其他情况下我们希望聚焦于覆盖率较低的分析函数。实验性功能focus_areas正好满足此需求。

例如,通过目标定位smb_check_perm_dacl

1
2
3
4
5
"focus_areas": [
  {"filter": {"functions": ["smb_check_perm_dacl"]}, "weight": 20.0},
  {"filter": {"files": ["^fs/smb/server/"]}, "weight": 2.0},
  {"weight": 1.0}
]

我们识别了多个整数溢出并能快速建议和确认补丁。

为到达易受攻击代码,syzkaller构建了通过验证并导致整数溢出的ACL。用Python重写后如下:

1
2
3
4
def build_sd():
    sd = bytearray(0x14)
    # ... ACL构建逻辑 ...
    return bytes(sd)

ANYBLOB

anyTypes结构在模糊测试内部使用,文档较少——可能因为不打算直接使用。它定义在prog/any.go中,可表示多种结构:

1
2
3
4
5
6
type anyTypes struct {
    union  *UnionType
    array  *ArrayType
    blob   *BufferType
    // ... 省略 ...
}

在commit 9fe8aa4中实现,用例是将复杂结构压缩为扁平字节数组,并仅应用通用变异。

阅读测试用例更能说明其工作原理。使用从此处和此处获取的公开pcap生成新语料库:

1
2
3
4
5
6
7
8
9
import json
import os

# ... 数据包加载逻辑 ...

if __name__ == "__main__":
    json_file = "packets.json"
    packets = load_packets(json_file)
    # ... 语料库生成 ...

之后使用syz-db将所有候选打包到语料库数据库并恢复模糊测试。由此我们立即触发了ksmbd: fix use-after-free in ksmbd_sessions_deregister()并将整体覆盖率提高了几个百分点。

超越KASAN的消毒器覆盖

除KASAN外,我们还尝试了KUBSAN和KCSAN等其他消毒器。改进不显著:KCSAN产生许多误报或在无关组件中报告看似无安全影响的漏洞。有趣的是,KUBSAN能识别一个KASAN未检测到的问题:

1
id = le32_to_cpu(psid->sub_auth[psid->num_subauth - 1]);

此案例中用户可将psid->num_subauth设为0,导致错误读取psid->sub_auth[-1]。虽然此访问仍在同一结构分配(smb_sid)内,但UBSAN的数组索引边界检查考虑了数组的声明边界:

1
2
3
4
5
6
struct smb_sid {
    __u8 revision;
    __u8 num_subauth;
    __u8 authority[NUM_AUTHS];
    __le32 sub_auth[SID_MAX_SUB_AUTHORITIES];
} __attribute__((packed));

因此能捕获此漏洞。

覆盖率

一个未解决的问题是多进程模糊测试。由于各种锁定机制,且我们重用相同认证状态,我们注意到当仅使用一个进程时模糊测试更稳定且覆盖率增长更快。我们在单个调用中发送多个请求,但最初担心这会遗漏竞态条件。

检查执行日志可见syzkaller在一个进程内创建多个线程,与调用标准系统调用时相同:

1
2
3
4
1.887619984s ago: executing program 0 (id=1628):
syz_ksmbd_send_req(&(0x7f0000000d40)=...)
syz_ksmbd_send_req(&(0x7f0000000900)=...) (async, rerun: 32)
// ... 其他异步调用 ...

观察模糊测试过程中自动添加的async关键字,允许非阻塞并行运行命令(在commit fd8caa5中实现)。因此没有UAF因看似缺乏并行性而被遗漏。

最终基于syzkaller基准测试,我们在20个VM中每秒执行20-30个进程,仍可能意味着运行数百条命令。参考我们使用平均配置的服务器——未特别优化模糊测试性能。

我们使用syzkaller内置函数级指标测量覆盖率。虽然意识到这未捕获SMB等协议中关键的状态转换,但仍提供了代码执行的实用近似值。总体fs/smb/server目录达到约60%。对处理大多数SMB命令解析和分派的smb2pdu.c,我们达到70%。

下图显示关键文件的覆盖率: ![覆盖率截图]

发现的漏洞

在研究期间,我们总共报告了23个漏洞。大部分是use-after-free或越界读写发现。鉴于数量,影响自然不同。例如fix the warning from __kernel_write_iter是仅能在特定设置(kernel.panic_on_warn)下用于DoS的简单警告,validate zero num_subauth before sub_auth is accessed是简单的1字节越界读取,而prevent rename with empty string仅导致内核异常。

还有其他需要更周密分析可利用性的问题(例如fix type confusion via race condition when using ipc_msg_send_request)。然而在评估有潜力的候选后,我们识别了一些强大原语,允许攻击者至少本地利用发现获得远程代码执行。

发现的漏洞列表如下:

描述 提交 CVE
通过验证*pos防止越界流写入 0ca6df4 CVE-2025-37947
防止使用空字符串重命名 53e3e5b CVE-2025-37956
修复ksmbd_session_rpc_open中的use-after-free a1f46c9 CVE-2025-37926
修复__kernel_write_iter的警告 b37f2f3 CVE-2025-37775
修复smb_break_all_levII_oplock()中的use-after-free 18b4fac CVE-2025-37776
…(完整表格见原文)…

注意我们意识到自2024年2月Linux内核成为CNA以来CVE分配的争议。个人观点是虽然存在许多有争议案例,但当前方法实用:现在为具有潜在安全影响的修复分配CVE,特别是内存损坏和其他可能可利用的漏洞类。

结论

我们的研究产生了数十个漏洞,尽管通常不鼓励使用伪系统调用且存在若干缺点。例如在所有案例中,我们必须手动执行分类过程,通过查找相关崩溃日志条目、生成C程序并手动最小化。

由于系统调用可使用资源绑定,此方法也可应用于涉及发送数据包的ksmbd。未来研究探索此方向是理想的——SMB命令可产生资源,然后馈入不同命令。由于时间限制,我们遵循了伪系统调用方法,依赖自定义补丁。

下一部分我们将聚焦于利用CVE-2025-37947。

参考文献

  • pwning tech - Tickling ksmbd: fuzzing SMB in the Linux kernel
  • https://github.com/google/syzkaller
  • Dongliang Mu - Some explanation of main syzkaller logic, execprog, syz-repro
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计