Attacker Value
High
(1 user assessed)
Exploitability
Low
(1 user assessed)
User Interaction
None
Privileges Required
Low
Attack Vector
Local
2

CVE-2021-3490

Disclosure Date: June 04, 2021
Add MITRE ATT&CK tactics and techniques that apply to this CVE.
Privilege Escalation
Techniques
Validation
Validated

Description

The eBPF ALU32 bounds tracking for bitwise ops (AND, OR and XOR) in the Linux kernel did not properly update 32-bit bounds, which could be turned into out of bounds reads and writes in the Linux kernel and therefore, arbitrary code execution. This issue was fixed via commit 049c4e13714e (“bpf: Fix alu32 const subreg bound tracking on bitwise operations”) (v5.13-rc4) and backported to the stable kernels in v5.12.4, v5.11.21, and v5.10.37. The AND/OR issues were introduced by commit 3f50f132d840 (“bpf: Verifier, do explicit ALU32 bounds tracking”) (5.7-rc1) and the XOR variant was introduced by 2921c90d4718 (“bpf:Fix a verifier failure with xor”) ( 5.10-rc1).

Add Assessment

1
Ratings
Technical Analysis

So this is a pretty interesting bug in the Linux Kernel from versions 5.7.x to 5.13-rc4. It occurs due to kernel/bpf/verifier.c having a function named adjust_scalar_min_max_vals which is responsible for adjusting the minimum and maximum values an eBPF register can represent, which is used as part of eBPF bounds tracking of registers to make sure that when users submit eBPF programs for the kernel to run, they don’t end up going out of bounds and leaking kernel memory or doing other nasty stuff.

adjust_scalar_min_max_vals has a few switch statements for various types of operations, such as BPF_AND, BPF_OR, BPF_SUB, etc. For the purpose of this we will focus just on the AND vulnerability but the OR vulnerability was also introduced at the same time, and the BPF_XOR vulnerability was introduced with 5.10-rc1 of the Linux kernel.

asdf
asdf
asdf
asdf
asdfasdfasdf
asdf
asdfasfd
asdf
asdfa
sfasdf
asdf
adsf
asfd
asfd
asdf
asfdasfd

I have rated the exploitation of this vulnerability as fairly difficult as whilst chompie1337 did push out a PoC for this bug which we have used to transform it into a Metasploit module, and this is fairly platform agnostic, the actual exploitation details involve a lot of fairly advanced concepts and many steps to get successful code execution to work.

That being said the value of this is high as pretty much any Linux system you can think of is going to have eBPF enabled unless they have explicitly disabled it via sudo sysctl kernel.unprivileged_bpf_disabled=1 which will prevent unprivileged users from being able to load eBPF programs. This setting however is not default and will need to be applied to each affected machine so I’d imagine some people will either not be aware of this, or will end up deploying machines and forget that this setting needs to be set at some point, opening up the door for exploitation of this and similar issues.

When executing BPF_AND’s switch code, as can be seen at https://github.com/torvalds/linux/blob/c9e73e3d2b1eb1ea7ff068e05007eec3bd8ef1c9/kernel/bpf/verifier.c#L7575-L7579, Of particular note is the call to scalar32_min_max_and and the lines at https://github.com/torvalds/linux/blob/c9e73e3d2b1eb1ea7ff068e05007eec3bd8ef1c9/kernel/bpf/verifier.c#L7083-L7093.

In this case the code will call tnum_subreg_is_const(src_reg->var_off); to check if the source register’s lower 32 bits are known, and tnum_subreg_is_const(dst_reg->var_off); to check if the destination register’s lower 32 bits are known. If both the source register’s lower 32 bits are known and the destination register’s lower 32 bits are known, then it assumes that scalar64_min_max_and will be called (it will) and that its safe to skip updating the registers for the known 32 bit values.

However here lies a problem, as if we look at scalar_min_max_and, which is the official name for scalar64_min_max_and, we see something odd in its code at https://github.com/torvalds/linux/blob/c9e73e3d2b1eb1ea7ff068e05007eec3bd8ef1c9/kernel/bpf/verifier.c#L7116-L7127. Specifically it uses tnum_is_const(src_reg->var_off); and tnum_is_const(dst_reg->var_off); to determine if the full contents of the 64 bit source and destination registers are known. Only if the full 64 bit content of the registers are known, does it then mark the destination register as having a known value.

This leads to an edge case where if the operation involves registers where the upper 32 bits are unknown but the lower 32 bits are known, then we can make eBPF improperly verify the register as the 32 bit version of the call will assume the 64 bit version of the call will have properly validated the register’s contents, however in reality the register will not be verified since its upper 32 bits are still unknown.

The patch fixes this by ensuring that scalar32_min_max_and and related affected functions now call __mark_reg32_known on the destination register before returning if the lower 32 bits of the destination and source registers are known. The contents of this function can be seen below:

/* Mark the unknown part of a register (variable offset or scalar  * value) as known to have the value @imm.
*/
static void __mark_reg32_known(struct bpf_reg_state *reg, u64 imm)
{
    reg->var_off = tnum_const_subreg(reg->var_off, imm);
    reg->s32_min_value = (s32)imm;
    reg->s32_max_value = (s32)imm;
    reg->u32_min_value = (u32)imm;
    reg->u32_max_value = (u32)imm;
}

Essentially this code will ensure that all the signed and unsigned minimum and maximum versions of the register are properly set, ensuring the correct bounds will be conserved when calling the final boundary updating functions are called to determine the 64 bit value, whereas before when calling the __update_reg_bounds, __reg_deduce_bounds and __reg_bound_offset functions within adjust_scalar_min_max_vals, a condition could occur whereby due to some internal logic on how to calculate these values, there could be a case where the bounds would be incorrectly determined.

More details on exactly how this is done can be found in the The Vulnerability section of chompie1337’s excellent paper at https://www.graplsecurity.com/post/kernel-pwning-with-ebpf-a-love-story, but suffice to say its possible to make a register with a maximum 32 bit value of 0 and a minimum 32 bit value of 1, thereby severely messing up the logic of the bounds tracker.

CVSS V3 Severity and Metrics
Base Score:
7.8 High
Impact Score:
5.9
Exploitability Score:
1.8
Vector:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Attack Vector (AV):
Local
Attack Complexity (AC):
Low
Privileges Required (PR):
Low
User Interaction (UI):
None
Scope (S):
Unchanged
Confidentiality (C):
High
Integrity (I):
High
Availability (A):
High

General Information

Vendors

  • Linux

Products

  • Linux kernel
Technical Analysis