# CVE-2022-2588
###### tags: `CVE` `Note` `MCL` `poc` `DirtyCred`
> ** RESERVED ** This candidate has been reserved by an organization or individual that will use it when announcing a new security problem. When the candidate has been publicized, the details for this candidate will be provided.
:::info
affact kernel [`v3.17`, `v?`)
:::
<small>以下 code 都是以 linux kernel `v5.16.15` 為例</small>
## Abstract
Dirty Cred 是一個提權漏洞,能夠繞過 kernel 中 cred 的檢查機制,使用時需搭配能夠寫入 heap 相關的漏洞。
## Background
### Out Of Bound Write (OOB)
越界寫入,寫到了本來不應該寫入的位置。
:::info
單純的 `OOB` 無法利用,原因是我們需要有寫入,否則只有 read 也只能洩漏資料
:::
### Use After Free (UAF)
對於 free 掉的記憶體,卻還是拿來使用
:::spoiler UAF
```c=
#include <stdio.h>
#include <stdlib.h>
typedef struct person
{
long int height, weight, age;
} person_t;
int main(void)
{
person_t *p1, *p2;
p1 = (person_t*) malloc(sizeof(person_t));
p1->age = 18;
printf("Before free, p1->age: %ld\n", p1->age);
free(p1);
# p1 已經 free 掉了,但還是拿來用
printf("After free, p1->age: %ld\n", p1->age);
return 0;
}
```
```console
$ gcc UAF.c -o UAF
$ ./UAF
> Before free, p1->age: 18
> After free, p1->age: 18
```
:::
### Double Free
對於同一塊記憶體 free 兩次,導致 free list 連續兩塊是同樣地址,導致下次 malloc 取出的 chunk 同時也存在於 free list 中
:::spoiler double free (on glibc 2.35)
```c=
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
unsigned long int *p1, *p2, *p3[7];
p1 = (unsigned long int*) malloc(0x20);
p2 = (unsigned long int*) malloc(0x20);
// 塞滿 tcache
for (int i=0;i<7;i++)
p3[i] = (unsigned long int*) malloc(0x20);
for (int i=0;i<7;i++)
free(p3[i]);
// fast bin 無檢查
free(p1);
free(p2);
free(p1);
return 0;
}
```
:::
### Linux File
`struct file` 描述了 file 的基本訊息,其中 `struct cred` 是表示 file 的權限,如果能夠修改到,等同可以串改整個 file 的權限。
::: info
file 結構只保存已經被打開的 file 的訊息,因此沒有 open 權限的不會有 file 結構
:::
:::spoiler kernel code
[`struct file`](https://elixir.bootlin.com/linux/v5.15.16/source/include/linux/fs.h#L965)
```c=962
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
/*
* Protects f_ep, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
enum rw_hint f_write_hint;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner;
# 權限相關
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct hlist_head *f_ep;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
errseq_t f_wb_err;
errseq_t f_sb_err; /* for syncfs */
} __randomize_layout
__attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
```
:::
### Linux Credentials
存放 task 中的權限訊息,例如 `SUID` `SGID` `SBIT`,如果能夠修改到這個結構,等同可以串改整個 task 的權限。
:::spoiler kernel code
[`struct cred`](https://elixir.bootlin.com/linux/v5.15.16/source/include/linux/cred.h#110)
```c=110
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
# 各個權限
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct ucounts *ucounts;
struct group_info *group_info; /* supplementary groups for euid/fsgid */
/* RCU deletion */
union {
int non_rcu; /* Can we skip RCU deletion? */
struct rcu_head rcu; /* RCU deletion hook */
};
} __randomize_layout;
```
:::
### slab & slob & slub
> (Todo)
#### slab
主要解決 buddy system 一次都是要 1 個 page 大小的問題,在原先 buddy system 上再多上 slab 機制,讓小於 page 大小 的空間都由 slab 去分配。
#### slob
可以把它想成 slab 的縮小版,專門用於 memory 比較小的設備,ex: IoT,因此內容比較精簡,但更容易記憶體碎片化
#### slub
slab 的加強版,解決了 slab 的問題,為目前 linux 預設的 slab allocator
1. 每個 node 有 3 個 list,分別記錄空閒、部分空閒、非空閒 slab,當來不及回收時,三個 list 紀錄的會長時間留在 slab 管理器中,因此 slub 只保留部分空閒 slab
2. 每個 cpu 紀錄的是 object 的地址,這些 object 可能來自不同的 slab,不利於 slab 的回收,slub 改成紀錄一個實際可用的 slub,不影響其他 slub 的回收
3. shared共享链表可能导致一个slab持有较多slab,无法即使释放给伙伴系统。slub去掉了该链表。可见,slub出现的主要目的是为了减少slab的数量,提高内存的使用率。同时,出于对内存使用率的极致追求,slub去除了slab的着色做法,取而代之是slub复用,通过slub复用减轻cache冲突的情况
## Methology
此攻擊手法的關鍵點在於**把 `unprivileged credential 換成 privileged credential`**,至於如何做到,需要根據搭配的漏洞加以構造 payload。
### OOB write

將原先指向 unprivileged 的 `struct cred` 透過修改 pointer 的方式,改指向 privileged 的 `struct cred`,但 page 是 4k(0x1000) 對齊,所有只有 1/16 的成功率。
### UAF
1. 如果 UAF 發生在 `privileged credential` 上,那就只要 free 掉要攻擊的 `unprivileged credential` 然後換成 `privileged credential` 就好
2. 如果 UAF 發生在 `unprivileged credential` 上,那需搭配 invalid write 的漏洞
### Double Free

1. allocate 一個 vulnerable object(ptr1 和 ptr2 都指向它)
2. free ptr1(ptr2 指向它)
3. allocate 另一個 vulnerable object(new_ptr1, new_ptr2, ptr2 都指向它)
4. free ptr2(new_ptr1, new_ptr2 都指向它)
5. 清掉整條 memory cache
6. 讓這條 memory cache 是 allocate credential object 的
7. free new_ptr2
8. allocate 新的 credential object(vuln)
接下來的手法就和 UAF 如出一轍
此外還有個重點,因為 Dirty Cred 需要在檢查 `cred` 與寫入 data 之間,將 `unprivileged credential 換成 privileged credential`,而此步需要花時間做,因此若能延長這中間的時間,勢必可以提高攻擊成功機率。
### Extending time window
#### Userfaultfd
userfaultfd 是 linux 提供給 user space 處理 page fault 的機制,因此我們可以自行造成 page fault,並且自己處理,那麼就能自行拖延時間了。
但在 linux kernel `v4.13` 做了一點修改,在 Dirty Cred 所需要用的的 `vfs_writev()` 中
,將權限檢查移到了從 user space 拿資料之後,導致無法再利用。
:::spoiler kernel code
[`fs/read_write.c`](https://elixir.bootlin.com/linux/v4.12/source/fs/read_write.c#L988)
`v4.12`
```c=988
ssize_t vfs_writev(struct file *file, const struct iovec __user *vec,
unsigned long vlen, loff_t *pos, int flags)
{ // 權限檢查
if (!(file->f_mode & FMODE_WRITE))
return -EBADF;
if (!(file->f_mode & FMODE_CAN_WRITE))
return -EINVAL;
// 寫入
return do_readv_writev(WRITE, file, vec, vlen, pos, flags);
}
```
`v4.13`
[`fs/read_write.c`](https://elixir.bootlin.com/linux/v4.13/source/fs/read_write.c#L964)
```c=964
ssize_t vfs_writev(struct file *file, const struct iovec __user *vec,
unsigned long vlen, loff_t *pos, int flags)
{
struct iovec iovstack[UIO_FASTIOV];
struct iovec *iov = iovstack;
struct iov_iter iter;
ssize_t ret;
ret = import_iovec(WRITE, vec, vlen, ARRAY_SIZE(iovstack), &iov, &iter);
if (ret >= 0) {
file_start_write(file);
ret = do_iter_write(file, &iter, pos, flags);
file_end_write(file);
kfree(iov);
}
return ret;
}
```
:::
:::danger
在 linux kernel `v5.11` 開始,禁止了 userfaultfd,因為他們認為在 page fault 本該就是 kernel space 該處理的事情,拉到 user space 會有安全上的疑慮。 [reference](https://lwn.net/Articles/819834/)
:::
#### Lock in filesystem
當 process 對檔案進行寫入時,會將 inode 上鎖,防止另一個 process 也要寫入,但是權限的**判斷**是不會上鎖的,因此可以有如下操作:
1. 第一支 process 大量寫入文件,讓 lock 時間長一點
2. 第二支 process 試圖寫內容進文件,觸發權限檢查
3. 第三支 process 利用漏洞將替換掉整個 `struct file`
::: spoiler kernel code
以 ext4 為例
[`ext4_file_operations`](https://elixir.bootlin.com/linux/v5.15.16/source/fs/ext4/file.c#L915)
```c=915
const struct file_operations ext4_file_operations = {
.llseek = ext4_llseek,
.read_iter = ext4_file_read_iter,
// 看 write
.write_iter = ext4_file_write_iter,
.iopoll = iocb_bio_iopoll,
.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ext4_compat_ioctl,
#endif
.mmap = ext4_file_mmap,
.mmap_supported_flags = MAP_SYNC,
.open = ext4_file_open,
.release = ext4_release_file,
.fsync = ext4_sync_file,
.get_unmapped_area = thp_get_unmapped_area,
.splice_read = generic_file_splice_read,
.splice_write = iter_file_splice_write,
.fallocate = ext4_fallocate,
};
```
[`ext4_file_write_iter`](https://elixir.bootlin.com/linux/v5.15.16/source/fs/ext4/file.c#L666)
```c=666
static ssize_t
ext4_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct inode *inode = file_inode(iocb->ki_filp);
if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
return -EIO;
#ifdef CONFIG_FS_DAX
if (IS_DAX(inode))
return ext4_dax_write_iter(iocb, from);
#endif
if (iocb->ki_flags & IOCB_DIRECT)
return ext4_dio_write_iter(iocb, from);
else
return ext4_buffered_write_iter(iocb, from);
}
```
[`ext4_buffered_write_iter`](https://elixir.bootlin.com/linux/v5.15.16/source/fs/ext4/file.c#L253)
```c=253
static ssize_t ext4_buffered_write_iter(struct kiocb *iocb,
struct iov_iter *from)
{
ssize_t ret;
struct inode *inode = file_inode(iocb->ki_filp);
if (iocb->ki_flags & IOCB_NOWAIT)
return -EOPNOTSUPP;
ext4_fc_start_update(inode);
inode_lock(inode);
ret = ext4_write_checks(iocb, from);
if (ret <= 0)
goto out;
current->backing_dev_info = inode_to_bdi(inode);
ret = generic_perform_write(iocb->ki_filp, from, iocb->ki_pos);
current->backing_dev_info = NULL;
out: // 在寫入之前都會先做 lock
inode_unlock(inode);
ext4_fc_stop_update(inode);
if (likely(ret > 0)) {
iocb->ki_pos += ret;
ret = generic_write_sync(iocb, ret);
}
return ret;
}
```
:::
#### Filesystem in Userspace (FUSE)
FUSE 是一個 unix 類的介面,可以讓沒有權限編 kernel 的使用者建立自己的檔案系統,那麼使用者就能註冊一個 handler,來指定對文件的操作。
## Conclusion
Dirty Cred 並不是屬於程式邏輯的 bug,而是利用 race condition。利用時需要搭配其餘與 heap 相關的漏洞才能達成,所以利用層面廣,但是也是有些限制在的。
目前可利用的 structure 如下圖,其中星號是有 `struct file`,十字架是有 `struct cred`

目前搭配 CVE 如下圖,其中星號是有同時有 `double free` 和 `UAF`,十字架是有 `OOB`

## Poc
* 攻擊流程
1. 攻擊者在 kernel heap 上有多個 unprivileged task
2. 用其他漏洞將其中一個 task free 掉,等待其他 privileged task 填入
3. 等到 linux kernel 將一個 privileged task 填入
> 如果是攻擊 file,就在權限檢查後將 `struct file` 換掉,變成去寫入另一個檔案
> 如果是攻擊 cred,將 `struct cred` 換掉後,變成 `privileged cred` 即可
* 使用限制
1. 攻擊者操作 linux os 的權限
2. 有一個 heap 的漏洞
## Reference
[DirtyCred](https://dl.acm.org/doi/pdf/10.1145/3548606.3560585)