Zadanie 2: filtr sygnałów¶
Data ogłoszenia: 30.03.2021
Termin oddania: 04.05.2021 (ostateczny 18.05.2021)
Materiały dodatkowe¶
z2-tests.tar
(ostatnia modyfikacja: 27.04.2021)
Wprowadzenie¶
Debugowanie programów w systemach unixopodobnych odbywa się przez użycie
interfejsu ptrace. Możemy za jego pomocą zatrzymać proces, mieć dostęp
do jego przestrzeni adresowej i rejestrów, a także przechwytywać sygnały
przesyłane do programu. Przechwytywanie sygnałów jest ważne, gdyż są
one używane do realizacji breakpointów — debugger może ustawić breakpoint
sprzętowy (przez ustawienie specjalnych rejestrów debugowania) bądź
programowy (przez wstawienie instrukcji breakpoint w kod programu),
a napotkanie go w trakcie wykonania spowoduje dostarczenie przez jądro
sygnału SIGTRAP
do programu. Sygnał ten zostanie przechwycony przez
debugger, który przekaże wtedy kontrolę programiście.
Specjalnym rodzajem breakpointów są breakpointy warunkowe, czyli takie, które powinny zatrzymać program tylko jeśli zachodzi jakiś warunek (np. dana zmienna ma wartość ≥ 13). Klasyczna implementacja takich breakpointów jest dość nieefektywna — jedyne breakpointy obsługiwane przez sprzęt są bezwarunkowe, więc realizacja warunkowych breakpointów polega na dostarczeniu sygnału do debuggera za każdym razem, ewaluacji warunku w debuggerze, po czym wznowienia programu jeśli warunek nie zachodzi. Wymaga to dwóch przełączeń kontekstu na każde trafienie breakpointa.
Na szczęście współczesne wersje jądra mają podsystem eBPF pozwalający na ładowanie prostych fragmentów kodu i wykonywanie ich w jądrze. Można użyć tego mechanizmu do ewaluacji warunku breakpointa bezpośrednio w jądrze, bez konieczności wołania debuggera za każdym razem.
Zadanie¶
Dodać do syscalla ptrace
możliwość wpięcia do śledzonego procesu
filtra sygnałów — programu eBPF, który będzie wykonany przy dostarczaniu
sygnału spowodowanego wyjątkiem procesora do procesu i zadecyduje, czy
faktycznie należy dostarczyć sygnał, czy też go zignorować (tak, że nigdy
nie zostanie dostarczony do debuggera, a program będzie kontynuował
wykonanie).
Tak zainstalowany program eBPF powinien mieć następujące możliwości:
dostęp (tylko do odczytu) do struktury siginfo opisującej sygnał
dostęp (do odczytu i zapisu) do rejestrów procesu
dostęp (do odczytu i zapisu) do przestrzeni adresowej procesu
Ustalenia techniczne¶
Należy dodać nowy typ programów eBPF: BPF_PROG_TYPE_SIGFILTER
(mający numer o 1 większy od BPF_PROG_TYPE_SK_LOOKUP
).
Należy dodać nowy typ podpięcia programów eBPF: BPF_SIGFILTER
(mający numer o 1 większy od BPF_XDP
).
Należy dodać dwie nowe podfunkcje do syscalla ptrace
:
PTRACE_SET_SIGFILTER
(wartość0x420f
): parametrdata
jest (przerzutowanym na wskaźnik) deskryptorem pliku wskazującym na program eBPF typuBPF_PROG_TYPE_SIGFILTER
. Ustawia filtr sygnałów podanego procesu na podany program. Podany proces musi już być śledzony przez wywołujący proces. Jeśli podany proces ma już ustawiony filtr sygnałów, należy go zastąpić podanym. Zwraca 0 lub kod błędu.PTRACE_UNSET_SIGFILTER
(wartość0x4210
): usuwa filtr sygnałów z podanego procesu. Podany proces musi już być śledzony przez wywołujący proces. Zwraca 0 lub kod błędu.
Gdy proces śledzący przestanie śledzić dany proces, należy automatycznie usunąć jego filtr sygnałów.
Nowo utworzone procesy nie powinny mieć filtra sygnałów — nawet procesy utworzone
przez clone
przez proces z aktywnym filtrem sygnałów.
Filtr sygnałów powinien być wykonywany przez jądro podczas dostarczania do procesu
sygnałów wynikających z wyjątku procesora (SIGTRAP
, SIGSEGV
, SIGFPE
, SIGILL
, …).
Dotyczy to tylko sygnałów faktycznie spowodowanych wyjątkiem — nie należy wykonywać filtra
sygnałów w przypadku, gdy np. użytkownik ręcznie wyśle sygnał SIGTRAP
przez syscall kill
.
Podczas dostarczania sygnału dotyczącego wyjątku procesora, który normalnie zostałby dostarczony do procesu śledzącego, należy wywołać program wpięty jako filtr sygnałów dla danego procesu. Jeśli program ten zwróci wynik inny niż 0, należy zignorować sygnał (nie dostarczać go do procesu śledzącego, wznowić wykonanie procesu śledzonego). Jeśli program zwróci wynik 0 (bądź jego wykonanie zakończy się błędem), należy normalnie dostarczyć sygnał.
Filtr sygnałów powinien zostać wywołany ze strukturą siginfo
jako swoim kontekstem.
Powinna to być 32-bitowa struktura jeśli proces śledzący (który zainstalował filtr)
jest 32-bitowy, bądź 64-bitowa struktura jeśli proces jest 64-bitowy.
Należy dodać 3 nowe funkcje dostępne dla nowego typu programów (i tylko dla nich):
int bpf_getregset (unsigned type, unsigned long offset, void *ptr, unsigned long size)
Czyta blok danych z podanego regsetu pod podany wskaźnik (analogicznie do
PTRACE_GETREGSET
, choć bez strukturyiovec
). Zwraca 0 dla udanego odczytu, bądź kod błędu.int bpf_setregset (unsigned type, unsigned long offset, const void *ptr, unsigned long size)
Zapisuje blok danych do podanego regsetu (analogicznie do
PTRACE_SETREGSET
, choć bez strukturyiovec
). Zwraca 0 dla udanego zapisu, bądź kod błędu.int bpf_copy_to_user (void __user *uptr, const void *ptr, unsigned long size)
Wrapper na
copy_to_user
, analogiczny do istniejącej funkcjibpf_copy_from_user
. Zwraca 0 bądź-EFAULT
w przypadku błędu.
Powyższe funkcje powinny mieć kolejne numery po bpf_sock_from_file
.
Nowy typ programów powinien mieć dostęp do następujących funkcji (i żadnych innych):
bazowy wspólny zbiór funkcji
bpf_copy_from_user
powyższe 3 funkcje
Zasady oceniania¶
Za zadanie można uzyskać do 10 punktów. Na ocenę zadania składają się dwie części:
wynik testów (od 0 do 10 punktów)
ocena kodu rozwiązania (od 0 do -10 punktów)
Najczęstsze błędy - spis oznaczeń w USOSwebie¶
Uruchomienie testów wymaga uprawnień roota (-0.0)
Patch niezgodny ze specyfikacją (np. wymagane
-p2
, bądź inny zestaw opcji niż podany w wymaganiach) (-0.1)Warning w
ptrace_check_attach
podczas wykonaniaptrace
(-0.5)Wyciek pamięci przy
bpf_getregset
(-0.3)Wskaźnik void* w
task_struct
zamiast użycia adekwatnego typu (-0.0)Trzymanie deskryptora w
task_struct
zamiaststruct bpf_prog*
(-0.5)Brak zwalniania istniejącego eBPF przy wielokrotnym wykonaniu
PTRACE_SET_SIGFILTER
(-0.3)CPU pinning przy uruchamianiu eBPF (-0.3)
Potencjalne user-after-free: brak czyszczenia wskaźnika
struct bpf_prog*
w procesie potomnym (-0.5)Brak konwersji przekazywanej struktury dla trybu 32-bit (-0.3)
Nieprawidłowy limit przy walidacji uprawnień dostępu do pamięci (-0.2)
Przechowywanie informacji w
task_struct
o trybie (32/64-bit) przy wyłączonymCONFIG_COMPAT
(-0.0)Budowanie kodu w przypadku gdy BPF nie jest włączony w konfiguracji jądra (-0.1)
Wyciek: brak gwarancji zwolnienia programu eBPF przy zakończeniu procesu śledzącego (-0.3)
Problemy z lockami (w sytuacji gdy proces śledzący kończy pracę przed procesem śledzonym) (-0.5)
Forma rozwiązania¶
Jako rozwiązanie należy wysłać paczkę zawierającą:
patcha na jądro w wersji 5.11.2, w jednym z następujących formatów:
patch wygenerowaniy przez diffa z opcjami
-uprN
nakładający się przezpatch -p1
git format-patch
krótki opis rozwiązania
Rozwiązania prosimy nadsyłać na adres p.zuk@mimuw.edu.pl
z kopią
do mwk@mimuw.edu.pl
.
Wskazówki¶
Dobrym miejscem do wpięcia się w proces dostarczania odpowiednich sygnałów jest funkcja force_sig_info_to_task
.