FlawOpen

flawopen.com/Incidents/rust-unsafe-smallvec-overflow-january-2021

● servo/rust-smallvec · CVE-2021-25900 · January 2021 · CVSS 9.8 · Critique
Recherche · FlawOpen

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.

💡 Explication en Langage Simple (ELI5)

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

  • Référence CWE pour la faiblesse décrite dans ce guide : CWE-122 CWE-787

Chronologie de l'Incident

2021-01-08

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.

2021-01

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

Étape 1

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.

Étape 2

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.

Étape 3

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.

Étape 4

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é

IMPLÉMENTATION VULNÉRABLE
// 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);
    }
}
PATCH SÉCURISÉ ET ROBUSTE
// 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

Sources