flawopen.com/Incidents/rust-unsafe-smallvec-overflow-january-2021
CVE-2021-25900 : débordement mémoire smallvec
SmallVec::insert_many se fiait à la borne basse de size_hint ; un itérateur pouvait la dépasser et déborder le tampon. Corrigé en 0.6.14 et 1.6.1.
Un entrepôt est réputé sûr parce qu'un robot vérifie chaque étagère avant qu'on y pose quoi que ce soit. Pour quelques tâches, le robot est éteint et une personne travaille à la main en promettant d'être prudente. L'une d'elles a demandé au livreur « combien de cartons apportez-vous, à peu près ? », a monté une étagère de cette taille et a commencé à empiler. L'estimation n'était qu'une supposition, pas une garantie, et quand plus de cartons que prévu sont arrivés, la pile a dépassé le bout de l'étagère.
Concepts Clés et Termes
SmallVec- Une collection Rust qui garde un petit nombre d'éléments en ligne et passe à un tampon sur le tas quand elle grandit. Sa rapidité vient de code unsafe qui gère ce tampon à la main.
size_hint- Une méthode d'Iterator qui annonce des bornes estimées, basse et haute, du nombre d'éléments restants. C'est une indication : du code safe peut renvoyer n'importe quelle valeur, même fausse.
Rust unsafe- Le code d'un bloc unsafe peut faire ce que le compilateur ne vérifie pas, comme écrire via des pointeurs bruts ; l'auteur doit garantir la sûreté mémoire pour tout appelant safe.
Solidité (soundness)- Une API safe est solide si aucun code safe, aussi inhabituel soit-il, ne peut provoquer de comportement indéfini à travers elle. insert_many ne l'était pas : un size_hint légal pouvait corrompre la mémoire.
Correspondances de référentiels
Chronologie de l'Incident
Yechan Bae et le groupe Rust du SSLab de Georgia Tech signalent le bogue ; RustSec publie RUSTSEC-2021-0003 et smallvec 0.6.14 et 1.6.1 apportent le correctif.
CVE-2021-25900 est attribuée ; cargo audit commence à signaler les versions concernées dans les arbres de dépendances.
Déroulement de l'Attaque Étape par Étape
Un itérateur sous-estime sa longueur
insert_many reçoit un itérateur dont la borne basse de size_hint est inférieure au nombre d'éléments réellement produits. Du code safe a le droit de le faire.
La place est réservée d'après l'indication
insert_many ne réserve que la borne basse et décale d'autant les éléments existants.
Tous les éléments sont écrits quand même
Dans un bloc unsafe, la boucle écrit chaque élément via un pointeur brut : les éléments en trop atterrissent après la fin de l'allocation.
Le tas est corrompu
Le débordement écrase la mémoire voisine sur le tas. Les programmes qui passent à insert_many des itérateurs influencés par des entrées non fiables sont exposés.
Code Source : Vulnérable vs Sécurisé
// Allouer en fonction d’une *estimation*...
let (lower, _) = iter.size_hint();
self.reserve(lower);
unsafe {
let ptr = self.as_mut_ptr().add(index);
for (i, item) in iter.enumerate() {
// ...mais écrire autant d’
// éléments que ceux réellement reçus.
ptr::write(ptr.add(i), item);
}
}// Vérifier à nouveau la capacité pour chaque élément.
// Ne jamais laisser une valeur issue de code sûr
// déterminer une limite utilisée par du code unsafe.
for (i, item) in iter.enumerate() {
self.reserve(1);
unsafe {
let ptr = self.as_mut_ptr()
.add(index + i);
ptr::write(ptr, item);
}
}
// Le correctif réel a également fortement réduit
// la surface de code unsafe.Liste de Contrôle de Sécurité pour l'Ingénierie
- ✓Ne dimensionnez jamais un tampon utilisé par du code unsafe d'après une méthode de trait safe comme size_hint, len sur un objet trait ou un comparateur ; revérifiez la capacité avant chaque écriture.
- ✓Écrivez un commentaire // SAFETY: pour chaque bloc unsafe indiquant l'invariant sur lequel il repose, et refusez les revues où cet invariant dépend de code safe non fiable.
- ✓Exécutez Miri en CI sur des tests utilisant des implémentations hostiles : un itérateur dont le size_hint ment, un Drop qui panique, un comparateur qui n'est pas un ordre total.
- ✓Gardez les blocs unsafe aussi petits que possible ; le correctif de smallvec a aussi réduit le code unsafe d'insert_many pour qu'il puisse être vérifié.
- ✓Lancez cargo audit (ou cargo deny) en CI pour que les dépendances visées par un avis RustSec, comme smallvec avant 0.6.14 ou 1.6.1, fassent échouer la build.
Sources
- RustSec — RUSTSEC-2021-0003 : débordement de tampon dans SmallVec::insert_many
- servo/rust-smallvec — ticket 252
- CVE-2021-25900 — fiche CVE
- MITRE CWE-787
- Heartbleed : un contrôle de limites manquant, la moitié d'Internet
- Baron Samedit : dix ans de root cachés dans une barre oblique inverse
- Citrix Bleed : comment une valeur de retour mal lue a contourné la MFA