2. Tworzenie zasady piaskownicy

Gdy masz wykonawcę, prawdopodobnie warto zdefiniować zasady piaskownicy dla użytkownika piaskownicy. W przeciwnym razie użytkownik piaskownicy jest chroniony tylko przez domyślne zasady systemu Syscall.

Zasady piaskownicy umożliwiają ograniczenie wywołań syscall i argumentów, które może utworzyć użytkownik piaskownicy, oraz do plików, do których ma dostęp. Potrzebujesz dogłębnej wiedzy na temat wywołań syscall wymaganych przez kod, który zamierzasz umieścić w piaskownicy. Jednym ze sposobów obserwowania wywołań syscall jest uruchamianie kodu za pomocą paska narzędzi wiersza poleceń systemu Linux.

Gdy masz już listę wywołań syscall, możesz zdefiniować tę zasadę za pomocą obiektu PolicyBuilder. PolicyBuilder jest wyposażony w wiele funkcji pomocniczych i ułatwiających wygodne wykonywanie wielu typowych operacji. Poniższa lista to tylko mały wycinek dostępnych funkcji:

  • Dodaj dowolne wywołanie syscall do listy dozwolonych na potrzeby uruchamiania procesu:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • Dodaj do listy dozwolonych wszystkie otwarte wywołania systemu /read/write*:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • Dodaj do listy dozwolonych wszystkie wywołania systemowe związane z wyjściem, dostępem i stanem:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • Dodaj do listy dozwolonych wszystkie połączenia systemowe związane ze snem/czasem:
    • AllowTime();
    • AllowSleep();

Te funkcje zapewniające wygodę dodają wszystkie odpowiednie wywołania Syscall do listy dozwolonych. Ta sama zasada ma tę zaletę, że może być używana w przypadku różnych architektur, w których niektóre wywołania syscall są niedostępne (np. ARM64 nie ma połączenia OPEN syscall), ale zmniejsza się ryzyko dla bezpieczeństwa związane z włączeniem większej liczby sycsall niż jest to konieczne. Na przykład zezwalanie na to, co umożliwia przeglądarce Sandboxee wywołanie dowolnego otwartego powiązanego wywołania syscall. Jeśli chcesz dodać do listy dozwolonych tylko 1 konkretny wywołania systemowego, możesz użyć AllowSyscall();, aby zezwolić na wiele wywołań systemowych jednocześnie. AllowSyscalls()

Obecnie zasada sprawdza tylko identyfikator syscall. Jeśli potrzebujesz dalszego wzmocnienia tej zasady i chcesz zdefiniować zasadę, w której zezwalasz na wywołanie systemu Syscall tylko z konkretnymi argumentami, musisz użyć właściwości AddPolicyOnSyscall() lub AddPolicyOnSyscalls(). Te funkcje wykorzystują nie tylko identyfikator połączenia syscall jako argument, ale także nieprzetworzony filtr seccomp-bpf wykorzystujący makra pomocnicze bpf z jądra systemu Linux. Więcej informacji na temat BPF znajdziesz w dokumentacji jądra. Jeśli stwierdzisz, że piszesz powtarzający się kod BPF, który powinien mieć opakowanie użyteczności, możesz zgłosić prośbę o dodanie funkcji.

Oprócz funkcji związanych z syscaller PolicyBuilder udostępnia też szereg funkcji związanych z systemem plików, takich jak AddFile() czy AddDirectory(), które pozwalają połączyć plik lub katalog w piaskownicy. Za pomocą aplikacji pomocniczej AddTmpfs() można dodać w piaskownicy tymczasową pamięć masową.

Szczególnie przydatną funkcją jest AddLibrariesForBinary(), która dodaje biblioteki i tag łączący wymagane przez plik binarny.

Wymyślenie instrukcji syscall do listy dozwolonych nadal wymaga ręcznej pracy. Utwórz zasadę z wywołaniami syscalls, które znasz, i uruchamiaj je z użyciem wspólnego zadania. Jeśli nastąpi naruszenie, dodaj wywołanie syscall do listy dozwolonych i powtórz ten proces. Jeśli napotkasz naruszenie, które uważasz za ryzykowne, a program dobrze radzi sobie z błędami, możesz spróbować zwrócić błąd, używając parametru BlockSyscallWithErrno().

#include "sandboxed_api/sandbox2/policy.h"
#include "sandboxed_api/sandbox2/policybuilder.h"
#include "sandboxed_api/sandbox2/util/bpf_helper.h"

std::unique_ptr<sandbox2::Policy> CreatePolicy() {
  return sandbox2::PolicyBuilder()
    .AllowSyscall(__NR_read)  // See also AllowRead()
    .AllowTime()              // Allow time, gettimeofday and clock_gettime
    .AddPolicyOnSyscall(__NR_write, {
        ARG(0),        // fd is the first argument of write (argument #0)
        JEQ(1, ALLOW), // allow write only on fd 1
        KILL,          // kill if not fd 1
    })
    .AddPolicyOnSyscall(__NR_mprotect, {
        ARG_32(2), // prot is a 32-bit wide argument, so it's OK to use *_32
                   // macro here
        JNE32(PROT_READ | PROT_WRITE, KILL), // prot must be the RW, otherwise
                                             // kill the process
        ARG(1), // len is a 64-bit argument
        JNE(0x1000, KILL),  // Allow single page syscalls only, otherwise kill
                            // the process
        ALLOW,              // Allow for the syscall to proceed, if prot and
                            // size match
    })
    // Allow the openat() syscall but always return "not found".
    .BlockSyscallWithErrno(__NR_openat, ENOENT)
    .BuildOrDie();
}