# 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 ![](https://i.imgur.com/98jLmjS.png) 將原先指向 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 ![](https://i.imgur.com/NaPBU2s.png) 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` ![](https://i.imgur.com/PPW3nYK.png) 目前搭配 CVE 如下圖,其中星號是有同時有 `double free` 和 `UAF`,十字架是有 `OOB` ![](https://i.imgur.com/9yCWYno.png) ## 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)