2. Criar uma política de sandbox

Depois de ter um executor, você provavelmente vai querer definir uma política do Sandbox para o Sandboxee. Caso contrário, o Sandboxee será protegido apenas pela política de syscall padrão.

Com a política do sandbox, o objetivo é restringir as chamadas de sistema e os argumentos que o sandbox pode fazer, bem como os arquivos que ele pode acessar. Você precisa entender detalhadamente as syscalls exigidas pelo código que planeja colocar em sandbox. Uma maneira de observar as chamadas de sistema é executar o código com a ferramenta de linha de comando strace do Linux.

Depois de ter a lista de syscalls, use o PolicyBuilder para definir a política. O PolicyBuilder vem com muitas funções convenientes e auxiliares que permitem várias operações comuns. A lista a seguir é apenas um pequeno trecho das funções disponíveis:

  • Adicionar à lista de permissões qualquer syscall para inicialização do processo:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • Adicione à lista de permissões qualquer chamada de sistema open/read/write*:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • Adicione à lista de permissões todas as chamadas de sistema relacionadas a saída/acesso/estado:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • Adicione à lista de permissões qualquer chamada de sistema relacionada a tempo/suspensão:
    • AllowTime();
    • AllowSleep();

Essas funções de conveniência permitem a inclusão na lista de permissões de qualquer chamada do sistema relevante. Isso tem a vantagem de que a mesma política pode ser usada em diferentes arquiteturas em que determinados syscalls não estão disponíveis (por exemplo, o ARM64 não tem o syscall OPEN), mas com o pequeno risco de segurança de ativar mais syscalls do que o necessário. Por exemplo, AllowOpen() permite que o Sandboxee chame qualquer syscall relacionada a abertura. Se você quiser adicionar apenas uma syscall específica à lista de permissões, use AllowSyscall();. Para adicionar várias syscalls de uma só vez, use AllowSyscalls().

Até agora, a política só verifica o identificador de syscall. Se você precisar fortalecer ainda mais a política e quiser definir uma política em que só permite uma syscall com argumentos específicos, use AddPolicyOnSyscall() ou AddPolicyOnSyscalls(). Essas funções não apenas usam o ID da syscall como argumento, mas também um filtro seccomp-bpf bruto usando as macros auxiliares bpf do kernel do Linux. Consulte a documentação do kernel para mais informações sobre o BPF. Se você estiver escrevendo um código BPF repetitivo que acha que deveria ter um wrapper de usabilidade, envie uma solicitação de recurso.

Além das funções relacionadas a syscalls, o PolicyBuilder também oferece várias funções relacionadas ao sistema de arquivos, como AddFile() ou AddDirectory(), para vincular a montagem de um arquivo/diretório no sandbox. O auxiliar AddTmpfs() pode ser usado para adicionar um armazenamento de arquivos temporário na sandbox.

Uma função particularmente útil é AddLibrariesForBinary(), que adiciona as bibliotecas e o vinculador exigidos por um binário.

Infelizmente, criar a lista de permissões de syscalls ainda é um trabalho manual. Crie uma política com as syscalls que seu binário precisa e execute-a com uma carga de trabalho comum. Se uma violação for acionada, coloque a syscall na lista de permissões e repita o processo. Se você encontrar uma violação que considere arriscada para a lista de permissões e o programa processe erros normalmente, tente fazer com que ela retorne um erro usando 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();
}