============================ Zadanie 2: BPF consistency checker ============================ Data ogłoszenia: 28.03.2023 Materiały dodatkowe =================== - Plik .config: :download:`CONFIG_Z2` - Testy: :download:`z2-tst-public.tar.gz` 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: .. code-block:: c 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.