Skip to content
GoogleGHSA-hfqc-63c7-rj9f

Linux Kernel: Vulnerability in the eBPF verifier register limit tracking

MediumCVE-2024-41003 · Published Jul 16, 2024

### Summary A bug in the verifier’s register limit tracking was found by using https://github.com/google/buzzer that allows an attacker to trick the eBPF verifier into thinking a register has a value different from the one it takes when executing the program. Using this bug, an LPE exploit was written allowing the attacker to gain an arbitrary kernel memory R/W primitive that can then be used to gain a full system compromise. ### Severity Moderate - ### Proof of Concept This is a complex operation that involves following the possible branches a bpf program can take and calculate all the possible states a register can take for all given possible branches a program can take. To do this, the verifier has a [bpf_reg_state](https://elixir.bootlin.com/linux/v6.10-rc1/source/include/linux/bpf_verifier.h#L73) structure. Particularly, the verifier keeps track of the possible minimum and maximum values a register can take when it is interpreted as an uint64_t, int64_t, uint32_t, int32_t. Additionally, the verifier keeps track of all the bits that are known to be set on a register, this is tracked in the var_off [field](https://elixir.bootlin.com/linux/v6.10-rc1/source/include/linux/bpf_ver...

GitHub advisory

Affected versions

PackageAffectedFixed in
Kernel
Product
all versionsNo fix yet
Details and references

### Summary A bug in the verifier’s register limit tracking was found by using https://github.com/google/buzzer that allows an attacker to trick the eBPF verifier into thinking a register has a value different from the one it takes when executing the program. Using this bug, an LPE exploit was written allowing the attacker to gain an arbitrary kernel memory R/W primitive that can then be used to gain a full system compromise. ### Severity Moderate - ### Proof of Concept This is a complex operation that involves following the possible branches a bpf program can take and calculate all the possible states a register can take for all given possible branches a program can take. To do this, the verifier has a [bpf_reg_state](https://elixir.bootlin.com/linux/v6.10-rc1/source/include/linux/bpf_verifier.h#L73) structure. Particularly, the verifier keeps track of the possible minimum and maximum values a register can take when it is interpreted as an uint64_t, int64_t, uint32_t, int32_t. Additionally, the verifier keeps track of all the bits that are known to be set on a register, this is tracked in the var_off [field](https://elixir.bootlin.com/linux/v6.10-rc1/source/include/linux/bpf_verifier.h#L143) of bpf_reg_state . Var_off works by keeping a u64 value that acts as a mask, for any given bit that is known to the verifier, the mask will take the value of 0 at that bit’s position and the bit’s value will be tracked in the value member of the var_off. To better illustrate this, here are a few examples from the verifier’s perspective of the limits of a register for a few given operations. For simplicity we will only illustrate the limits for when the register gets interpreted as a signed 32 bit value. U64, S64 and U32 values would follow a similar idea: </span></p><br /><div dir="ltr" style="margin-left:0pt;"> Operation | S32 min value | S32 max value | Var off (value, mask) | Explanation -- | -- | -- | -- | -- R1 = input() | S32_MIN (0x80000000) | S32_MAX (0x7FFFFFFF) | (0, 0xFFFFFFFF) | If R1 takes an arbitrary value from the user, the verifier knows nothing about it, therefore the limits are the absolute maximum/minimum.Note that the mask has all 1’s indicating no bit has known value R1 = 1337 | 1337 | 1337 | (1337, 0x0) | If the verifier is initialized to a constant value, then everything about it is known, and the limits and var off are adjusted accordingly R1 = input()R1 \|= 2 | 0x80000002 | S32_MAX (0x7FFFFFFF) | (2, 0xFFFFFFFD)(hex(FD) == binary(1111 1101) | Here R1 has the second bit set via the or operation. This gives the verifier enough information to update the possible minimum value as well as marking one bit in the var off mask as known After doing some changes to [buzzer](http://github.com/google/buzzer) and implementing a new fuzzing strategy guided by coverage, we noticed the following log ``` REG INVARIANTS VIOLATION (true_reg1): range bounds violation u64=[0xffffffff, 0xfffffffe] s64=[0xffffffff, 0xfffffffe] u32=[0xffffffff, 0xfffffffe] s32=[0xffffffff, 0xfffffffe] var_off=(0xffffffff, 0x0) REG INVARIANTS VIOLATION (true_reg2): range bounds violation u64=[0xffffffffffffffff, 0xfffffffffffffffe] s64=[0xffffffffffffffff, 0xfffffffffffffffe] u32=[0xffffffff, 0xfffffffe] s32=[0xffffffff, 0xfffffffe] var_off=(0xffffffffffffffff, 0x0) REG INVARIANTS VIOLATION (false_reg2): const tnum out of sync with range bounds u64=[0x0, 0xffffffffffffffff] s64=[0x8000000000000000, 0x7fffffffffffffff] u32=[0x0, 0xffffffff] s32=[0x80000000, 0x7fffffff] var_off=(0xffffffffffffffff, 0x0) ``` Upon closer inspection, it became clear that there was a problem with the limit tracking of the registers; The minimum value is greater than the maximum value. And while this ended up not being too relevant for the bug that turned into a vuln, it led us in the right direction. This error code is produced [here](https://elixir.bootlin.com/linux/v6.10-rc2/source/kernel/bpf/verifier.c#L2260), which in turn is call

Severity from
GitHub (reviewed advisory)

More Google advisories

All Google
Advisory
LibRaw: Out of bounds write in LibRaw::sonyParseSR2
MediumAug 14, 2024
TurboMeeting: Post-Authentication Command Injection
High7.2Jul 24, 2024
TurboMeeting: Insecure Password Reset Mechanism
High8.1Jul 24, 2024
TurboMeeting: Boolean-based SQL Injection
Critical9.8Jul 24, 2024
Kioxia: Open JTAG Debug Port
High7.3Jul 16, 2024
Nuvoton / Dell iDRAC: RootBlock
CriticalJun 25, 2024

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.