Sploitus

Exploit for aeBPF2-analysis

githubexploit · 2026-09-08

Exploit Code

README250 lines
## https://sploitus.com/exploit?id=315DA6E7-4779-50B5-BC34-65282F75B8A3
# aeBPF2, eBPF verifier scalar-ID pruning vulnerability

[한국어 문서](README_IN_KOREAN.md)

Linux kernel version: Linux/ARM64 5.15.94 (custom challenge kernel)

## Introduction

This repository analyzes and exploits an eBPF verifier state-pruning vulnerability
in the DreamHack `aeBPF2` challenge kernel. The custom verifier backports scalar-ID
propagation and equality-based range refinement, but its state-equivalence check
does not compare scalar IDs correctly.

An unprivileged socket-filter program can make the verifier prove that a scalar is
zero while the JIT executes the same register with an attacker-controlled value.
The exploit converts this verifier/runtime disagreement into a heap disclosure,
corrupts two adjacent `bpf_array` objects, builds reusable arbitrary kernel
read/write primitives, patches the current process credentials, and reads
`/dev/vda`.

The complete exploit is available in [`exploit.c`](exploit.c). A source-by-source
record of the material used during analysis is in
[`REFERENCES.md`](REFERENCES.md).

## Environment

| Item | Value |
| --- | --- |
| Architecture | ARM64 |
| Kernel | Linux 5.15.94, custom verifier |
| Memory | 64 MiB |
| BPF program type | `BPF_PROG_TYPE_SOCKET_FILTER` |
| Initial privilege | uid/euid 1000 |
| Unprivileged BPF | enabled |
| BPF JIT | always enabled |
| KASLR | enabled |
| Kernel hardening | BTI, PAC, `panic_on_warn=1` |
| Target | root-only `/dev/vda` |

## Vulnerability analysis

### Scalar IDs and equality propagation

The verifier tracks relationships between scalar registers with an `id` field.
In this kernel, a 64-bit register copy can assign an ID to the source and copy it
to the destination. A later equality branch narrows both operands and
`find_equal_scalars()` propagates the refined bounds to every register with the
same ID.

The relevant state is conceptually represented as follows:

```c
struct bpf_reg_state {
    enum bpf_reg_type type;
    s32 off;
    /* pointer metadata */
    u32 id;
    u32 ref_obj_id;
    struct tnum var_off;
    s64 smin_value, smax_value;
    u64 umin_value, umax_value;
    s32 s32_min_value, s32_max_value;
    u32 u32_min_value, u32_max_value;
    /* liveness and precision metadata */
};
```

The custom `regsafe()` implementation accepts two imprecise scalar states as
equivalent without checking their scalar IDs. It also lacks an ID check in its
precise scalar range comparison. Therefore two states with identical bounds and
tnums but different relationships to other registers can be merged.

Simplified vulnerable logic:

```c
if (old->type == SCALAR_VALUE) {
    if (!old->precise && !cur->precise)
        return true;                 /* scalar ID is ignored */

    return range_within(cur, old) &&
           tnum_in(old->var_off, cur->var_off);
                                      /* scalar ID is still ignored */
}
```

### Constructing the state mismatch

The BPF program creates two control-flow paths that agree on scalar bounds but
disagree on the relationship between `R7` and `R8`.

```text
Path A, verified first:
    R8 = R7                 R7.id == R8.id

Path B, taken at runtime:
    R8 = map_value[16]      R8 has an unrelated ID and attacker value W

CACHE:
    Path B is pruned because regsafe() ignores the ID mismatch

After CACHE:
    if R7 != R9: exit
    R8 s>>= 3
```

`R9` is constrained to `[4, 6]`. On the equal branch, the verifier intersects
`R7` with `R9`; `find_equal_scalars()` then narrows `R8` to `[4, 6]` because it
uses Path A's shared ID. An arithmetic right shift by three consequently makes
the verifier treat `R8` as the constant zero.

At runtime Path B was taken and `R8` is still `W >> 3`. The exploit stores
`(desired_delta ops
paciasp
autiasp
ldr x0, [x0]       // fake_ops[0]
ret
```

Using a legitimate BTI landing pad is required because the target enables ARM64
Branch Target Identification. The fake tables also retain valid `map_free` and
`map_release_uref` callbacks to avoid crashing during normal BPF reference
handling.

### Phase 3: reusable arbitrary read/write

The two corrupted maps have different roles:

| Map | Fake lookup result | Purpose |
| --- | --- | --- |
| victim 0 | `victim0_fake_ops[0]` | returns the selected kernel address |
| victim 1 | `victim0_fake_ops` | rewrites victim 0's target slot |

A normal configuration map supplies a new 64-bit target address. A verified BPF
store through victim 1 updates `victim0_fake_ops[0]`. A lookup through victim 0
then returns that address while the verifier still treats it as a valid
`PTR_TO_MAP_VALUE`.

