2. Crea una policy sandbox
Una volta nominato un esecutore, probabilmente vorrai definire un criterio sandbox per il sandboxee. In caso contrario, il sandboxee è protetto solo dal criterio di chiamata di sistema predefinito.
Con la Sandbox Policy, l'obiettivo è limitare le chiamate di sistema e gli argomenti che Sandboxee può effettuare, nonché i file a cui può accedere. Devi avere una conoscenza dettagliata delle chiamate di sistema richieste dal codice che prevedi di inserire nel sandbox. Un modo per osservare le chiamate di sistema è eseguire il codice con lo strumento a riga di comando strace di Linux.
Una volta ottenuto l'elenco delle chiamate di sistema, puoi utilizzare PolicyBuilder per definire la policy. PolicyBuilder include molte funzioni di praticità e di assistenza che consentono di eseguire molte operazioni comuni. Il seguente elenco è solo un piccolo estratto delle funzioni disponibili:
- Inserisci nella lista consentita qualsiasi chiamata di sistema per l'avvio del processo:
AllowStaticStartup();
AllowDynamicStartup();
- Consenti qualsiasi chiamata di sistema open/read/write*:
AllowOpen();
AllowRead();
AllowWrite();
- Inserisci nella lista consentita tutte le chiamate di sistema relative a uscita/accesso/stato:
AllowExit();
AllowStat();
AllowAccess();
- Inserisci nella lista consentita tutte le chiamate di sistema relative al sonno/al tempo:
AllowTime();
AllowSleep();
Queste funzioni di utilità consentono di inserire nella lista consentita qualsiasi chiamata di sistema pertinente. Ciò ha il
vantaggio che la stessa policy può essere utilizzata su architetture diverse in cui
alcune chiamate di sistema non sono disponibili (ad es. ARM64 non ha la chiamata di sistema OPEN), ma con
il rischio di sicurezza minore di abilitare più chiamate di sistema del necessario. Ad esempio, AllowOpen() consente a Sandboxee di chiamare qualsiasi syscall correlata aperta. Se
vuoi inserire nella lista consentita una sola chiamata di sistema specifica, puoi utilizzare AllowSyscall();
.
Per consentire più chiamate di sistema contemporaneamente, puoi utilizzare AllowSyscalls()
.
Finora il criterio controlla solo l'identificatore della chiamata di sistema. Se hai bisogno di
rafforzare ulteriormente la policy e vuoi definirne una in cui consenti solo
una chiamata di sistema con argomenti particolari, devi utilizzare
AddPolicyOnSyscall()
o AddPolicyOnSyscalls()
. Queste funzioni non solo prendono
l'ID syscall come argomento, ma anche un filtro seccomp-bpf non elaborato utilizzando le macro helper bpf
del kernel Linux. Per saperne di più su BPF, consulta la documentazione
del kernel. Se ti ritrovi a scrivere codice BPF ripetitivo
che ritieni dovrebbe avere un wrapper di usabilità, non esitare a inviare una richiesta
di funzionalità.
Oltre alle funzioni correlate alle chiamate di sistema, PolicyBuilder fornisce anche una serie
di funzioni correlate al file system come AddFile()
o AddDirectory()
per
montare un file/una directory nella sandbox. L'helper AddTmpfs()
può essere
utilizzato per aggiungere uno spazio di archiviazione temporaneo dei file all'interno della sandbox.
Una funzione particolarmente utile è AddLibrariesForBinary()
, che aggiunge le
librerie e il linker richiesti da un binario.
Purtroppo, l'individuazione delle chiamate di sistema da inserire nella lista consentita è ancora un po' un lavoro manuale. Crea un criterio con le chiamate di sistema di cui sai che il tuo binario ha bisogno e
esegui il criterio con un workload comune. Se viene attivata una violazione, inserisci nella lista consentita la
chiamata di sistema e ripeti la procedura. Se riscontri una violazione che ritieni possa
essere rischioso inserire nella lista consentita e il programma gestisce gli errori in modo controllato, puoi provare a
far sì che restituisca un errore con 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();
}