FlawOpen

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

● servo/rust-smallvec · CVE-2021-25900 · January 2021 · CVSS 9.8 · Kritisch
Sicherheitsforschung · FlawOpen

CVE-2021-25900: Heap-Overflow in Rust-smallvec

SmallVec::insert_many vertraute der Untergrenze von size_hint; lieferte ein Iterator mehr, lief der Heap-Puffer über. Behoben in 0.6.14 und 1.6.1.

💡 Einfache Erklärung (ELI5)

Ein Lager gilt als sicher, weil ein Roboter jedes Regal prüft, bevor etwas daraufkommt. Für ein paar Aufgaben wird der Roboter abgeschaltet, und ein Mensch arbeitet von Hand und verspricht, vorsichtig zu sein. Einer davon fragte den Fahrer: „Ungefähr wie viele Kisten bringen Sie?“, baute ein Regal dieser Größe und fing an zu stapeln. Die Schätzung war eine Vermutung, keine Garantie – und als mehr Kisten kamen als angekündigt, lief der Stapel über das Regalende hinaus.

Kernkonzepte & Begriffe

SmallVec
Eine Rust-Collection, die wenige Elemente inline hält und beim Wachsen auf einen Heap-Puffer umsteigt. Ihre Geschwindigkeit verdankt sie unsafe-Code, der diesen Puffer von Hand verwaltet.
size_hint
Eine Iterator-Methode, die geschätzte Unter- und Obergrenzen für die verbleibenden Elemente meldet. Es ist ein Hinweis: safe-Code darf jeden Wert liefern, auch einen falschen.
Unsafe Rust
Code in einem unsafe-Block darf Dinge tun, die der Compiler nicht prüft, etwa über Rohzeiger schreiben; der Autor muss die Speichersicherheit für jeden denkbaren safe-Aufrufer gewährleisten.
Soundness
Eine safe-API ist sound, wenn kein safe-Code, so ungewöhnlich er sei, über sie undefiniertes Verhalten auslösen kann. insert_many war es nicht, denn ein zulässiger size_hint konnte Speicher beschädigen.

Framework-Zuordnungen

  • CWE-Referenz für die in diesem Leitfaden beschriebene Schwachstelle: CWE-122 CWE-787

Vorfall-Zeitstrahl

2021-01-08

Yechan Bae und die Rust-Gruppe am SSLab der Georgia Tech melden den Fehler; RustSec veröffentlicht RUSTSEC-2021-0003, und smallvec 0.6.14 und 1.6.1 enthalten die Korrektur.

2021-01

CVE-2021-25900 wird vergeben; cargo audit markiert betroffene Versionen fortan in Abhängigkeitsbäumen.

Schritt-für-Schritt Angriffsablauf

Schritt 1

Ein Iterator gibt eine zu kleine Länge an

insert_many wird mit einem Iterator aufgerufen, dessen size_hint-Untergrenze kleiner ist als die Zahl der tatsächlich gelieferten Elemente. Das darf safe-Code.

Schritt 2

Platz wird nach dem Hinweis reserviert

insert_many reserviert nur Platz für die Untergrenze und verschiebt vorhandene Elemente um genau diese Anzahl.

Schritt 3

Alle Elemente werden trotzdem geschrieben

Innerhalb eines unsafe-Blocks schreibt die Schleife jedes Element über einen Rohzeiger – überzählige landen hinter dem Ende der Allokation.

Schritt 4

Der Heap wird beschädigt

Der Überlauf überschreibt benachbarten Heap-Speicher. Programme, die insert_many Iteratoren übergeben, die von nicht vertrauenswürdigen Eingaben beeinflusst sind, sind betroffen.

Quellcode: Verwundbar vs. Sicher

VERWUNDBARE IMPLEMENTIERUNG
// Auf Grundlage eines *Hinweises* reservieren...
let (lower, _) = iter.size_hint();
self.reserve(lower);

unsafe {
    let ptr = self.as_mut_ptr().add(index);
    for (i, item) in iter.enumerate() {
        // ...aber so viele Elemente schreiben,
        // wie tatsächlich eintreffen.
        ptr::write(ptr.add(i), item);
    }
}
GEHÄRTETER SICHERHEITS-PATCH
// Die Kapazität für jedes Element erneut prüfen.
// Ein Wert aus sicherem Code darf niemals
// eine Grenze bestimmen, die von unsafe-Code verwendet wird.
for (i, item) in iter.enumerate() {
    self.reserve(1);
    unsafe {
        let ptr = self.as_mut_ptr()
                      .add(index + i);
        ptr::write(ptr, item);
    }
}

// Die echte Korrektur hat außerdem den
// unsafe-Codebereich deutlich verkleinert.

Checkliste für Engineering & Systemsicherheit

Quellen