Ingenieure von Apple haben die Bereitschaft angekündigt, den Modus „-fbounds-safety“ für den Clang-Compiler zu testen, der sichere Arbeiten mit Puffern im C-Code garantiert. Dieser Modus ist Teil eines von Apple unterstützten LLVM-Forks für das Swift-Projekt. Zukünftig ist eine schrittweise Übertragung der Funktionalität „-fbounds-safety“ in den Hauptcode von LLVM/Clang geplant.
Es wird darauf hingewiesen, dass der vorgeschlagene Schutzmechanismus bereits aktiv in Apple-Produkten wie dem XNU-Kernel, Firmware, Audiobibliotheken und Bilddecodern eingesetzt wird. Die Aktivierung des Modus „-fbounds-safety“ verringert die Anwendungsleistung im Durchschnitt um 5 % (Abweichung von -1 % bis 29 %), erhöht die Codegröße um 9,1 % (Abweichung von -1,4 % bis 38 %) und verlangsamt die Kompilierung um 11 %.
Die Verwendung des Modus „-fbounds-safety“ zur automatischen Erkennung von Ausnahmen, die über die mit einem Zeiger verbundene Speichergrenze hinausgehen, erfordert die Hinzufügung spezieller Annotationen im Code und das Einfügen der Header-Datei „ptrcheck.h“. Das Prinzip der vorgeschlagenen Schutzmethode besteht darin, dass die Prüfungen der erlaubten Grenzen automatisch hinzugefügt werden, basierend auf manuell festgelegten Annotationen oder von dem Compiler bekannten Größen.
Im Gegensatz zur Verwendung von erweiterten Zeigern (wide pointer) im Code, die neben der Adresse auch Informationen über die obere und untere Grenze des Puffers enthalten, beeinträchtigt die Verwendung des Modus „-fbounds-safety“ nicht die ABI (Application Binary Interface), ändert nicht das Format der exportierten Zeiger und erfordert keine Neugestaltung des gesamten Projekts. Im Modus „-fbounds-safety“ werden erweiterte Zeiger nur in Bereichen angewendet, die sich nicht mit der ABI überschneiden, während für Zeiger, die die ABI betreffen, normale Zeiger mit Prüfungen verwendet werden, die auf den Annotationen mit den Grenzinformationen basieren.
Anmerkungen sind an Zeigern in den Feldern von Strukturen und Funktionen, die auf Arrays von Objekten verweisen, sowie an globalen Variablen mit Zeigern anzuhängen. Für Zeiger in lokalen Variablen sind Anmerkungen nicht erforderlich, da sie automatisch als erweiterte Zeiger behandelt werden, die bereits Informationen über die zulässigen Grenzen enthalten. Hinweise zu Konstruktionen im Code, für die Anmerkungen erforderlich sind, werden vom Compiler beim Start mit der Flagge „-fbounds-safety“ ausgegeben.
Das Schutzmodell auf der Basis von „-fbounds-safety“ kann schrittweise implementiert werden, dateiweise, ohne die Entwicklung des gesamten Projekts zu unterbrechen. Die Hinzufügung des Schutzes zum Projekt beschränkt sich auf die Angabe von Anmerkungen in einer bestimmten Datei mit Code, die Beseitigung von Compilerwarnungen und das Testen des Programmablaufs, wonach diese Schritte für die nächste Datei wiederholt werden. Code mit hinzugefügten Anmerkungen bleibt mit normalem C-Code und Compilern, die „-fbounds-safety“ nicht unterstützen, kompatibel (beim Erstellen mit anderen Compilern oder ohne die Flagge „-fbounds-safety“ werden einfach keine zusätzlichen Grenzkontrollen hinzugefügt).
Während der Programmausführung wird bei Erkennung eines Zugriffs außerhalb der zulässigen Grenzen eine Ausnahme generiert, und das Programm wird beendet. Ein unerwartetes Ende kann auch auftreten, wenn fehlerhafte Anmerkungen angegeben werden, weshalb bei Verwendung von „-fbounds-safety“ zusätzliche Tests erforderlich sind.
Im folgenden Beispiel wurde der Parameter „int *p“ mit der Anmerkung „__counted_by(n)“ versehen, die zur Laufzeit eine zusätzliche Prüfung der zulässigen Grenzen hinzufügt. Wenn versucht wird, den Code im Modus „-fbounds-safety“ ohne Angabe dieser Anmerkung zu kompilieren, gibt der Compiler eine Warnung über das Fehlen von 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“ fügt der Compiler eine Prüfung ein, die dem Code „if (i = n) trap();“ ähnelt. }
Für lokale Variablen mit Zeigern erfolgt die Überprüfungsbindung automatisch, zum Beispiel: void foo(int i){ char *buf = (char *)malloc(10); // für den Zeiger buf werden die Grenzen gespeichert buf[i] = 0xff; // es wird automatisch eine Überprüfung hinzugefügt: „if (buf + i = buf + 10) trap();“ }
Der Compiler optimiert nach Möglichkeit und schließt die Hinzufügung überflüssigen Codes aus, wenn im Code bereits erforderliche Überprü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 bereits die Bedingung „i < count“ vorhanden ist und i nicht kleiner als 0 sein kann. }
Wesentliche Annotationen:
- „__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)“ — legt die obere Grenze des Puffers fest.
- „__null_terminated“ — berücksichtigt das Nullzeichen als Ende des Puffers.
- „__single“ — bindet den Zeiger an ein einzelnes Objekt. Standardmäßig für Zeiger verwendet, die ABI beeinflussen, es sei denn, die Annotation wird explizit gesetzt.
- «__bidi_indexable» — erweiterter Zeiger mit Informationen zu oberen und unteren Grenzen. Standardmäßig für Zeiger verwendet, die keinen Einfluss auf ABI haben.
- «__indexable» — erweiterter Zeiger mit Informationen zur oberen Grenze.
- «__unsafe_indexable» — Zeiger ohne Grenzenprüfung (für die Portabilität mit unsicherem Code, z. B. zum Abrufen von Zeigern aus externem Code).
Quelle: opennet.ru
