grub-devel
[Top][All Lists]
Advanced

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

Re: [RFC PATCH] efi: Disable stack smashing protection on grub_efi_init(


From: Glenn Washburn
Subject: Re: [RFC PATCH] efi: Disable stack smashing protection on grub_efi_init()
Date: Tue, 18 Jul 2023 16:20:12 -0500

On Tue, 18 Jul 2023 00:47:14 -0500
Glenn Washburn <development@efficientek.com> wrote:

> GCC is electing to instrument grub_efi_init() to give it stack smashing
> protection when configuring with --enable-stack-protector on the x86_64-efi
> target. In the function prologue, the canary at the top of the stack frame
> is set to the value of the stack guard. And in the epilogue, the canary is
> checked to verify if it is equal to the guard and if not to call the stack
> check fail function. The issue is that grub_efi_init() sets up the guard
> by initializing it with random bytes, if the firmware supports the RNG
> protocol. So in its prologue the canary will be set with the value of the
> uninitialized guard, likely NULL bytes. Then the guard is initialized, and
> finally the epilogue checks the canary against the guard, which will almost
> certainly be different. This causes the code path for a smashed stack to be
> taken, causing the machine to print out a message that stack smashing was
> detected, wait 5 seconds, and then reboot. Disable grub_efi_init()
> instrumentation so there is no stack smashing false positive generated.
> 
> Signed-off-by: Glenn Washburn <development@efficientek.com>
> ---
> I recently discovered that my OVMF (debian version 2020.11-2+deb11u1) was
> not initializing the stack guard because the firmware was did enable support
> for the RNG protocol. It turns out that the RNG protocol is not enabled
> unless a source of randomness is found, which can be added with the QEMU
> arguments "-device virtio-rng-pci". This issue did not occur when the
> guard was not initialized in grub_efi_init() because the uninitialized
> value of the guard would be used for the canary, and sinice nothing changed,
> the canary and guard would be the same at the end of grub_efi_init().
> 
> Initially to solve this, I tried to set the canary in grub_efi_init() after
> the guard gets initialized. The problem I ran into is that gcc outputs
> instructions that use the base pointer register as a general purpose
> register. I couldn't come up with another method of generically figuring
> out where the start of the frame was so that I could set the canary. The
> rsp register is offset by the functions local variables which are variable
> as code is inlined or otherwise changes. Knowing the offset of the stack
> pointer to the frame start, I could properly set the canary. But again,

I was limiting my thinking to having the frame detection be solely
within grub_efi_init(). I can think of a couple other options, but they
are kludges, and may not be as applicable to non-x86.

 1. From the caller of grub_efi_init(), pass in the value of %esp as an
    argument via an asm statement. This requires changing the
    declaration to "void grub_efi_init (grub_addr_t *frame_base). Then
    add frame_base-2 should be the canary.
    * I mainly don't like this because changing the declaration is ugly.

 2. Similar to (1), but store in unused register and then use asm in
    grub_efi_init() to get at the value.
    * This seems hacky, since this is effectively passing an extra
       argument that is invisible C function declaration.

 3. Similar to (1), but in caller store %esp to a global variable and
    then read global from grub_efi_init().
    * Less hacky, but still kinda hacky and requires an unnecessary
      memory write.

 4. Statically initialize guard to unique value. Then in
    stack_protector_init() search stack memory for value with
    statically initialized guard value and assume if found that its a
    canary. Once found the canary, overwrite it with the new random
    guard value. The stack is short at this point, so we can limit the
    search to say 8 pointer sizes.
    * I have changes that implementing this and it works. This is fairly
      clean, in that no asm calls are needed, but its still kinda hacky
      in that it only checks up to a certain depth, which might not be
      enough if the code (radically) changes. My working implementation
      assumes that the canary has pointer size alignment, which may not
      be true on some platforms(?).

Of these, I like (4) the best, as it involves no inline assembly and
feels minimally intrusive.

Glenn

> if local variables are added or subtracted, including in inlined functions
> called, that offset will need to be regenerated. And its fragile because
> different verions of GCC or optimization levels may change this offset also.
> 
> If there was a robust way to set the canary, I think that would be preferable
> so that we can get stack smashing support on the function. Any one have ideas?
> 
> Another thing that has troubled me is why has no one else seen this behavior
> in the last several years that this code has been active? Do distros already
> have a patch for this? Is the RNG protocol supported by virtual no bare
> metal firmwares? I don't hit this issue on test machine, so I'm lead to
> believe that it must not support the RNG protocol (but I've yet to check).
> Are there other explanations for this that I'm missing?
> 
> Glenn
> ---
>  grub-core/kern/efi/init.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/grub-core/kern/efi/init.c b/grub-core/kern/efi/init.c
> index 6fe1ff8c868e..e759cc315b85 100644
> --- a/grub-core/kern/efi/init.c
> +++ b/grub-core/kern/efi/init.c
> @@ -102,7 +102,7 @@ stack_protector_init (void)
>  
>  grub_addr_t grub_modbase;
>  
> -void
> +__attribute__ ((__optimize__ ("-fno-stack-protector"))) void
>  grub_efi_init (void)
>  {
>    grub_modbase = grub_efi_section_addr ("mods");



reply via email to

[Prev in Thread] Current Thread [Next in Thread]