Zadanie 2: BPF consistency checker

Data ogłoszenia: 28.03.2023

Materiały dodatkowe

Wprowadzenie

Zagwarantowanie poprawności danych jest ważnym elementem wielu systemów informatycznych. Zazwyczaj ciche zwrócenie niepoprawnych danych jest często znacznie gorsze niż zakończenie pracy z błędem. W celu wychwycenia sytuacji w których dane zostały uszkodzone (np. w skutek awarii sprzętu lub błędu oprogramowania), systemy stosują różnego rodzaju kody nadmiarowe.

Współczesne wersje jądra mają podsystem BPF pozwalający na ładowanie prostych fragmentów kodu i wykonywanie ich w jądrze. Możemy wykorzystać ten podsystem do wyliczenia i przechowywania kodów nadmiarowych w wybranych sytuacjach.

Zadanie

Zaimplementować system do wyliczania i sprawdzania kodów nadmiarowych. W tym celu należy dodać nowe wywołania systemowe, a także możliwość wpięcia do kernela mechanizmu sprawdzania — pary programów BPF, które będą wykonane przy syscallach (odpowiednio): a) open, openat, openat2, creat i zadecyduje dla ilu następnych operacji zapisu wykonanych na następnym pliku zostanie wyliczony kod nadmiarowy b) w przypadku plików dla których kody nadmiarowe mają zostać wyliczone: write, pwrite, writev, pwritev, pwritev2 wyliczą wartości kodów nadmiarowych i zapamiętają je.

Tak zainstalowany program BPF powinien mieć następujące możliwości:

  • dostęp (tylko do odczytu) do struktury checker_ctx opisanej dalej

  • dostęp (do odczytu) do pamięci w której znajdują sie zapisywane dane

  • dostęp (do odczytu) do informacji o pliku do którego odbywa się zapis

Wartości kodów nadmiarowych dla każdego pliku powinny być zapamiętane na liście, razem z wartościami przesunięcia i długością dla któregych zostały wyliczone. Do zarządzania kodami nadmiarowymi należy zaimplementować następujące wywołania systemowe:

int last_checksum(int fd, int * checksum, size_t * size, off_t * offset) - zwraca ostatnio wyliczony kod nadmiarowy, razem z jego długością i przesunięciem. int get_checksum(int fd, size_t size, off_t offset, int * checksum) - dla podanej długości i przesunięcia zwraca kod nadmiarowy lub -1 jeśli taki kod nie istnieje int count_checksums(int fd) - zwraca liczbę wyliczonych kodów zapamiętanych dla danego pliku int reset_checksums(int fd) - czyści listę kodów zapamiętanych dla danego pliku

Ustalenia techniczne

Należy dodać nowy typ programów BPF: BPF_PROG_TYPE_CHECKER (mający numer o 1 większy od BPF_PROG_TYPE_SYSCALL).

Należy dodać nowy typ podpięcia programów BPF: BPF_CHECKER (mający numer o 1 większy od BPF_LSM_CGROUP).

Dane przekazywane w kontekście wywołania BPF mają format:

struct checker_ctx {
  union {
     /* write */
     struct {
         loff_t offset;
         size_t size;
     };
     /* open */
     struct {
         u64 flags; /* open flags */
         umode_t mode; /* inode mode */
         kuid_t uid; /* owner */
         kgid_t gid; /* group */
     };
  };
};

Należy dodać dwa punkty podpięcia int bpf_checker_decide(struct checker_ctx *) - dla ilu następnych operacji zapisu wykonanych na następnym pliku zostanie wyliczony kod nadmiarowy (0 oznacza całkowity brak wyliczenia kodów nadmiarowych) int bpf_checker_calculate(struct checker_ctx *) - zwraca wyliczony kod nadmiarowy jeśli procedura przebiegła pomyślnie; w przypadku błędu, wykonywanego syscalla należy przerwać zwracając -EINVAL.

Nowododany typ BPF powinien móc podpiąć się do nich (i tylko do nich) poprzez BPF_RAW_TRACEPOINT_OPEN.

Nie trzeba wspierać w zadaniu funkcji mmap, ani vmsplice, splice, tee, sendfile.

Należy dodać nową funkcję dostępną dla nowego typu programów (i tylko dla nich), która umożliwi kopiowanie pamięci do buforu programu BPF.

int bpf_copy_to_buffer (void *ctx, unsigned long offset, void *ptr, unsigned long size)

Nowy typ programu powinien mieć dostęp do następujących funkcji (i żadnych innych):

  • bazowy wspólny zbiór funkcji

  • powyższa funkcja

  • istniejące funkcje get_current_uid_gid i get_current_pid_tgid

Kompilacja rozwiązania

Uruchomienie rozwiązania wymaga prawidłowo skonfigurowanego jądra, z ustawieniami takimi jak CONFIG_DEBUG_INFO_BTF=y które celowo nie zostały włączone w konfiguracji zzałączonej przy trzecim laboratorium, ponieważ owe opcje wydłużają kompilację oraz zwiększają wymagania na zasoby. Aby przetestować rozwiązanie najłatwiej wykorzystać plik konfiguracyjny załączony w sekcji z materiałami.

Skompilowanie testów wymaga pliku vmlinux.h, który nie został dołączony do testów. Można go wygenerować np. przy pomocy programu bpftool. Natomiast żadne dodatkowe zmiany w załączonej bibliotece libbpf nie są już wymagane.

Wskazówki

Podczas testowania rozwiazązania może się okazać, że wprowadzony przez studenta błąd uniemożliwia bootowanie maszyny wirtualnej. W takiej sytuacji polecam użyć parametrów -kernel, -initrd oraz -append “root=/dev/sda3” wraz z wcześniej skopiowanymi działającymi plikami vmlinuz i initrd.

W celu kompilacji jądra przy użyciu wielu rdzeni należy nie tylko pamiętać o opcji -j do polecenia make, ale również zapenić, że maszyna wirtualna ma możliwość wykorzystania wielu rdzeni (np. przez opcję -smp).

Forma rozwiązania

Jako rozwiązanie należy wysłać paczkę zawierającą patch na jądro w wersji 6.2.1 wygenerowany przez git format-patch` oraz krótki opis rozwiazania.