Skip to content

eBPF verifier ALU sign-extension bypass (CVE-2017-16995)

check_alu_op(), 32-bit bir MOV immediate'ini sanki 64-bit'miş gibi sign-extend ediyordu; bu yüzden track edilen register değeri gerçek runtime değerinden sapıyor ve bounds check'ler atlatılarak OOB kernel R/W elde ediliyordu (get-rekt, Jann Horn).

Mechanism

Note

check_alu_op() içinde (kernel/bpf/verifier.c), BPF_MOV/BPF_K path'i bilinen immediate'i operation width'ini ayırt etmeden kaydediyordu. insn->imm signed bir s32 olduğu için pratikte her durumda sign-extend ediliyor, böylece BPF_ALU64|MOV|K (64-bit'e sign-extend — doğru) ile BPF_ALU|MOV|K (üst 32 bit'i zero-pad etmesi gereken 32-bit bir MOV) birbirine karışıyordu. Dolayısıyla verifier'ın track ettiği constant (ve ondan türeyen min/max bounds), CPU'nun 32-bit bir MOV için hesapladığı değerle uyuşmuyordu.

High bit'i set olan bir immediate'te ikisi birbirinden ayrışır: runtime'da 32-bit bir MOV r2, 0xffffffff 0x00000000ffffffff (4294967295) verir, ama bug'lı verifier bunu 0xffffffffffffffff (yani -1) olarak modelliyor, register'ı küçük negatif bir constant gibi görüyordu. Verifier daha sonra sonraki aritmetik ve memory access'leri yanlış değere göre doğrular. Böyle bir register pointer aritmetiğinde offset olarak kullanıldığında, verifier'ın in-range sandığı access runtime'da çok dışarıda kalır — bir out-of-bounds read/write primitive'i.

Walkthrough

Fix tam olarak kusuru açığa çıkarır — daha önce koşulsuz olan sign-extension ALU class'ına göre ikiye ayrılır:

-    __mark_reg_known(regs + insn->dst_reg, insn->imm);
+    if (BPF_CLASS(insn->code) == BPF_ALU64)
+        __mark_reg_known(regs + insn->dst_reg, insn->imm);
+    else
+        __mark_reg_known(regs + insn->dst_reg, (u32)insn->imm);
Mistracking trace (get-rekt-linux-hardened.c)
BPF_MOV32_IMM(BPF_REG_2, 0xFFFFFFFF)        ; runtime r2 = 0x00000000FFFFFFFF
BPF_JMP_IMM(BPF_JNE, BPF_REG_2, 0xFFFFFFFF, 2)
BPF_MOV64_IMM(BPF_REG_0, 0)
BPF_EXIT_INSN()
; verifier belief: r2 sign-extended to 0xFFFFFFFFFFFFFFFF (-1), "small negative const"
; runtime value:   r2 == 4294967295

Weaponization: mistrack edilen register'ı bir map-value pointer üzerinden yapılan load veya store'da index/offset olarak kullan. Verifier access'i in-bounds onaylar; runtime'da pointer map element'in dışına düşer, kernel memory'sini okur ya da yazar. get-rekt exploit'i bu OOB R/W'yi root'a yükseltir (örn. cred field'larını ya da modprobe_path'i overwrite ederek), ve sadece-aritmetik bypass hiçbir userspace pointer dereference edilmediği için SMEP/SMAP'i es geçer.

Warning

Fix commit 95a762e2c8c9 ("bpf: fix incorrect sign extension in check_alu_op", Jann Horn), Fixes: 484611357c19. Bug v4.9'dan itibaren mevcuttur ve etkilenen kernel'larda (özellikle v4.14) kernel.unprivileged_bpf_disabled set edilmediği sürece unprivileged olarak exploit edilebilir. NVD'nin kaba version aralığı ("through 4.4") hatalıdır; asıl belirleyici commit geçmişidir (4.9+).

Mitigation

sysctl kernel.unprivileged_bpf_disabled=1. Upstream fix 32-bit ALU op'ları için immediate'i u32'ye maskeler, böylece track edilen değer runtime semantiğiyle uyuşur.

References