diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b5a92b56..3b43a558 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -51,15 +51,21 @@ jobs: with: submodules: true + - if: ${{ matrix.toolchain == 'gcc' }} + id: toolchain-version + name: Get the toolchain version + run: | + python3 -c 'import scripts.build_utils.toolchain_builder as tb + print(f"key=gcc-{tb.GCC_VERSION}-binutils-{tb.BINUTILS_VERSION}")' \ + >> "$GITHUB_OUTPUT" + - if: ${{ matrix.toolchain == 'gcc' }} id: toolchain-cache name: Restore the toolchain cache uses: actions/cache/restore@v4.2.2 - env: - gcc-version: 15.1.0 with: path: "${{ github.workspace }}/toolchain/tools-${{ matrix.toolchain }}-${{ matrix.arch }}" - key: "${{ runner.os }}-${{ runner.arch }}-${{ matrix.arch }}-gcc-${{ env.gcc-version }}" + key: "${{ runner.os }}-${{ runner.arch }}-${{ matrix.arch }}-${{ steps.toolchain-version.outputs.key }}" - name: Build the platform toolchain run: "${{ github.workspace }}/build.py --arch ${{ matrix.arch }} --toolchain ${{ matrix.toolchain }} --no-tune-native --toolchain-only" diff --git a/.gitignore b/.gitignore index b45008ea..3fa34d93 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ kernel-source/ bin/ hyper-* toolchain/tools-* +toolchain/*_sources +toolchain/*.tar.gz* __pycache__ diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ae68a1a..56d661e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.18) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index ed8cb85c..1ff2318a 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -4,6 +4,7 @@ include(build_stamp.cmake) include(target_helpers.cmake) include(module.cmake) include(ld_template.cmake) +include(ubsan.cmake) # The base kernel executable that contains an empty stub symbol table set( @@ -122,6 +123,11 @@ endif () ultra_compile_options(-O${CONFIG_OPTIMIZATION_LEVEL}) +if (CONFIG_UBSAN) + setup_ubsan() + ultra_sources_no_ubsan(ubsan.c) +endif () + ultra_link_options( -nostdlib -Wl,--no-pie diff --git a/kernel/Kconfig b/kernel/Kconfig index e6a6fa19..104f64f3 100644 --- a/kernel/Kconfig +++ b/kernel/Kconfig @@ -59,6 +59,38 @@ config VERBOSE_INIT_LEVELS config DEBUG_MM bool "Enable debug assertions for the memory management subsystem" +menuconfig UBSAN + bool "Undefined behavior sanitizer" + depends on ARCH_HAS_UBSAN + +if UBSAN + +config UBSAN_BOUNDS + bool "Check array indices against their statically known bounds" + default y + +config UBSAN_SHIFT + bool "Check for shifts that are out of range" + default y + +config UBSAN_SIGNED_OVERFLOW + bool "Check for signed integer overflow" + default y + +config UBSAN_DIV_ZERO + bool "Check for integer division by zero" + depends on TOOLCHAIN_GCC + +config UBSAN_UNREACHABLE + bool "Check for control flow reaching __builtin_unreachable()" + default y + +config UBSAN_BOOL + bool "Check for loads of bool values that are neither 0 nor 1" + default y + +endif + endmenu rsource "arch/Kconfig" diff --git a/kernel/arch/Kconfig b/kernel/arch/Kconfig index ab4613d0..870f819c 100644 --- a/kernel/arch/Kconfig +++ b/kernel/arch/Kconfig @@ -33,6 +33,9 @@ config ARCH_PHYS_ADDR_WIDTH default 8 if (ARCH_64BIT || ARCH_FORCE_64BIT_PHYS_ADDR_WIDTH) default 4 if ARCH_32BIT +config ARCH_HAS_UBSAN + bool + if ARCH_X86_64 rsource "x86_64/Kconfig" endif diff --git a/kernel/arch/x86_64/Kconfig b/kernel/arch/x86_64/Kconfig index db6dc0b0..721018ea 100644 --- a/kernel/arch/x86_64/Kconfig +++ b/kernel/arch/x86_64/Kconfig @@ -14,6 +14,7 @@ config ARCH_X86_64_BASE bool select ARCH_LITTLE_ENDIAN select ARCH_64BIT + select ARCH_HAS_UBSAN select SMBIOS select ACPI select ACPI_PM diff --git a/kernel/bug.c b/kernel/bug.c index dd935937..4e100f90 100644 --- a/kernel/bug.c +++ b/kernel/bug.c @@ -17,7 +17,7 @@ static bool s_panic_on_warn; parameter_with_flags(s_panic_on_warn, PARAM_RUNTIME_WRITABLE); -static void finish_warn_report(struct registers *regs) +void finish_warn_report(struct registers *regs) { if (s_panic_on_warn) { /* diff --git a/kernel/console.c b/kernel/console.c index a8d1306f..c207b4bf 100644 --- a/kernel/console.c +++ b/kernel/console.c @@ -23,7 +23,7 @@ error_t register_console(struct console *con) con->next = g_consoles; g_consoles = con; - log_flush_console(con); + log_flush_consoles(); return EOK; } diff --git a/kernel/include/common/bit.h b/kernel/include/common/bit.h index 6fc19d5d..2eaa82f0 100644 --- a/kernel/include/common/bit.h +++ b/kernel/include/common/bit.h @@ -7,7 +7,7 @@ #define BITS_PER_BYTE CHAR_BIT #define BITS_PER_TYPE(type) (sizeof(type) * BITS_PER_BYTE) -#define BIT_OF_TYPE(type, x) (((type)1) << (x)) +#define BIT_OF_TYPE(type, x) ((type)(((type)1) << (x))) #define BIT_U8(x) BIT_OF_TYPE(u8, (x)) #define BIT_U16(x) BIT_OF_TYPE(u16, (x)) #define BIT_U32(x) BIT_OF_TYPE(u32, (x)) diff --git a/kernel/include/log.h b/kernel/include/log.h index 012f3070..b3fe204f 100644 --- a/kernel/include/log.h +++ b/kernel/include/log.h @@ -65,11 +65,8 @@ enum log_level { void vprint(const char *msg, va_list vlist); -// Defined in console.h -struct console; - -// Writes every record the console has not seen yet to it -void log_flush_console(struct console *con); +// Writes out every record to every console that hasn't yet been output by it +void log_flush_consoles(void); PRINTF_DECL(1, 2) void print(const char *msg, ...); diff --git a/kernel/include/log_ring.h b/kernel/include/log_ring.h index c94a560a..da74bed5 100644 --- a/kernel/include/log_ring.h +++ b/kernel/include/log_ring.h @@ -3,6 +3,7 @@ #include #include +#include #include /* @@ -137,6 +138,15 @@ BUILD_BUG_ON( offset_of_after(struct log_info_record, level) ); +enum log_ring_read_flags : u32 { + /* + * Skip a record that was reserved and never published, as long as + * newer records exist. Only for readers that know such a record will + * never be finished, e.g. during a panic. + */ + LOG_RING_READ_SKIP_UNPUBLISHED = BIT_U32(0), +}; + /* * Read a record from the log ring. * @@ -148,11 +158,21 @@ BUILD_BUG_ON( * sequence number (if the requested record no longer exists * due to being overwritten by other log messages) is written to 'out_rec'. */ -error_t log_ring_read( +error_t log_ring_read_with_flags( struct log_ring *ring, u64 seq_num, char *out_buf, size_t buf_size, - struct log_record *out_rec + enum log_ring_read_flags flags, struct log_record *out_rec ); +static inline error_t log_ring_read( + struct log_ring *ring, u64 seq_num, char *out_buf, size_t buf_size, + struct log_record *out_rec +) +{ + return log_ring_read_with_flags( + ring, seq_num, out_buf, buf_size, 0, out_rec + ); +} + /* * Check whether reading a record with 'seq_num' would succeed. Note that this * doesn't mean a record with this number actually exists, only that there is diff --git a/kernel/include/panic.h b/kernel/include/panic.h index 13ad67fe..0d867ddd 100644 --- a/kernel/include/panic.h +++ b/kernel/include/panic.h @@ -5,6 +5,8 @@ #include #include +bool panic_in_progress(void); + NORETURN PRINTF_DECL(1, 2) void panic(const char *reason, ...); diff --git a/kernel/include/private/bug.h b/kernel/include/private/bug.h index 03b157c0..b06e6bcf 100644 --- a/kernel/include/private/bug.h +++ b/kernel/include/private/bug.h @@ -21,3 +21,7 @@ ".short " TO_STR(trap_flags) "\n" \ ".popsection\n" \ ) + +struct registers; + +void finish_warn_report(struct registers *regs); diff --git a/kernel/include/private/ubsan.h b/kernel/include/private/ubsan.h new file mode 100644 index 00000000..32d59c83 --- /dev/null +++ b/kernel/include/private/ubsan.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include + +#define UBSAN_TYPE_KIND_INT 0 +#define UBSAN_TYPE_INFO_SIGNED BIT_U16(0) +#define UBSAN_TYPE_INFO_LOG2_WIDTH MAKE_BIT_MASK_U16(15, 1) + +struct ubsan_source_location { + const char *file; + u32 line; + u32 column; +}; + +struct ubsan_type { + u16 kind; + u16 info; + char name[]; +}; + +struct ubsan_out_of_bounds_data { + struct ubsan_source_location loc; + const struct ubsan_type *array_type; + const struct ubsan_type *index_type; +}; + +struct ubsan_shift_data { + struct ubsan_source_location loc; + const struct ubsan_type *lhs_type; + const struct ubsan_type *rhs_type; +}; + +struct ubsan_value_data { + struct ubsan_source_location loc; + const struct ubsan_type *type; +}; + +struct ubsan_unreachable_data { + struct ubsan_source_location loc; +}; + +void __ubsan_handle_out_of_bounds( + struct ubsan_out_of_bounds_data *data, ptr_t index +); +void __ubsan_handle_shift_out_of_bounds( + struct ubsan_shift_data *data, ptr_t lhs, ptr_t rhs +); +void __ubsan_handle_add_overflow( + struct ubsan_value_data *data, ptr_t lhs, ptr_t rhs +); +void __ubsan_handle_sub_overflow( + struct ubsan_value_data *data, ptr_t lhs, ptr_t rhs +); +void __ubsan_handle_mul_overflow( + struct ubsan_value_data *data, ptr_t lhs, ptr_t rhs +); +void __ubsan_handle_negate_overflow( + struct ubsan_value_data *data, ptr_t value +); +void __ubsan_handle_divrem_overflow( + struct ubsan_value_data *data, ptr_t lhs, ptr_t rhs +); +void __ubsan_handle_load_invalid_value( + struct ubsan_value_data *data, ptr_t value +); + +NORETURN +void __ubsan_handle_builtin_unreachable(struct ubsan_unreachable_data *data); diff --git a/kernel/log.c b/kernel/log.c index d08fdfd4..84fdeaac 100644 --- a/kernel/log.c +++ b/kernel/log.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include @@ -11,6 +13,7 @@ #include #include #include +#include #include