This design requires only one heap overwrite. Subsequent arbitrary reads and
writes reuse three already loaded BPF programs.

### Phase 4: locate the current credentials

Ghidra analysis of the target `vmlinux` produced these addresses and offsets:

| Symbol or field | Pre-KASLR value / offset |
| --- | ---: |
| `array_map_ops` | `0xffffffc008773308` |
| `init_task` | `0xffffffc008a40a00` |
| BTI dereference gadget | `0xffffffc008410d70` |
| `task_struct.tasks` | `0x300` |
| `task_struct.pid/tgid` | `0x408` |
| `task_struct.real_cred` | `0x5b0` |
| `task_struct.cred` | `0x5b8` |
| `task_struct.comm` | `0x5c0` |

Starting at `init_task + kaslr_slide + 0x300`, the exploit follows the circular
task list. It compares both 32-bit values at `task + 0x408` with `getpid()` and
then reads the matching task's `cred` pointer.

The final write clears 24 bytes beginning at `cred + 0x10`. This covers `sgid`,
`euid`, `egid`, `fsuid`, `fsgid`, and `securebits`. The process retains real uid
1000 but gains effective and filesystem uid 0, which is sufficient to open the
root-only block device.

```text
[+] current task=0xffffffXXXXXXXXXX cred=0xffffffXXXXXXXXXX
[+] credentials patched: uid=1000 euid=0 gid=1000 egid=0
[+] FLAG: DH{REDACTED}
```

## Relation to CVE-2023-2163

CVE-2023-2163 is the closest public vulnerability pattern. Its unsafe eBPF
branch pruning also lets the verifier discard a path whose runtime state is not
equivalent, leading to arbitrary kernel read/write and privilege escalation.

The published CVE-2023-2163 exploit turns a verifier/runtime scalar mismatch into
a corrupted spilled stack pointer with `skb_load_bytes_relative()`. This custom
kernel rejects pointer spills for unprivileged BPF with:

```text
pointer spill to stack is allowed only to CAP_PERFMON and CAP_SYS_ADMIN
```

Consequently, the stack-pointer method is not usable here. This exploit preserves
the pruning idea but builds different primitives from a ring-buffer disclosure,
a packet-to-map heap overwrite, and fake map operations.

## PoC and testing

### Build

An ARM64 musl cross compiler is used to avoid runtime library dependencies.

```sh
make CROSS_COMPILE=/opt/homebrew/bin/aarch64-linux-musl-
```

Equivalent command:

```sh
/opt/homebrew/bin/aarch64-linux-musl-gcc \
  -static -Os -s -o exploit exploit.c
```

### Prepare the initramfs

```sh
mkdir -p /tmp/aebpf-rootfs
cd /tmp/aebpf-rootfs
gzip -dc /path/to/rootfs.cpio.gz | cpio -idmv
cp /path/to/aeBPF2-analysis/exploit ./exploit
chmod 755 ./exploit
find . -print0 | cpio --null -ov --format=newc | \
  gzip -9 > /tmp/aebpf-rootfs.cpio.gz
```

### Run QEMU

```sh
qemu-system-aarch64 \
  -initrd /tmp/aebpf-rootfs.cpio.gz \
  -kernel Image.gz \
  -M virt -cpu max -smp cores=1,threads=1 \
  -append 'console=ttyAMA0 root=/dev/ram oops=panic panic=1 panic_on_warn=1 quiet' \
  -no-reboot -monitor /dev/null -net none -vga none -nographic -m 64M \
  -drive file=flag,format=raw,if=virtio
```

Run `/exploit` from the uid 1000 shell. KASLR should remain enabled because the
heap disclosure calculates the slide dynamically.

## Rejected approaches

| Approach | Result |
| --- | --- |
| Direct map-pointer OOB arithmetic | neutralized by ALU sanitization |
| CVE-2023-2163 stack spill | rejected for unprivileged BPF |
| `modprobe_path` overwrite | overwrite worked; static usermode helper disabled |
| piped `core_pattern` | kernel reported `Core dump to \|/exploit disabled` |

## References

- [Detailed references and analysis provenance](REFERENCES.md)
- [Google Security Research: CVE-2023-2163 eBPF verifier bug](https://github.com/google/security-research/security/advisories/GHSA-j87x-j6mh-mv8v)
- [Linux fix: `bpf: Fix incorrect verifier pruning due to missing register precision taints`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=71b547f561247897a0a14f3082730156c0533fed)
- [Linux eBPF verifier documentation](https://github.com/torvalds/linux/blob/master/Documentation/bpf/verifier.rst)
- [Linux 5.15 BPF verifier source](https://github.com/torvalds/linux/blob/v5.15/kernel/bpf/verifier.c)
- [Linux 5.15 ARM64 BPF JIT source](https://github.com/torvalds/linux/blob/v5.15/arch/arm64/net/bpf_jit_comp.c)