Ingenieure von Apple haben die Bereitschaft zur Testung des Modus „-fbounds-safety“ für den Clang-Compiler angekündigt, der Garantien für einen sicheren Umgang mit Puffern im C-Code bietet. Der Modus ist Teil eines von Apple unterstützten LLVM-Forks für das Swift-Projekt. In Zukunft ist eine schrittweise Übertragung der Funktionalität von „-fbounds-safety“ in die Hauptcodebasis von LLVM/Clang geplant.
Es wird darauf hingewiesen, dass der vorgeschlagene Schutzmechanismus bereits aktiv in Apple-Produkten eingesetzt wird, wie im XNU-Kernel, Firmware, Audiobibliotheken und Bilddecodern. Die Aktivierung des Modus „-fbounds-safety“ senkt die Leistung von Anwendungen im Durchschnitt um 5 % (Schwankungen von -1 % bis 29 %), erhöht die Codegröße um 9,1 % (Schwankungen von -1,4 % bis 38 %) und verlangsamt die Kompilierung um 11 %.
Die Verwendung des Modus „-fbounds-safety“ zur automatischen Erkennung von Out-of-Bounds-Zugriffen auf den mit einem Zeiger verknüpften Speicher erfordert das Hinzufügen spezieller Annotationen im Code und das Einbinden der Header-Datei „ptrcheck.h“. Der Kern des vorgeschlagenen Schutzverfahrens besteht darin, Überprüfungen auf die Einhaltung der gültigen Grenzen automatisch anzuhängen, basierend auf manuell festgelegten Annotationen oder bekannten Größen, die dem Compiler vorliegen.
Im Gegensatz zur Verwendung von erweiterten Zeigern (wide pointer) im Code, die neben der Adresse Informationen über die obere und untere Grenze des Puffers enthalten, stört die Verwendung des Modus „-fbounds-safety“ nicht die ABI (Application Binary Interface), ändert nicht das Format der exportierten Zeiger und erfordert keine sofortige Überarbeitung des gesamten Projekts. Im Modus „-fbounds-safety“ werden erweiterte Zeiger nur in Bereichen verwendet, die nicht mit der ABI überlappen, während für Zeiger, die die ABI beeinflussen, normale Zeiger mit Prüfungen verwendet werden, die auf Basis von Annotationen mit Informationen zu den Grenzen erstellt werden.
Annotationen müssen an Zeigern in Strukturfeldern und Funktionsparametern, die auf Arrays von Objekten verweisen, sowie an globalen Variablen mit Zeigern angebracht werden. Für Zeiger in lokalen Variablen sind keine Annotationen erforderlich, da sie automatisch als erweiterte Zeiger behandelt werden, die bereits Informationen über die zulässigen Grenzen enthalten. Der Compiler gibt Hinweise zu Codekonstruktionen aus, bei denen Annotationen hinzugefügt werden müssen, wenn er mit dem Flag „-fbounds-safety“ ausgeführt wird.
Das Schutzmodell auf der Grundlage von «-fbounds-safety» kann schrittweise, Datei für Datei, implementiert werden, ohne die Entwicklung des gesamten Projekts zu unterbrechen. Das Hinzufügen von Schutz zu einem Projekt beschränkt sich auf die Angabe von Annotations in einer bestimmten Datei mit Code, die Beseitigung von Compilerwarnungen und die Durchführung von Programmtests, wonach diese Schritte für die nächste Datei wiederholt werden. Der Code mit den hinzugefügten Annotations bleibt mit normalem C-Code und Compilern, die «-fbounds-safety» nicht unterstützen, kompatibel (bei der Kompilierung mit anderen Compilern oder ohne den Flag «-fbounds-safety» werden einfach keine zusätzlichen Grenzprüfungen hinzugefügt).
Während der Programmausführung wird im Falle eines Zugriffs außerhalb der zulässigen Grenzen eine Ausnahme generiert, und das Programm beendet seine Ausführung. Ein unerwarteter Abschluss kann auch bei fehlerhaften Annotations auftreten, daher ist bei der Nutzung von «-fbounds-safety» zusätzliche Tests der Programmfunktionalität erforderlich.
Im folgenden Beispiel wurde der Parameter «int *p» mit der Annotation «__counted_by(n)» versehen, die eine zusätzliche Überprüfung der zulässigen Grenzen zur Laufzeit hinzufügt. Wenn versucht wird, den Code im Modus «-fbounds-safety» ohne Angabe dieser Annotation zu kompilieren, gibt der Compiler eine Warnung über fehlende Informationen zu den Array-Grenzen beim Verarbeiten des Ausdrucks «p[i] = 0» aus. #include void init_buf(int *__counted_by(n) p, int n) { for (int i = 0; i < n; ++i) p[i] = 0; // im Modus «-fbounds-safety» wird der Compiler automatisch eine Überprüfung hinzufügen, die dem Code «if (i = n) trap();» entspricht }
Für lokale Variablen mit Zeigern werden die Überprüfungen automatisch angehängt, zum Beispiel: void foo(int i){ char *buf = (char *)malloc(10); // für den Zeiger buf werden Angaben zu den Grenzen gespeichert buf[i] = 0xff; // es wird automatisch eine Überprüfung «if (buf + i = buf + 10) trap();» hinzugefügt }
Der Compiler optimiert nach Möglichkeit und schließt unnötige Codeeingaben aus, wenn im Code bereits die erforderlichen Prüfungen vorhanden sind. Zum Beispiel: for (size_t i = 0; i < count; ++i) { buf[i] = i; // die Überprüfung «if (i = count) trap();» wird nicht hinzugefügt, da oben bereits die Bedingung «i < count» besteht und i nicht kleiner als 0 sein kann. }
Hauptannotationen:
- «__counted_by(N)» — definiert die Größe des Puffers in Elementen des Zieltyps.
- «__sized_by(N)» — definiert die Größe des Puffers in Bytes.
- » __ended_by(P)» — setzt die obere Grenze des Buffers fest.
- «__null_terminated» — behandelt das Nullzeichen als Ende des Buffers.
- «__single» — bindet den Zeiger an ein einzelnes Objekt. Wird standardmäßig für Zeiger verwendet, die Einfluss auf die ABI haben, es sei denn, die Annotation wird ausdrücklich gesetzt.
- «__bidi_indexable» — erweiterter Zeiger mit Informationen über obere und untere Grenzen. Wird standardmäßig für Zeiger verwendet, die keinen Einfluss auf die ABI haben.
- «__indexable» — erweiterter Zeiger mit Informationen über die obere Grenze.
- «__unsafe_indexable» — Zeiger ohne Grenzkontrolle (für die Portabilität mit unsicherem Code, z. B. zum Abrufen von Zeigern aus externem Code).
Quelle: opennet.ru
