Live patching jądra: aktualizacja Linuksa bez restartu
Aktualizacja jądra to zwykle łatka plus restart. Na laptopie nie problem, ale na serwerze bazodanowym, hoście z setką maszyn wirtualnych albo klastrze, który ma trzymać uptime, każdy reboot to okno serwisowe, kolejka i ryzyko. Live patching rozwiązuje to inaczej: nakłada poprawkę na działające jądro, bez wyłączania systemu i bez utraty procesów. Poniżej jak to działa pod spodem i czym się to robi w praktyce.
Na czym to polega
Jądro w pamięci to zbiór funkcji. Większość poprawek bezpieczeństwa sprowadza się do podmiany jednej albo kilku z nich: stara funkcja miała błąd, nowa go nie ma. Live patching robi dokładnie to. Nie przepisuje kodu w miejscu, tylko przekierowuje wywołania starej funkcji do nowej wersji, która trafia do jądra jako moduł.
Kluczowy jest ten mechanizm przekierowania. W Linuksie robi to ftrace. Kompilator zostawia na początku każdej funkcji miejsce na instrumentację (te same punkty, których używa ftrace do śledzenia, o czym pisaliśmy przy okazji ftrace). Podsystem livepatch podpina tam własny handler, który przechwytuje wejście do funkcji, zanim ta ruszy stos czy argumenty, i przeskakuje do nowej wersji. Stara funkcja fizycznie zostaje w pamięci, po prostu nikt już do niej nie wchodzi.
Model spójności, czyli najtrudniejsza część
Podmiana pojedynczej funkcji jest łatwa. Problem zaczyna się, gdy łatka zmienia kilka funkcji naraz albo zmienia sposób, w jaki funkcje się między sobą dogadują. Nie można pozwolić, żeby jeden proces był w połowie na starym kodzie, a drugi na nowym, jeśli te wersje mają się różnić semantyką.
Dlatego livepatch używa hybrydowego modelu spójności per-task. Po włączeniu łatki jądro wchodzi w stan przejściowy: każde zadanie jest migrowane osobno, w bezpiecznym momencie. Zadania śpiące sprawdzane są przez analizę stosu (jeśli żadna łatana funkcja nie leży na stosie, zadanie można przełączyć), zadania w przestrzeni użytkownika przełączają się przy powrocie z wywołania systemowego, a wątki bezczynne przy okazji pętli idle. Postęp przejścia widać w /sys/kernel/livepatch/<łatka>/transition oraz per proces w /proc/<pid>/patch_state. Dopiero gdy wszystkie zadania przejdą, łatka jest w pełni aktywna.
Ten model dołączył do mainline w Linuksie 4.9. Sam podsystem livepatch (CONFIG_LIVEPATCH) trafił do jądra wcześniej, w 4.0 z 2015 roku, jako wspólny mianownik dwóch niezależnych rozwiązań: kpatch od Red Hata i kGraft od SUSE.
Krótka historia i kto co robi
- Ksplice (2008) był pierwszy. Powstał jako projekt na MIT, potem kupił go Oracle i zamknął. Dziś łata praktycznie tylko Oracle Linux i jest częścią płatnego wsparcia.
- kpatch (Red Hat, 2014) to natywne rozwiązanie dla RHEL, CentOS Stream, Almy i Rocky. Otwarte, ale nie automatyczne: łatki instaluje się samemu.
- kGraft (SUSE, 2014) to odpowiednik po stronie SUSE, dziś jako SUSE Linux Enterprise Live Patching.
- Canonical Livepatch to usługa dla Ubuntu, wpięta w Ubuntu Pro. Automatyczna, ale tylko dla Ubuntu.
- KernelCare od TuxCare gra inaczej: jeden agent obsługujący kilkadziesiąt dystrybucji naraz (RHEL, Debian, Ubuntu, Oracle, Alma, Rocky, Amazon Linux). Wygodne w środowiskach mieszanych, gdzie nie chce się utrzymywać osobnego narzędzia pod każdą dystrybucję.
Wspólny mianownik jest taki, że kpatch, kGraft i Canonical Livepatch stoją na tym samym upstreamowym podsystemie livepatch. Różnią się głównie tym, kto buduje i dostarcza łatki oraz na jakiej dystrybucji.
W praktyce: budowa i załadowanie łatki
Na rodzinie Red Hata narzędziem jest kpatch-build. Bierze zwykłego diffa źródeł jądra i sam składa z niego moduł livepatch, wyłuskując tylko zmienione funkcje.
# instalacja
sudo dnf install kpatch kpatch-build
# zbudowanie modułu z patcha (kpatch-build sam pobierze pasujące źródła)
kpatch-build -t vmlinux cve-fix.patch
# załadowanie na gorąco
sudo kpatch load livepatch-cve-fix.ko
# lista aktywnych łatek
kpatch list
Podsystem widać też bezpośrednio w sysfs:
ls /sys/kernel/livepatch/
cat /sys/kernel/livepatch/livepatch_cve_fix/enabled # 1 = aktywna
cat /sys/kernel/livepatch/livepatch_cve_fix/transition # 0 = przejście zakończone
Na Ubuntu z Ubuntu Pro nie buduje się niczego ręcznie, wystarczy sprawdzić status usługi:
canonical-livepatch status
Nowsze łatki potrafią być kumulatywne: moduł z flagą atomic replace zastępuje wszystkie wcześniejsze łatki naraz, co upraszcza zarządzanie stosem poprawek. Jeśli łatka musi dołożyć pole do istniejącej struktury jądra, robi się to przez zmienne cieniowe (shadow variables), bo samej struktury nie da się rozszerzyć w locie.
Ograniczenia
Live patching to nie magia i nie zastępuje aktualizacji:
- Tylko funkcje. Załatasz błąd w kodzie funkcji. Nie zmienisz układu struktury danych, formatu na dysku ani ABI. Shadow variables łagodzą to częściowo, ale nie każdą zmianę da się tak obejść.
- Nie każda poprawka się nadaje. Duże zmiany architektoniczne albo takie dotykające inicjalizacji jądra po prostu wymagają restartu.
- Łatana funkcja musi być śledzona przez ftrace. Kod niewidoczny dla ftrace jest poza zasięgiem, są też konflikty z kretprobe na tych samych funkcjach.
- To rozwiązanie pomostowe. Odracza restart, nie kasuje go. Jądro w pamięci robi się coraz bardziej odklejone od tego na dysku, więc okno serwisowe i tak kiedyś trzeba zaplanować, choćby po to, żeby zejść na w pełni świeże jądro.
Kiedy to ma sens
Wszędzie tam, gdzie restart jest drogi, a okno bezpieczeństwa liczy się w godzinach: bazy danych, hosty wirtualizacji, węzły klastra, systemy z twardym SLA na dostępność. Krytyczne CVE w jądrze, na przykład świeża rodzina DirtyClone, można zamknąć w minuty, bez kolejkowania reboota na weekend. Na maszynach o podwyższonym ryzyku warto live patching łączyć z modułem wykrywającym manipulacje jądra w czasie działania, takim jak LKRG: jeden łata, drugi pilnuje.
Jeśli budujesz własne jądro, pamiętaj, że po stronie konfiguracji potrzeba CONFIG_LIVEPATCH (oraz CONFIG_DYNAMIC_FTRACE_WITH_REGS), a do pełnego modelu spójności także CONFIG_HAVE_RELIABLE_STACKTRACE na wspieranej architekturze. Jak włączać opcje jądra, opisaliśmy przy kompilacji jądra.
Źródła: Dokumentacja jądra: livepatch, Red Hat: what is Linux kernel live patching, TuxCare: live patching tools overview