Dans les articles précédents de la série, nous avons discuté de la sécurité de la mémoire et de la sécurité des threads dans Rust. Dans ce dernier article, nous examinerons les implications d'une véritable application Rust utilisant le projet CSS Quantum .Le moteur CSS applique des règles CSS à la page. Il s'agit d'un processus descendant qui descend de l'arbre DOM, après avoir calculé le CSS parent, les styles enfants peuvent être calculés indépendamment: idéal pour le calcul parallèle. En 2017, Mozilla a fait deux tentatives pour paralléliser le système de style à l'aide de C ++. Les deux ont échoué.
Le développement Quantum CSS a commencé à augmenter la productivité. L'amélioration de la sécurité n'est qu'un bon effet secondaire.
Il existe un certain lien entre la protection de la mémoire et les bogues de sécurité de l'information. Par conséquent, nous nous attendions à ce que l'utilisation de Rust réduise la surface d'attaque dans Firefox. Cet article examinera les vulnérabilités potentielles qui ont été identifiées dans le moteur CSS depuis la version initiale de Firefox en 2002. Ensuite, regardez ce qui aurait pu et ne pouvait pas être évité avec Rust.
Pour toujours, 69 erreurs de sécurité ont été détectées dans le composant CSS de Firefox. Si nous avions une machine à remonter le temps et que nous pouvions l'écrire Rust dès le début, alors 51 (73,9%) erreurs deviendraient impossibles. Bien que Rust facilite l'écriture d'un bon code, il n'offre aucune protection absolue.
Rouille
Rust est un langage de programmation système moderne sans danger pour les types et la mémoire. Comme effet secondaire de ces garanties de sécurité, les programmes Rust sont également thread-safe au moment de la compilation. Ainsi, la rouille est particulièrement adaptée pour:
- traitement sécurisé des données entrantes non fiables;
- simultanéité pour améliorer les performances;
- intégration de composants individuels dans la base de code existante.
Cependant, Rust ne corrige pas explicitement certaines classes d'erreur, en particulier les erreurs de correction. En fait, lorsque nos ingénieurs ont réécrit Quantum CSS, ils ont accidentellement répété un bogue de sécurité critique, qui était précédemment corrigé dans le code C ++, ils ont accidentellement supprimé le
correctif de bogue 641731 , qui permet une fuite de l'historique global via SVG. L'erreur a été réenregistrée en tant que
bogue 1420001 . Une fuite d'historique est considérée comme une vulnérabilité de sécurité critique. Le correctif initial était une vérification supplémentaire pour voir si le document SVG était une image. Malheureusement, cette vérification a été manquée lors de la réécriture du code.
Bien que les tests automatisés devraient trouver des violations de la règle
:visited
comme ça, dans la pratique, ils n'ont pas trouvé cette erreur. Pour accélérer les tests automatiques, nous avons temporairement désactivé le mécanisme qui a testé cette fonctionnalité - les tests ne sont pas particulièrement utiles s'ils ne sont pas effectués. Le risque de réimplémentation des erreurs logiques peut être réduit par une bonne couverture des tests. Mais il y a toujours le danger de nouvelles erreurs logiques.
À mesure qu'un développeur se familiarise avec Rust, son code devient encore plus sécurisé. Bien que Rust n'empêche pas toutes les vulnérabilités possibles, il corrige toute une classe des bugs les plus graves.
Erreurs de sécurité CSS quantique
En général, par défaut, Rust empêche les erreurs liées à la mémoire, aux limites, aux variables nulles / non initialisées et aux débordements d'entiers. Le bogue non standard mentionné ci-dessus reste possible: un plantage se produit en raison d'un échec d'allocation de mémoire.
Erreurs de sécurité par catégorie
- Mémoire: 32
- Frontières: 12
- Mise en œuvre: 12
- Nul: 7
- Débordement de pile: 3
- Débordement d'entier: 2
- Autre: 1
Dans notre analyse, tous les bogues sont liés à la sécurité, mais seulement 43 ont reçu une note officielle (il est attribué par les ingénieurs de sécurité de Mozilla sur la base d'hypothèses qualifiées sur l '"exploitabilité"). Les bogues ordinaires peuvent indiquer des fonctions manquantes ou une sorte de dysfonctionnement, ce qui n'entraîne pas nécessairement une fuite de données ou un changement de comportement. Les erreurs de sécurité officielles vont d'une faible importance (s'il existe une forte restriction sur la surface d'attaque) à une vulnérabilité critique (peut permettre à un attaquant d'exécuter du code arbitraire sur la plate-forme de l'utilisateur).
Les vulnérabilités de mémoire sont souvent classées comme des problèmes de sécurité graves. Sur les 34 problèmes critiques / graves, 32 étaient liés à la mémoire.
Répartition de la gravité des bogues de sécurité
- Total: 70
- Erreurs de sécurité: 43
- Critique / sérieux: 34
- Rouille fixe: 32
Comparaison de Rust et C ++
Bogue 955913 - débordement de tampon de tas dans la fonction
GetCustomPropertyNameAt
. Le code a utilisé la mauvaise variable pour l'indexation, ce qui a conduit à l'interprétation de la mémoire après la fin du tableau. Cela peut provoquer un blocage lors de l'accès à un mauvais pointeur ou de la copie de la mémoire dans une chaîne transmise à un autre composant.
L'ordre de toutes les
propriétés CSS (y compris personnalisées, c'est-à-dire personnalisées) est stocké dans le tableau
mOrder
. Chaque élément est représenté soit par une valeur de propriété CSS, soit, dans le cas des propriétés personnalisées, une valeur commençant par
eCSSProperty_COUNT
(nombre total de propriétés CSS non personnalisées). Pour obtenir le nom des propriétés personnalisées, vous devez d'abord obtenir la valeur de
mOrder
, puis accéder au nom dans l'index correspondant du tableau
mVariableOrder
, qui stocke les noms des propriétés personnalisées dans l'ordre.
Code C ++ vulnérable:
void GetCustomPropertyNameAt(uint32_t aIndex, nsAString& aResult) const { MOZ_ASSERT(mOrder[aIndex] >= eCSSProperty_COUNT); aResult.Truncate(); aResult.AppendLiteral("var-"); aResult.Append(mVariableOrder[aIndex]);
Le problème se produit sur la ligne 6 lors de l'utilisation de
aIndex
pour accéder à l'élément du tableau
mVariableOrder
. Le fait est que
aIndex
doit être utilisé avec le tableau
mOrder
, pas avec
mVariableOrder
. L'élément correspondant à la propriété personnalisée représentée par
aIndex
dans
mOrder
est en fait
mOrder[aIndex] - eCSSProperty_COUNT
.
Code C ++ corrigé:
void Get CustomPropertyNameAt(uint32_t aIndex, nsAString& aResult) const { MOZ_ASSERT(mOrder[aIndex] >= eCSSProperty_COUNT); uint32_t variableIndex = mOrder[aIndex] - eCSSProperty_COUNT; aResult.Truncate(); aResult.AppendLiteral("var-"); aResult.Append(mVariableOrder[variableIndex]); }
Code Rust correspondant
Bien que Rust soit quelque peu similaire au C ++, il utilise d'autres abstractions et structures de données. Le code de rouille sera très différent de C ++ (voir ci-dessous pour plus de détails). Tout d'abord, regardons ce qui se passe si le code vulnérable est traduit aussi littéralement que possible:
fn GetCustomPropertyNameAt(&self, aIndex: usize) -> String { assert!(self.mOrder[aIndex] >= self.eCSSProperty_COUNT); let mut result = "var-".to_string(); result += &self.mVariableOrder[aIndex]; result }
Le compilateur Rust acceptera ce code car la longueur des vecteurs ne peut pas être déterminée avant l'exécution. Contrairement aux tableaux, dont la longueur doit être connue, le
type Vec in Rust a une taille dynamique. Cependant, la vérification des limites est intégrée à l'implémentation du vecteur de bibliothèque standard. Si un index non valide apparaît, le programme se termine immédiatement de manière contrôlée, empêchant tout accès non autorisé.
Le
code réel CSS quantique utilise des structures de données très différentes, il n'y a donc pas d'équivalent exact. Par exemple, nous utilisons les puissantes structures de données intégrées de Rust pour unifier l'arrangement et les noms de propriété. Cela élimine le besoin de maintenir deux baies indépendantes. Les structures de données de rouille améliorent également l'encapsulation des données et réduisent la probabilité de telles erreurs logiques. Étant donné que le code doit interagir avec le code C ++ dans d'autres parties du navigateur, la nouvelle fonction
GetCustomPropertyNameAt
ressemble pas au code idiomatique Rust. Mais il offre toujours toutes les garanties de sécurité, tout en offrant une abstraction plus compréhensible des données sous-jacentes.
tl; dr
Étant donné que les vulnérabilités sont souvent associées à des violations de la sécurité de la mémoire, le code Rust devrait réduire considérablement le nombre de
CVE critiques. Mais même Rust n'est pas parfait. Les développeurs doivent toujours rechercher les erreurs de correction et les attaques de fuite de données. La prise en charge des bibliothèques sécurisées nécessite toujours des révisions de code, des tests et du fuzzing.
Les compilateurs ne peuvent pas détecter toutes les erreurs de programmation. Néanmoins, Rust enlève notre fardeau de sécurité de la mémoire, nous permettant de nous concentrer sur l'exactitude logique du code.