2. Sandbox-Richtlinie erstellen

Sobald Sie einen Executor haben, möchten Sie wahrscheinlich eine Sandbox-Richtlinie für die Sandboxee definieren. Andernfalls ist das Sandboxee nur durch die Standard-Syscall-Richtlinie geschützt.

Ziel der Sandbox-Richtlinie ist es, die Systemaufrufe und Argumente, die der Sandboxee ausführen kann, sowie die Dateien, auf die er zugreifen kann, einzuschränken. Sie müssen genau wissen, welche Systemaufrufe für den Code erforderlich sind, den Sie in einer Sandbox ausführen möchten. Eine Möglichkeit, Systemaufrufe zu beobachten, besteht darin, den Code mit dem Linux-Befehlszeilentool „strace“ auszuführen.

Sobald Sie die Liste der Systemaufrufe haben, können Sie mit PolicyBuilder die Richtlinie definieren. PolicyBuilder verfügt über viele Komfort- und Hilfsfunktionen, die viele gängige Operationen ermöglichen. Die folgende Liste enthält nur einen kleinen Auszug der verfügbaren Funktionen:

  • Setzen Sie alle Systemaufrufe für den Prozessstart auf die Zulassungsliste:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • Setzen Sie alle offenen /read/write*-Syscalls auf die Zulassungsliste:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • Setzen Sie alle Sysaufrufe im Zusammenhang mit Exit, Zugriff oder Status auf die Zulassungsliste:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • Setzen Sie alle schlaf-/zeitbezogenen Systemanrufe auf die Zulassungsliste:
    • AllowTime();
    • AllowSleep();

Diese praktischen Funktionen setzen alle relevanten Systemaufrufe auf die Zulassungsliste. Dies hat den Vorteil, dass dieselbe Richtlinie für verschiedene Architekturen verwendet werden kann, in denen bestimmte Systemaufrufe nicht verfügbar sind (z.B. ARM64 hat keinen OFFENEN Systemaufruf), aber mit dem geringen Sicherheitsrisikos, das die Aktivierung von mehr Systemen als nötig darstellt. Mit der Funktion „AllowOpen()“ kann das Sandboxee beispielsweise einen beliebigen offenen Systemaufruf aufrufen. Wenn Sie nur einen bestimmten Systemaufruf auf die Zulassungsliste setzen möchten, können Sie AllowSyscall(); verwenden, um mehrere Systemaufrufe gleichzeitig zuzulassen. Verwenden Sie dazu AllowSyscalls().

Bisher wird mit der Richtlinie nur die Systemaufruf-ID geprüft. Wenn Sie die Richtlinie weiter stärken und eine Richtlinie definieren möchten, in der nur ein Systemaufruf mit bestimmten Argumenten zugelassen wird, müssen Sie AddPolicyOnSyscall() oder AddPolicyOnSyscalls() verwenden. Diese Funktionen verwenden nicht nur die Syscall-ID als Argument, sondern auch einen unbearbeiteten seccomp-bpf-Filter, der die bpf-Hilfsmakros aus dem Linux-Kernel verwendet. Weitere Informationen zu BPF finden Sie in der Kernel-Dokumentation. Wenn Sie sich wiederholende BPF-Codes schreiben, von denen Sie denken, dass sie einen Usability-Wrapper haben sollten, können Sie gerne eine Funktionsanfrage stellen.

Neben den syscall-bezogenen Funktionen bietet PolicyBuilder auch eine Reihe von dateisystembezogenen Funktionen wie AddFile() oder AddDirectory(), um eine Datei/ein Verzeichnis mit Bindung in der Sandbox bereitzustellen. Mit dem Helper AddTmpfs() kann ein temporärer Dateispeicher in der Sandbox hinzugefügt werden.

Eine besonders nützliche Funktion ist AddLibrariesForBinary(), die die für ein Binärprogramm erforderlichen Bibliotheken und Verknüpfungen hinzufügt.

Die Systemaufrufe für die Zulassungsliste zu erstellen, ist leider noch ein Teil des manuellen Prozesses. Erstellen Sie eine Richtlinie mit den Systemaufrufen, die Ihnen Ihre Binäranforderungen kennen, und führen Sie sie mit einer gemeinsamen Arbeitslast aus. Wenn ein Verstoß ausgelöst wird, setzen Sie den Systemaufruf auf die Zulassungsliste und wiederholen Sie den Vorgang. Wenn Sie auf einen Verstoß stoßen, den Sie auf die Zulassungsliste setzen möchten, und das Programm Fehler ordnungsgemäß verarbeitet, können Sie versuchen, stattdessen mit BlockSyscallWithErrno() einen Fehler zurückzugeben.

#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();
}