eBPF tail_call OOB (CVE-2022-2905)¶
The eBPF verifier's range analysis over-approximated the
bpf_tail_callindex, so a program could callbpf_tail_callwith a key larger than the prog-array'smax_entries; the x86 JIT then indexedbpf_array->ptrout of bounds, a slab out-of-bounds read into kernel heap.
Mechanism¶
Over-approximate edilmiş bir index safety kontrolünden neden kaçar
bpf_tail_call(ctx, &prog_array, index), bir eBPF program'ından bir
BPF_MAP_TYPE_PROG_ARRAY map'inin index slot'unda saklanan başka bir
program'a atlar. Arkadaki depo bpf_array->ptr, map oluşturulurken tam
olarak max_entries slot için allocate edilir, dolayısıyla her erişim
index < max_entries koşulunu sağlamalıdır.
bpf_tail_call iki şekilde compile edilir:
indexknown bir sabit değilse, kernel interpreter'a düşer; bu da açık bir runtime bound kontrolü yapar (if (index >= array->map.max_entries) return;).- Verifier
index'in aralık içinde bir sabit olduğunu kanıtlarsa, x86 JIT verifier'ın kanıtına güvenerek runtime bound kontrolü olmadanbpf_array->ptr[index]'i index'leyen hızlı bir inline yol yayımlar.
Bug, o kanıttadır. Verifier'ın value-range analizi, gerçek değer kümesini
over-approximate eden tnum ve min/max bounds kullanır. Index known bir
sabit olduğunda verifier onu tnum_range(0, map->max_entries - 1) ve
tnum_in(range, reg->var_off) ile doğruluyordu. Ama bir tnum range, kesin
bir aralık değil bir bitmask superset'idir: tnum_range(0, 2), {0,1,2}
yerine {0,1,2,3}'ü temsil eden 00XX verir. Bu yuvarlama yüzünden, aslında
max_entries'ten büyük bir key "aralık içinde" sayılabiliyordu — yani
verifier onun güvenli bir sabit index olduğuna inanıyordu. Dolayısıyla interpreter mode'a zorlamak (güvenli fallback) ya
da program'ı reddetmek yerine, verifier onu kabul etti ve JIT'in
kontrolsüz hızlı yolu yayımlamasına izin verdi. BRF yazarlarının deyişiyle:
"calling bpf_tail_call with a key larger than the max_entries
does not violate the semantic rules enforced by the verifier ... since
the value range analysis mechanism in the verifier over-approximates the
range, a key larger than the max_entries could be deemed within the
range."
Runtime'da JIT'lenmiş kod key > max_entries ile bpf_array->ptr[key]'i
hesaplar ve slab allocation'ının sonunun ötesinden bir bpf_prog * okur —
kontrollü bir slab out-of-bounds read ve bir information-leak / potansiyel
type-confusion primitive'i. Red Hat advisory'si bunu şöyle özetler: "A
bpf_tail_call with a key larger than the max_entries of the
map can cause an out-of-bound access when the x86 JIT compiler tries to
index bpf_array->ptr using the invalid key."
CVE-2022-2905, kök nedenin bir tnum range-analizi kusuru olduğunu bulan
BRF (the eBPF Runtime Fuzzer) tarafından keşfedildi. Fix commit'i
a657182a5c51 ("bpf: Don't use tnum_range on array range checking for poke
descriptors", Daniel Borkmann), Fixes: d2e4c1e6c294; sabit index'i artık
doğrudan max_entries'e karşı karşılaştırır. v6.0-rc4'te fix'lenmiştir
(backport'lar: stable 5.10.140 / 5.15.64 / 5.19.6). Etki information
disclosure'dır (CVSS 5.5). Not: CVE-2022-49985 aynı patch için duplicate
bir kayıttır — karıştırmayın.
Walkthrough¶
BRF'nin yayımladığı proof-of-concept minimaldir: küçük bir max_entries ile
bir prog-array oluştur, sonra onun ötesinde bir index ile tail-call yap.
// Figure 7, BRF paper (simplified PoC of CVE-2022-2905)
DEFINE_BPF_MAP(map_0,
BPF_MAP_TYPE_PROG_ARRAY, // map type
uint32_t, // key type
uint32_t, // value type
36 // max_entries
);
SEC("cgroup/sock_create")
int func(struct bpf_sock *ctx) {
bpf_tail_call(ctx, &map_0, 49); // 49 > 36 == max_entries
return 0;
}
1. Prog-array'i oluştur max_entries = 36 ile; kernel bpf_array->ptr'yi
36 bpf_prog * slot için allocate eder.
2. Out of bounds tail-call yap. Literal key 49, 36'yı aşar. Zafiyetli
kernel'de verifier'ın over-approximate edilmiş aralığı 49'u "aralık içinde"
sayar, dolayısıyla program reddedilmek ya da interpreter'a düşürülmek yerine
kabul edilip JIT-compile edilir.
3. Runtime'da OOB read. Program çalıştığında, JIT'lenmiş hızlı yol
bpf_array->ptr[49]'u index'ler ve allocation'ın sonunun 13 slot ötesinden
bir pointer okur:
slab object: [ ptr[0] ptr[1] ... ptr[35] ] | <adjacent slab memory>
read index 49 ------------------------------------------^ OOB
4. Gözlemle / doğrula. Bir KASAN kernel'de erişim bir slab out-of-bounds read olarak raporlanır (CVE özeti "slab-out-of-bound read in bpf" ile uyumlu):
==================================================================
BUG: KASAN: slab-out-of-bounds in ... (bpf_tail_call / array_map ...)
Read of size 8 at addr ffff8881........ by task poc/PID
==================================================================
Leak'lenen pointer komşu kernel object'lerine işaret eder; heap grooming ile
birleştirildiğinde bu bir info-leak (KASLR'ı etkisizleştirir) hâline gelir ya
da OOB slot'u attacker-etkili bir bpf_prog-biçimli object tutacak şekilde
yapılırsa, bir control-flow primitive'i olur.
Detection¶
- Verifier-acceptance bug'ı: kötü niyetli bir program zafiyetli kernel'lerde
hatasız load olur, dolayısıyla load zamanında tespit edin. Unprivileged
context'lerden gelen
BPF_PROG_LOAD'u denetleyin vemax_entries'e yakın/ üzerinde sabit key'lerle yapılan prog-array tail call'ları işaretleyin. - KASAN build'leri slab OOB'yi doğrudan trigger zamanında raporlar.
- Yamalı kernel'ler program'ı reddeder ya da interpreter mode'a zorlar; daha önce kabul edilen bir program'ın aniden reddedilmeye kayması fix'i gösterir.
Mitigation¶
- v6.0-rc4'e ya da fix'i içeren bir backport'a yamalayın (örneğin stable 5.19.6).
kernel.unprivileged_bpf_disabled=1ayarlayarak eBPF program'ları load etmek için root /CAP_BPFgerektirin, böylece unprivileged attack surface'ı kaldırın.- Container'larda
bpfsyscall'ını seccomp ile bloklayın veCAP_BPF/CAP_SYS_ADMIN'i drop edin.