Firefox se dote de petites sandbox pour renforcer la sécurité de son code

Firefox se dote de petites sandbox pour renforcer la sécurité de son code

Bacs à sable et grains contaminés

Avatar de l'auteur
Vincent Hermann

Publié dans

Logiciel

03/03/2020 10 minutes
8

Firefox se dote de petites sandbox pour renforcer la sécurité de son code

Firefox 74 va inaugurer un nouveau mécanisme de protection, tiré des travaux de recherche de plusieurs universités américaines sur une mini-sandbox nommée RLBox. Le processus implique de convertir le code en WebAssembly avant de le recompiler en code natif.

Dans un billet paru le 25 février, Mozilla a annoncé l’arrivée d’un mécanisme supplémentaire de sécurité. Firefox 74, qui doit sortir demain, aura ainsi dans sa mouture Linux une isolation supplémentaire pour certains composants, provoquant une révision générale et en travaux du modèle de sécurité.

On parle bien de sécurité du code, celle-là même liée de près à la manière dont un logiciel se sert de la mémoire vive. L’occasion de revenir sur certains concepts essentiels, ainsi que les avantages et inconvénients des techniques utilisées actuellement – et qui ne sont d’ailleurs pas remisées au placard.

Firefox, un projet complexe

Comme un très grand nombre d’applications sur des systèmes comme Linux, macOS ou Windows, Firefox est écrit, en majeure partie, dans un mélange de C et de C++. Depuis des décennies, ces langages sont les plus utilisés pour la programmation système, quand les développeurs souhaitent obtenir les meilleures performances.

Or, en dehors des évolutions les plus récentes de C++, ces deux langages réclament une attention particulière dans leur utilisation de la mémoire. Notamment, dans le cas de C, des pointeurs qui, comme leur nom l’indique, pointent vers des zones de mémoire. Il suffit d’une petite erreur ou d’une omission pour provoquer un risque de sécurité.

Certains se rappelleront peut-être d’une présentation faite par Microsoft à la conférence Bluehat IL, où l’ingénieur Matt Miller avait confirmé que 70 % des failles corrigées par l’éditeur étaient liées à des bugs de corruption mémoire, tout particulièrement les dépassements de mémoire tampon. Ces derniers surviennent quand un processus doit écrire dans un espace mémoire particulier, mais le fait dans un autre. Si ce dernier n’est pas libre, des données sont écrasées, entrainant un comportement imprévisible du système. Et une potentielle porte ouverte aux pirates.

Mozilla connaît bien le problème et en parle d’ailleurs dans son billet. Ses développeurs se servent de deux technologies pour augmenter la sécurité de son code. Tout d’abord un mécanisme de sandbox, soit un espace isolé dans lequel le code exécuté (y compris l’interprétation des pages web) n’a normalement pas d’impact sur le système. Sauf, bien sûr, en cas de faille de sécurité dans le mécanisme lui-même.

L’autre est un langage créé par la fondation : Rust. Le projet a largement gagné en lumière depuis son introduction il y a plusieurs années, fournissant les mêmes performances que C/C++ (prévisibles dans le temps), tout en intégrant des traits que l’on trouve plus volontiers dans des langages managés comme C# et Java. Comme ceux-là, Rust est notamment un langage dit « memory safe », à typage sûr. Il apparaît si robuste que Microsoft l’envisage pour ses propres produits.

Firefox est donc un mélange de C, C++ et Rust, avec l'utilisation d’une sandbox globale. N'étant pas satisfaite de la situation actuelle, chaque technique ayant ses limitations, l'équipe revoit actuellement sa façon de travailler.

Une question de granularité

Une sandbox globale seule ne serait pas un problème si l’intégralité de Firefox était écrite en Rust. Mais le langage n’est véritablement utilisé que depuis le remaniement technique de Quantum, donc privilégié pour tous les nouveaux composants. Une grande majorité du logiciel est encore en C/C++.

C’est la limite de Rust, et elle est humaine : réécrire des millions de lignes de code réclame des moyens qu’une petite équipe comme celle de Firefox ne peut fournir rapidement. Le passage au Rust continuera d’être progressif, au fur et à mesure que les composants seront modernisés, voire remplacés.

