Re: Avoiding stack buffer clear being optimised out

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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.)





[Index of Archives]     [Linux C Programming]     [Linux Kernel]     [eCos]     [Fedora Development]     [Fedora Announce]     [Autoconf]     [The DWARVES Debugging Tools]     [Yosemite Campsites]     [Yosemite News]     [Linux GCC]

  Powered by Linux