eBPF od zera, programowalne jądro Linux
Kprobes pozwalają wstrzyknąć kod w dowolne miejsce jądra, ale robisz to, pisząc moduł kernela. Jeden błąd i masz kernel panic. eBPF rozwiązuje ten problem: ładujesz mały program do jądra, a weryfikator gwarantuje, że nie wykona niczego niebezpiecznego. To dziś podstawa nowoczesnej obserwowalności, sieci i bezpieczeństwa w Linuksie.
Czym jest eBPF?
eBPF (extended Berkeley Packet Filter) to maszyna wirtualna wewnątrz jądra. Pozwala uruchamiać niewielkie programy w przestrzeni jądra, podpięte pod zdarzenia, bez rekompilacji kernela i bez restartu. Z grubsza działa tak:
- Piszesz program w C kompilowanym do bytecode’u BPF albo w wyższym języku przez
bpftrace. - Ładujesz go wywołaniem systemowym
bpf(). - Weryfikator sprawdza go przed uruchomieniem: brak nieograniczonych pętli, brak dostępu poza zakresem pamięci, gwarancja zakończenia, brak wycieku wskaźników jądra do userspace.
- JIT kompiluje bytecode do natywnego kodu maszynowego, więc narzut jest minimalny.
- Program podpina się pod hook: kprobe, tracepoint, XDP, gniazdo sieciowe, LSM, perf event, cgroup czy uprobe.
To właśnie weryfikator odróżnia eBPF od modułu jądra. Moduł może wszystko, łącznie z wywaleniem systemu. Program eBPF, który nie przejdzie weryfikacji, po prostu się nie załaduje.
Mapy: komunikacja z userspace
Program eBPF nie działa w próżni. Wymienia dane z przestrzenią użytkownika przez mapy (BPF maps): tablice haszujące, tablice, ring buffery. Program w jądrze zapisuje, narzędzie w userspace czyta. Tak powstają liczniki, histogramy i strumienie zdarzeń.
CO-RE i BTF: przenośność
Klasyczny problem kprobes to niestabilny ABI: nazwy i układ struktur jądra zmieniają się między wersjami. eBPF rozwiązuje to przez CO-RE (Compile Once, Run Everywhere) i BTF (BPF Type Format). Kernel z CONFIG_DEBUG_INFO_BTF=y udostępnia własne typy w /sys/kernel/btf/vmlinux, a program eBPF dopasowuje się do nich w czasie ładowania. Jeden binarny program działa na wielu wersjach jądra.
Wymagania
Większość współczesnych dystrybucji ma eBPF włączone. Kluczowe opcje:
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_DEBUG_INFO_BTF=y # dla CO-RE
Sprawdzisz tak:
grep -E 'CONFIG_BPF_SYSCALL|CONFIG_DEBUG_INFO_BTF' /boot/config-$(uname -r)
ls /sys/kernel/btf/vmlinux # istnieje, jeśli BTF jest dostępny
Do pracy przydadzą się pakiety bpftrace, bpfcc-tools (zestaw narzędzi BCC) i bpftool.
bpftrace, najszybsze wejście
bpftrace to język jednolinijkowców do eBPF. Nie musisz pisać ani kompilować kodu w C. Przykład, pokaż każdy uruchamiany proces w systemie:
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
printf("%s -> %s\n", comm, str(args->filename));
}'
Użyliśmy tracepointa, a nie kprobe. Tracepointy to stabilny interfejs. W przeciwieństwie do nazw funkcji jądra nie zmieniają się przy każdej wersji, więc skrypt jest przenośny.
Policz wywołania systemowe per program przez 10 sekund:
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
# Ctrl+C kończy i wypisuje posortowane liczniki
Histogram opóźnień odczytu z dysku:
sudo bpftrace -e '
tracepoint:block:block_rq_issue { @start[args->dev, args->sector] = nsecs; }
tracepoint:block:block_rq_complete /@start[args->dev, args->sector]/ {
@us = hist((nsecs - @start[args->dev, args->sector]) / 1000);
delete(@start[args->dev, args->sector]);
}'
Gotowe narzędzia BCC
Pakiet bpfcc-tools to kilkadziesiąt gotowych programów eBPF, których nie musisz pisać sam:
sudo execsnoop-bpfcc # każdy nowy proces (jak exec w czasie rzeczywistym)
sudo opensnoop-bpfcc # kto otwiera jakie pliki
sudo tcpconnect-bpfcc # nowe połączenia TCP wychodzące
sudo biolatency-bpfcc # histogram opóźnień we/wy bloku
sudo tcpretrans-bpfcc # retransmisje TCP (diagnoza problemów sieci)
To samo co strace czy tcpdump, ale dla całego systemu naraz i z pomijalnym narzutem. Jeśli interesuje Cię szersze profilowanie, zobacz 5 narzędzi do diagnostyki wydajności.
Sieć: XDP
XDP (eXpress Data Path) to program eBPF podpięty najwcześniej jak się da, w sterowniku karty sieciowej, zanim jądro zbuduje sk_buff. Dla każdego pakietu zwraca werdykt: XDP_PASS, XDP_DROP albo XDP_TX. Dzięki temu osiąga miliony pakietów na sekundę, dlatego stoi za rozwiązaniami anty-DDoS (Cloudflare, Katran od Meta) i siecią kontenerów (Cilium).
Podejrzyj, czy na interfejsie wisi program XDP:
ip link show dev eth0 # szukaj "xdp" w wyjściu
sudo bpftool net show # programy sieciowe (XDP, tc) per interfejs
Gotowe filtry znajdziesz w projekcie xdp-tools, a tc z eBPF pozwala robić to samo nieco dalej w stosie sieciowym, z dostępem do pełnego pakietu.
Bezpieczeństwo runtime
Skoro eBPF widzi każde wywołanie systemowe, każde połączenie i każde uruchomienie procesu, to idealny fundament pod wykrywanie ataków:
- Falco: reguły wykrywające podejrzane zachowania (shell w kontenerze, zapis do
/etc/passwd, odczyt/etc/shadow). - Tetragon (Cilium) i Tracee (Aqua): obserwacja i egzekwowanie polityk na poziomie jądra.
- LSM-BPF (
CONFIG_BPF_LSM=y): pozwala pisać moduły Linux Security Module jako programy eBPF. To programowalny AppArmor, decydujesz w jądrze, czy dana operacja ma prawo się wykonać.
W odróżnieniu od logów z userspace, ataku trudniej tu uniknąć, bo zdarzenie jest przechwytywane w jądrze, w momencie wykonania.
eBPF to też powierzchnia ataku
Skoro program eBPF działa w jądrze, błąd w samym mechanizmie oznacza eskalację uprawnień. Weryfikator to kawał skomplikowanego kodu i sam bywał źródłem podatności, choćby CVE-2021-3490 i seria LPE przez błędy śledzenia zakresów wartości. Dwa wnioski praktyczne:
- Wyłącz nieuprzywilejowany eBPF. Ustaw
kernel.unprivileged_bpf_disabled = 1(lub2) oraznet.core.bpf_jit_harden = 2. W konfiguracji jądra włączCONFIG_BPF_UNPRIV_DEFAULT_OFF=y. Dzięki temubpf()wymagaCAP_BPF/roota. - Pamiętaj o rootkitach eBPF. Programy typu
kprobeczyfmod_retpotrafią ukrywać pliki, procesy i połączenia, co robią rodziny złośliwego oprogramowania jak BPFDoor czy Symbiote.
Czy Twój kernel jest poprawnie utwardzony pod kątem eBPF (i nie tylko), sprawdzisz w Kernel Security Checker. Zakładki konfiguracji, sysctl i CVE pokrywają wszystkie powyższe ustawienia.
eBPF vs strace vs ftrace
| strace | ftrace | eBPF | |
|---|---|---|---|
| Zakres | jeden proces | jądro | jądro + sieć + userspace |
| Narzut | wysoki | niski | bardzo niski |
| Programowalność | brak | mała | pełna |
| Przenośność | wysoka | średnia | wysoka (CO-RE) |
| Bezpieczeństwo | bezpieczny | bezpieczny | weryfikator BPF |
strace jest świetny do jednego procesu, ftrace i kprobes do jądra, ale to eBPF łączy wszystko: obserwowalność, sieć i bezpieczeństwo, z narzutem, na który możesz sobie pozwolić na produkcji. Jeśli masz uruchomić tylko jedną rzecz, zacznij od bpftrace. Kilka jednolinijkowców i widzisz, co naprawdę robi Twój system.