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)
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.