Skip to content

Commit

Permalink
Add kernelCTF CVE-2023-4244_lts (#88)
Browse files Browse the repository at this point in the history
  • Loading branch information
kungfulon authored Apr 26, 2024
1 parent ee45d38 commit 63bb5d1
Show file tree
Hide file tree
Showing 8 changed files with 1,119 additions and 0 deletions.
51 changes: 51 additions & 0 deletions pocs/linux/kernelctf/CVE-2023-4244_lts/docs/exploit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# CVE-2023-4244

## Triggering the vulnerability

First we create a `nft_table`. We add a `nft_chain` called `c_victim` which will be our UaF target object.

Then we add `nft_chain` called `c_primitive`, which hosts a rule that host an `nft_immediate` expression which has `NFT_JUMP` verdict that jumps to `c_victim`. At this moment, `c_victim->use = 1`.

We then add a `nft_set` with GC enabled, and GC interval set to `1`. Then we add a catchall element to the set that has `NFT_JUMP` verdict that jumps to `c_victim` and timeout set to `1`. At this moment, `c_victim->use = 2`.

In the same transaction with the new catchall element, we immediately call delete on the `nft_set`, which will call `nft_map_deactivate` to deactivate all elements and will not remove them from the linked list. At this moment, `c_victim->use = 1`.

After this, GC will most likely kicks in before transaction release phase worker does its job. GC will call `deactivate` on the expired elements that are still in the linked list. The catchall element we added before surely would be expired at this moment. `c_victim->use = 0` now.

Because `c_victim->use = 0`, now we can delete `c_victim` while still having a reference to it from the immediate expression in a rule in `c_primitive` chain, thus achieving UaF condition.

The vulnerable object is of type `nft_chain` which is in `kmalloc-cg-128` cache.

## Leaking data from kernel memory

After triggering the UaF, we have a dangling pointer to the freed `nft_chain` from a `nft_immediate` expression.

We can use `DUMP` request on the rule hosting the expression to read a NULL-terminated string pointed to by `name` field of the `nft_chain` that the expression holds reference to.

We will use heap spray to reclaim the freed `nft_chain` and control `name` field to leak kernel base and kernel heap.

## Leaking kernel base address

We trigger the UaF, then spray fake `nft_chain` objects using `nft_rule` objects.

For each `nft_rule` we create, we add 9 `nft_notrack` expressions to the `nft_rule` so `nft_chain->name` will overlap with `ops` field of an expression, which is `nft_notrack_ops`.

When we read from `name` field of the fake `nft_chain`, we will retrieve address of `nft_notrack_eval` function in the kernel, because `ops->eval` is the first field of `nft_notrack_ops` and it points to `nft_notrack_eval`.

Most of the time the address will not contain NULL bytes so we can leak kernel base address.

## Leaking kernel heap address

First we trigger the UaF. For each spray iteration:

- Create a `nft_set`
- Create a `nft_rule`, add 5 `nft_notrack` expressions and 1 `nft_lookup` expression that binds to the `nft_set` we just created before, so `nft_chain->name` will overlap with `nft_lookup->binding.list.next` of `nft_lookup` expression, which points to the `nft_set`. This rule fits in `kmalloc-cg-128` cache and can be used to reclaim the freed `nft_chain`
- If we only add one binding to the set, when leaking we will read the content of `nft_set->bindings.next`, which points to the lookup expression. So we add another binding to the linked list to leak `&nft_set->bindings` instead. We have to do this with a seperate `nft_rule because adding another lookup object to the same rule above will make the rule bigger than 128 bytes, which is the kmalloc cache that nft_chain objects belong to

After spraying, we try to leak the address of `&nft_set->bindings`. Sometimes it will fail due to NULL bytes so we have to try the whole process again.

## RIP control and getting root shell

We delete all the `nft_set` we created before, then use Cross cache attack to reclaim the whole page that has been returned back to the page allocator. We will store a fake `nft_rule`, JOP gadget for stack pivot and ROP chain which do `commit_creds(prepare_kernel_cred(0))`, `switch_task_namespaces(find_task_by_vpid(1), init_nsproxy)` then jump to KPTI trampoline to get back to userland. The fake `nft_rule` will host a fake `nft_expr` that has a fake `nft_expr_ops` that has `validate` function points to a JOP gadget.

After setting up, we trigger the vulnerability again, spray fake `nft_chain` that has `rules.next` points to the fake rule we just set up in kernel heap, then trigger `validate` on the immediate expression holding the dangling pointer to the freed `nft_chain`. It will call `validate` on the fake chain we just sprayed, recursively call `validate` on the fake rule then `validate` on the fake expression, trigger the JOP gadget, pivot the stack and trigger the ROP chain, then spawn a root shell when getting back to userland.
44 changes: 44 additions & 0 deletions pocs/linux/kernelctf/CVE-2023-4244_lts/docs/vulnerability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# CVE-2023-4244

In nftables we can create sets with timeout for elements. The sets will do GC after a controllable period. Normally if an element is manually released, the busy mark is set so GC won't catch them.

However in commit `628bd3e49cba1c066228e23d71a852c23e26da73` (netfilter: nf_tables: drop map element references from preparation phase), new functions (`nft_map_*deactivate`) were introduced.

These functions do the same job as (`nft_setelem_*deactivate`) functions in order to release references to external objects (chain, obj) when deleting the set, but with fewer checks and operations.

They lack calls to `nft_set_elem_mark_busy` in order to tell GC not to catch the deactivated elements, so it is possible to trigger double deactivation of elements by calling `nft_map_*deactivate` then let GC do its job (which will deactivate expired elements) before the release phase of the transaction (where GC job will be stopped).

## Requirements to trigger the vulnerability

|Capabilities|Kernel configuration|Are user namespaces needed?|
|---|---|---|
|CAP_NET_ADMIN|CONFIG_NF_TABLES|Yes|

## Commit which introduced the vulnerability

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=628bd3e49cba1c066228e23d71a852c23e26da73

## Commit which fixed the vulnerability

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3e91b0ebd994635df2346353322ac51ce84ce6d8

## Affected kernel versions

- 6.5-rc1 - 6.5-rc5
- 6.4 - 6.4.10
- 6.3.10 - 6.3.13
- 6.1.36 - 6.1.55
- 5.15.121 - 5.15.133
- 5.10.188 - 5.10.197

## Affected component, subsystem

netfilter/nf_tables

## Cause

Use-after-free

## Which syscalls or syscall parameters are needed to be blocked to prevent triggering the vulnerability?

Disable the ability to communicate with nf_tables subsystem under unprivileged user namespace, or prevent creation of unprivileged user namespace.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
CFLAGS=-std=gnu17 -Wall -O0 -Ldeps/lib -lnftnl -lmnl -Ideps/include -static

.PHONY: exploit

exploit:
tar -xzf deps.tar.gz && $(CC) -o exploit exploit.c $(CFLAGS) && rm -rf deps

clean:
rm -rf exploit && rm -rf deps
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 63bb5d1

Please sign in to comment.