Compiling SerenityOS Kernel with -ftrivial-auto-var-init

Introduction

In recent years the industry has collectively decided that it’s probably time we do something to address the various bugs that can occur from uninitialized stack memory. A few examples of bug classes that can originate from uninitialized stack memory include:

  • Kernel information disclosure, where uninitialized struct members or struct padding is copied back to usermode, leaking kernel information such as stack or heap addresses, or secret data like stack cookies.

  • Control flow based on uninitialized memory can cause a variety of issues at at runtime, including stack corruptions like buffer overflows, heap corruptions due to deleting stray pointers. Even basic logic bugs can result from control flow operating on uninitialized data.

TODO: Add more history from the “Automatic initialization of variables section https://isopenbsdsecu.re/mitigations/missing_features/

In 2018 JF Bastien contributed “Automatic variable initialization” to LLVM and later presented that work in 2019 with his “Mitigating Undefined Behavior” talk at the LLVM Developers Meeting.

At CppCon 2019 Joe Bialek and Shayne Hiet-Block presented their “Killing Uninitialized Memory: Protecting the OS Without Destroying Performance” talk which describes the /initAll mechanism that was built into the MSVC compiler and integrated into the windows build system to combat uninitialized stack memory bugs throughout the windows kernel and hyper visor stack.

SerenityOS attempts to employ a variety of security mitigations to harden the system against would be attackers. I was interested in pursuing some sort of stack zeroing mechanism to mitigate this entire bug class in the SerenityOS kernel. SerenityOS utilizes GCC (version 11.2 at the time of writing) as it’s primary toolchain, with secondary support for Clang+LLVM available as well.

So I started researching what options were available for GCC.

Forward porting…

With a little bit of effort I was able to backport the GCC -ftrivial-auto-var-init feature and the subsequent related fixes to the SerenityOS GCC 11.2 toolchain.

First we needed the original commit which contains the majority of the implementation.

commit cd5638353ee77f6c5c072f6ca9a19d77ad2f2bf8
Author: Brian Gianforcaro <bgianf@serenityos.org>
Date:   Sat Sep 11 08:27:12 2021 -0700

    Toolchain: Backport -ftrivial-auto-var-init to our GCC 11.2 toolchain

    This change back ports the patch to support -ftrivial-auto-var-init so
    that it compiles on top of the 11.2 release. It was originally committed
    to target the GCC 12 release cycle. The back porting was just fixing up
    patch collisions, no major surgery was needed.

When trying to compile Serenity with a newly built toolchain with just the first patch applied I immediately ran into an ICE (Internal Compiler Error). I put the project on hold for a few days and decided to see if the issue had been reported upstream already. Much to my surprise it had, and there was already a fix available in the GCC development branch.

Toolchain: Backport `__builtin_clear_padding` fix to gcc 11.2
commit 463ce32c0edd689f56b619a9eeabc77cc44b528c
Author: Brian Gianforcaro <bgianf@serenityos.org>
Date:   Sun Oct 3 03:07:40 2021 -0700

    Toolchain: Backport '__builtin_clear_padding' fix to gcc 11.2

    This change backports a fix for a bug which previously would cause us
    to ICE when compiling the Kernel, with this patch we continue chugging
    along.

While investigating I also found a few other ICE fixes that it looked like we should really have if we were going to ever merge this to master, so I took care of porting those as well.

Toolchain: Backport 'avoid auto-init of empty types' fix to gcc 11.2
commit 8ddf95bb4fc61482fda8d7460c208c43873131ab
Author: Brian Gianforcaro <bgianf@serenityos.org>
Date:   Sat Sep 11 09:05:07 2021 -0700

    Toolchain: Backport 'avoid auto-init of empty types' fix to gcc 11.2

    This is a follow up fix to the main `-ftrivial-auto-var-init` patch
    that we need as well. This patch didn't require any work to apply.
Toolchain: Backport 'avoid ICE with auto-init and nested functions' fix
commit 41ec4ff60a0e5d04171631ccdadeeb147a4e86aa
Author: Brian Gianforcaro <bgianf@serenityos.org>
Date:   Sat Sep 11 09:14:53 2021 -0700

    Backport 'avoid ICE with auto-init and nested functions' fix
    

    This is a follow up fix to the main `-ftrivial-auto-var-init` patch
    that we need as well. This patch didn't require any work to apply.

After finding all the fixes I needed to backport in order to get the system to actually build without crashing the compiler, I was eager to try it out. The following patch is all that was needed to integrate the new compiler flag into the SerenityOS Kernel build system.

commit bf59bf2d0fb3e3b7745e60ddde715a175d7b1c3d
Author: Brian Gianforcaro <bgianf@serenityos.org>
Date:   Sat Sep 11 09:17:06 2021 -0700

    CMake: Enable `-ftrivial-auto-init=pattern` in the Kernel

diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt
index 1f1c529018..ffd067d289 100644
--- a/Kernel/CMakeLists.txt
+++ b/Kernel/CMakeLists.txt
@@ -409,6 +409,10 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
     if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11.1")
         # Zero any registers used within a function on return (to reduce data lifetime and ROP gadgets).
         add_compile_options(-fzero-call-used-regs=used-gpr)
+
+        # Auto initialize trivial types on the stack. Note: This isn't natively supported by the GCC 11 series.
+        # It was back ported for serenity, see Toolchain/Patches/gcc-ftrivial-auto-var-init.patch
+        add_compile_options(-ftrivial-auto-var-init=pattern)
     endif()
     link_directories(${TOOLCHAIN_ROOT}/${SERENITY_ARCH}-pc-serenity/lib)
     link_directories(${TOOLCHAIN_ROOT}/lib/gcc/${SERENITY_ARCH}-pc-serenity/${GCC_VERSION}/)
diff --git a/Kernel/Prekernel/CMakeLists.txt b/Kernel/Prekernel/CMakeLists.txt
index dbf1d8be8f..bf361e1aa2 100644
--- a/Kernel/Prekernel/CMakeLists.txt
+++ b/Kernel/Prekernel/CMakeLists.txt
@@ -88,4 +88,5 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/Prekernel" DESTINATION boot)
 get_target_property(PREKERNEL_TARGET_OPTIONS ${PREKERNEL_TARGET} COMPILE_OPTIONS)
 list(REMOVE_ITEM PREKERNEL_TARGET_OPTIONS "-fsanitize-coverage=trace-pc")
 list(REMOVE_ITEM PREKERNEL_TARGET_OPTIONS "-fsanitize=kernel-address")
+list(REMOVE_ITEM PREKERNEL_TARGET_OPTIONS "-ftrivial-auto-var-init=pattern")
 set_target_properties(${PREKERNEL_TARGET} PROPERTIES COMPILE_OPTIONS "${PREKERNEL_TARGET_OPTIONS}")

Investigation

After booting the system and seeing it crash, I attached a debugger to QEMU and saw that we now have a Undefined Behavior Sanitizer crash on startup while detecting which processor features are available.

(gdb) bt
#0  __ubsan_handle_type_mismatch_v1 (data=..., ptr=...)
#1  0xc1460d63 in operator() (f=..., __closure=...)
#2  Kernel::Processor::cpu_detect (this=...)
#3  0xc1461a4d in Kernel::Processor::cpu_setup (this=...)
#4  0xc1462f39 in Kernel::Processor::early_initialize (this=..., cpu=...)
#5  0xc145e886 in Kernel::init (boot_info=...)
#6  0x0010309d in Kernel::init ()