C11: Neue Version der Programmiersprache, Teil 1

Seite 4: Exkurs: Valide Pufferlänge

Inhaltsverzeichnis

Bei der Verwendung von Speicher ist man gezwungen, über deren untere sowie obere Grenze exakt Buch zu führen, damit nicht im fremden Speicher operiert wird. Das nicht zu beachten bringt das Programm zum Absturz – im mildesten Fall. Das nachfolgende Beispiel soll verdeutlichen, dass C so manche Stolpersteine aufweist. Ein Programmierer könnte einen "Bounds Check" zum Beispiel folgendermaßen implementieren:

size_t len;
char *ptr = malloc(10); /* zeigt zum Anfang des Speicherbereichs */
char *max = ptr + 10; /* zeigt an das Ende des Speicherbereichs */

/* nun die Überprüfung */
if (ptr + len > max)
return ERANGE;

Die Variable len stellt im Beispiel ein Argument dar, das als maximaler Wert dient. Beispielsweise gibt sie an, wie viel Speicher kopiert werden soll. Wenn len nun groß ist, kann es zu einem arithmetischen Überlauf (Zeigerüberlauf) kommen – was laut Standard zu undefiniertem Verhalten führt. Normalerweise wird das in der Hardware zu einer negativen Zahl (wrap around), die dann kleiner ist als max. Damit wäre die Bedingung falsch, was ursprünglich vermieden werden sollte.

Um das abzufangen, könnte der Programmierer folgendes Konstrukt verwenden:

if (ptr + len < ptr || ptr + len > max)
return ERANGE;

Die zusätzliche Überprüfung (ptr + len < ptr) soll den Fall abfangen, dass es zu einem Überlauf kommt. Aber auch das führt nicht unter allen Umständen zum erhofften Ergebnis. Je nach Compiler, Compiler-Version und Compiler-Flags kann der Compiler zu eifrig optimieren. Er nimmt dann an, dass ptr plus einen Wert mindestens >= ptr sein muss. Dem Compiler ist es gestattet, einen arithmetischen Überlauf als
undefiniertes Verhalten zu betrachten. Der Compiler nimmt daraufhin weiterhin an, dass undefiniertes Verhalten niemals vorliegt – warum auch nicht, es ist ja ein Programmfehler für den Compiler. Also kann der Compiler ptr + len < ptr vollständig entfernen. Damit wäre man wieder beim ursprünglichen Konstrukt.

Eine korrekte Version sieht beispielsweise so aus:

if (len > max - ptr)
return EINVAL;

Dabei ist sicherzustellen, dass ptr kleiner oder gleich zu max ist. Mit -Wstrict-overflow=3 kann man GCC anweisen, Warnungen auszugeben, falls Code "wegoptimiert" wird, aufgrund der Annahme, dass es vorzeichenbehaftete Überläufe nicht gibt. Man sieht an dem Beispiel: Schön, dass es doch Ecken und Kanten gibt, die auf den ersten Blick unkritisch wirken, aber aufgrund des laschen Korsetts der Sprache C und deren Auslegung doch nicht immer trivial sind. (ane)