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

Une fois que vous disposez d'un exécuteur, vous souhaiterez probablement définir une règle de bac à sable pour le bac à sable. Sinon, le Sandboxee n'est protégé que par la stratégie d'appel système par défaut.

L'objectif des règles 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 avoir une compréhension détaillée des appels système requis par le code que vous prévoyez de mettre en bac à sable. Une façon d'observer les appels système consiste à 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 PolicyBuilder pour définir la règle. PolicyBuilder est fourni avec de nombreuses fonctions pratiques et d'assistance qui permettent de réaliser de nombreuses opérations courantes. La liste suivante n'est qu'un échantillon des fonctions disponibles:

  • Ajoutez un appel système à la liste d'autorisation pour le démarrage du processus :
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • Ajoutez tous les appels système /read/write* à la liste d'autorisation :
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • Ajoutez à la liste d'autorisation tous les appels système liés à la sortie, à l'accès ou à l'état :
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • Ajoutez à la liste d'autorisation tous les appels système liés à l'heure de veille :
    • AllowTime();
    • AllowSleep();

Ces fonctions pratiques ajoutent à la liste d'autorisation 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 un risque de sécurité mineur : l'activation de plus de systèmes que nécessaire n'est nécessaire. Par exemple, AllowOpen() permet à Sandboxee d'appeler n'importe quel appel système associé ouvert. Si vous ne souhaitez ajouter à la liste d'autorisation qu'un seul appel système spécifique, utilisez AllowSyscall(); pour autoriser plusieurs appels système à la fois. Vous pouvez utiliser AllowSyscalls().

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

En plus des 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 installer un fichier ou un répertoire dans le bac à sable via des liaisons. L'outil d'aide AddTmpfs() peut être utilisé 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, trouver les appels système à la liste d'autorisation reste un peu de travail. Créez une stratégie avec les appels système dont vous connaissez les besoins en binaire et exécutez-la avec une charge de travail commune. Si une infraction est déclenchée, ajoutez l'appel système à la liste d'autorisation et répétez le processus. Si vous rencontrez un cas de non-respect que vous pensez risqué d'ajouter à la liste d'autorisation et que le programme gère les erreurs correctement, vous pouvez essayer de le faire renvoyer une erreur avec 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();
}