ksmbd 条件竞争漏洞挖掘:思路与案例

ksmbd 条件竞争漏洞挖掘:思路与案例 本文介绍从代码审计的角度分析、挖掘条件竞争、UAF 漏洞思路,并以 ksmbd 为实例介绍审计的过程和几个经典漏洞案例。 分析代码版本为:linux-6.5.5 相关漏...

ksmbd 条件竞争漏洞挖掘:思路与案例

本文介绍从代码审计的角度分析、挖掘条件竞争、UAF 漏洞思路,并以 ksmbd 为实例介绍审计的过程和几个经典漏洞案例。

分析代码版本为:linux-6.5.5

相关漏洞在一年前已修复完毕.

掌握背景:Linux 内核条件竞争 UAF 常见场景

首先我们看一下 Linux 内核下 UAF 漏洞产生的几种常见情况,UAF 的核心原理是 内存被释放了,程序仍让能使用这块内存,导致该现象的常见场景:

  • 指针在程序中被拷贝(比如存放到不同的对象中、放到链表中),其中一个指针释放后另一个指针没有被清理
  • 程序中并发访问导致内存对象还在使用时被其他线程释放.

image.png

对于 Linux 内核/驱动而言,目前最常见的 UAF 是由于条件竞争导致的,即一个线程在使用某块内存时其他线程将其释放了。

那么 Linux 内核为什么会有条件竞争问题呢,其根本原因如下:

  • Linux 以进程为调度主体,不同的进程可能会同时运行
  • 不同的进程实体,通过系统调用进入内核后共用同一个内核里面的资源,比如物理内存、内核堆内存等

下图表示一个多核系统上两个进程同时在不同的 CPU 核运行,访问内核中的共享变量,实际上由于调度中断单核情况下也存在并发场景

image.png

并发执行+共享对象 是条件竞争的根因,因此驱动在开发设计时要考虑到并发场景,利用锁、引用计数等机制让资源在并发访问时不出问题.

常见的并发场景:

并发场景 解释
用户进程之间 多线程并发系统调用,比如IOCTL、MMAP、READ、WRITE 等
用户进程与内核线程之间 内核线程中访问的共享对象,可能被用户态进程修改、释放
内核线程之间 不同内核线程之间使用共享对象

以 IOCTL 为例用户态进程通过 SVC 指令进入内核,首先进入 ioctl 系统调用入口,然后在 vfs_ioctl​ 里面会调用 f_op->unlocked_ioctl 注册的函数指针,进入每个文件/驱动自己实现的回调中。

image.png

image.png

由于多核可以并发 SVC 同时执行系统调用,且从系统调用入口到驱动回调中没有锁保护,所以在各个驱动接口中需要自己实现锁保护并发资源访问

同理以 mmap 回调为例,SVC 进入内核后会进入 vm_mmap_pgoff 里面会获取当前进程 mm 对象的写锁,然后才通过 do_mmap 执行驱动对应的 mmap 回调

image.png

分析系统调用 —-> 驱动回调的调用路径之间锁的使用情况可知:

  • 同一个进程的不同线程共享 mm 对象(即使 mm 指针一样)所以线程之间无法并发 mmap,但是 mmap 和其他回调比如 ioctl 是可以互相并发的.
  • fork 出来的子进程或者通过 IPC 将 fd 共享给其他进程情况下,可以通过多进程并发同时进入驱动的 mmap 回调中.

挖掘条件竞争漏洞,首先就需要通过分析内核代码,清楚了解目标函数执行上下文中是否已经有锁保护,有哪些锁保护,然后以并发执行的视角分析并发场景下的各个代码时序,判断是否存在 UAF。

分析目标:梳理目标软件脉络

分析 ksmbd 的背景是去年偶然间看到一篇介绍通过 syzkaller fuzz ksmbd 协议的文章:Tickling ksmbd: fuzzing SMB in the Linux kernel ,其核心原理是新增一个伪系统调用 syz_ksmbd_send_req 用来将 syzkaller 生成的数据喂给内核的协议中解析

  1. #define KSMBD_BUF_SIZE 16000
  2. static long syz_ksmbd_send_req(volatile long a0, volatile long a1, volatile long a2, volatile long a3)
  3. {
  4. int sockfd;
  5. int packet_reqlen;
  6. int errno;
  7. struct sockaddr_in serv_addr;
  8. char packet_req[KSMBD_BUF_SIZE]; // max frame size
  9. debug("[*]{syz_ksmbd_send_req} entered ksmbd send...\n");
  10. sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  11. memset(&serv_addr, '\0', sizeof(serv_addr));
  12. serv_addr.sin_family = AF_INET;
  13. serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  14. serv_addr.sin_port = htons(445);
  15. errno = connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
  16. // prepend kcov handle to packet
  17. packet_reqlen = a1 + 8 > KSMBD_BUF_SIZE ? KSMBD_BUF_SIZE - 8 : a1;
  18. *(unsigned long*)packet_req = procid + 1;
  19. memcpy(packet_req + 8, (char*)a0, packet_reqlen);
  20. if (write(sockfd, (char*)packet_req, packet_reqlen + 8) < 0)
  21. return -4;
  22. if (read(sockfd, (char*)a2, a3) < 0)
  23. return -5;
  24. if (close(sockfd) < 0)
  25. return -6;
  26. debug("[+]{syz_ksmbd_send_req} successfully returned\n");
  27. return 0;
  28. }

image.png

