Skip to content

eBPF improper program verification OOB (CVE-2020-8835)

The verifier's 32-bit jump bounds refinement marked register bits "known" that were not, letting a register the verifier believes is 0 hold 1 at runtime — an OOB read/write primitive (Manfred Paul, Pwn2Own 2020).

Mechanism

Note

Her BPF register durumu umin_value/umax_value (unsigned 64-bit aralık), smin/smax (signed aralık) ve var_off (known/unknown bit'lerden oluşan bir tnum) taşır. Bir 32-bit conditional jump variant'ı (BPF_JMP32) yalnızca alt 32 bit'i karşılaştırır, dolayısıyla verifier register'ın 32-bit görünümünü, tam 64-bit değeri over-constrain etmeden güncellemelidir.

Note

Bir 32-bit conditional jump'tan (is_jmp32) sonra, kernel/bpf/verifier.c içindeki __reg_bound_offset32(), register'ın tnum'unu alt 32 bit'ini tnum_range(reg->umin_value & mask, reg->umax_value & mask) ile kesiştirerek daraltıyordu. Bu, low-32 bounds arasındaki her değerin bounds'un bit pattern'ını paylaştığını yanlışça varsayıyordu. ZDI'nın deyişiyle: umin ve umax'ın ikisinin de 00...01 ile bitmesi, aralarındaki her değerin de öyle bittiği anlamına gelmez. Aslında "known" olmayan bit'ler "known" işaretlenir, dolayısıyla attacker verifier'ın 0 olarak modellediği ama runtime'da 1 olan bir register hazırlar.

Bug'lı daraltma:

static void __reg_bound_offset32(struct bpf_reg_state *reg)
{
    u64 mask = 0xffffFFFF;
    struct tnum range = tnum_range(reg->umin_value & mask, reg->umax_value & mask);
    struct tnum lo32  = tnum_cast(reg->var_off, 4);
    struct tnum hi32  = tnum_lshift(tnum_rshift(reg->var_off, 32), 32);
    reg->var_off = tnum_or(hi32, tnum_intersect(lo32, range));
}

Bu fake-zero'yu bir ölçekle (örneğin 6000) çarpıp bir map-value pointer'ından çıkarmak verification'ı geçer (offset 0 sanılır) ama runtime'da out of bounds'tur.

Hatalı çıkarımın özü — [umin, umax] aralığını tnum_range ile bit-pattern'a çevirmek, uçların paylaştığı bit'leri tüm aralık için "known" sayar:

register value (alt 4 bit), aralık [umin=0b0000 .. umax=0b0011]:

  umin = 0 0 0 0
  umax = 0 0 1 1
         | | | |
         | | | +-- bit gerçekten "known 0/1" değil
         | | +---- bit gerçekten "known" değil
         | +------ üst 2 bit her iki uçta da 0 -> doğru: known 0
         +-------- (tnum_range, üst bit'leri 0 olarak işaretler: OK)

  aralıktaki gerçek değerler: 0000, 0001, 0010, 0011
                                       ^^^^^^^^^^
                               alt 2 bit hem 0 hem 1 olabilir = UNKNOWN

  verifier'ın "0" modeli  : x x 0 0   (alt bit'leri yanlışça 0 sabitledi)
  runtime'da mümkün olan  : x x ? ?   <- buradan "0 sanılan ama 1 olan" register

  not: gerçek tnum/bit genişlikleri kernel sürümüne göre değişir; bu yalnızca
       mantık hatasının kavramsal şemasıdır.

Walkthrough

Exploit, OOB'yi bir BPF map üzerinden arbitrary kernel R/W'ye çevirir:

  1. Register'ı öyle hazırla ki verifier 0 görsün ve runtime 1 görsün; onunla bir OOB map-element pointer'ı inşa et.
  2. Map'in btf pointer'ını OOB-overwrite et, sonra onu BPF_OBJ_GET_INFO_BY_FD üzerinden (btf_id alanı) geri oku — bir arbitrary read.
  3. array_map_ops'u map data'sına kopyala ve kontrollü bir write için map_push_elem'i map_get_next_key'e yeniden yönlendir (gadget *next = index + 1;).
  4. init_pid_ns'ten cred struct'ına kadar yürü ve root için uid/gid'i sıfırla (ya da modprobe_path'i overwrite et).

Warning

Bug, 581738a681b6 ("provide better register bounds after jmp32", mainline 5.5) tarafından eklendi ve f2d67fec0b43'te ("Undo incorrect __reg_bound_offset32 handling") düz bir revert ile düzeltildi. 5.6.1 / 5.5.14 / 5.4.29'da fix'lendi. Unprivileged istismar, unprivileged_bpf_disabled'ın kapalı olmasını gerektirir. Aynı 32-bit ALU bounds mistracking ailesi sonraki CVE'lerde tekrar ortaya çıkar (örneğin CVE-2021-3490).

Mitigation

sysctl kernel.unprivileged_bpf_disabled=1, non-root için attack surface'ı kaldırır; upstream fix bug'lı daraltmayı tamamen kaldırdı. CONFIG_BPF_JIT_ALWAYS_ON ve slab hardening, OOB-sonrası istismar edilebilirliği azaltır ama verifier kusurunun kendisini azaltmaz.

References