Verbesserungen der statischen Analyse in GCC 14
(developers.redhat.com)-fanalyzerin GCC 14 verfolgt mehrere Ausführungspfade von C-Code per symbolischer Ausführung und konzentriert sich darauf, Fehler bereits zur Compile-Zeit zu finden, die vor dem Lauf schwer zu entdecken sind- Die Verbesserungen gliedern sich diesmal in Erkennung endloser Schleifen, Visualisierung von Buffer Overflows, Nachverfolgung von C-Strings und standardmäßig aktivierte Taint-Analyse; der Fokus liegt darauf, Diagnoseergebnisse leichter verständlich zu machen
-Wanalyzer-out-of-boundsgeht über eine einfache Warnung hinaus und zeigt mit einem Textdiagramm die Beziehung zwischen Buffer und Schreibposition; bei UTF-8-Strings wird auch angezeigt, in welches Zeichen hinein der Overflow erfolgt- Das neue Attribut
null_terminated_string_arg(PARAM_IDX)teilt dem Analyzer und Code-Lesern mit, dass eine API nullterminierte Strings erwartet, und macht Übergabepfade mit nicht nullterminierten Buffern besser sichtbar - Die Taint-Analyse wird bei Auswahl von
-fanalyzerstandardmäßig aktiviert, wodurch sich Flüsse leichter erkennen lassen, in denen von Angreifern kontrollierte Werte ohne Grenzprüfung für Größen, Indizes, Offsets usw. verwendet werden
Welchen Problemumfang -fanalyzer abdeckt
-fanalyzerist ein statischer Analyse-Pass von GCC, der mehrere Ausführungspfade von C-Quellcode per symbolischer Ausführung verfolgt, um Fehler zur Compile-Zeit zu finden- Die Verbesserungen in GCC 14 wurden im Vorfeld des offiziellen Releases im April 2024 zusammengefasst; zum Zeitpunkt der Veröffentlichung des Artikels wurde GCC 14.1 im Laufe des April 2024 erwartet
- Das Pre-Release von GCC 14.0 wird bereits in Fedora 40 Beta verwendet
- Die Beispiele lassen sich im Compiler Explorer zusammen mit den neuen Compiler-Optionen ausprobieren
Erkennung einfacher Endlosschleifen
- GCC 14 ergänzt die neue Warnung
-Wanalyzer-infinite-loop - Der Beispielcode zeigt in verschachtelten
for-Schleifen, dass die Bedingung der zweiten Schleifej < nlautet, der Inkrementausdruck aber fälschlichi++geblieben ist- Das entspricht dem Fehler, nach dem Kopieren der ersten
for-Schleife dasiim Inkrementausdruck nicht injgeändert zu haben - Der Analyzer verfolgt auf diesem Pfad fortlaufend den True-Zweig von
j < nund warnt vor der Schleifenwiederholung
- Das entspricht dem Fehler, nach dem Kopieren der ersten
- Die aktuelle Diagnoseausgabe lässt sich am leichtesten verstehen, wenn man die Ereignisnummern
(1)bis(5)der Reihe nach liest - Für GCC 15 bleibt als Wunsch offen, die Lesbarkeit mit einer Art ASCII-Art zur Hervorhebung des Kontrollfluss-Pfads zu verbessern
- Der entsprechende Code kann im Compiler-Explorer-Beispiel ausprobiert werden
Buffer Overflow als Text visualisieren
- In GCC 13 begann der Analyzer mit
-Wanalyzer-out-of-bounds, Prüfungen auf Grenzverletzungen zu unterstützen - In GCC 14 kann die räumliche Beziehung eines vorhergesagten Buffer Overflows als textbasiertes Diagramm ausgegeben werden
- Im Beispiel mit
char buf[10], auf das erststrcpy(buf, "hello")angewendet und danachstrcat(buf, " world!")aufgerufen wird, wird ein stackbasierter Buffer Overflow erkannt- Die bisherige Meldung zeigte an, dass
bufeine Kapazität von 10 Byte hat und ein Schreibzugriff von Byte 10 bis Byte 12 außerhalb des zulässigen Bereichs erfolgt - Das Diagramm in GCC 14 zeigt zusätzlich den von
strcpygefüllten Ziel-Buffer und das vorhandene terminierendeNUL-Byte, an demstrcatansetzt
- Die bisherige Meldung zeigte an, dass
- Auch im Beispiel mit Nicht-ASCII-Strings wird die Overflow-Position entlang der UTF-8-Darstellung angezeigt
- Als Beispiel dient Code, der
"サツキ"inchar buf[11]kopiert und anschließend"メイ"anhängt - Das Diagramm zeigt, dass der Overflow mitten im Zeichen
メ, also bei U+30E1, auftritt
- Als Beispiel dient Code, der
- Der zugehörige Code ist im ASCII-String-Beispiel und im Nicht-ASCII-String-Beispiel zu sehen
Verbesserte Nachverfolgung von C-String-Operationen
- Der Analyzer in GCC 14 verbessert die Nachverfolgung von C-String-Operationen und simuliert APIs, die beim Scannen eines Buffers nach dem nullterminierenden Byte suchen
- Wenn ein Zeiger auf einen nicht nullterminierten Buffer zeigt und ein Pfad existiert, auf dem er an eine solche API übergeben wird, erfolgt eine Warnung
- Das neue Funktionsattribut
null_terminated_string_arg(PARAM_IDX)signalisiert dem Analyzer und Code-Lesern, dass ein bestimmter Parameter ein nullterminierter String sein muss - Im Beispiel ist
example_fn(const char *p)mit den Attributennull_terminated_string_arg(1)undnonnullversehenchar str[3] = "abc";hat keinen Platz für ein nullterminierendes Byte- Beim Aufruf von
example_fn(str)warnt der Analyzer vor einem stackbasierten Buffer Over-Read, bei dem 1 Byte hinter dem Ende vonstrgelesen wird - Die Diagnose enthält außerdem eine Note, dass das erste Argument von
example_fnein Zeiger auf einen nullterminierten String sein muss
- Dieses Beispiel lässt sich im Compiler Explorer ausprobieren
Taint-Analyse standardmäßig aktiviert
- Die Taint-Analyse des Analyzers verfolgt durch Angreifer kontrollierte Eingaben, Sanitizing-Punkte und Stellen, an denen Werte ohne Sanitizing verwendet werden
- In früheren GCC-Releases war sie wegen Bugs und vieler False Positives nicht standardmäßig aktiviert und hinter einem separaten Kommandozeilenargument verborgen
- In GCC 14 wird die Taint-Analyse nach diversen Fehlerkorrekturen standardmäßig aktiviert, wenn
-fanalyzerausgewählt wird - Durch diese Änderung werden auch die folgenden sechs Taint-basierten Warnungen aktiviert
Kernel-Analyse am Beispiel von CVE-2011-2210
- Ein aus dem Linux-Kernel entnommenes Beispiel zu CVE-2011-2210 wird als Fall für die Taint-Analyse verwendet
- Durch das Hinzufügen von
__attribute__((tainted_args))zum Makro__SYSCALL_DEFINExwird dem Analyzer mitgeteilt, dass die Argumente vonosf_getsysinfoWerte sind, die eine Vertrauensgrenze überschritten haben und als tainted values behandelt werden müssen - Der Analyzer in GCC 14 warnt bei
copy_to_user(buffer, hwrpb, nbytes), dass der von Angreifern kontrollierte Wertnbytesohne obere Grenzprüfung als Größe verwendet wird- Die Diagnose zeigt, dass
nbytesinif (nbytes < sizeof(*hwrpb))nur eine untere Grenzprüfung erhalten hat - Der dritte Parameter von
copy_to_userist über das Attributaccess(write_only, 1, 3)als Größenparameter gekennzeichnet
- Die Diagnose zeigt, dass
- Das Problem liegt darin, dass die Sanitizing-Bedingung als
if (nbytes < sizeof(*hwrpb))geschrieben wurde- Die beabsichtigte Bedingung war von der Form
if (nbytes > sizeof(*hwrpb)) - In der korrigierten Version warnt der Analyzer nicht
- Die beabsichtigte Bedingung war von der Form
- Die Arbeit, den Analyzer im Kernel auszuführen, Schwachstellen zu finden und False Positives des Analyzers zu beheben, wird fortgesetzt
1 Kommentare
Hacker-News-Kommentare
Für mich ist fanalyzer eines der Killer-Features von GCC im Vergleich zu Clang. Weil er Fehler erklärt, wird C-Programmierung viel einfacher, und die Fehlermeldungen wirken entwicklerfreundlich, fast schon ähnlich wie bei Rust.
Einer der größten Gründe, warum Leute beim Lernen von Technik aufgeben, sind oft frustrierende oder unklare Fehler. Das ist etwas off topic, aber ich meinte: Ich mag C, und mit Fehlermeldungen auf Rust-Niveau würde es mir noch besser gefallen.
error: missing semicolonbekam man oft über 1000 Zeilen Fehlermeldungen zur Template-Instanziierung.Ich habe den GCC-Analyzer ein paarmal benutzt, aber keinen vernünftigen Frontend gefunden, der die Ausgabe gut lesbar macht. Bei Clang gibt es mehrere brauchbare Optionen wie recht ordentliche HTML-Ausgabe, CodeChecker oder Xcode-Integration. Mich würde interessieren, wie man die Ausgabe auf GCC-Seite eigentlich lesen soll. Außerdem scheint GCC viel mehr False Positives als Clang zu erzeugen.
In einem anderen Thread gibt es 36 weitere Kommentare: https://news.ycombinator.com/item?id=39918278
„GCC 14 Boasts Nice ASCII Art for Visualizing Buffer Overflows (phoronix.com)“, vor 2 Stunden gepostet.
Vor ein paar Monaten habe ich ein kleines Linux-Utility gebaut. Es war ein Drop-in-Shim als Ersatz für eine beliebige ausführbare Datei: Beim Aufruf tat es so, als wäre es das eigentliche Programm, forkte dann das Original und verband sich mit stdout/stderr.
Die Fehlerausgabe wurde an einen angepassten GPT-Assistenten geschickt, der den Kontext des Programms kannte. Der Assistent wandelte den ursprünglichen Fehler in eine für Menschen besser lesbare Form um und schrieb ihn dann auf stderr des Shims aus. Ich nutzte das, weil ich die verschachtelten Compiler-Dumps von GCC/Clang zu Concepts/Templates leid war, aber theoretisch hätte man es für jedes Programm verwenden können. Es funktionierte gut, aber ich wurde schwer krank und konnte nicht weiter daran arbeiten. Wenn jemand das noch einmal richtig und allgemeiner bauen würde, könnte das ein gutes Projekt sein.
Ich hätte gern ein besseres Ausgabeformat für die Analyseergebnisse. So wie es jetzt ist, ist es die Hölle für Screenreader.
Ein Beispiel für die Ausgabe gibt es hier: https://godbolt.org/z/aan6Kfxds
Es ist einfach das erste Beispiel aus dem Artikel mit der zusätzlichen Kommandozeilenoption
-fdiagnostics-format=sarif-stderr. Es gab auch Experimente, Diagramme als SVG auszugeben, aber das wurde nicht weit genug ausgearbeitet, um es in GCC 14 aufzunehmen.Der ursprüngliche Code gab
-1zurück, wennnbytes < sizeof(*hwrpb)war, und-2, wenncopy_to_user(buffer, hwrpb, nbytes)fehlschlug. Der eingespielte Fix prüftenbytes > sizeof(*hwrpb), aber ich haltecopy_to_user(buffer, hwrpb, sizeof(*hwrpb))für die richtige Korrektur.Es ergibt keinen Sinn, aus einem
hwrpb-Pointer etwas mit einer anderen Größe alssizeof(*hwrpb)herauszukopieren.nbytes = 4übergibt undsizeof(hwrpb)16 Byte ist, kann man 12 Byte zu viel kopieren und Speicher lesen, der dem Aufrufer nicht gehört. Das sollte man vermeiden.Die bessere Lösung ist, nur die minimale Bytezahl zu kopieren, die sowohl Aufrufer als auch Aufgerufener unterstützen. Etwa so:
nbytes = MIN(nbytes, sizeof(hwrpb));. Unter der Annahme, dass die Versionsinformation inhwrpb->sizeerhalten bleibt, kann man damit Abwärts- und Aufwärtskompatibilität gewährleisten, selbst wenn Teile der Struktur nicht initialisiert sind.nbyteshinaus übergeben.Wirklich hervorragend. Der Arbeitsaufwand dahinter scheint enorm zu sein. Vom Schwierigkeitsgrad her wirkt es vergleichbar mit der Einführung von fat pointers/Array-Views in Standardbibliothek und C-Standard.
-Wstringop-overflowist eine Warnung, die ich als Erstes abschalte, weil sie viel zu viele False Positives hat. Ich bezweifle, dass eine Analyzer-Variante davon besser sein wird.Sehr cool. Ich entwickle heute nicht mehr viel in C, daher frage ich mich, wie oft
strcpyundstrcatnoch verwendet werden. Als ich zuletzt nachgesehen habe, galten sie fast als ähnlich tabu wiegoto.Natürlich weiß ich, dass
gotoin der Kernel-Entwicklung oft bevorzugt wird. Ich frage mich nur, wie hilfreich C-String-Analyse in der Praxis wirklich ist.gotoeindeutig korrekt und elegant. Wenn man es um jeden Preis vermeiden will, kann das zu schwer wartbarem, hässlichem und verschachteltem Code führen. Es ist nicht häufig, aber es hat legitime Anwendungsfälle.Funktionen wie
strcpysind zwar weniger empfehlenswert, aber es gibt Situationen mit stärkeren Invarianten, etwa sprachseitigen Invarianten, in denen garantiert ist, dass sie korrekt sind, solange diese Invarianten nicht verletzt werden. Wenn sie doch verletzt sind, hat man ohnehin schon ein viel größeres Problem. Seltene Fälle, aber man kann argumentieren, dass eine nominell sicherere Alternative ohne Nutzen etwas weniger effizient sein könnte.gotologisch äquivalent zu strukturierten Programmierkonstrukten verwendet, die C nicht hat, ist eine gewisse Nutzung vongotoin C immer noch idiomatisch. Man muss vorsichtig sein, aber es ist eben C.longjmp mag ich allerdings überhaupt nicht.
gotoist nichts verkehrt. Die strxcpy-Familie dagegen ist kompletter Müll und sollte aus keinem Grund verwendet werden. Dass sie im Kernel benutzt wird, ist erschreckend.Diese Funktionen und alle gescheiterten Versuche, sie „zu reparieren“, hätte man in die Umlaufbahn schießen sollen.
gotoist in Ordnung, wenn man es bedacht einsetzt.strcpyundstrcatsind auch in dem Sinne „in Ordnung“, dass man weiß, dass der Code korrekt ist und im Fehlerfall etwas Großes schiefläuft. Leider trifft diese Beschreibung auf den Großteil von C zu.gotoso tabu ist wiestrcatundstrcpy.gotoist okay, aberstrcatundstrcpy, wenn sie ohne ein gleichsinnig korrekt dimensioniertesmallocim selben Scope verwendet werden, sind eher ein Code Smell.Sehr gut. Schön, dass überall detaillierte Berichte beigefügt sind, die erklären, was genau falsch gelaufen ist.