La sandbox, elle, est utilisée au niveau des processus. Or, puisque le nombre de processus augmente pour tirer parti des processeurs à cœurs multiples, il faut multiplier ces espaces mémoire. Il y a un impact non négligeable en termes de consommation de ressources, commun aujourd’hui à tous les navigateurs : les gigaoctets de mémoire dévorés quand les onglets se multiplient sont bien connus.

Mozilla cite l’exemple de Graphite, utilisé pour le rendu graphique de certaines polices complexes. Le composant est trop petit pour lui appliquer une sandbox, qui consommerait plus de ressources que Graphite lui-même. La réécriture en Rust demanderait trop de travail à la petite équipe d’ingénieurs pour une bibliothèque utilisée dans des cas spécifiques.

Puisque rien ne convenait, une troisième approche a été choisie : convertir des composants en WebAssembly (ou wasm). Ce dernier, créé initialement par Apple, Google, Microsoft et Mozilla, est devenu un standard et est pour rappel un bytecode, un code précompilé.

RLBox, la petite sandbox pour WebAssembly

RLBox est une API open source (sous licence MIT), développée par les universités américaines d’Austin, San Diego et Stanford. Firefox 74 pour Linux s’en sert justement pour la bibliothèque Graphite, qui sert donc de galop d’essai.

Comment cela fonctionne-t-il ? Il faut d’abord convertir le code C/C++ en WebAssembly. Pas besoin pour Mozilla de réinventer la roue : un backend a déjà été ajouté à Clang et LLVM, traditionnellement utilisés pour la compilation du navigateur. L’éditeur se sert également de wasi-sdk, une bibliothèque standard pour C/C++.

Le code wasm obtenu doit ensuite être recompilé en code natif. Certains demanderont alors pourquoi convertir en wasm si, finalement, on doit revenir à du code natif. Le wasm n’est-il pas justement précompilé pour être exécuté au dernier moment sur la machine ? C’est une possibilité.

Mais, comme on s’en doute et l’expliquent les développeurs, cela signifierait une compilation à la volée chaque fois qu’une nouvelle instance du composant est créée, chacune avec sa propre sandbox. La consommation en ressources grimperait en flèche, sans parler du temps d’exécution. Le code compilé est donc partagé entre plusieurs processus, la sandbox ne venant isoler que celui associé à chaque police chargée par Graphite.

La compilation du wasm vers du code natif ne réclame pas non plus de nouveau composant. Mozilla a déjà le sien : Cranelift, qui s’occupe déjà du code WebAssembly dans le moteur JavaScript de Firefox. Il s’agit pour l’instant d’une version modifiée, mais puisque les travaux actuels doivent à terme bénéficier au moteur JavaScript, les apports seront reversés pour une base de code unique.

Après compilation, Firefox n’a plus qu’à appeler le code protégé par la sandbox. Ces appels sont simplifiés – par rapport à une machine virtuelle classique – par le respect du modèle de sécurité de WebAssembly. Les fonctions protégées par RLBox sont accessibles de la même manière que le code natif classique.

Cette nouvelle approche, qui semble particulièrement satisfaire Mozilla, n’est cependant pas l’alpha et l’omega de la sécurité. L'équipe en convient et pointe d’ailleurs plusieurs problèmes concrets ou potentiels, y compris en sécurité.

Une première étape, des problèmes à résoudre et dangers à éviter

Mozilla le reconnait volontiers, l’utilisation de RLBox sur un composant comme Graphite est idéal. Le composant était suffisamment petit pour tester le comportement du processus, et assez peu utilisé pour qu’un éventuel dérapage n’ait pas de conséquences lourdes. Mais les développeurs sont confiants.

Pour l’instant, on parle donc d’un seul composant dans une seule édition, Firefox 74 pour Linux 64 bits. À la version suivante, macOS sera lui aussi concerné. Nous ne savons en revanche pas quand la mouture Windows – la plus utilisée – transitera elle aussi vers le nouveau modèle. Il y aura d’ici là des difficultés à dépasser.

Il faut notamment faire attention avec les pointeurs : ceux utilisés par le code WebAssembly sont en 32 bits, tandis que la première plateforme visée est en x86_64.

Mais les éléments les plus cruciaux à contrôler concernent bel et bien la sécurité : enfermer un composant dans une sandbox ne garantit en rien que le modèle de sécurité envisagé tiendra ses promesses. Notamment parce qu’il faut pouvoir s’assurer que les données renvoyées par cette sandbox sont authentiques.

