2. Créer une règle de bac à sable
Une fois que vous avez un exécutant, vous voudrez probablement définir une règle de bac à sable pour le Sandboxee. Sinon, le Sandboxee n'est protégé que par la stratégie Syscall par défaut.
L'objectif de la stratégie 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. 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 avez 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 d'effectuer de nombreuses opérations courantes. La liste suivante n'est qu'un petit extrait des fonctions disponibles :
- Ajouter n'importe quel appel système à la liste d'autorisation pour le démarrage du processus :
AllowStaticStartup();
AllowDynamicStartup();
- Ajoutez à la liste d'autorisation tous les appels système open/read/write* :
AllowOpen();
AllowRead();
AllowWrite();
- Autorisez 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 au sommeil/au temps :
AllowTime();
AllowSleep();
Ces fonctions pratiques autorisent tout appel système pertinent. L'avantage est que la même stratégie 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). Toutefois, cela présente un risque de sécurité mineur, car cela permet d'activer plus d'appels système que nécessaire. Par exemple, AllowOpen() permet à Sandboxee d'appeler n'importe quel syscall associé à 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 syscall. Si vous avez besoin de renforcer davantage la règle et que vous souhaitez en définir une 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 syscall 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 vous retrouvez à écrire du code BPF répétitif qui, selon vous, devrait disposer d'un wrapper d'usabilité, 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 monter un fichier ou un répertoire dans le bac à sable. L'assistant AddTmpfs()
peut être utilisé pour ajouter un espace de stockage de fichiers temporaires dans le bac à sable.
AddLibrariesForBinary()
est une fonction particulièrement utile, car elle ajoute les bibliothèques et l'éditeur de liens requis par un binaire.
Malheureusement, l'identification des appels système à ajouter à la liste d'autorisation nécessite encore un peu de travail manuel. Créez une règle avec les appels système dont votre binaire a besoin et exécutez-la avec une charge de travail courante. 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 une infraction qui vous semble risquée à autoriser et que le programme gère les erreurs de manière fluide, vous pouvez essayer de la faire renvoyer une erreur à la place 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();
}