Ich reverse hier gerade ein 64-bit ARM Binary, das OpenSSL
statisch reinkompiliert hat. In einer lteren Version, aber das
spielt hier keine Rolle.
OpenSSL hat eine Funktion namens
OPENSSL_cleans(ptr,len). Das ruft man auf Buffer auf, die
gleich out of scope gehen, damit kein Schlsselmaterial im Speicher
rumliegt. Smarte Defense-in-Depth-Manahme. Inhaltlich ist das blo
ein memset(ptr,0,len).
Vor ein paar Jahren fiel auf, dass Compiler unter anderem die
Funktion haben, sogenannte Dead Stores wegzuoptimieren. Das hier
ist z.B. ein dead store:
void foo() {
int i;
for (i=0; i<5; ++i) {
puts("huhu");
}
i=0; /* Hat keine Auswirkungen, kann weg */
}
In welchem Kontext benutzt man jetzt OPENSSL_cleanse? In so einem
hier:
char key[128];
[...]
OPENSSL_cleanse(key,sizeof(key));
return 0;
}
Wenn der Compiler versteht, dass
OPENSSL_cleanse keine
Seiteneffekte hat auer
key zu berschreien, dann ist das
ein klarer Fall fr die Dead Store Elimination. hnlich sieht es mit
einem
memset() for einem
free() aus.
Das ist vor ein paar Jahren aufgefallen, dass Compiler das nicht
nur tun knnen sondern sogar in der Praxis wirklich tun. Pltzlich
lagen im Speicher von Programmen Keymaterial herum. Also musste
eine Strategie her, wie man den Compiler dazu bringt, das memset
nicht wegzuoptimieren. Das ist leider in portablem C nicht so
einfach. Hier ist, wie ich das in dietlibc gemacht habe:
1 #include
2
3 void explicit_bzero(void* dest,size_t len) {
4 memset(dest,0,len);
5 asm volatile("": : "r"(dest) : "memory");
6 }
Das magische asm-Statement sagt dem Compiler, dass der
Inline-Assembler-Code (der hier leer ist) lesend auf dest zugreift,
was er aber nicht tatschlich tut. Damit ist der memset kein Dead
Store mehr und bleibt drinnen. Leider ist das asm-Statement eine
gcc-Erweiterung (die aber auch clang und der Intel-Compiler
verstehen).
Hier ist die Lsung von OpenSSL:
18 typedef void *(*memset_t)(void *, int, size_t);
19
20 static volatile memset_t memset_func = memset;
21
22 void OPENSSL_cleanse(void *ptr, size_t len)
23 {
24 memset_func(ptr, 0, len);
25 }
Die Idee ist, memset nicht direkt aufzurufen sondern ber einen
Function Pointer. Wenn man den volatile deklariert, dann muss der
Compiler annehmen, dass sich der Wert asynchon ndern kann. Kann er
aber nicht, weil da nie jemand was anderes als memset reinschreibt.
Das kann gcc leider erkennen, weil die Helden von OpenSSL das
static volatile deklariert haben, und es damit nur innerhalb dieser
Compilation Unit sichtbar ist, und da sind keine anderen Zugriffe.
Niemand nimmt auch nur die Adresse davon.
Wenn ich das mit einem aktuellen gcc 13.1 bersetze, kommt eine
Zeiger-Dereferenzierung heraus. Aber in dem Binary kommt ein ...
inline memcpy raus. Die haben ihr altes OpenSSL mit einem alten gcc
gebaut.
Gut, der alte gcc...