Skip to content
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-2024-41009_lts_cos #118

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
110 changes: 110 additions & 0 deletions pocs/linux/kernelctf/CVE-2024-41009_lts_cos/docs/exploit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Background
`BPF_FUNC_ringbuf_reserve` is used to allocate a memory chunk from `BPF_MAP_TYPE_RINGBUF`. It reverses 8 bytes space to record header structure.
```C
/* 8-byte ring buffer record header structure */
struct bpf_ringbuf_hdr {
u32 len;
u32 pg_off;
};
```
And return `(void *)hdr + BPF_RINGBUF_HDR_SZ` for ebpf program to use. ebpf program is unable to modify `bpf_ringbuf_hdr` due to it is outside of memory chunk.

But with malformed `&rb->consumer_pos`, it's possible to make second allocated memory chunk overlapping with first chunk.
As the result, ebpf program is able to edit first chunk's hdr.

For example, we create a `BPF_MAP_TYPE_RINGBUF` with size is 0x4000. Modify `consumer_pos` to 0x3000 before call `BPF_FUNC_ringbuf_reserve`.

Allocate chunk A, it will be in `[0x0,0x3008]`, and ebpf program is able to edit `[0x8,0x3008]`. Now allocate chunk B with size 0x3000, it will sucess because we edit consumer_pos ahead to pass the check. Chunk B will be in `[0x3008,0x6010]`, and ebpf program is able to edit `[0x3010,0x6010]`.

```C
/* check for out of ringbuf space by ensuring producer position
* doesn't advance more than (ringbuf_size - 1) ahead
*/
if (new_prod_pos - cons_pos > rb->mask) {
spin_unlock_irqrestore(&rb->spinlock, flags);
return NULL;
}
```

Due to ringbuf memory layout in the following description.
```C
/* Each data page is mapped twice to allow "virtual"
* continuous read of samples wrapping around the end of ring
* buffer area:
* ------------------------------------------------------
* | meta pages | real data pages | same data pages |
* ------------------------------------------------------
* | | 1 2 3 4 5 6 7 8 9 | 1 2 3 4 5 6 7 8 9 |
* ------------------------------------------------------
* | | TA DA | TA DA |
* ------------------------------------------------------
* ^^^^^^^
* |
```

`[0x0,0x4000]` and `[0x4000,0x8000]` points to same data pages.
It means that chunk B at `[0x4000,0x4008]` is chunk A's hdr.

`BPF_FUNC_ringbuf_submit`/`BPF_FUNC_ringbuf_discard` use hdr's pg_off to locate the meta pages.

```C
bpf_ringbuf_restore_from_rec(struct bpf_ringbuf_hdr *hdr)
{
unsigned long addr = (unsigned long)(void *)hdr;
unsigned long off = (unsigned long)hdr->pg_off << PAGE_SHIFT;

return (void*)((addr & PAGE_MASK) - off);
}
static void bpf_ringbuf_commit(void *sample, u64 flags, bool discard)
{
unsigned long rec_pos, cons_pos;
struct bpf_ringbuf_hdr *hdr;
struct bpf_ringbuf *rb;
u32 new_len;

hdr = sample - BPF_RINGBUF_HDR_SZ;
rb = bpf_ringbuf_restore_from_rec(hdr);
```

# Exploit
We modify `pg_off` of chunk A to `2`, so the meta pages that calculated with `bpf_ringbuf_restore_from_rec` will point to our controlled content at mmap-ed consumer pos data.
```C
static void bpf_ringbuf_commit(void *sample, u64 flags, bool discard)
{
...
rb = bpf_ringbuf_restore_from_rec(hdr);
...

if (flags & BPF_RB_FORCE_WAKEUP)
irq_work_queue(&rb->work);\
...
```
By crafting `work` field inside `bpf_ringbuf` and call `bpf_ringbuf_commit` with `BPF_RB_FORCE_WAKEUP` it will call our crafted `irq_work` object to `irq_work_queue`.
Crafted irq_work will processed at `irq_work_single` and will execute our controlled function pointer.
```C
void irq_work_single(void *arg)
{
struct irq_work *work = arg;
int flags;

flags = atomic_read(&work->node.a_flags);
flags &= ~IRQ_WORK_PENDING;
atomic_set(&work->node.a_flags, flags);

...
lockdep_irq_work_enter(flags);
work->func(work); // [1]
lockdep_irq_work_exit(flags);
...
}
```

# KASLR Bypass
To bypass kASLR we refer to this [technique](https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2023-6817_mitigation/docs/exploit.md#kaslr-bypass).

# ROP Chain
By observation we see RBX/RDI will contain the address of `work` field and we can control the ROP data started at `RDI + 0x18`. Then, we use this ROP gadget for stack pivot to our controlled data.
```
0x00000000004b78b1 : push rbx ; or byte ptr [rbx + 0x41], bl ; pop rsp ; pop r13 ; pop rbp ; ret
```
Then we continue to execute ROP payload that will overwrite `core_pattern` to our exploit. By trigger crash it will execute our exploit as high privileged.
12 changes: 12 additions & 0 deletions pocs/linux/kernelctf/CVE-2024-41009_lts_cos/docs/vulnerability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
- Requirements:
- Capabilites: NA
- Kernel configuration: CONFIG_BPF_SYSCALL=y
- User namespaces required: No
- Introduced by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=457f44363a8894135c85b7a9afd2bd8196db24ab
- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit?id=cfa1a2329a691ffd991fcf7248a57d752e712881
- Affected Version: v5.8 - v6.9
- Affected Component: bpf
- Syscall to disable: bpf
- URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-41009
- Cause: Buffer overlapping
- Description: A buffer overlapping vulnerability in the Linux kernel's bpf ringbuf. It is possible to make a second allocated memory chunk overlapping with the firstchunk and as a result, the BPF program is able to edit the first chunk's header. Once first chunk's header is modified, then bpf_ringbuf_commit() refers to the wrong page and could cause a crash. We recommend upgrading past commit cfa1a2329a691ffd991fcf7248a57d752e712881
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
exploit: exploit.c
$(CC) -O3 -ggdb -static -Wall -lpthread -o $@ $^

real_exploit: exploit.c
$(CC) -O3 -ggdb -static -Wall -lpthread -DKASLR_BYPASS_INTEL=1 -o exploit $^
Binary file not shown.
Loading
Loading