2. Crea una política de zona de pruebas

Una vez que tengas un ejecutor, es probable que desees definir una política de Sandbox para el Sandboxee. De lo contrario, el Sandboxee solo está 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 tener un conocimiento detallado de las llamadas al sistema que requiere el código que planeas ejecutar en el entorno 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 PolicyBuilder para definir la política. PolicyBuilder incluye muchas funciones auxiliares y de conveniencia que permiten realizar muchas operaciones comunes. La siguiente lista es solo un pequeño fragmento de las funciones disponibles:

  • Permite cualquier llamada al sistema para el inicio del proceso:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • Incluye en la lista de entidades permitidas cualquier llamada al sistema abierta/de lectura/escritura*:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • Incluye en la lista de entidades permitidas cualquier llamada al sistema relacionada con la salida, el acceso o el estado:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • Incluye en la lista de entidades permitidas cualquier llamada al sistema relacionada con el tiempo o la suspensión:
    • AllowTime();
    • AllowSleep();

Estas funciones de conveniencia incluyen en la lista de entidades permitidas 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 hay ciertas llamadas al sistema disponibles (p.ej., ARM64 no tiene la llamada 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 syscall relacionado con la apertura. Si solo deseas 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 reforzar 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 que usa las macros auxiliares 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 activar la vinculación de un archivo o directorio en el entorno de pruebas. El asistente AddTmpfs() se puede usar para agregar un almacenamiento de archivos temporal dentro de la zona de pruebas.

Una función particularmente útil es AddLibrariesForBinary(), que agrega las bibliotecas y el vinculador que requiere un objeto binario.

Lamentablemente, determinar qué llamadas al sistema se deben incluir en la lista de entidades permitidas sigue siendo un trabajo un poco manual. Crea una política con las llamadas al sistema que sabes que necesita tu objeto binario y ejecútala con una carga de trabajo común. Si se activa un incumplimiento, agrega a la lista de entidades permitidas la llamada al sistema y repite el proceso. Si te encuentras con un incumplimiento que crees que podría ser riesgoso incluir en la lista de entidades permitidas y el programa controla los errores de forma correcta, puedes intentar que devuelva un error 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();
}