On 30/11/2022 17:26, Jonny Grant wrote:
Hello
Does GCC have a clear way to avoid memset being compiled out by optimiser?
This article came up, so I combined the broken.c with GCC
gcc -Wall -O2 -o broken broken.c
Note, I've been using gcc for many years, I'm not looking for just tips how to compile code. I only want to discuss this optimiser issue :-)
https://blog.cloudflare.com/the-linux-kernel-key-retention-service-and-why-you-should-use-it-in-your-next-application/
If I modify to clear the buffer, it gets removed by the compiler
The only way I could get it to not remove the memset is by adding another printf, (propagating a return code after checking memset wasn't enough)
// gcc -Wall -O2 -o broken broken.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
static int encrypt(void)
{
uint8_t key[] = "hunter2";
printf("encrypting with super secret key: %s\n", key);
void * addr = memset(key, 0, 8);
//printf("encrypting with super secret key: %s\n", key);
if(addr) return 1;
else return 0;
}
static void log_completion(void)
{
/* oh no, we forgot to init the msg */
char msg[8];
printf("not important, just fyi: %s\n", msg);
}
int main(void)
{
int ret = encrypt();
printf("ret:%d\n", ret);
/* notify that we're done */
log_completion();
return ret;
}
Jonny
If you are happy with gcc extensions, inline assembly can give you what
you want. It is even highly portable to different processor targets
(and to clang), because it has no actual assembly code!
<https://godbolt.org/z/6nfY5o89a>
The simplest "sledgehammer" technique here is a memory barrier using a
memory clobber after the "memset" :
asm volatile("" ::: "memory");
This tells the compiler to output the instruction sequence "", and that
it might read or write to anything in memory in unexpected ways. So the
compiler will flush out any "uncommitted" writes to memory before
"executing" this code. And it will re-read anything that had been
cached in registers. So it is quite general, but can have a significant
performance impact on some kinds of code. (Still, it is much less
dramatic than a real memory barrier or memory fence instruction.)
A more targeted approach is to follow the "memset" with :
asm volatile("" :: "m" (key) : );
This again has an empty set of assembly instructions. But it tells the
compiler that those instructions need the value of "key", available in
memory. So the "memset" must again be completed before "executing" the
assembly instructions. However, nothing else need be touched.
Of course, none of this helps if the compiler has made extra copies of
the key, or left copies on the stack from other calls.
(I hope that Jonathan or other gcc experts can correct me if I am
assuming too much from such inline assembly. I have used this kind of
construct on a number of occasions, but I have always checked the
generated assembly afterwards.)