2. Crea una policy della sandbox

Una volta che hai un executor, probabilmente vorrai definire una Sandbox Policy per il Sandboxee. In caso contrario, il Sandboxee è protetto solo dalla policy Syscall predefinita.

Con la policy della sandbox, l'obiettivo è limitare le syscall e gli argomenti che il Sandboxee può eseguire, nonché i file a cui può accedere. Dovrai avere una conoscenza dettagliata delle syscall richieste dal codice che intendi inserire nella sandbox. Un modo per osservare le syscall è eseguire il codice con lo strumento a riga di comando strace di Linux.

Una volta che hai l'elenco delle syscall, puoi utilizzare il PolicyBuilder per definire la policy.

PolicyBuilder include molte funzioni di praticità e funzioni helper che consentono di eseguire molte operazioni comuni (molte delle quali sono già incluse in DefaultPolicyBuilder()). Il seguente elenco è solo un piccolo estratto delle funzioni disponibili:

  • Consenti qualsiasi syscall per l'avvio del processo:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • Consenti qualsiasi syscall open/read/write*:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • Consenti qualsiasi syscall relativa a uscita/accesso/stato:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • Consenti qualsiasi syscall relativa a sleep/time:
    • AllowTime();
    • AllowSleep();

Queste funzioni di praticità consentono di consentire qualsiasi syscall pertinente. Questo ha il vantaggio che la stessa policy può essere utilizzata su architetture diverse in cui alcune syscall non sono disponibili (ad es. ARM64 non ha syscall OPEN), ma con il piccolo rischio per la sicurezza di abilitare più syscall del necessario. Ad esempio, AllowOpen() consente al Sandboxee di chiamare qualsiasi syscall correlata a open. Se vuoi consentire solo una syscall specifica, puoi utilizzare AllowSyscall(); per consentire più syscall contemporaneamente puoi utilizzare AllowSyscalls().

Finora la policy controlla solo l'identificatore syscall. Se devi rafforzare ulteriormente la policy e vuoi definirne una in cui consenti solo una syscall con argomenti specifici, devi utilizzare AddPolicyOnSyscall() o AddPolicyOnSyscalls(). Queste funzioni non accettano solo l'ID syscall come argomento, ma anche un filtro seccomp-bpf non elaborato utilizzando le macro di assistenza bpf del kernel Linux. Per ulteriori informazioni su BPF, consulta la documentazione del kernel. Se ti ritrovi a scrivere codice BPF ripetitivo che ritieni debba avere un wrapper di usabilità, non esitare a presentare una richiesta di funzionalità.

Oltre alle funzioni relative alle syscall, PolicyBuilder fornisce anche una serie di funzioni relative 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 file binario.

Purtroppo, l'individuazione delle syscall da consentire è ancora un po' di lavoro manuale. Crea una policy con le syscall di cui sai che il tuo file binario ha bisogno ed eseguila con un carico di lavoro comune. Se viene attivata una violazione, consenti la syscall e ripeti la procedura. Se riscontri una violazione che ritieni possa essere rischiosa da consentire e il programma gestisce gli errori in modo corretto, puoi provare a far sì che restituisca un errore con BlockSyscallWithErrno().

L'esempio seguente crea sandbox2::PolicyBuilder direttamente per dimostrare la limitazione degli argomenti write e mprotect e il blocco di openat (che DefaultPolicyBuilder() altrimenti consentirebbe):

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