« On ne peut pas faire confiance à ce qui sort de la sandbox, car un adversaire pourrait l’avoir compromise », explique Mozilla, qui cite plusieurs exemples.

/* Returns values between zero and sixteen. */

int return_the_value();

Le programme décrit par l’éditeur s’attend dans le cas présent à recevoir une valeur comprise entre 0 et 16. Il faut donc ajouter un mécanisme de contrôle vérifiant que le résultat tombe bien dans l’éventail attendu. 

extern const char* do_the_thing();

Dans cet exemple, le programme de Mozilla attend que le pointeur retourné désigne une zone mémoire contrôlée par la sandbox. Mais là encore, en cas de compromission de cette dernière, un attaquant pourrait orienter le pointeur vers une zone extérieure. D’où la nécessité de valider les pointeurs avant de les utiliser.

Cette méfiance est globale : Mozilla considère les données associées aux sandbox comme « contaminées ». Tant qu’elles y restent, ce n’est pas un problème : ces données peuvent être librement travaillées et produire d’autres données « sales ». Mais si Firefox – puisque l’on parle de lui – a besoin d’une information, le processus de « nettoyage » qui s’en suit doit parvenir à un résultat clair : s’assurer que les données sortantes sont bien ce qu’elles prétendent être.

Ces opérations doivent « être les plus explicites possibles », indique Mozilla. Les fonctions capables de renvoyer des données sales sont donc listées. C’est le compilateur qui servira finalement de « vérificateur de contamination ». Il renverra notamment des erreurs quand des données sales sont utilisées dans des contextes réclamant des données identifiées, et réciproquement. Puisque les contextes sont connus, les processus de validation n’ont plus qu’à y être appliqués.

Dans les mois à venir

Pour l’instant, l’objectif de Mozilla est de généraliser l’utilisation de RLBox à l’ensemble des composants provenant de sources tierces. Firefox utilise de nombreuses briques de logiciel libre extérieures, et l’éditeur compte fractionner petit à petit le code en une série de processus isolés aux droits restreints et consommant peu de ressources.

Comme dit, la mise en place de RLBox ne remet pas en question la réécriture de Firefox en Rust. Mais il s’agit d’un travail de longue haleine. Mozilla le dit clairement : « Nous écrivons une bonne partie du nouveau code en Rust, mais Firefox dispose d’une grande base de code de plusieurs millions de lignes en C/C++ qui ne s’en iront pas de sitôt ».

Écrit par Vincent Hermann

Tiens, en parlant de ça :

Sommaire de l'article

Introduction

Firefox, un projet complexe

Une question de granularité

RLBox, la petite sandbox pour WebAssembly

Une première étape, des problèmes à résoudre et dangers à éviter

Dans les mois à venir

Fermer

Commentaires (8)


L’un des problèmes à régler c’est surtout la création de périphériques audio illimité jusqu’à ce que ça plante. Ça fais déjà plusieurs versions.. J’aime bien Firefox, mais si c’est pour qu’il plante à la fin de la journée <img data-src=" />


Ton bug est tracé et référencé chez Mozilla ?


Alors, j’ai pas comprit, Cranelift c’est un compilateur qui compile à la création d’un onglet, ou au chargement d’une page ou d’un site web ?



Il compile autant du code “natif” de firefox (comme le module graphite) que des assets issue du web et qu’il considère comme sale et a nettoyer si la ressources doit être utilisée ailleurs ?


Si j’ai bien compris, il compile du code intégré à Firefox, mais développé en externe et qui ne gère pas forcément bien les accès mémoire. Le code est donc sandboxé et ses sorties sont filtrées.



Et (pareil, si j’ai bien compris), il sert au moment de la compilation de Firefox pour préparer le code qui sera, lors de l’utilisation, sandboxé à chaque fois qu’il est appelé.


Le billet semble actif, la solution n’est peut-être pas simple.


Merci pour cet article détaillé :)

Intéressant cette stratégie de Mozilla <img data-src=" />


Ce qui me chiffonne, c’est que les utilisateurs de Firefox sous linux soient les premiers à servir de cobayes (en l’occurrence RLBox), alors qu’ils n’ont toujours pas droit à l’accélération matérielle correcte du rendu. Et ce qui me chiffonne avec RUST, c’est la taille des chapitres portant sur la gestion des chaînes de caractères.



Bon je râle mais j’ai pas encore trouvé mieux <img data-src=" />