2. Créer une règle de bac à sable

Une fois que vous disposez d'un exécutant, vous souhaiterez probablement définir une Sandbox Policy pour le bac à sable. Sinon, le bac à sable n'est protégé que par la règle Syscall par défaut.

L'objectif de la règle de bac à sable est de limiter les appels système et les arguments que le bac à sable peut effectuer, ainsi que les fichiers auxquels il peut accéder. Vous devez bien comprendre les appels système requis par le code que vous prévoyez de mettre en bac à sable. Pour observer les appels système, vous pouvez exécuter le code avec l'outil de ligne de commande strace de Linux.

Une fois que vous disposez de la liste des appels système, vous pouvez utiliser le PolicyBuilder pour définir la règle.

PolicyBuilder est fourni avec de nombreuses fonctions pratiques et d'assistance qui permettent d'effectuer de nombreuses opérations courantes (dont la plupart sont déjà incluses dans DefaultPolicyBuilder()). La liste suivante n'est qu'un petit extrait des fonctions disponibles :

  • Autoriser n'importe quel appel système pour le démarrage du processus :
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • Autoriser tous les appels système ouverts/read/write*:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • Autoriser tous les appels système liés à la sortie/l'accès/l'état :
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • Autoriser tous les appels système liés au délai de veille/à l'heure :
    • AllowTime();
    • AllowSleep();

Ces fonctions pratiques autorisent tous les appels système pertinents. L'avantage est que la même règle peut être utilisée sur différentes architectures où certains appels système ne sont pas disponibles (par exemple, ARM64 n'a pas d'appel système OPEN), mais avec le risque de sécurité mineur d'activer plus d'appels système que nécessaire. Par exemple, AllowOpen() permet au bac à sable d'appeler n'importe quel appel système lié à l'ouverture. Si vous ne souhaitez autoriser qu'un seul appel système spécifique, vous pouvez utiliser AllowSyscall();. Pour autoriser plusieurs appels système à la fois, vous pouvez utiliser AllowSyscalls().

Jusqu'à présent, la règle ne vérifie que l'identifiant de l'appel système. Si vous devez renforcer davantage la règle et que vous souhaitez définir une règle dans laquelle vous n'autorisez qu'un appel système avec des arguments spécifiques, vous devez utiliser AddPolicyOnSyscall() ou AddPolicyOnSyscalls(). Ces fonctions prennent non seulement l'ID de l'appel système comme argument, mais aussi un filtre seccomp-bpf brut à l'aide des macros d'assistance bpf du noyau Linux. Pour en savoir plus sur BPF, consultez la documentation du noyau. Si vous écrivez du code BPF répétitif qui, selon vous, devrait avoir un wrapper d'utilisabilité, n'hésitez pas à envoyer une demande de fonctionnalité.

Outre les fonctions liées aux appels système, PolicyBuilder fournit également un certain nombre de fonctions liées au système de fichiers, telles que AddFile() ou AddDirectory(), pour monter un fichier/répertoire dans le bac à sable. L'assistance AddTmpfs() peut être utilisée pour ajouter un stockage de fichiers temporaire dans le bac à sable.

AddLibrariesForBinary() est une fonction particulièrement utile qui ajoute les bibliothèques et l'éditeur de liens requis par un binaire.

Malheureusement, il faut encore un peu de travail manuel pour trouver les appels système à ajouter à la liste d'autorisation. Créez une règle avec les appels système dont vous savez que votre binaire a besoin et exécutez-la avec une charge de travail courante. Si une violation est déclenchée, autorisez l'appel système et répétez le processus. Si vous rencontrez une violation que vous pensez risquée d'autoriser et que le programme gère les erreurs correctement, vous pouvez essayer de le faire renvoyer une erreur à la place avec BlockSyscallWithErrno().

L'exemple suivant construit directement sandbox2::PolicyBuilder pour illustrer la restriction des arguments write et mprotect et le blocage de openat (que DefaultPolicyBuilder() autoriserait sinon) :

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