2. Sandbox-Richtlinie erstellen
Sobald Sie einen Executor haben, sollten Sie eine Sandbox Richtlinie für den Sandboxee definieren. Andernfalls ist der Sandboxee nur durch die Standard-Syscall Richtlinie geschützt.
Mit der Sandbox-Richtlinie sollen die Syscalls und Argumente eingeschränkt werden, die der Sandboxee ausführen kann, sowie die Dateien, auf die er zugreifen kann. Sie müssen genau wissen, welche Syscalls für den Code erforderlich sind, den Sie in der Sandbox ausführen möchten. Eine Möglichkeit, Syscalls zu beobachten, besteht darin, den Code mit dem Befehlszeilentool strace von Linux auszuführen.
Sobald Sie die Liste der Syscalls haben, können Sie mit dem PolicyBuilder die Richtlinie definieren.
PolicyBuilder bietet viele praktische Funktionen und Hilfsfunktionen, die viele
gängige Vorgänge ermöglichen (viele davon sind bereits in
DefaultPolicyBuilder() enthalten). Die folgende Liste ist nur ein kleiner Auszug der
verfügbaren Funktionen:
- Alle Syscalls für den Prozessstart auf die Zulassungsliste setzen:
AllowStaticStartup();AllowDynamicStartup();
- Alle open_, _read_ /write*-Syscalls auf die Zulassungsliste setzen:
AllowOpen();AllowRead();AllowWrite();
- Alle Syscalls im Zusammenhang mit Beenden/Zugriff/Status auf die Zulassungsliste setzen:
AllowExit();AllowStat();AllowAccess();
- Alle Syscalls im Zusammenhang mit Sleep/Zeit auf die Zulassungsliste setzen:
AllowTime();AllowSleep();
Mit diesen praktischen Funktionen können Sie alle relevanten Syscalls auf die Zulassungsliste setzen. Das hat den Vorteil, dass dieselbe Richtlinie für verschiedene Architekturen verwendet werden kann, bei denen bestimmte Syscalls nicht verfügbar sind (z. B. hat ARM64 keinen OPEN-Syscall). Es besteht jedoch das geringe Sicherheitsrisiko, dass mehr Syscalls aktiviert werden als erforderlich. Mit AllowOpen() kann der Sandboxee beispielsweise alle Syscalls im Zusammenhang mit „open“ aufrufen. Wenn Sie nur einen bestimmten Syscall auf die Zulassungsliste setzen möchten, können Sie AllowSyscall(); verwenden. Wenn Sie mehrere Syscalls gleichzeitig auf die Zulassungsliste setzen möchten, verwenden Sie AllowSyscalls().
Bisher wird in der Richtlinie nur die Syscall-ID geprüft. Wenn Sie die Richtlinie weiter stärken und eine Richtlinie definieren möchten, in der nur ein Syscall mit bestimmten Argumenten zulässig ist, müssen Sie AddPolicyOnSyscall() oder AddPolicyOnSyscalls() verwenden. Diese Funktionen verwenden nicht nur die Syscall-ID als Argument, sondern auch einen rohen seccomp-bpf-Filter mit den bpf-Hilfsmakros aus dem Linux-Kernel. Weitere Informationen zu BPF finden Sie in der Kernel
Dokumentation. Wenn Sie sich dabei wiederholenden BPF-Code schreiben, für den Sie einen Usability-Wrapper benötigen, können Sie eine Funktionsanfrage einreichen.
Neben Syscall-bezogenen Funktionen bietet der PolicyBuilder auch eine Reihe von dateisystembezogenen Funktionen wie AddFile() oder AddDirectory(), um eine Datei oder ein Verzeichnis in die Sandbox einzubinden. Mit der Hilfsfunktion AddTmpfs() können Sie einen temporären Dateispeicher in der Sandbox hinzufügen.
Eine besonders nützliche Funktion ist AddLibrariesForBinary(), mit der die Bibliotheken und der Linker hinzugefügt werden, die für eine Binärdatei erforderlich sind.
Leider ist es immer noch etwas manuelle Arbeit, die Syscalls zu finden, die auf die Zulassungsliste gesetzt werden sollen. Erstellen Sie eine Richtlinie mit den Syscalls, die Ihre Binärdatei benötigt, und führen Sie sie mit einer gängigen Arbeitslast aus. Wenn ein Verstoß ausgelöst wird, setzen Sie den Syscall auf die Zulassungsliste und wiederholen Sie den Vorgang. Wenn Sie auf einen Verstoß stoßen, bei dem Sie der Meinung sind, dass es riskant sein könnte, ihn auf die Zulassungsliste zu setzen, und das Programm Fehler ordnungsgemäß behandelt, können Sie mit BlockSyscallWithErrno() versuchen, stattdessen einen Fehler zurückzugeben.
Im folgenden Beispiel wird sandbox2::PolicyBuilder direkt erstellt, um die Argumente write und mprotect einzuschränken und openat zu blockieren (was DefaultPolicyBuilder() andernfalls zulassen würde):
#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();
}