Beseitigung von Speichersicherheitslücken an der Quelle
(security.googleblog.com)Die Ursachen von Speichersicherheitslücken beseitigen
Ein paradoxes Ergebnis
- Wenn die Codebasis, die in speicherunsicheren Sprachen geschrieben ist, wächst, führt die Umstellung neuer Funktionen auf speichersichere Sprachen dennoch zu einem deutlichen Rückgang von Speichersicherheitslücken
- Der Grund dafür ist, dass Schwachstellen im Lauf der Zeit exponentiell abnehmen
Mathematische Erklärung
- Die Lebensdauer von Schwachstellen folgt einer Exponentialverteilung
- Schwachstellen entstehen überwiegend in neuem Code, und mit der Zeit wird der Code sicherer
- Die Schwachstellendichte in 5 Jahre altem Code ist 3,4- bis 7,4-mal niedriger als in neuem Code
Praxisbeispiel bei Android
- Seit 2019 hat das Android-Team begonnen, neue Entwicklungen auf speichersichere Sprachen umzustellen
- Stand 2024 ist der Anteil der Speichersicherheitslücken von 76 % auf 24 % gesunken
- Mit dem Rückgang der Speichersicherheitslücken ist auch das gesamte Sicherheitsrisiko gesunken
Die Entwicklung der Strategie für Speichersicherheit
-
- Generation: Reaktives Patchen – Schwachstellen werden entdeckt und behoben
-
- Generation: Proaktive Abschwächung – Die Ausnutzung von Schwachstellen wird erschwert
-
- Generation: Proaktive Schwachstellenfindung – Schwachstellen werden im Voraus aufgespürt
-
- Generation: Hochzuverlässige Prävention – Durch die Umstellung auf speichersichere Sprachen wird die Entstehung von Schwachstellen selbst verhindert
Vorteile hochzuverlässiger Prävention
- Beendet den endlosen Wettlauf zwischen Verteidigern und Angreifern
- Erhöht die Sicherheit und senkt die Kosten durch speichersichere Sprachen
- Verbessert die Korrektheit des Codes und die Produktivität der Entwickler
Von der Erkenntnis zur Praxis
- Es ist nicht nötig, den gesamten bestehenden speicherunsicheren Code zu verwerfen oder neu zu schreiben
- Durch bessere Interoperabilität wird die Umstellung auf speichersichere Sprachen beschleunigt
- Entwicklung von Werkzeugen zur Verbesserung der Interoperabilität zwischen Rust und C++ sowie zwischen Rust und Kotlin
Die Rolle früherer Generationen
- Selektiver Einsatz von proaktiver Abschwächung und Erkennung
- Mit der Umstellung auf speichersicheren Code nimmt der Bedarf an Abschwächung und Erkennung ab
Fazit
- Der Einsatz speichersicherer Sprachen in neuem Code führt zu einem exponentiellen Rückgang von Schwachstellen
- Mehr als sechs Jahre konsistenter Ergebnisse bei Android belegen die Wirksamkeit dieses Ansatzes
Zusammenfassung von GN⁺
- Um Speichersicherheitslücken zu reduzieren, ist die Umstellung auf speichersichere Sprachen wichtig
- Am Beispiel des Android-Teams zeigt sich ein deutlicher Rückgang von Speichersicherheitslücken
- Statt einer vollständigen Neuschreibung des bestehenden Codes ist eine bessere Interoperabilität der praktikable Weg
- Der Einsatz speichersicherer Sprachen wie Rust kann Sicherheit und Produktivität gleichzeitig steigern
1 Kommentare
Hacker-News-Kommentare
Interessanter Artikel. Der Kern ist, dass man nicht die ganze Welt neu schreiben muss
Es bringt bereits spürbare Verbesserungen, wenn man nur neue Entwicklung in speichersichere Sprachen verlagert, und das ist viel einfacher und günstiger, als alles zu portieren, um Wirkung zu sehen.
Dadurch steigt auch der Wert von Sprachen und Tools, die sich robust mit unsicherem Legacy-Code integrieren lassen.
Ich frage mich, ob es bei sehr altem Code auch einen Effekt wie eine „Badewannenkurve“ gibt. Ich erinnere mich noch daran, wie viele beim schweren OpenSSL-Bug, wohl zu Zeiten von Heartbleed, entsetzt auf den Code geschaut haben.
Beeindruckend ist aber, dass die Zahl der Probleme von 2019 bis 2023 auch ohne Neuschreibung um fast 60 % gesunken ist, allein dadurch, dass neuer Code in speichersicheren Sprachen hinzugefügt wurde, vermutlich überwiegend Rust und teilweise Kotlin. Ich frage mich auch, warum es zwischen 2021 und 2023 eine Plateauphase gab.
Dass für 2024 extrapolierte Zahlen verwendet wurden, verrät die Absicht, gute Werte zu zeigen; hilfreicher wäre gewesen, stattdessen den Anstieg von 2022 auf 2023 zu analysieren und zu erklären.
Schade ist auch, dass nicht genannt wird, in welchen Sprachen und in welchem Umfang neuer speichersicherer Code geschrieben wurde. Offenbar werden sowohl Rust als auch Kotlin verwendet, aber ob Rust 95 % oder 50 % ausmacht und in welchen Bereichen bei größerem Kotlin-Anteil Kotlin statt Rust eingesetzt wird, bleibt offen.
Die Diagramme in diesem Artikel zeichnen sich durch Klarheit und Prägnanz aus. Sie zeigen gut, wie eine sorgfältige Auswahl und Beschriftung von Daten die beabsichtigte Aussage ganz natürlich in den Fließtext einbetten kann.
Die Schlussfolgerung aus dem exponentiellen Rückgang der Schwachstellen ist, dass man sich auf reinen neuen Code konzentrieren sollte. Ressourcen in riesige, wahllose „Alles in Rust neu schreiben“-Projekte zu stecken, ist selbst mit dem Ziel maximaler Speichersicherheit ineffizient.
Bemerkenswert ist, wie gut zusammenpasst, dass die einfachste Strategie und zugleich die von praxisnahen Rust-Experten empfohlene Strategie laut den Daten auch die beste ist, um Speicherschwachstellen zu minimieren.
Die Aussage „Das Android-Team beobachtete, dass die Rollback-Rate von Rust-Änderungen weniger als halb so hoch ist wie die von C++“ ist überraschend.
„Schwachstellen nehmen exponentiell ab. Es gibt eine Halbwertszeit. [...] Eine groß angelegte Studie zur Lebensdauer von Schwachstellen, die 2022 auf der Usenix Security vorgestellt wurde, bestätigte dieses Phänomen ebenfalls. Die Forschenden stellten fest, dass sich die Mehrzahl der Schwachstellen in neuem oder kürzlich geändertem Code befindet.“
Dann wäre es für die Sicherheit womöglich besser, keine neuen Features mehr hinzuzufügen, wenn sie nicht zwingend nötig sind. Windows LTSC dürfte vermutlich die sicherste Windows-Version sein.
Für Schwachstellen, die noch nicht ausgenutzt wurden, gibt es diesen Rückgangsmechanismus aber nicht. Sie lösen weder Nutzerbeschwerden noch Bug-Reports aus, bleiben einfach bestehen und werden dann von einem Gegner mit ausreichend Ressourcen und Motivation gefunden und ausgenutzt.
Solche Gegner gibt es heute mehr als früher.
Natürlich heißt das nicht, dass sich alle Bugs nur in jüngstem Code befinden. Jeder hat schon Bugs gesehen, die jahrelang unentdeckt geblieben sind. Bei solchen Bugs muss man fragen, warum die Tests sie übersehen haben.
Deshalb sollten sich Tests auf kürzlich geänderten Code konzentrieren. Besonders Mutation Testing lässt sich sehr gezielt auf geänderten Code oder stark damit gekoppelten Code anwenden, wodurch der Aufwand dieser Tests stark sinkt.
Bei Google gab es ein System, das Mutation Testing zusammen mit Code-Review auf diese Weise einsetzte.
Natürlich ist das nicht überall gleich praktikabel, aber es sollte häufiger eingesetzt werden als heute.
Versionen an der vordersten Front enthalten viele neue Schwachstellen. In der Regel ist die älteste noch unterstützte Release meist die sicherste.
Natürlich gibt es Ausnahmen, wenn die Funktionen neuer Versionen den Mehrwert rechtfertigen, aber im Allgemeinen meide ich lieber Versionen, die auf „.0“ enden.
Es gibt eine Korrelation zwischen neuem Code und Speicher-Sicherheitslücken. Der Blogbeitrag liefert auch eine mögliche Erklärung, wonach Sicherheitslücken eine schnell abnehmende Halbwertszeit haben. Ich verstehe allerdings nicht, warum dabei von Kausalität zwischen den beiden Faktoren gesprochen wird
Für diese Korrelation gibt es mehrere plausible Erklärungen. Neuer Code hängt oft mit neuen Features zusammen, und Menschen konzentrieren sich darauf, Schwachstellen in neuen Features zu finden. Außerdem wurde älterer Code real häufiger genutzt und hat dadurch möglicherweise mehr Randfälle ausgeführt, in denen sich Speicherfehler verstecken könnten
Ich finde es unangenehm zu sagen, dass neuer Code Speicher-Sicherheitslücken verursacht und dass Schwachstellen eine schnell abnehmende Halbwertszeit haben. Nach bloßer Anzahl mag das stimmen, aber wenn ich an hochwirksame Open-Source-Schwachstellen wie Heartbleed oder Bugs bei der CPU-Cache-Invalidierung denke, scheint es in Bezug auf die Auswirkung nicht zu passen
Das gilt auch für meine jetzige Firma. Der frühe Code wurde von den Gründern geschrieben, und ein Teil des neueren Codes stammt von Auftragnehmern mit engen Deadlines
Wenn die Schlussfolgerung stimmt, dass „zum Beispiel auf Basis der durchschnittlichen Lebensdauer von Schwachstellen fünf Jahre alter Code eine 3,4-fach geringere Schwachstellendichte als neuer Code hat und bei Verwendung der in Android und Chromium beobachteten Lebensdauer sogar eine 7,4-fach geringere“, heißt das dann, dass mit Fehlern übersäter C-Code sicher wird, wenn man ihn fünf Jahre offline liegen lässt?
In dieser Studie stecken wichtige Daten, und unter dem Geteilten liegt sicher auch Wahrheit, aber das schlecht begründete Selbstvertrauen und die überzogene Ausweitung sind für mich extrem störend
„Produktivitätssteigerung: Sicheres Programmieren verschiebt das Auffinden von Bugs weiter nach links, also vor den Code-Check-in, und verbessert so Code-Korrektheit und Produktivität der Entwickler. Dieser Wandel zeigt sich in Kennzahlen wie der Rollback-Rate, etwa bei dringenden Code-Rücknahmen wegen unerwarteter Bugs“
„Das Android-Team hat beobachtet, dass die Rollback-Rate von Rust-Änderungen weniger als halb so hoch ist wie die von C++“
Ich schreibe seit 20 Jahren große Produktionssysteme in mehreren Sprachen, aber als ich 2016 Rust entdeckte, wusste ich sofort, dass das die Sprache ist, in die ich mich vertiefen würde. Ich habe noch am selben Tag das Buch von Klabnik und Carol gekauft und habe das gedruckte Exemplar immer noch
Ehrlich gesagt hat es meine Liebe zum Programmieren wiederbelebt
Im Text ist von „memory-safe languages (MSL)“ im Plural die Rede, aber die einzige Sprache, die ausdrücklich als Ziel der Umstellung und zur Verbesserung der Interoperabilität genannt wird, ist Rust
Kotlin wird ebenfalls im Kontext verbesserter Rust<>Kotlin-Interoperabilität erwähnt und hat in gewissem Maß Speicher-Sicherheitsfunktionen, aber ich weiß nicht, ob auf demselben Niveau wie Rust. Ich frage mich, ob Google nur diese beiden nutzt oder ob auch andere Sprachen gemeint sind
Im Kern geht es darum, ob etwas standardmäßig sicher ist oder nicht, weil darin die Wurzel des Problems liegt. Die Formulierung soll eher auf die Grundursache zielen, als eine bestimmte Sprache herauszugreifen oder zu empfehlen
Was Android als Gegenstand dieses Beitrags angeht, kenne ich keine Versuche, auf andere speichersichere Sprachen als diese beiden umzusteigen. Ich verfolge nicht die gesamte Android-Entwicklung, aber ich lese solche Beiträge ziemlich regelmäßig, und ich kann mich nicht erinnern, dass es um etwas anderes als Rust oder Kotlin ging
Der Kern ist, dass der eigene Code dann nicht mehr Speicher-Sicherheitsbugs ausgesetzt ist. Wenn es nicht zwingend nötig ist, könnte es sogar besser sein, eine Sprache ohne Rust-artiges manuelles Speichermanagement zu wählen
Google hat seine Sicht auf Speichersicherheit auch unter https://security.googleblog.com/2024/03/secure-by-design-goo... veröffentlicht und behandelt dort ebenfalls verwendete speichersichere Sprachen wie Java, Go und Rust
Im restlichen Google-Konzern wird Go für einen Teil der Systemsoftware verwendet, aber für Android habe ich das noch nicht gesehen
Wenn die Lebensdauer von Schwachstellen einer Exponentialverteilung folgt, dann lautet die Logik, dass ein Fokus auf sichere Standards wie Speichersicherheit in neuem Code überproportional wertvoll ist, sowohl theoretisch als auch auf Basis von sechs Jahren Android-Codebasis-Daten
Überraschend. Das ist das erste Mal, dass ich sehe, wie ein solches Argument genutzt wird, um Sicherheits-Geländer zu untermauern, die Security nach links verschieben, und ich finde das großartig. Besonders nützlich ist das für große Legacy-Codebasen, bei denen man leicht sagen könnte: „Bei 100 Millionen Zeilen altem C++ können wir von Speichersicherheit ohnehin nicht profitieren, also warum überhaupt anfangen?“
Es scheint auch zu bedeuten, dass selbst leichte Schwachstellen-Erkennung überproportional großen Nutzen bringen kann. Also schon dann, wenn man nicht den Backlog, sondern nur neuen Code und neue Abhängigkeiten betrachtet
Die daraus gezogene Schlussfolgerung ist etwas unerquicklich. Der offensichtliche Einwand — dass man weniger Schwachstellen findet, weil man sich alten Code weniger gründlich ansieht — wird nicht behandelt.
Es ist viel üblicher, sich aktuelle Commit-Logs anzusehen, als eine Bibliothek, die seit 20 Jahren unverändert ist.
Im Text gibt es keine Theorie dazu, warum alter Code weniger Bugs haben soll, aber meine ist einfach: weil sie bereits gefunden wurden.
Wenn man annimmt, dass jeder Code eine feste Zahl unbekannter Bugs pro 1000 Zeilen hat, dann steigt mit der Zeit die Wahrscheinlichkeit, sie zu entdecken, weil der Code in Produktion mit verschiedensten Eingaben immer wieder ausgeführt wird. Durch Fixes und die dabei stattfindenden Code-Reviews kann man im Mittel erwarten, dass er besser wird.
Deshalb sinkt mit der Zeit die Zahl der Bugs pro 1000 Zeilen im bestehenden Code. Er wurde eben im realen Einsatz erprobt.
Wie im Artikel gesagt: Wenn man neue Bugs weiterhin mit derselben Rate einführt, gibt es keinen Fortschritt. Wenn aber speichersichere Sprachen bei neuen Features dafür sorgen, dass weniger Bugs eingebracht werden, dann wird die Gesamtzahl der Bugs mit der Zeit sinken.
Commits dienen nur der Zuordnung. Wenn eine alte Bibliothek 20 Jahre lang unverändert blieb und 20 Jahre lang Fuzzing und manuelle Codeprüfung überstanden hat, ist sie vermutlich ziemlich robust.
Zum Beispiel könnten sich Ingenieurinnen und Ingenieure in den letzten Jahren darauf konzentriert haben, die riskantesten Teile in speichersicheren Sprachen neu zu schreiben, während risikoärmerer alter Code seltener verändert wurde.
Oder Änderungen bei Prozessen oder Personal könnten zu mehr Defekten geführt haben.
Trotzdem klingt die Erklärung plausibel, dass jeder Bug eine gewisse Entdeckungswahrscheinlichkeit pro Zeiteinheit hat und mit der Zeit immer weniger verbleibende Defekte übrig bleiben. Wenn Maintainer mehr Schwachstellen beheben, als sie neu einführen, dann wird alter Code weniger Schwachstellen haben, und die verbleibenden sind wahrscheinlich schwerer zu finden.
Da neuer Mac-Code größtenteils im speichersicheren Swift geschrieben wird, während Windows weiterhin überwiegend C oder C++ verwendet, frage ich mich, wie sich diese Logik auf Mac und Windows anwenden lässt.
Aktuelle Zahlen zur Sprachverteilung in neueren Windows-Versionen habe ich nicht gefunden, aber Microsoft setzt Rust sowohl für neue Entwicklung als auch für die Neuschreibung bestehender Funktionen ein [1] [2].
[0] Siehe Abschnitt „Evolution of the programming languages“ https://blog.timac.org/2023/1128-state-of-appkit-catalyst-sw...
[1] https://www.theregister.com/2023/04/27/microsoft_windows_rus...
[2] https://www.theregister.com/2024/01/31/microsoft_seeks_rust_...
Wenn man hier an das Endstadium denkt, dann gilt: Je seltener Schwachstellen werden, desto wertvoller werden sie. Die verbleibenden Schwachstellen werden von staatlichen Akteuren sorgfältig gehortet und nur gegen hochwertige Ziele sparsam eingesetzt.
Wenn dieser Blogpost die vierte Generation beschreibt, könnte die fünfte Generation etwas Ähnliches wie der Lockdown Mode von iOS sein: ein Kontrollkästchen, das sicherheitsbewusste Nutzer aktivieren können, um gegen Leistungseinbußen mehr Sicherheit zu bekommen.
Im Idealfall erkennt und erfasst dieses Kontrollkästchen Angriffe etwa per Virtualisierung und sendet sie anschließend zur Analyse an Sicherheitsteams. Das schafft Abschreckung für Angreifer. Sie werden ihre seltenen Schwachstellen nicht verbrennen wollen, wenn die Möglichkeit besteht, dass ein Nutzer dieses Sicherheitskästchen aktiviert hat — und viele hochwertige Ziele werden es aktivieren.
Eine Art Herdenimmunität für Software-Schwachstellen statt für biologische Krankheitserreger.
Sicherheitsbewusste Nutzer sind wahrscheinlich auch datenschutzbewusst. Daher sollte man nicht stillschweigend sämtliche Nutzeraktivitäten übertragen, sondern bei erkannter Attacke eine Benachrichtigung anzeigen. Schon ein paar KB an ungewöhnlicher Netzwerkaktivität könnten für ein Sicherheitsteam ausreichen, um den Angriff zu rekonstruieren, und man könnte vor einer Freigabe die Zustimmung der Nutzer einholen.