通过分析这篇文章可以得到一些信息:

  • 定位 ksmbd 处理数据包的入口,即 ksmbd_conn_handler_loop
  • 还存在一些简单的内存越界漏洞,代表可能还会存在一些较复杂、或者隐藏较深的漏洞

    基于这些信息开始对 ksmbd 的源码进行分析,分析思路:

  • 从入口出追踪客户端发送的网络数据流,梳理出请求处理过程中的数据流过程、校验逻辑.

  • 分析数据解析过程,尝试挖掘内存越界、溢出等漏洞
  • 分析请求处理时的一些上下文约束,比如是否可并发、是否有锁,对象生命周期管理,尝试挖掘条件竞争、UAF漏洞

从 ksmbd_conn_handler_loop 往上追踪,这是在 ksmbd_tcp_new_connection 创建的内核线程回调函数

  1. static int ksmbd_tcp_new_connection(struct socket *client_sk)
  2. {
  3. t = alloc_transport(client_sk);
  4. csin = KSMBD_TCP_PEER_SOCKADDR(KSMBD_TRANS(t)->conn);
  5. KSMBD_TRANS(t)->handler = kthread_run(ksmbd_conn_handler_loop,
  6. KSMBD_TRANS(t)->conn,
  7. "ksmbd:%u",
  8. ksmbd_tcp_get_port(csin));

其调用路径:

image.png

因此可以知道每当有一个客户端连接 445 端口时,ksmbd_kthread_fn 就会通过 ksmbd_tcp_new_connection 创建一个内核线程,然后 ksmbd_conn_handler_loop 里面处理每个 socket 请求的业务.

在 ksmbd_tcp_new_connection —> alloc_transport 会为每一个连接创建两个关键的对象(ksmbd_transport​ 和 ksmbd_conn​),用于管理 tcp 连接下的各种协议状态

image.png

两个对象互相保存对方的指针,方便从一个对象中拿到另一个对象进行操作,对象的大概作用:

  • ksmbd_transport:负责链路数据的收发,比如从网络连接中读取数据
  • ksmbd_conn:管理整个 smb 连接的状态,比如登录、文件操作,会话密钥等,每个 TCP 连接对应一个 conn 对象

其中 ksmbd_conn 是非常核心的对象,在 SMB 请求处理的各个环节都能看到, ksmbd_conn_handler_loop 的大概处理流程如下

image.png

conn 下每收到一个请求都会新建一个 work,然后把 work 放到 ksmbd_wq, workqueue 会动态分配到不同 worker 执行。这边在介绍一下内核的 workqueue 机制,workqueue 和 work 的关系如下:

image.png

核心概念是:work 先注册到 workqueue ,然后具体由 worker 执行,在代码中每个 worker 对应一个 work_thread 内核线程,一个 workqueue 里面会存在多个 worker,这些 worker 之间并发执行.

因此同一时刻可能会有 handle_ksmbd_work 实例访问同一个 conn 对象,这样就有了 RACE 的可能.

image.png

继续分析 handle_ksmbd_work 的大体逻辑:

image.png

handle_ksmbd_work 主要逻辑就是解析 work->request_buf 中的网络报文数据,根据里面的请求类型、参数调用对应的请求处理函数进行处理(conn->cmds​),同时可以看到 cmds->proc 执行时上下文是没有锁的,因此如果 cmd 里面如果有访问到共享变量就需要自行加锁避免并发.

cmds 中可用的回调函数如下

  1. static struct smb_version_cmds smb2_0_server_cmds[NUMBER_OF_SMB2_COMMANDS] = {
  2. [SMB2_NEGOTIATE_HE] = { .proc = smb2_negotiate_request, },
  3. [SMB2_SESSION_SETUP_HE] = { .proc = smb2_sess_setup, },
  4. [SMB2_TREE_CONNECT_HE] = { .proc = smb2_tree_connect,},
  5. [SMB2_TREE_DISCONNECT_HE] = { .proc = smb2_tree_disconnect,},
  6. [SMB2_LOGOFF_HE] = { .proc = smb2_session_logoff,},
  7. [SMB2_CREATE_HE] = { .proc = smb2_open},
  8. [SMB2_QUERY_INFO_HE] = { .proc = smb2_query_info},
  9. [SMB2_QUERY_DIRECTORY_HE] = { .proc = smb2_query_dir},
  10. [SMB2_CLOSE_HE] = { .proc = smb2_close},
  11. [SMB2_ECHO_HE] = { .proc = smb2_echo},
  12. [SMB2_SET_INFO_HE] = { .proc = smb2_set_info},
  13. [SMB2_READ_HE] = { .proc = smb2_read},
  14. [SMB2_WRITE_HE] = { .proc = smb2_write},
  15. [SMB2_FLUSH_HE] = { .proc = smb2_flush},
  16. [SMB2_CANCEL_HE] = { .proc = smb2_cancel},
  17. [SMB2_LOCK_HE] = { .proc = smb2_lock},
  18. [SMB2_IOCTL_HE] = { .proc = smb2_ioctl},
  19. [SMB2_OPLOCK_BREAK_HE] = { .proc = smb2_oplock_break},
  20. [SMB2_CHANGE_NOTIFY_HE] = { .proc = smb2_notify},
  21. };

这些回调函数就会根据请求和 conn 对象实现 smb 协议的业务逻辑,之后就可以对这些回调函数进行审计,这里再次回顾一下这些回调函数执行的上下文状态:

  • 回调函数会在不同的 worker 线程中被调用,存在并发性
  • 同一个连接的不同请求可能并发处理,处理时会访问同一个 conn 对象

经过分析 ksmbd 中的共享变量、对象也主要是集中在 conn 对象中(类似于 file_operation 回调的共享 filp 对象),因此在分析条件竞争漏洞时可重点关注对 conn 对象的访问、操作。

实例分析:加深理解

本节以一些真实案例介绍条件竞争漏洞的挖掘、分析经验

smb2_open 条件竞争 UAF

smb2_open 的命令字为 SMB2_CREATE_HE,其用途对标的是 Linux 用户态的 open 函数,用于打开远程 smb 服务器上的一个文件。

  1. [SMB2_CREATE_HE] = { .proc = smb2_open},

函数的代码很长,大致逻辑是首先从数据包中提取出要打开的文件名和打开的模式,对文件名校验后通过内核 vfs 子系统的 API 打开共享目录下的文件。

对该函数进行审计的思路是:

  • 常规数据解析类漏洞,比如堆栈溢出等
  • 文件名校验逻辑是否有误,导致目录穿越
  • 对象管理是否有误,导致 UAF

对数据解析和文件名校验、打开逻辑进行分析没有发现问题,分析其对象管理时,发现 smb2_open 打开文件后会分配 struct ksmbd_file 对象管理打开文件对应的 struct file 对象,ksmbd_file 和 ksmbd_cnn 的关系如下图所示:

image.png

  • ksmbd_conn 对象里面的 sessions 数组中保存了当前连接的会话对象(ksmbd_session)
  • ksmbd_session 对象的 file_table 保存了打开的所有文件对象(ksmbd_file)
  • ksmbd_file 对象的 filp 指向了真正打开的 VFS 文件对象(struct file)

下面看一下 ksmbd_file 对象的创建和初始化过程,相关代码如下:

image.png

首先调用 ksmbd_open_fd 分配 fp,其中会调用 kmem_cache_zalloc 分配 ksmbd_file 对象,然后通过 __open_id 将 fp 存放到 ksmbd_session 的 file_table 里面(work->sess->file_table), work->sess 是进入回调函数前从 conn 对象中获取的

  1. struct ksmbd_session *ksmbd_session_lookup(struct ksmbd_conn *conn,
  2. unsigned long long id)
  3. {
  4. struct ksmbd_session *sess;
  5. sess = xa_load(&amp;conn->sessions, id);
  6. if (sess)
  7. sess->last_active = jiffies;
  8. return sess;
  9. }
  10. work->sess = ksmbd_session_lookup_all(conn, sess_id);

ksmbd_open_fd 返回后设置 fp 对象的其他字段,注意到此时 fp 已经被放入了 sess->file_table ,此时其他线程也可以同时获取该对象,而且此时 fp 的引用计数为 1

下面可以看一下 smb_close 的实现,其核心逻辑位于 ksmbd_close_fd

  1. int ksmbd_close_fd(struct ksmbd_work *work, u64 id)
  2. {
  3. struct ksmbd_file *fp;
  4. struct ksmbd_file_table *ft;
  5. ft = &amp;work->sess->file_table;
  6. read_lock(&amp;ft->lock);
  7. fp = idr_find(ft->idr, id);
  8. if (fp) {
  9. set_close_state_blocked_works(fp);
  10. if (!atomic_dec_and_test(&amp;fp->refcount))
  11. fp = NULL;
  12. }
  13. read_unlock(&amp;ft->lock);
  14. __put_fd_final(work, fp);
  15. return 0;
  16. }

当 fp->refcount 减一后为 0 时会进入 __put_fd_final 释放 fp 的内存,因此可以在线程 A 执行 smb2_open 时,其他线程通过 smb2_close 释放 fp 就能导致 UAF。

RACE 场景下的时序关系如下图:

image.png

触发后的 kasan 日志如下

  1. [ 224.236369] BUG: KASAN: slab-use-after-free in __open_id+0xfc/0x160 [ksmbd]
  2. [ 224.236457] Write of size 8 at addr ffff8881bf504788 by task kworker/6:1/90
  3. [ 224.236469] CPU: 6 PID: 90 Comm: kworker/6:1 Tainted: G OE 6.5.4 #1
  4. [ 224.236478] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/22/2020
  5. [ 224.236485] Workqueue: ksmbd-io handle_ksmbd_work [ksmbd]
  6. [ 224.236572] Call Trace:
  7. [ 224.236577] <TASK>
  8. [ 224.236583] dump_stack_lvl+0x48/0x70
  9. [ 224.236595] print_report+0xd2/0x660
  10. [ 224.236605] ? __virt_addr_valid+0x103/0x180
  11. [ 224.236617] ? kasan_complete_mode_report_info+0x8a/0x230
  12. [ 224.236639] ? __open_id+0xfc/0x160 [ksmbd]
  13. [ 224.236721] kasan_report+0xd0/0x120
  14. [ 224.236731] ? __open_id+0xfc/0x160 [ksmbd]
  15. [ 224.236816] __asan_store8+0x8e/0xe0
  16. [ 224.236825] __open_id+0xfc/0x160 [ksmbd]
  17. [ 224.236908] ksmbd_open_durable_fd+0x21/0x40 [ksmbd]
  18. [ 224.236991] smb2_open+0x1276/0x3d00 [ksmbd]
  19. [ 224.237083] ? __pfx_smb2_open+0x10/0x10 [ksmbd]
  20. [ 224.237167] ? ksmbd_release_crypto_ctx+0xd1/0x100 [ksmbd]
  21. [ 224.237281] ? ksmbd_crypt_message+0x48d/0xc70 [ksmbd]
  22. [ 224.237368] ? __pfx_ksmbd_crypt_message+0x10/0x10 [ksmbd]
  23. [ 224.237463] ? xas_descend+0x82/0x130
  24. [ 224.237473] ? xas_descend+0x82/0x130
  25. [ 224.237481] ? xas_start+0x8a/0x1d0
  26. [ 224.237490] ? __rcu_read_unlock+0x51/0x80
  27. [ 224.237507] ? ksmbd_smb2_check_message+0xa56/0xc90 [ksmbd]
  28. [ 224.237595] handle_ksmbd_work+0x2a7/0x800 [ksmbd]
  29. [ 224.237684] process_one_work+0x4d3/0x840
  30. [ 224.237700] worker_thread+0x91/0x6e0
  31. [ 224.237715] ? __pfx_worker_thread+0x10/0x10
  32. [ 224.237726] kthread+0x188/0x1d0
  33. [ 224.237735] ? __pfx_kthread+0x10/0x10
  34. [ 224.237744] ret_from_fork+0x44/0x80
  35. [ 224.237754] ? __pfx_kthread+0x10/0x10
  36. [ 224.237763] ret_from_fork_asm+0x1b/0x30
  37. [ 224.237777] </TASK>
  38. [ 224.237785] Allocated by task 90:
  39. [ 224.237790] kasan_save_stack+0x38/0x70
  40. [ 224.237800] kasan_set_track+0x25/0x40
  41. [ 224.237809] kasan_save_alloc_info+0x1e/0x40
  42. [ 224.237818] __kasan_slab_alloc+0x9d/0xa0
  43. [ 224.237824] kmem_cache_alloc+0x17f/0x3c0
  44. [ 224.237833] ksmbd_open_fd+0x2d/0x550 [ksmbd]
  45. [ 224.237916] smb2_open+0x1200/0x3d00 [ksmbd]
  46. [ 224.237999] handle_ksmbd_work+0x2a7/0x800 [ksmbd]
  47. [ 224.238082] process_one_work+0x4d3/0x840
  48. [ 224.238091] worker_thread+0x91/0x6e0
  49. [ 224.238100] kthread+0x188/0x1d0
  50. [ 224.238106] ret_from_fork+0x44/0x80
  51. [ 224.238114] ret_from_fork_asm+0x1b/0x30
  52. [ 224.238124] Freed by task 774:
  53. [ 224.238128] kasan_save_stack+0x38/0x70
  54. [ 224.238137] kasan_set_track+0x25/0x40
  55. [ 224.238146] kasan_save_free_info+0x2b/0x60
  56. [ 224.238155] ____kasan_slab_free+0x180/0x1f0
  57. [ 224.238164] __kasan_slab_free+0x12/0x30
  58. [ 224.238170] slab_free_freelist_hook+0xd2/0x1a0
  59. [ 224.238178] kmem_cache_free+0x1b2/0x360
  60. [ 224.238187] __ksmbd_close_fd+0x34a/0x490 [ksmbd]
  61. [ 224.238280] ksmbd_close_fd+0xb0/0x110 [ksmbd]
  62. [ 224.238362] smb2_close+0x2fc/0x690 [ksmbd]
  63. [ 224.238445] handle_ksmbd_work+0x2a7/0x800 [ksmbd]
  64. [ 224.238528] process_one_work+0x4d3/0x840
  65. [ 224.238537] worker_thread+0x91/0x6e0
  66. [ 224.238546] kthread+0x188/0x1d0
  67. [ 224.238552] ret_from_fork+0x44/0x80
  68. [ 224.238560] ret_from_fork_asm+0x1b/0x30

smb2_close 条件竞争 UAF

发现 smb2_open 的漏洞后,忽然想到如果并发 smb2_close 会出现什么样的效果.再次回顾下 smb2_close —> ksmbd_close_fd 的代码

  1. int ksmbd_close_fd(struct ksmbd_work *work, u64 id)
  2. {
  3. struct ksmbd_file *fp;
  4. struct ksmbd_file_table *ft;
  5. ft = &amp;work->sess->file_table;
  6. read_lock(&amp;ft->lock);
  7. fp = idr_find(ft->idr, id);
  8. if (fp) {
  9. set_close_state_blocked_works(fp);
  10. if (!atomic_dec_and_test(&amp;fp->refcount))
  11. fp = NULL;
  12. }
  13. read_unlock(&amp;ft->lock);
  14. __put_fd_final(work, fp);
  15. return 0;
  16. }

让我们以并发的思维人脑模拟执行一下上面的代码:

  1. 假设两个线程 A B 并发进入 ksmbd_close_fd,且此时 id 对应 fp 的引用计数为 1
  2. 由于持有的是 read_lock 读锁,所以两个线程可以同时拿到 fp 并进入 atomic_dec_and_test
  3. 由于 atomic_dec_and_test 的逻辑其中一个线程会进入 fp = NULL 分支,所以只有一个线程能正常释放 fp.

因此上述场景是无法产生 UAF 的,那假如线程 A 先进入 __put_fd_final ,然后线程 B 执行到 atomic_dec_and_test ,然后线程 A 在 __put_fd_final 里面释放 fp 是否可行呢?

由于 __put_fd_final 释放 fp 前会先获取 ft->lock 的写锁,将 fp 从 idr 中移除后才会去释放 fp,因此无法构造上面的场景,因为写锁的获取要等所有读锁释放后才能获取。

单纯并发 ksmbd_close_fd 不可行,那利用其他接口配合这个逻辑是否可以产生不一样的效果呢,经过思考和尝试,当其他线程在使用 fp 时,多个线程进入 close 就可以把 fp 提前释放。

以 smb2_read 为例,函数首先通过 ksmbd_lookup_fd_slow 获取 fp 并增加其引用计数,使用完成后会通过 ksmbd_fd_put 释放引用

  1. fp = ksmbd_lookup_fd_slow(work, req->VolatileFileId, req->PersistentFileId);
  2. nbytes = ksmbd_vfs_read(work, fp, length, &amp;offset);
  3. ksmbd_fd_put(work, fp);

并发导致 UAF 的场景如下:

  1. 线程 A 持有 fp (比如通过 smb2_read),此时 fp->refcount = 2
  2. 5 个线程 B1 B2 …. B5,同时进入 ksmbd_close_fd 就会尝试最多减 5 次引用计数,导致 fp->refcount = 0,被释放
  3. 线程 A 后面使用 fp 时就是被释放的 fp

触发漏洞后的 kasan 日志如下

  1. [ 115.537085] BUG: KASAN: slab-use-after-free in smb2_read+0x241/0x850 [ksmbd]
  2. [ 115.537205] Read of size 4 at addr ffff8881ac7099cc by task kworker/6:2/76
  3. [ 115.537218] CPU: 6 PID: 76 Comm: kworker/6:2 Tainted: G OE 6.5.4 #1
  4. [ 115.537227] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/22/2020
  5. [ 115.537235] Workqueue: ksmbd-io handle_ksmbd_work [ksmbd]
  6. [ 115.537321] Call Trace:
  7. [ 115.537327] <TASK>
  8. [ 115.537333] dump_stack_lvl+0x48/0x70
  9. [ 115.537349] print_report+0xd2/0x660
  10. [ 115.537361] ? __virt_addr_valid+0x103/0x180
  11. [ 115.537375] ? kasan_complete_mode_report_info+0x8a/0x230
  12. [ 115.537387] ? smb2_read+0x241/0x850 [ksmbd]
  13. [ 115.537472] kasan_report+0xd0/0x120
  14. [ 115.537482] ? smb2_read+0x241/0x850 [ksmbd]
  15. [ 115.537570] __asan_load4+0x8e/0xd0
  16. [ 115.537579] smb2_read+0x241/0x850 [ksmbd]
  17. [ 115.537680] ? __pfx_smb2_read+0x10/0x10 [ksmbd]
  18. [ 115.537782] handle_ksmbd_work+0x2a7/0x800 [ksmbd]
  19. [ 115.537870] process_one_work+0x4d3/0x840
  20. [ 115.537889] worker_thread+0x91/0x6e0
  21. [ 115.537904] ? __pfx_worker_thread+0x10/0x10
  22. [ 115.537915] kthread+0x188/0x1d0
  23. [ 115.537925] ? __pfx_kthread+0x10/0x10
  24. [ 115.537934] ret_from_fork+0x44/0x80
  25. [ 115.537946] ? __pfx_kthread+0x10/0x10
  26. [ 115.537955] ret_from_fork_asm+0x1b/0x30
  27. [ 115.537969] </TASK>
  28. [ 115.537977] Allocated by task 76:
  29. [ 115.537983] kasan_save_stack+0x38/0x70
  30. [ 115.537994] kasan_set_track+0x25/0x40
  31. [ 115.538003] kasan_save_alloc_info+0x1e/0x40
  32. [ 115.538012] __kasan_slab_alloc+0x9d/0xa0
  33. [ 115.538019] kmem_cache_alloc+0x17f/0x3c0
  34. [ 115.538028] ksmbd_open_fd+0x2d/0x550 [ksmbd]
  35. [ 115.538110] smb2_open+0x1200/0x3ca0 [ksmbd]
  36. [ 115.538193] handle_ksmbd_work+0x2a7/0x800 [ksmbd]
  37. [ 115.538298] process_one_work+0x4d3/0x840
  38. [ 115.538307] worker_thread+0x91/0x6e0
  39. [ 115.538316] kthread+0x188/0x1d0
  40. [ 115.538323] ret_from_fork+0x44/0x80
  41. [ 115.538331] ret_from_fork_asm+0x1b/0x30
  42. [ 115.538341] Freed by task 1114:
  43. [ 115.538346] kasan_save_stack+0x38/0x70
  44. [ 115.538355] kasan_set_track+0x25/0x40
  45. [ 115.538364] kasan_save_free_info+0x2b/0x60
  46. [ 115.538373] ____kasan_slab_free+0x180/0x1f0
  47. [ 115.538382] __kasan_slab_free+0x12/0x30
  48. [ 115.538388] slab_free_freelist_hook+0xd2/0x1a0
  49. [ 115.538396] kmem_cache_free+0x1b2/0x360
  50. [ 115.538405] __ksmbd_close_fd+0x34a/0x490 [ksmbd]
  51. [ 115.538487] ksmbd_close_fd+0xb0/0x110 [ksmbd]
  52. [ 115.538569] smb2_close+0x2fc/0x690 [ksmbd]
  53. [ 115.538652] handle_ksmbd_work+0x2a7/0x800 [ksmbd]
  54. [ 115.538734] process_one_work+0x4d3/0x840
  55. [ 115.538743] worker_thread+0x91/0x6e0
  56. [ 115.538752] kthread+0x188/0x1d0
  57. [ 115.538758] ret_from_fork+0x44/0x80
  58. [ 115.538777] ret_from_fork_asm+0x1b/0x30

smb2_write 条件竞争 UAF

有了上面的经验,我开始关注代码中对对象的使用:

  1. 使用共享对象是是否持有引用计数
  2. 能否并发释放

于是在浏览代码时发现 smb2_write 上来就是使用了 work->tcon 对象

  1. int smb2_write(struct ksmbd_work *work)
  2. {
  3. WORK_BUFFERS(work, req, rsp);
  4. if (test_share_config_flag(work->tcon->share_conf, KSMBD_SHARE_FLAG_PIPE)) {
  5. ksmbd_debug(SMB, "IPC pipe write request\n");
  6. return smb2_write_pipe(work);
  7. }

追踪一下赋值点

image.png

  1. int smb2_get_ksmbd_tcon(struct ksmbd_work *work)
  2. {
  3. struct smb2_hdr *req_hdr = ksmbd_req_buf_next(work);
  4. unsigned int tree_id;
  5. work->tcon = ksmbd_tree_conn_lookup(work->sess, tree_id);
  6. return 1;
  7. }

可以看到 work->tcon 没有持有 tcon 对象的引用计数,那么能够并发释放吗,其并发逻辑位于

image.png

smb2_tree_disconnect 为 __handle_ksmbd_work 的 cmd 回调,其中也没有锁保护,因此可以并发释放 tcon.

因此在 smb2_write 执行时,多线程发起 smb2_tree_disconnect 请求就能在 smb2_write 使用 tcon 时将其释放,导致 UAF,Crash 的日志如下:

  1. [ 5521.190232] BUG: KASAN: slab-use-after-free in smb2_write+0x16e/0x840 [ksmbd]
  2. [ 5521.190327] Read of size 8 at addr ffff8881c5ef6708 by task kworker/6:0/1913
  3. [ 5521.190341] CPU: 6 PID: 1913 Comm: kworker/6:0 Tainted: G OE 6.5.4 #1
  4. [ 5521.190350] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/22/2020
  5. [ 5521.190358] Workqueue: ksmbd-io handle_ksmbd_work [ksmbd]
  6. [ 5521.190446] Call Trace:
  7. [ 5521.190451] <TASK>
  8. [ 5521.190457] dump_stack_lvl+0x48/0x70
  9. [ 5521.190473] print_report+0xd2/0x660
  10. [ 5521.190485] ? __virt_addr_valid+0x103/0x180
  11. [ 5521.190499] ? kasan_complete_mode_report_info+0x8a/0x230
  12. [ 5521.190511] ? smb2_write+0x16e/0x840 [ksmbd]
  13. [ 5521.190596] kasan_report+0xd0/0x120
  14. [ 5521.190606] ? smb2_write+0x16e/0x840 [ksmbd]
  15. [ 5521.190694] __asan_load8+0x8b/0xe0
  16. [ 5521.190704] smb2_write+0x16e/0x840 [ksmbd]
  17. [ 5521.190790] ? _raw_spin_lock+0x82/0xf0
  18. [ 5521.190807] ? __pfx_smb2_write+0x10/0x10 [ksmbd]
  19. [ 5521.190894] ? ksmbd_smb2_check_message+0xa56/0xc90 [ksmbd]
  20. [ 5521.191001] handle_ksmbd_work+0x2a7/0x800 [ksmbd]
  21. [ 5521.191090] process_one_work+0x4d3/0x840
  22. [ 5521.191109] worker_thread+0x91/0x6e0
  23. [ 5521.191124] ? __pfx_worker_thread+0x10/0x10
  24. [ 5521.191135] kthread+0x188/0x1d0
  25. [ 5521.191145] ? __pfx_kthread+0x10/0x10
  26. [ 5521.191154] ret_from_fork+0x44/0x80
  27. [ 5521.191166] ? __pfx_kthread+0x10/0x10
  28. [ 5521.191175] ret_from_fork_asm+0x1b/0x30
  29. [ 5521.191189] </TASK>
  30. [ 5521.191197] Allocated by task 1913:
  31. [ 5521.191203] kasan_save_stack+0x38/0x70
  32. [ 5521.191214] kasan_set_track+0x25/0x40
  33. [ 5521.191223] kasan_save_alloc_info+0x1e/0x40
  34. [ 5521.191231] __kasan_kmalloc+0xc3/0xd0
  35. [ 5521.191240] kmalloc_trace+0x48/0xc0
  36. [ 5521.191249] ksmbd_tree_conn_connect+0x75/0x2c0 [ksmbd]
  37. [ 5521.191335] smb2_tree_connect+0x11d/0x4c0 [ksmbd]
  38. [ 5521.191419] handle_ksmbd_work+0x2a7/0x800 [ksmbd]
  39. [ 5521.191502] process_one_work+0x4d3/0x840
  40. [ 5521.191511] worker_thread+0x91/0x6e0
  41. [ 5521.191520] kthread+0x188/0x1d0
  42. [ 5521.191526] ret_from_fork+0x44/0x80
  43. [ 5521.191534] ret_from_fork_asm+0x1b/0x30
  44. [ 5521.191544] Freed by task 1922:
  45. [ 5521.191549] kasan_save_stack+0x38/0x70
  46. [ 5521.191558] kasan_set_track+0x25/0x40
  47. [ 5521.191567] kasan_save_free_info+0x2b/0x60
  48. [ 5521.191575] ____kasan_slab_free+0x180/0x1f0
  49. [ 5521.191585] __kasan_slab_free+0x12/0x30
  50. [ 5521.191591] slab_free_freelist_hook+0xd2/0x1a0
  51. [ 5521.191599] __kmem_cache_free+0x1a2/0x2f0
  52. [ 5521.191608] kfree+0x78/0x120
  53. [ 5521.191616] ksmbd_tree_conn_disconnect+0x94/0xb0 [ksmbd]
  54. [ 5521.191701] smb2_tree_disconnect+0x183/0x1b0 [ksmbd]
  55. [ 5521.191785] handle_ksmbd_work+0x2a7/0x800 [ksmbd]
  56. [ 5521.191868] process_one_work+0x4d3/0x840
  57. [ 5521.191877] worker_thread+0x91/0x6e0
  58. [ 5521.191886] kthread+0x188/0x1d0
  59. [ 5521.191892] ret_from_fork+0x44/0x80
  60. [ 5521.191913] ret_from_fork_asm+0x1b/0x30

smb2_lock 条件竞争 UAF

下面看一个稍微复杂一点的例子,对于复杂代码我们依然采用一样的策略,关注对象使用、锁、引用计数, smb2_lock 的关键代码如下:

image.png

这里涉及的对象为存放在 conn->lock_list 中的 smb_lock 对象,在 【1】 — 【2】 之间其他线程,通过 SMB2_LOCKFLAG_UNLOCK 进入 【0】 释放 smb_lock 就会导致 UAF.

产生漏洞的本质原因是 [1] 分支提前将 smb_lock 对象放入了 work->conn->lock_list 这样在其释放 llist_lock 后,其他线程就能释放 smb_lock,[2] 处使用的 smb_lock 就是已经被释放的对象。

这也是一种常见的条件竞争常见,即提前将对象放入了共享资源池中,后续使用时一旦被并发释放就会导致 UAF.

panic 日志如下

  1. [ 192.743133] BUG: KASAN: slab-use-after-free in smb2_lock+0x17a7/0x2010 [ksmbd]
  2. [ 192.743228] Write of size 8 at addr ffff88810a5ca028 by task kworker/6:2/76
  3. [ 192.743241] CPU: 6 PID: 76 Comm: kworker/6:2 Tainted: G OE 6.5.4 #1
  4. [ 192.743250] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/22/2020
  5. [ 192.743258] Workqueue: ksmbd-io handle_ksmbd_work [ksmbd]
  6. [ 192.743345] Call Trace:
  7. [ 192.743350] <TASK>
  8. [ 192.743357] dump_stack_lvl+0x48/0x70
  9. [ 192.743372] print_report+0xd2/0x660
  10. [ 192.743384] ? __virt_addr_valid+0x103/0x180
  11. [ 192.743398] ? kasan_complete_mode_report_info+0x8a/0x230
  12. [ 192.743422] ? smb2_lock+0x17a7/0x2010 [ksmbd]
  13. [ 192.743507] kasan_report+0xd0/0x120
  14. [ 192.743518] ? smb2_lock+0x17a7/0x2010 [ksmbd]
  15. [ 192.743605] __asan_store8+0x8e/0xe0
  16. [ 192.743615] smb2_lock+0x17a7/0x2010 [ksmbd]
  17. [ 192.743700] ? xas_descend+0x82/0x130
  18. [ 192.743710] ? __rcu_read_unlock+0x51/0x80
  19. [ 192.743730] ? __pfx_smb2_lock+0x10/0x10 [ksmbd]
  20. [ 192.743814] ? ksmbd_smb2_check_message+0xa56/0xc90 [ksmbd]
  21. [ 192.743902] handle_ksmbd_work+0x2a7/0x800 [ksmbd]
  22. [ 192.743990] process_one_work+0x4d3/0x840
  23. [ 192.744009] worker_thread+0x91/0x6e0
  24. [ 192.744024] ? __pfx_worker_thread+0x10/0x10
  25. [ 192.744035] kthread+0x188/0x1d0
  26. [ 192.744045] ? __pfx_kthread+0x10/0x10
  27. [ 192.744054] ret_from_fork+0x44/0x80
  28. [ 192.744066] ? __pfx_kthread+0x10/0x10
  29. [ 192.744075] ret_from_fork_asm+0x1b/0x30
  30. [ 192.744089] </TASK>
  31. [ 192.744097] Allocated by task 76:
  32. [ 192.744103] kasan_save_stack+0x38/0x70
  33. [ 192.744114] kasan_set_track+0x25/0x40
  34. [ 192.744123] kasan_save_alloc_info+0x1e/0x40
  35. [ 192.744132] __kasan_kmalloc+0xc3/0xd0
  36. [ 192.744141] kmalloc_trace+0x48/0xc0
  37. [ 192.744151] smb2_lock+0x4c6/0x2010 [ksmbd]
  38. [ 192.744233] handle_ksmbd_work+0x2a7/0x800 [ksmbd]
  39. [ 192.744316] process_one_work+0x4d3/0x840
  40. [ 192.744325] worker_thread+0x91/0x6e0
  41. [ 192.744334] kthread+0x188/0x1d0
  42. [ 192.744340] ret_from_fork+0x44/0x80
  43. [ 192.744349] ret_from_fork_asm+0x1b/0x30
  44. [ 192.744358] Freed by task 83:
  45. [ 192.744363] kasan_save_stack+0x38/0x70
  46. [ 192.744372] kasan_set_track+0x25/0x40
  47. [ 192.744382] kasan_save_free_info+0x2b/0x60
  48. [ 192.744390] ____kasan_slab_free+0x180/0x1f0
  49. [ 192.744411] __kasan_slab_free+0x12/0x30
  50. [ 192.744418] slab_free_freelist_hook+0xd2/0x1a0
  51. [ 192.744426] __kmem_cache_free+0x1a2/0x2f0
  52. [ 192.744436] kfree+0x78/0x120
  53. [ 192.744443] smb2_lock+0x1488/0x2010 [ksmbd]
  54. [ 192.744526] handle_ksmbd_work+0x2a7/0x800 [ksmbd]
  55. [ 192.744608] process_one_work+0x4d3/0x840
  56. [ 192.744617] worker_thread+0x91/0x6e0
  57. [ 192.744626] kthread+0x188/0x1d0
  58. [ 192.744632] ret_from_fork+0x44/0x80
  59. [ 192.744641] ret_from_fork_asm+0x1b/0x30
  60. [ 192.744650] Last potentially related work creation:
  61. [ 192.744655] kasan_save_stack+0x38/0x70
  62. [ 192.744664] __kasan_record_aux_stack+0xb3/0xd0
  63. [ 192.744673] kasan_record_aux_stack_noalloc+0xb/0x20
  64. [ 192.744682] kvfree_call_rcu+0x2d/0x4e0
  65. [ 192.744690] kernfs_unlink_open_file+0x18b/0x1a0
  66. [ 192.744699] kernfs_fop_release+0x6d/0x180
  67. [ 192.744707] __fput+0x1e1/0x480
  68. [ 192.744716] ____fput+0xe/0x20
  69. [ 192.744725] task_work_run+0x109/0x190
  70. [ 192.744733] exit_to_user_mode_prepare+0x16b/0x190
  71. [ 192.744743] syscall_exit_to_user_mode+0x29/0x60
  72. [ 192.744755] do_syscall_64+0x67/0x90
  73. [ 192.744764] entry_SYSCALL_64_after_hwframe+0x6e/0xd8
  74. [ 192.744780] Second to last potentially related work creation:
  75. [ 192.744784] kasan_save_stack+0x38/0x70
  76. [ 192.744794] __kasan_record_aux_stack+0xb3/0xd0
  77. [ 192.744802] kasan_record_aux_stack_noalloc+0xb/0x20
  78. [ 192.744811] kvfree_call_rcu+0x2d/0x4e0
  79. [ 192.744819] kernfs_unlink_open_file+0x18b/0x1a0
  80. [ 192.744827] kernfs_fop_release+0x6d/0x180
  81. [ 192.744834] __fput+0x1e1/0x480
  82. [ 192.744842] ____fput+0xe/0x20
  83. [ 192.744851] task_work_run+0x109/0x190
  84. [ 192.744858] exit_to_user_mode_prepare+0x16b/0x190
  85. [ 192.744867] syscall_exit_to_user_mode+0x29/0x60
  86. [ 192.744877] do_syscall_64+0x67/0x90
  87. [ 192.744885] entry_SYSCALL_64_after_hwframe+0x6e/0xd8

smb20_oplock_break_ack 条件竞争 UAF

这个漏洞的模式稍微有点区别,smb20_oplock_break_ack 会在在释放 fp 和 opinfo 的引用后继续使用 opinfo

  1. static void smb20_oplock_break_ack(struct ksmbd_work *work)
  2. {
  3. fp = ksmbd_lookup_fd_slow(work, volatile_id, persistent_id);
  4. opinfo = opinfo_get(fp);
  5. // 【0】use fp and opinfo with refcount
  6. opinfo_put(opinfo);
  7. ksmbd_fd_put(work, fp);
  8. // 【1】use opinfo after drop refcount
  9. opinfo->op_state = OPLOCK_STATE_NONE;
  10. wake_up_interruptible_all(&amp;opinfo->oplock_q);
  11. rsp->StructureSize = cpu_to_le16(24);
  12. rsp->OplockLevel = rsp_oplevel;
  13. rsp->Reserved = 0;
  14. rsp->Reserved2 = 0;
  15. rsp->VolatileFid = volatile_id;
  16. rsp->PersistentFid = persistent_id;
  17. inc_rfc1001_len(work->response_buf, 24);
  18. return;
  19. }

【0】 中的代码是正确的,使用对象时应该要在持有引用计数的情况下使用,避免被其他线程 RACE 释放,但是 【1】 处时 opinfo 的引用计数已经释放,其他线程可以并发释放 opinfo,这样后续对 opinfo 的操作就会导致 UAF.

opinfo 会在 fp 被释放时进行释放,关键调用栈如下:

image.png

smb2_tree_disconnect 条件竞争 UAF

这个漏洞和 smb2_close 条件竞争 UAF 很像,看看关键代码如下:

  1. int smb2_tree_disconnect(struct ksmbd_work *work)
  2. {
  3. struct smb2_tree_disconnect_rsp *rsp;
  4. struct smb2_tree_disconnect_req *req;
  5. struct ksmbd_session *sess = work->sess;
  6. struct ksmbd_tree_connect *tcon = work->tcon;
  7. WORK_BUFFERS(work, req, rsp);
  8. rsp->StructureSize = cpu_to_le16(4);
  9. inc_rfc1001_len(work->response_buf, 4);
  10. if (!tcon || test_and_set_bit(TREE_CONN_EXPIRE, &amp;tcon->status)) {
  11. rsp->hdr.Status = STATUS_NETWORK_NAME_DELETED;
  12. smb2_set_err_rsp(work);
  13. return 0;
  14. }
  15. ksmbd_close_tree_conn_fds(work);
  16. ksmbd_tree_conn_disconnect(sess, tcon);
  17. work->tcon = NULL;
  18. return 0;
  19. }

和 smb2_close 的区别是这里没有读写锁的保护,而是利用 test_and_set_bit 原子操作来避免 tcon 被多次释放,但其实 UAF 的位置也就是 test_and_set_bit

race 场景如下:

  1. 两个线程 A B 同时执行到 test_and_set_bit 前
  2. 线程 A 先执行将 tcon->status 设置为 TREE_CONN_EXPIRE
  3. 并通过 ksmbd_tree_conn_disconnect 释放 work->tcon
  4. 线程 B 执行 test_and_set_bit 时, tcon 已经被释放,导致 UAF

通过这两个例子可以看出,一个正确的 free 逻辑需要考虑的情况比较复杂,漏洞挖掘人员也应该重点关注。

ksmbd_session_lookup_all 条件竞争 UAF

ksmbd_session_lookup_all —> ksmbd_session_lookup 中会在无锁情况访问 sess ,在 [0] [1] 之间其他线程释放 sess 就会 UAF

  1. struct ksmbd_session *ksmbd_session_lookup(struct ksmbd_conn *conn,
  2. unsigned long long id)
  3. {
  4. struct ksmbd_session *sess;
  5. sess = xa_load(&amp;conn->sessions, id);
  6. // [0] race window begin
  7. // [1] race window end
  8. if (sess)
  9. sess->last_active = jiffies;
  10. return sess;
  11. }

总结

本文介绍以 ksmbd 为例介绍如何从0开始分析一个目标,并发现其中可能的条件竞争攻击面,最后结合多个实际的漏洞案例对漏洞挖掘、分析进行讲解。

  • 发表于 2024-11-27 10:00:01
  • 阅读 ( 3613 )
  • 分类:二进制

0 条评论

hac425
hac425

19 篇文章

站长统计