Siebzig Prozent aller CVEs sind Memory-Safety-Bugs. Buffer Overflows, Use-after-free, Double Frees. Die Art von Schwachstellen, die einem Angreifer erlauben, von einem malformed JPEG bis zu Root-Zugriff vorzudringen.
Wir wissen seit 1975, wie man die meisten davon in Hardware stoppt. Der Cambridge-CAP-Computer nutzte capability-basierte Adressierung. Carnegies Hydra tat es ebenfalls. Die Idee war simpel: Jeder Speicherzugriff trägt seinen eigenen Permission Slip. Die Hardware erzwingt ihn. Kein Permission Slip, kein Zugriff.
Und dann verbrachte Capability-Hardware die nächsten vier Jahrzehnte damit, gegen flache Speichermodelle zu verlieren. Erst jetzt, mit ARMs Morello-Board und dem CHERI-Instruction-Set, erleben Capability-Architekturen ein glaubwürdiges Comeback.
Die Frage ist nicht, ob Capabilities funktionieren. Das tun sie. Die Frage ist, warum es der Industrie vierzig Jahre gedauert hat, sich dafür zu interessieren.
Ein Capability-Pointer ist ein unverfälschbares Ticket, nicht nur eine Adresse
Ein normaler Pointer ist eine Ganzzahl. Eine Adresse. Er hat keine Länge, keine Berechtigungen und keine Herkunft. Wenn Sie eine Adresse erraten können, können Sie darauf zugreifen. Das ist kein Feature. Das ist ein Design-Bug, den C vom PDP-11 geerbt hat.
Ein Capability-Pointer hingegen ist ein hardware-erzwungenes Tupel. Er enthält die virtuelle Adresse, die Basis und Länge der autorisierten Region, Lese-/Schreib-/Ausführungsberechtigungen und ein 1-Bit-Validity-Tag. Das Tag liegt außerhalb des normalen Speichers, in dedizierter Hardware-Metadaten, was Capabilities unverfälschbar macht. Sie können keine gültige Capability aus einer Ganzzahl konstruieren. Sie können sie nur von etwas erhalten, das bereits Autorität besaß.
Bei CHERI sieht das auf einer 64-Bit-Maschine wie ein 128-Bit-Pointer aus. Die zusätzlichen 64 Bits tragen Bounds und Berechtigungen. Das Tag-Bit liegt in einer Shadow-Tabelle oder in übrigen ECC-Bits, je nach Implementierung.
Hier sieht man, wie das Einschränken einer Capability in der Praxis auf einem CHERI-System aussieht:
#include <cheriintrin.h>
#include <stdio.h>
int main(void) {
char buffer[64];
// In the purecap ABI, &buffer[0] is already a capability
// carrying 64-byte bounds and read/write permissions.
char *cap = buffer;
printf("Original length: %zu\n", cheri_length_get(cap));
// Narrow authority to the first 16 bytes.
char *narrow = cheri_bounds_set(cap, 16);
printf("Narrowed length: %zu\n", cheri_length_get(narrow));
// This access is safe and hardware-authorized.
narrow[15] = 'x';
// This would trap with a CHERI bounds violation:
// narrow[20] = 'y';
return 0;
}
Wenn ein Angreifer einen Capability-Pointer korrumpiert, löscht sich das Tag-Bit. Die Hardware löst bei der nächsten Dereferenzierung eine Exception aus. Die Exploit-Kette stirbt beim ersten Hop.
Das ist ein radikal besseres Sicherheitsmodell als ASLR und Stack Canaries, die nur Geschwindigkeitsbumpen sind.
Der iAPX 432 vergiftete den Brunnen für eine Generation
Warum haben wir also vierzig Jahre lang so getan, als wären Pointer einfach nur Ganzzahlen?
Intel brachte 1981 den iAPX 432 heraus. Er hatte Hardware-Capabilities, objektorientierte Speichersegmente und sogar hardware-unterstützte Garbage Collection. Er war auch ungefähr fünf- bis zehnmal langsamer als der 8086, enorm komplex und inkompatibel mit jedem existierenden Compiler.
IBM hatte mehr Glück mit dem System/38, das Capabilities für seinen Single-Level-Store nutzte. Es funktionierte. Es war stabil. Es war aber auch ein proprietäres Midrange-System ohne Pfad in das entstehende offene Ökosystem. Als die UNIX-Revolution ankam, brachte sie flache Address Spaces, C-Pointer und die Annahme, dass ein Pointer in ein Register passt. Diese Annahme wurde zur ABI der gesamten Industrie.
In den 1990ern war Capability-Hardware eine Forschungskuriosität. Der kommerzielle Imperativ war Rückwärtskompatibilität und rohe Performance. Memory Safety war ein Anliegen auf Sprachebene, wenn überhaupt. C und C++ erklärten es schlicht zum Problem des Programmierers.
Das funktionierte gut genug, bis das Internet jeden Buffer Overflow remote ausnutzbar machte.
CHERIs Hybrid-Modus durchbricht die Kompatibilitätsblockade
Moderne Capability-Hardware, speziell CHERI, hat die Lektion des iAPX 432 gelernt. Sie erzwingt keine All-or-nothing-Migration.
CHERI unterstützt einen hybriden ABI, bei dem Legacy-Integer-Pointer und Capability-Pointer im selben Prozess koexistieren. Sie können nur Ihren Netzwerk-Parser oder Ihre sandboxed Bibliothek mit Capabilities kompilieren, während der Rest Ihrer Anwendung normale Pointer nutzt. Das Betriebssystem (CheriBSD oder der CHERI-Linux-Port) verwaltet den Übergang.
Das ist wichtig, weil die echte Barriere nie die Silizium-Fläche war. Es war die Software-Trägheit. Es gibt Milliarden von Zeilen C und C++, die sizeof(void *) == 8 voraussetzen. Eine Capability-Architektur, die das Umschreiben all dieser Zeilen erfordert, ist tot bei Ankunft. Eine, die inkrementell adoptiert werden kann, hat eine Chance.
Der Hardware-Mechanismus ist elegant. Capability-aware Loads und Stores nutzen dedizierte Instruktionen, die das Tag und die Bounds parallel zur Address Translation prüfen. Integer-Pointer umgehen die Prüfungen komplett. Sie zahlen für Sicherheit nur dort, wo Sie sie nutzen.
Die echten Kosten sind TLB-Druck und Pointer-Bloat, nicht die Zykluszahl
Capabilities sind nicht kostenlos. Der Overhead verteilt sich auf drei Eimer.
Erstens, Pointer-Größe. In einem purecap ABI ist jeder Pointer 128 Bit. Das verdoppelt den Cache-Druck für pointer-heavy Datenstrukturen. Linked Lists, Bäume und Vtables werden alle dicker.
Zweitens, Bounds-Granularität. CHERI erzwingt Bounds auf Byte-Granularität, was bedeutet, dass das Memory-Subsystem bei jeder Dereferenzierung Bounds prüfen muss. Die Prüfung selbst ist schnell, aber feingranulare Capabilities können TLB- und Cache-Druck erhöhen, wenn Sie viele kleine geschützte Regionen erstellen.
Drittens, und am wichtigsten, Software-Änderungen. Compiler müssen capability-aware Prologe generieren. ABIs ändern sich. Memory Allocators müssen bounded Capabilities statt raw Pointer zurückgeben. Debugger müssen 128-Bit-Register verstehen.
Der gemessene Runtime-Overhead auf CHERI-Hardware liegt typischerweise im einstelligen Prozentbereich für die meisten Workloads und bei bis zu zehn bis fünfzehn Prozent für pointer-heavy Benchmarks. Das ist weniger als der Overhead vieler moderner Mitigations wie Memory Tagging oder Sandboxing, und es bietet stärkere Garantien.
Der Trade-off ist nicht Performance. Der Trade-off ist Ökosystem-Umbruch.
Sie können das heute auf einem Dev-Board ausführen
Wenn Sie Capability-Hardware ausprobieren wollen, brauchen Sie keine Zeitmaschine oder einen Forschungszuschuss.
ARMs Morello-Board implementiert CHERI-Erweiterungen auf einem Neoverse-N1-Core. CheriBSD läuft out of the box darauf. Wenn Sie die Hardware nicht haben, emuliert QEMU-CHERI die gesamte Capability-Architektur.
Die Toolchain ist Standard-LLVM. Sie kompilieren mit cheri-clang und der Flag -march=morello+cheri. FreeBSD- und Linux-Ports existieren. Der LLVM-Integrated-Assembler, Linker und Debugger verstehen alle Capabilities.
Starten Sie mit einer einzelnen Funktion. Wrappen Sie einen Parser oder eine Deserialisierungsroutine in eine eingeschränkte Capability. Laufen Sie Ihren Fuzzer dagegen. Wenn ein Out-of-Bounds-Write den Heap korrumpiert hätte, bekommen Sie stattdessen eine saubere Hardware-Exception.
Capability-Hardware ist kein theoretischer Fix für Memory Safety. Es ist eine funktionierende, ausgelieferte Technologie, die die Industrie vierzig Jahre lang ignoriert hat, weil die Anreize falsch waren. Flacher Speicher war schneller zu bauen, einfacher zu portieren und gut genug, bis Remote-Exploitation zum Standard-Threat-Model wurde.
Das Silizium funktioniert. Die Compiler funktionieren. Die Betriebssysteme funktionieren. Das Einzige, was bleibt, ist zu entscheiden, welche Teile Ihrer Codebase zuerst geschützt werden sollen.