2. Criar uma política de sandbox
Depois de ter um executor, é provável que você queira definir uma Sandbox Policy para o Sandboxee. Caso contrário, o Sandboxee só será protegido pela política de syscall padrão.
Com a política de sandbox, o objetivo é restringir as syscalls e os argumentos que o Sandboxee 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 syscalls é executar o código com a ferramenta de linha de comando strace do Linux.
Depois de ter a lista de syscalls, você pode usar o PolicyBuilder para definir a política.
PolicyBuilder vem com muitas funções de conveniência e funções auxiliares que permitem muitas
operações comuns (muitas delas já incluídas em
DefaultPolicyBuilder()). A lista a seguir é apenas um pequeno trecho das
funções disponíveis:
- Permitir qualquer syscall para inicialização do processo:
AllowStaticStartup();AllowDynamicStartup();
- Permitir qualquer syscall/read/write* aberto:
AllowOpen();AllowRead();AllowWrite();
- Autorizar qualquer syscall relacionado a saída/acesso/estado:
AllowExit();AllowStat();AllowAccess();
- Permitir qualquer syscall relacionado a tempo/suspensão:
AllowTime();AllowSleep();
Essas funções de conveniência permitem qualquer syscall relevante. Isso tem a vantagem de que a mesma política pode ser usada em diferentes arquiteturas em que determinadas syscalls não estão disponíveis (por exemplo, o ARM64 não tem 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 aberto. Se você quiser permitir apenas uma syscall específica, use AllowSyscall(); para permitir 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ó permita uma syscall com argumentos específicos, use AddPolicyOnSyscall() ou AddPolicyOnSyscalls(). Essas funções não apenas usam o ID de 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 syscall, o PolicyBuilder também oferece várias funções relacionadas ao sistema de arquivos, como AddFile() ou AddDirectory(), para montar um arquivo/diretório no sandbox. O auxiliar AddTmpfs() pode ser usado para adicionar um armazenamento de arquivos temporário no sandbox.
Uma função particularmente útil é AddLibrariesForBinary(), que adiciona as bibliotecas e o vinculador exigidos por um binário.
Infelizmente, ainda é um pouco de trabalho manual para criar as syscalls a serem adicionadas à lista de permissões. Crie uma política com as syscalls que você sabe que seu binário precisa e execute-a com uma carga de trabalho comum. Se uma violação for acionada, adicione a syscall à lista de permissões e repita o processo. Se você encontrar uma violação que considera arriscada para adicionar à lista de permissões e o programa processar erros normalmente, tente fazer com que ele retorne um erro usando BlockSyscallWithErrno().
O exemplo a seguir cria sandbox2::PolicyBuilder diretamente para demonstrar a restrição de argumentos write e mprotect e o bloqueio de openat (que DefaultPolicyBuilder() permitiria de outra forma):
#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();
}