Zadanie 2: BPF redactor

Data ogłoszenia: 26.03.2024

Materiały dodatkowe

Wprowadzenie

Redagowanie to proces polegający na dokonywaniu zmian w tekście przed jego publikacją. W systemach informatycznych, termin “redakcja danych” pojawia się w odniesieniu do usuwaniu z plików wrażyliwych danych takich jak hasła.

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 wykonania redkacji pliku w trakcie jego odczytu, aby zapobiec odczytaniu wrażliwych danych przez użytkownika.

Zadanie

Zaimplementować system do redagowania plików podczas ich odczytu. 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 w celu ustalenia czy przy następnych operacjach odczytu wykonanych na pliku zostanie wykonana redakcja. b) w przypadku plików dla których redakcja ma zostać wykonana: read, pread, readv, preadv, preadv2 dokona redakcji oraz zapamięta liczbę zmodyfikowanych bajtów.

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

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

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

  • dostęp (do odczytu) do informacji o pliku z którego czytamy

Liczba znaków zmienionych podczas redakcji każdego pliku powinna być zapamiętana. Do zarządzania redakcją należy zaimplementować następujące wywołania systemowe:

int count_redactions(int fd) - zwraca liczbę zmienionych znaków podczas redakcji danego pliku

int reset_redactions(int fd) - czyści licznik zapamiętanych zmian dla danego pliku

Ustalenia techniczne

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

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

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

struct redactor_ctx {
union {
        struct {
                loff_t offset;
                size_t size;
        };
        struct {
                u64 flags;
                umode_t mode;
                kuid_t uid;
                kgid_t gid;
        };
};
};

Należy dodać dwa punkty podpięcia:

int bpf_redactor_decide(struct redactor_ctx *) - jeśli zwróci dodatnią liczbę, to plik zostanie poddany redakcji

int bpf_redactor_redact(struct redactor_ctx *) - dokona redakcji danych odczytywanych z pliku oraz zwróci liczbę wyredaktowanych; 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ć nowe funkcje dostępne dla nowego typu programów (i tylko dla nich), która umożliwią redakcję przez program BPF.

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

int bpf_copy_from_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ższe funkcje

  • 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. Z uwagi na to, może być wymagane powiększenie obrazu używanego na laboratoriach o kilka lub kilkanaście gigabajtów. 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.

Skompilowanie rozwiązania i testów w maszynie wirtualnej wymaga też instalację pakietów dwarves oraz clang.

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, -hda oraz -append “root=/dev/sda3” wraz z wcześniej skopiowanymi działającymi plikami vmlinuz i initrd (https://qemu-project.gitlab.io/qemu/system/linuxboot.html). Jeśli nie przygotowaliście sobie wcześniej plików vmlinuz i initrd na taką ewentualność, to ewentualnie można je wykopiować z obrazu bazowago używanego na laboratorium.

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.7.6 wygenerowany przez git format-patch` oraz krótki opis rozwiazania.