123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- 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.
|