2. Crea una política de zona de pruebas
Una vez que tengas un ejecutor, es probable que quieras definir una política de zona de pruebas para el Sandboxee. De lo contrario, el Sandboxee solo estará protegido por la política de llamadas al sistema predeterminada.
Con la política de zona de pruebas, el objetivo es restringir las llamadas al sistema y los argumentos que puede realizar el Sandboxee, así como los archivos a los que puede acceder. Deberás comprender en detalle las llamadas al sistema que requiere el código que planeas poner en la zona de pruebas. Una forma de observar las llamadas al sistema es ejecutar el código con la herramienta de línea de comandos strace de Linux.
Una vez que tengas la lista de llamadas al sistema, puedes usar el PolicyBuilder para definir la política.
PolicyBuilder incluye muchas funciones de conveniencia y funciones auxiliares que permiten muchas
operaciones comunes (muchas de las cuales ya están incluidas en
DefaultPolicyBuilder()). La siguiente lista es solo un pequeño extracto de
funciones disponibles:
- Incluir en la lista de entidades permitidas cualquier llamada al sistema para el inicio del proceso:
AllowStaticStartup();AllowDynamicStartup();
- Permite cualquier llamada al sistema abierta/read/write*:
AllowOpen();AllowRead();AllowWrite();
- Permite cualquier llamada al sistema relacionada con la salida, el acceso o el estado:
AllowExit();AllowStat();AllowAccess();
- Permite cualquier llamada al sistema relacionada con el tiempo o la suspensión:
AllowTime();AllowSleep();
Estas funciones de conveniencia permiten cualquier llamada al sistema pertinente. Esto tiene la ventaja de que se puede usar la misma política en diferentes arquitecturas en las que no están disponibles ciertas llamadas al sistema (p.ej., ARM64 no tiene llamadas al sistema OPEN), pero con el riesgo de seguridad menor de habilitar más llamadas al sistema de las que podrían ser necesarias. Por ejemplo, AllowOpen() permite que el Sandboxee llame a cualquier llamada al sistema relacionada con la apertura. Si solo quieres incluir en la lista de entidades permitidas una llamada al sistema específica, puedes usar AllowSyscall(); para permitir varias llamadas al sistema a la vez, puedes usar AllowSyscalls().
Hasta ahora, la política solo verifica el identificador de la llamada al sistema. Si necesitas fortalecer aún más la política y quieres definir una política en la que solo permitas una llamada al sistema con argumentos particulares, debes usar AddPolicyOnSyscall() o AddPolicyOnSyscalls(). Estas funciones no solo toman el ID de la llamada al sistema como argumento, sino también un filtro seccomp-bpf sin procesar con las macros de asistencia de bpf del kernel de Linux. Consulta la documentación
del kernel
para obtener más información sobre BPF. Si te encuentras escribiendo código BPF repetitivo que crees que debería tener un wrapper de usabilidad, no dudes en presentar una solicitud de función.
Además de las funciones relacionadas con las llamadas al sistema, PolicyBuilder también proporciona varias funciones relacionadas con el sistema de archivos, como AddFile() o AddDirectory(), para montar un archivo o directorio en la zona de pruebas. Se puede usar el asistente AddTmpfs() para agregar un almacenamiento de archivos temporales dentro de la zona de pruebas.
Una función particularmente útil es AddLibrariesForBinary(), que agrega las bibliotecas y el vinculador que requiere un archivo binario.
Lamentablemente, encontrar las llamadas al sistema para incluir en la lista de entidades permitidas sigue siendo un poco de trabajo manual. Crea una política con las llamadas al sistema que sabes que necesita tu archivo binario y ejecútala con una carga de trabajo común. Si se activa una violación, permite la llamada al sistema y repite el proceso. Si te encuentras con una violación que crees que podría ser riesgosa para incluir en la lista de entidades permitidas y el programa controla los errores correctamente, puedes intentar que muestre un error con BlockSyscallWithErrno().
En el siguiente ejemplo, se construye sandbox2::PolicyBuilder directamente para demostrar la restricción de los argumentos write y mprotect y el bloqueo de openat (que DefaultPolicyBuilder() permitiría de otro modo):
#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();
}