flawopen.com/Speichersicherheit/Rust unsafe
Ist Rusts unsafe-Block weiterhin speichersicher?
unsafe schaltet Rusts Prüfungen nicht ab. Es erlaubt bestimmte Operationen, deren Sicherheitsbedingungen der Compiler nicht beweisen kann; der Programmierer muss diese Pflichten erfüllen. Eine unsound unsafe-Abstraktion kann dazu führen, dass gewöhnliche safe-Aufrufe undefiniertes Verhalten (UB) auslösen, obwohl sie das Wort unsafe nie verwenden.
Begriffe
- Unsafe-Operation:
- Operation mit Sicherheitsbedingungen, die der Compiler nicht beweisen kann, etwa das Dereferenzieren eines Raw-Pointers.
- Sound-Abstraktion:
- Eine safe-API, über die safe-Aufrufer kein undefiniertes Verhalten auslösen können.
- size_hint():
- Größenabschätzung eines Iterators zur Optimierung; keine exakte Anzahl und kein Beweis der Speichersicherheit.
- Undefiniertes Verhalten (UB):
- Verhalten, das Rust nicht festlegt; unsafe-Code muss verhindern, dass safe-Aufrufer es auslösen.
Wie eine unsound safe-Abstraktion scheitert
Eine safe-API erhält einen Iterator
Der Aufrufer übergibt einen gewöhnlichen Iterator an eine Funktion ohne unsafe-Vorbedingung.
Die Implementierung reserviert anhand eines Hinweises
Die Funktion reserviert nur die gemeldete Untergrenze, die kleiner als die tatsächlich gelieferte Elementzahl sein kann.
Ungeprüfte Schreibvorgänge überschreiten die Kapazität
Die Implementierung schreibt jedes Element über einen Raw-Pointer, ohne den Speicher zu erweitern oder die Kapazität pro Element zu prüfen.
Safe-Nutzung erreicht UB
Liefert der Iterator mehr Elemente als Platz vorhanden ist, kann ein safe-Aufruf außerhalb des Puffers schreiben. Verwenden Sie Vec::extend oder eine Implementierung, die sicher wächst.
// UNSOUND: reserve uses only the lower hint, // then unchecked writes consume every item. fn append_unsound(dst: &mut Vec<u8>, iter: impl Iterator<Item = u8>) { let (lower, _) = iter.size_hint(); dst.reserve(lower); let start = dst.len(); let mut written = 0; unsafe { let out = dst.as_mut_ptr().add(start); for item in iter { // UB once writes exceed spare capacity. out.add(written).write(item); written += 1; } dst.set_len(start + written); } }
// SOUND: Vec::extend handles capacity // growth and length updates safely. fn append(dst: &mut Vec<u8>, iter: impl Iterator<Item = u8>) { dst.extend(iter); }
Was Rusts Garantie tatsächlich besagt
Die präzise Garantie lautet: Safe Rust kann kein undefiniertes Verhalten verursachen, sofern der zugrunde liegende unsafe-Code sound ist. Die Rust Reference definiert Soundness an der Schnittstelle: unsafe-Code muss verhindern, dass safe-Clients UB auslösen. Der Compiler überprüft nicht alle Sicherheitsbedingungen einer unsafe-Operation; Entwickler und Reviewer müssen sie begründen.
Das ist weiterhin ein großer praktischer Vorteil gegenüber C und C++: Rust macht speicherkritische Operationen und ihre Pflichten sichtbarer, sodass Reviews unsafe-Schnittstellen und darauf aufbauende safe-Abstraktionen prüfen können. Dazu gehören unsafe-Blöcke, Verträge von unsafe-Funktionen, unsafe-Trait-Implementierungen, FFI-Deklarationen und unsafe-Attribute; sie liegen nicht immer in einem einzigen kleinen Block. Ein konkreter Fall ist CVE-2021-25900 in smallvec: SmallVec::insert_many reservierte anhand der unteren Iterator-Größenschätzung und schrieb mehr Elemente, als in die Allokation passten; bewertet mit CVSS 9.8.
Soundness ist der Maßstab, nicht bloße Sorgfalt
Eine API ist unsound, wenn safe-Code sie verwenden kann, um undefiniertes Verhalten auszulösen. Iterator::size_hint liefert etwa für Optimierungen nützliche Grenzen, aber keine exakte Anzahl: Ein korrekter Iterator kann mehr Elemente liefern als die Untergrenze, ein fehlerhafter Iterator kann seine Schätzung verletzen. Die Dokumentation sagt ausdrücklich, dass der Hinweis nicht dazu dienen darf, Bounds-Checks in unsafe-Code wegzulassen. Fehlerhaftes Verhalten eines safe-Traits kann auch Logikfehler oder Panics verursachen; das ist nicht automatisch UB. Unsafe-Code muss Speicherinvarianten dennoch erhalten, wenn Aufrufercode unerwartet reagiert.
Unsafe-Code ist daher nicht lokal begrenzt: Safe-APIs können von einer Speicherinvariante abhängen, die an anderer Stelle hergestellt wird. Ist sie falsch, liegt der Fehler in der Abstraktion, die die safe-API bereitstellt, nicht bei safe-Aufrufern, die den Vertrag einhalten.
Praktische Regeln
- ✓Behandeln Sie safe-Trait-Methoden nicht als Beweis für Speichersicherheit. Nutzen Sie
size_hint()nur zur Optimierung; behalten Sie Prüfungen bei oder erweitern Sie den Speicher bei Bedarf. - ✓Erhalten Sie Invarianten bei Panics. Aufrufercode wie
Iterator::next, ein Destruktor oder eine Closure kann abwickeln. Teilinitialisierte Zustände müssen gültig bleiben und korrekt zerstört werden. - ✓Schreiben Sie einen
// SAFETY:-Kommentar, der die nötigen Bedingungen und ihre Begründung nennt. Unerklärter unsafe-Code ist nicht überprüfbar. - ✓Halten Sie Blöcke eng begrenzt. Eine ganze Schleife einzuschließen, nur um das Schlüsselwort nicht zu wiederholen, vergrößert den Prüfaufwand.
- ✓Führen Sie Miri für ausgeführte Tests aus. Es kann bestimmte UB-Fälle in ausgeführten Pfaden erkennen, etwa Zugriffe außerhalb von Grenzen, Use-after-free und ungültiges Aliasing; es beweist nicht, dass jeder unsafe-Code sound ist.
- ✓Prüfen Sie Abhängigkeiten.
cargo auditprüft bekannte RustSec-Advisories;cargo geigerhilft, unsafe-Nutzung zu erfassen. Beide ersetzen keine Prüfung der unsafe-Verträge des ausgelieferten Codes.
Häufige Fragen
#![forbid(unsafe_code)] kann unsafe-Syntax in dem Crate verbieten, in dem es aktiviert ist, und eignet sich für Anwendungs-Crates. Abhängigkeiten sind davon nicht betroffen; manche Implementierungen, FFI-Grenzen und Bibliotheken benötigen unsafe-Operationen.
Ja. Ein unsafe trait dokumentiert Pflichten, die Implementierungen erfüllen müssen; unsafe-Code darf sich darauf stützen. TrustedLen ist ein instabiles Standardbibliotheks-Trait für Iteratoren, die eine exakte Größenangabe versprechen. Iterator::size_hint bietet diese Garantie nicht.
Nein. Rusts Sicherheitsmodell verringert die Stellen, an denen Speicher-Sicherheitspflichten entstehen, und macht sie an unsafe-Schnittstellen sichtbar. Unsafe-Code, Abhängigkeiten, FFI-Verträge und safe-APIs auf unsafe-Implementierungen müssen weiterhin geprüft werden.
Referenzen
- The Rust Reference — unsafe code and soundness
- Rust Reference — the unsafe keyword and proof obligations
- Rust standard library — Iterator::size_hint contract
- Rustonomicon — working with unsafe
- RustSec Advisory RUSTSEC-2021-0003 — smallvec buffer overflow
- GitHub Security Advisory GHSA-43w2-9j62-hq99 — CVE-2021-25900
- The Rustonomicon — the guide to unsafe Rust
- RustSec Advisory Database
- Miri — an interpreter that detects undefined behaviour
- CWE-787 — Out-of-bounds Write
- Case study: the smallvec overflow (CVE-2021-25900)