Fragen? Antworten! Siehe auch: Alternativlos
#include <string.h> void foo(int x) { char buf[10]; int i; for (i=0; i<sizeof(buf); ++i) buf[i]=x++; memset(buf,0,sizeof(buf)); }Die Funktion hat einen lokalen Puffer, buf, tut damit irgendwas (die sinnlose for-Schleife) und ruft dann memset auf. Das ist ein lokaler Puffer, der ist auf dem Stack und eh "weg", wenn die Funktion beendet wird. Insofern ist das gut, was gcc da macht. Und warum würde man überhaupt memset am Funktionsende machen, wenn der Buffer gleich weg ist!
Nun, das kommt häufiger vor als ihr vielleicht denkt. Das macht man nämlich in Verschlüsselungs-Code, damit nicht Teile des Schlüssels irgendwo im Speicher rumgammeln, wo sie später gefunden werden können. Auch abgeleitete Daten will man so wegräumen. Das ist natürlich dann doof, wenn der Compiler das wegschmeißt.
Das Problem ist schon älter, der Microsoft-Compiler macht das schon länger so. Und weil das für Krypto-Code ein Problem ist, gibt es im Windows-API eine spezielle Funktion namens SecureZeroMemory, die am Ende auch nur memset macht, aber der Compiler weiß das nicht und optimiert es daher nicht weg.
Ich kompilier obige Funktion mal mit gcc 4.7.2:
$ gcc -O3 -c t.c $ objdump -dr t.o t.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <foo>: 0:f3 c3 repz retq $
Wie ihr seht ist von dem Körper der Funktion nichts übrig geblieben, weil natürlich auch die Schleife nur in einen Buffer schreibt, der dann nicht rausgereicht wird. Diese Optimierung nennt man Dead Store Elimination. clang/LLVM macht das schon länger. Der Intel-Compiler auch. Wenn ihr also Krypto-Code geschrieben habt, der memset benutzt, um Schlüssel wegzuräumen, und das war bisher kein Problem, weil ihr gcc benutzt habt: Jetzt ist das auch für euch ein Problem.
Diese Optimierung greift übrigens auch, wenn der Puffer nicht lokal auf dem Stack ist sondern per malloc vom Heap geholt und am Ende das memset vor dem free ist.
Typischer Anfängerfehler in Krypto-Code.
Vielleicht sollte ich noch sagen, was die Lösung ist: Eigenes memset haben, in eine Library tun (NICHT inline über ein Header-File!). Und nicht Link Time Optimization anschalten, sonst wird das doch wieder geinlined und fliegt möglicherweise insgesamt raus. Oooooder, wer ein bisschen das Abenteuer sucht, kann auch den gcc-Optimizer manuell davon überzeugen, das nicht wegzumachen. Dafür tut man hinter das memset folgende Zeile:
asm volatile("" : : : "memory");Langfristig vermutlich die solidere Lösung, tut aber nicht in clang (Bug, wenn ihr mich fragt; benutze aber auch den Subversion-Snapshot, nicht die Release-Version).
Update: Lieber "asm volatile" als "asm". Sollte egal sein, ist es im Moment auch, aber ist vermutlich ein bisschen vorausschauender.
Update: Weil so viele Leute fragen, wieso man nicht den Puffer volatile deklariert: Das ist doof, weil es auch fast alle anderen Optimierungen ausschaltet. Wir reden hier von Krypto-Code, wo (allgemein gesprochen jetzt) jemand Monate investiert hat, um noch den letzten Taktzyklus rauszuoptimieren. Da will man den Optimierer schon grundsätzlich behalten.
Update: Hier empfiehlt gerade noch jemand das hier:
volatile size_t foo = (size_t)memset(buf,0,sizeof(buf));Das sieht gut aus, aber ich halte das eher für Glück. Der Compiler kann ja sehen, da das eine lokale Variable ist, und dass daher niemand von extern die Adresse haben kann (wenn man die nicht nimmt und irgendwas externes damit aufruft). Hab das aber jetzt nicht im Standard nachgeschlagen, ist nur Bauchgefühl.
Update: Habe einen Bug bei LLVM gefiled, und da hat jemand rausgefunden, dass dieses asm-Statement mit gcc und llvm funktioniert:
asm volatile("" : : "r"(&buf)" : "memory")Die Begründung ist, dass LLVM erkennt, dass das ganze Array nie benutzt wird, und es damit komplett entfernt, weil es "in Register gepasst hätte" (unabhängig davon, ob es tatsächlich in Register passt). Und tatsächlich, wenn man dafür sorgt, dass das tatsächlich im Speicher landet, dann wird auch ohne den Zusatz mit dem anderen asm-Statement ein memset ausgegeben. Insofern ist das kein Bug in LLVM sondern sogar ein Feature :-)