Dans la suite, nous allons considérer que chaque processus représente une zone de privilège, définissant ainsi ses accès et/ou permissions. Je vais alors décrire l'architecture que j'ai conçue pour organiser ces processus et garder une exécution aussi proche que possible que dans l'approche multithread. \section{La notion d'étage} L'architecture que nous allons construire se place contextuellement dans celle de VLC. Elle se base fortement sur la réalisation d'un pipeline de traitement dynamique, créé au fur et à mesure de la lecture des flux multimédia. Les différents éléments de ce pipeline seront créés éventuellement dans la même zone de privilège, éventuellement dans une zone de privilège déjà existante ou même donnera lieu à la création d'une nouvelle zone de privilège. Pour désigner ce concept du côté de l'API publique de la sandbox, on crée la notion d'étage, ou \og{}stages\fg{}, qui permet de ne pas forcément indiquer comment sera créée la partie du pipeline demandée par le client. L'étage représente donc une abstraction utilisée par la partie \og{}VLC\fg{} mais réellement manipulée par la partie \og{}sandbox\fg{} du processus en question. \section{Le modèle broker} Dans ce modèle, on considère un processus privilégié qu'on appelle broker, qui va contrôler tous les échanges entre les différents processus qu'on appelle workers, mais également leur cycle de vie et leurs permissions et accès. %TODO: schéma broker-worker Le broker est donc en charge de faire le routage des différents messages, de transmettre les ressources et faire la vérification d'accès. Il est également utile pour vérifier que les processus workers sont encore en vie et signaler les erreurs. % TODO: expliquer IPC Les avantages du modèle broker sont qu'il est généralement le modèle le plus simple à implémenter, et n'a pas besoin de fonctionnalité particulière du point de vue du système, à part pour créer les communications inter-processus (IPC). Nous pouvons même simuler toutes les problématiques d'accès par des jetons d'accès représenté par une certaine IPC, que le broker associera à un accès réel et reproduira les actions demandées par le worker. Il est également bien plus simple à comprendre, à développer, à isoler et à suivre étant donné que tous les échanges passent par le broker avant d'être transmis au bon processus. Ce modèle constitue ainsi un cadre privilégié pour développer la sandbox et l'amener sur tous les systèmes, à condition de garder en tête les contraintes des autres modèles. Le processus broker sera initialisé dans le processus principal et sera donc père de tous les autres processus de la sandbox. Cela permettra d'utiliser des techniques comme \texttt{ptrace} sous Linux ou de pouvoir créer des objets destinés aux processus fils et de leur associer des droits d'accès sous Windows sans avoir à recourir à des droits de superutilisateur. \section{Le modèle orchestrateur} Dans ce modèle, on considère un processus privilégié qu'on appelle orchestrateur. Celui-ci n'est plus au centre des communications inter-processus mais permet d'initialiser les connexions entre deux workers qui vont ensuite discuter directement. On doit donc s'attendre à de meilleures performances étant donné qu'on diminue les changements de contexte et appels systèmes. On garde cet \og{}orchestrateur\fg{} pour initialiser chaque nouvel étage mais les modules peuvent fonctionner indépendamment après, du moment que tous les éléments qui sont liés à l'étage ont été créés. Ce modèle nécessite la possibilité d'initialiser une IPC entre deux workers qui doivent communiquer, mais potentiellement aussi mettre en place une autre IPC plus efficace pour les transferts de données du pipeline, commme des mémoire partagées. Il faut également que dans ce modèle, les workers ne puissent pas récupérer les droits des autres workers. C'est grâce à ce modèle qu'on peut mettre en valeur la puissance d'expression et de contrôle des jetons d'accès, car aucun contrôle de l'orchestrateur n'est nécessaire pour appliquer une réduction des privilèges et définir précisément les accès disponibles dans chaque partie du pipeline par conception plutôt que par des règles et contrôles d'accès. %TODO: schéma orchestrateur-worker \section{Les processus worker} %TODO: décrire rôle du processus worker Dans les deux modèles précédents, on a utilisé des processus workers qui vont avoir le même rôle, mais éventuellement des implémentations différentes. Chaque worker est équipé d'un thread jouant le rôle de boucle événementielle et récupérant les messages soit de tous les autres modules connectés, soit uniquement depuis le broker selon le modèle utilisé. La boucle événementielle sera décrite plus tard. Les workers peuvent démarrer de plusieurs façons. Soit un nouveau processus est créé en étant configuré comme un worker, avec des IPC correctes déjà attribuées. Soit le processus est créé depuis un fork et le thread worker peut démarrer tout de suite en ayant connaissance de son IPC vers l'orchestrateur ou le broker. La seconde option peut notamment apparaître lorsqu'un processus Zygote est utilisé. \section{La notion de processus Zygote} Afin d'optimiser le démarrage de nouveaux processus, beaucoup de systèmes utilisent un processus spécial, appelé \og{}Zygote\fg{}. Ce processus est responsable de créer les nouveaux processus et permet un démarrage plus rapide en préchargeant toutes les bibliothèques et préparant toutes les initialisations au préalable. % TODO expliquer et détailler ASLR et android Les nouveaux processus sont alors créés à partir d'un appel système \inltype{fork} qui est moins coûteux que la création complète d'un processus depuis rien. Mais n'est pas officiellement disponible sous Windows, officieusement disponible sur les versions Windows 10 via les détails internes liés à l'implémentation des pico-processus, et ne permet pas de pouvoir efficacement faire de l'ASLR entre les différents processus de l'application. Ce dernier point existe notamment sur Android, même si des mitigations existent. Le processus Zygote permet également d'efficacement cloisonner les descripteurs de fichier à hériter. Mais cela peut être préféré à une méthode plus systématique de libération de tous les descripteurs associés au processus si l'on ne souhaite pas utiliser ce processus pour des raisons de performances également. Néanmoins, l'autre partie très intéressante du Zygote concerne le débogage de la sandbox. Sous Linux, \texttt{gdb} n'est capable de s'accrocher qu'à un seul processus à la fois. Il faut alors choisir avec \texttt{set follow-fork-mode} si l'on veut déboguer le processus parent ou bien le processus enfant lors d'un appel à \inltype{fork}. Dans l'architecture que j'ai construite, nous réalisons les opérations de façon synchrone, même entre deux processus, ce qui permettra d'attacher des débogueurs au processus Zygote et basculer directement sur le processus enfant à chaque création de nouveau processus. \section{Démarrage de la sandbox} Du côté de l'application cliente qui utilise libvlc et doit être sandboxée, il faut pouvoir gérer les deux cas de création de processus. Je propose alors une API inspirée des travaux existants de Guillaume Fournier sur la sandbox seccomp, mais ne dépendant pas directement des objets libvlc et permettant plus d'évolution sur les paramètres passables au démarrage de la sandbox. \begin{code}{c}{API de sandboxing} vlc_sandbox_t *vlc_sandbox_Init( struct vlc_sandbox_cfg *cfg ); VLC_API int vlc_sandbox_Start( vlc_sandbox_t *sandbox ); VLC_API void vlc_sandbox_Close( vlc_sandbox_t *sandbox ); VLC_API bool vlc_sandbox_ShouldExit( vlc_sandbox_t *sandbox ); /* donne la valeur de retour pour le programme */ VLC_API int vlc_sandbox_Exit( vlc_sandbox_t *sandbox ); \end{code} Le client peut alors initialiser la sandbox avec le code qui suit. La structure \inltype{vlc_sandbox_cfg} est prévue à terme pour transmettre l'ensemble des politiques à appliquer aux différentes zones de privilèges, les paramètres de création des nouveaux objets dans la sandbox et tout paramètre de sécurité important. \begin{code}{c}{Démarrage de la sandbox} /* count et args contiennent les arguments modifiés pour le démarrage */ vlc_sandbox_t *sandbox = vlc_sandbox_Init( &(struct vlc_sandbox_cfg) { .argc = count, .argv = args, /* point d'entrée dans la sandbox */ .bootstrap = bootstrap_app, .create_vlc = libvlc_new, .delete_vlc = libvlc_release, }); vlc_sandbox_Start( sandbox ); /* À partir d'ici on fonctionne dans la sandbox et ce processus est devenu le * broker / orchestrateur. Quand le broker termine, libérer les ressources */ return vlc_sandbox_Close( sandbox ); \end{code} Dans le cas de la création de worker par fork, la boucle événementielle des workers est directement appelée. Dans le cas de la création de nouveaux processus pour chaque worker, \inltype{vlc_sandbox_Init} sera rappelé mais détectera dans les arguments de la ligne de commande qu'il s'agit d'un worker et initialisera ses paramètres correctement, puis \inltype{vlc_sandbox_Start} démarrera la boucle événementielle du worker. On a donc bien une API qui peut utiliser les deux modes de démarrage des processus.