From f0973db6dd8835a2bf6a3255a596dee639e89a64 Mon Sep 17 00:00:00 2001 From: Daniel Bertalan Date: Sat, 12 Aug 2023 12:48:09 +0200 Subject: [PATCH] LibSanitizer: Add `_abort` variants of UBSan handlers These variants make the choice to terminate the process a compile-time decision rather than a runtime-configurable setting through `UBSAN_OPTIONS`. As the compiler can treat any code following these UBSan handler calls as dead code, it allows for significant optimizations, such as removing redundant UBSan checks and not needing to worry about saving volatile registers before calling these. Generation of this kind of instrumentation can be enabled with `-fsanitize=undefined -fno-sanitize-recover=undefined`. Build system support will come in a separate PR. --- .../Libraries/LibSanitizer/UBSanitizer.cpp | 294 ++++++++++++++---- 1 file changed, 234 insertions(+), 60 deletions(-) diff --git a/Userland/Libraries/LibSanitizer/UBSanitizer.cpp b/Userland/Libraries/LibSanitizer/UBSanitizer.cpp index aabc809ff7a..d4caf0a88a8 100644 --- a/Userland/Libraries/LibSanitizer/UBSanitizer.cpp +++ b/Userland/Libraries/LibSanitizer/UBSanitizer.cpp @@ -21,8 +21,32 @@ Atomic AK::UBSanitizer::g_ubsan_is_deadly; abort(); \ } while (0) +// FIXME: Dump backtrace of this process (with symbols? without symbols?) in case the user wants non-deadly UBSAN +// Should probably go through the kernel for SC_dump_backtrace, then access the loader's symbol tables +// rather than going through the symbolizer service? +#define ABORT_IF_DEADLY() \ + do { \ + if (g_ubsan_is_deadly.load(AK::MemoryOrder::memory_order_acquire)) { \ + WARNLN_AND_DBGLN("UBSAN: UB is configured to be deadly"); \ + abort(); \ + } \ + } while (0) + extern "C" { +[[gnu::constructor]] static void init_ubsan_options() +{ + auto const* options_ptr = getenv("UBSAN_OPTIONS"); + auto options = options_ptr != NULL ? StringView { options_ptr, strlen(options_ptr) } : StringView {}; + // FIXME: Parse more options and complain about invalid options + if (!options.is_null()) { + if (options.contains("halt_on_error=1"sv)) + g_ubsan_is_deadly.store(true, AK::MemoryOrder::memory_order_relaxed); + else if (options.contains("halt_on_error=0"sv)) + g_ubsan_is_deadly.store(false, AK::MemoryOrder::memory_order_relaxed); + } +} + static void print_location(SourceLocation const& location) { if (!location.filename()) { @@ -30,35 +54,13 @@ static void print_location(SourceLocation const& location) } else { WARNLN_AND_DBGLN("UBSAN: at {}, line {}, column: {}", location.filename(), location.line(), location.column()); } - // FIXME: Dump backtrace of this process (with symbols? without symbols?) in case the user wants non-deadly UBSAN - // Should probably go through the kernel for SC_dump_backtrace, then access the loader's symbol tables rather than - // going through the symbolizer service? - - static bool checked_env_for_deadly = false; - if (!checked_env_for_deadly) { - checked_env_for_deadly = true; - auto const* options_ptr = getenv("UBSAN_OPTIONS"); - auto options = options_ptr != NULL ? StringView { options_ptr, strlen(options_ptr) } : StringView {}; - // FIXME: Parse more options and complain about invalid options - if (!options.is_null()) { - if (options.contains("halt_on_error=1"sv)) - g_ubsan_is_deadly = true; - else if (options.contains("halt_on_error=0"sv)) - g_ubsan_is_deadly = false; - } - } - if (g_ubsan_is_deadly) { - WARNLN_AND_DBGLN("UB is configured to be deadly"); - VERIFY_NOT_REACHED(); - } } // Calls to these functions are automatically inserted by the compiler, // so there is no point in declaring them in a header. #pragma GCC diagnostic ignored "-Wmissing-declarations" -void __ubsan_handle_load_invalid_value(InvalidValueData&, ValueHandle) __attribute__((used)); -void __ubsan_handle_load_invalid_value(InvalidValueData& data, ValueHandle) +static void handle_load_invalid_value(InvalidValueData& data, ValueHandle) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -66,9 +68,18 @@ void __ubsan_handle_load_invalid_value(InvalidValueData& data, ValueHandle) WARNLN_AND_DBGLN("UBSAN: load-invalid-value: {} ({}-bit)", data.type.name(), data.type.bit_width()); print_location(location); } +[[gnu::used]] void __ubsan_handle_load_invalid_value(InvalidValueData& data, ValueHandle handle) +{ + handle_load_invalid_value(data, handle); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_load_invalid_value_abort(InvalidValueData& data, ValueHandle handle) +{ + handle_load_invalid_value(data, handle); + ABORT_ALWAYS(); +} -void __ubsan_handle_nonnull_arg(NonnullArgData&) __attribute__((used)); -void __ubsan_handle_nonnull_arg(NonnullArgData& data) +static void handle_nonnull_arg(NonnullArgData& data) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -77,8 +88,18 @@ void __ubsan_handle_nonnull_arg(NonnullArgData& data) print_location(location); } -void __ubsan_handle_nullability_arg(NonnullArgData&) __attribute__((used)); -void __ubsan_handle_nullability_arg(NonnullArgData& data) +[[gnu::used]] void __ubsan_handle_nonnull_arg(NonnullArgData& data) +{ + handle_nonnull_arg(data); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_nonnull_arg_abort(NonnullArgData& data) +{ + handle_nonnull_arg(data); + ABORT_ALWAYS(); +} + +static void handle_nullability_arg(NonnullArgData& data) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -86,9 +107,18 @@ void __ubsan_handle_nullability_arg(NonnullArgData& data) WARNLN_AND_DBGLN("UBSAN: null pointer passed as argument {}, which is declared to never be null", data.argument_index); print_location(location); } +[[gnu::used]] void __ubsan_handle_nullability_arg(NonnullArgData& data) +{ + handle_nullability_arg(data); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_nullability_arg_abort(NonnullArgData& data) +{ + handle_nullability_arg(data); + ABORT_ALWAYS(); +} -void __ubsan_handle_nonnull_return_v1(NonnullReturnData const&, SourceLocation&) __attribute__((used)); -void __ubsan_handle_nonnull_return_v1(NonnullReturnData const&, SourceLocation& location) +static void handle_nonnull_return_v1(NonnullReturnData const&, SourceLocation& location) { auto loc = location.permanently_clear(); if (!loc.needs_logging()) @@ -96,9 +126,18 @@ void __ubsan_handle_nonnull_return_v1(NonnullReturnData const&, SourceLocation& WARNLN_AND_DBGLN("UBSAN: null pointer return from function declared to never return null"); print_location(loc); } +[[gnu::used]] void __ubsan_handle_nonnull_return_v1(NonnullReturnData const& data, SourceLocation& location) +{ + handle_nonnull_return_v1(data, location); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_nonnull_return_v1_abort(NonnullReturnData const& data, SourceLocation& location) +{ + handle_nonnull_return_v1(data, location); + ABORT_ALWAYS(); +} -void __ubsan_handle_nullability_return_v1(NonnullReturnData const& data, SourceLocation& location) __attribute__((used)); -void __ubsan_handle_nullability_return_v1(NonnullReturnData const&, SourceLocation& location) +static void handle_nullability_return_v1(NonnullReturnData const&, SourceLocation& location) { auto loc = location.permanently_clear(); if (!loc.needs_logging()) @@ -106,9 +145,18 @@ void __ubsan_handle_nullability_return_v1(NonnullReturnData const&, SourceLocati WARNLN_AND_DBGLN("UBSAN: null pointer return from function declared to never return null"); print_location(loc); } +[[gnu::used]] void __ubsan_handle_nullability_return_v1(NonnullReturnData const& data, SourceLocation& location) +{ + handle_nullability_return_v1(data, location); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_nullability_return_v1_abort(NonnullReturnData const& data, SourceLocation& location) +{ + handle_nullability_return_v1(data, location); + ABORT_ALWAYS(); +} -void __ubsan_handle_vla_bound_not_positive(VLABoundData&, ValueHandle) __attribute__((used)); -void __ubsan_handle_vla_bound_not_positive(VLABoundData& data, ValueHandle) +static void handle_vla_bound_not_positive(VLABoundData& data, ValueHandle) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -116,9 +164,18 @@ void __ubsan_handle_vla_bound_not_positive(VLABoundData& data, ValueHandle) WARNLN_AND_DBGLN("UBSAN: VLA bound not positive {} ({}-bit)", data.type.name(), data.type.bit_width()); print_location(location); } +[[gnu::used]] void __ubsan_handle_vla_bound_not_positive(VLABoundData& data, ValueHandle handle) +{ + handle_vla_bound_not_positive(data, handle); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_vla_bound_not_positive_abort(VLABoundData& data, ValueHandle handle) +{ + handle_vla_bound_not_positive(data, handle); + ABORT_ALWAYS(); +} -void __ubsan_handle_add_overflow(OverflowData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used)); -void __ubsan_handle_add_overflow(OverflowData& data, ValueHandle, ValueHandle) +static void handle_add_overflow(OverflowData& data, ValueHandle, ValueHandle) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -126,9 +183,18 @@ void __ubsan_handle_add_overflow(OverflowData& data, ValueHandle, ValueHandle) WARNLN_AND_DBGLN("UBSAN: addition overflow, {} ({}-bit)", data.type.name(), data.type.bit_width()); print_location(location); } +[[gnu::used]] void __ubsan_handle_add_overflow(OverflowData& data, ValueHandle lhs, ValueHandle rhs) +{ + handle_add_overflow(data, lhs, rhs); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_add_overflow_abort(OverflowData& data, ValueHandle lhs, ValueHandle rhs) +{ + handle_add_overflow(data, lhs, rhs); + ABORT_ALWAYS(); +} -void __ubsan_handle_sub_overflow(OverflowData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used)); -void __ubsan_handle_sub_overflow(OverflowData& data, ValueHandle, ValueHandle) +static void handle_sub_overflow(OverflowData& data, ValueHandle, ValueHandle) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -136,9 +202,18 @@ void __ubsan_handle_sub_overflow(OverflowData& data, ValueHandle, ValueHandle) WARNLN_AND_DBGLN("UBSAN: subtraction overflow, {} ({}-bit)", data.type.name(), data.type.bit_width()); print_location(location); } +[[gnu::used]] void __ubsan_handle_sub_overflow(OverflowData& data, ValueHandle lhs, ValueHandle rhs) +{ + handle_sub_overflow(data, lhs, rhs); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_sub_overflow_abort(OverflowData& data, ValueHandle lhs, ValueHandle rhs) +{ + handle_sub_overflow(data, lhs, rhs); + ABORT_ALWAYS(); +} -void __ubsan_handle_negate_overflow(OverflowData&, ValueHandle) __attribute__((used)); -void __ubsan_handle_negate_overflow(OverflowData& data, ValueHandle) +static void handle_negate_overflow(OverflowData& data, ValueHandle) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -146,9 +221,18 @@ void __ubsan_handle_negate_overflow(OverflowData& data, ValueHandle) WARNLN_AND_DBGLN("UBSAN: negation overflow, {} ({}-bit)", data.type.name(), data.type.bit_width()); print_location(location); } +[[gnu::used]] void __ubsan_handle_negate_overflow(OverflowData& data, ValueHandle value) +{ + handle_negate_overflow(data, value); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_negate_overflow_abort(OverflowData& data, ValueHandle value) +{ + handle_negate_overflow(data, value); + ABORT_ALWAYS(); +} -void __ubsan_handle_mul_overflow(OverflowData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used)); -void __ubsan_handle_mul_overflow(OverflowData& data, ValueHandle, ValueHandle) +static void handle_mul_overflow(OverflowData& data, ValueHandle, ValueHandle) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -156,9 +240,18 @@ void __ubsan_handle_mul_overflow(OverflowData& data, ValueHandle, ValueHandle) WARNLN_AND_DBGLN("UBSAN: multiplication overflow, {} ({}-bit)", data.type.name(), data.type.bit_width()); print_location(location); } +[[gnu::used]] void __ubsan_handle_mul_overflow(OverflowData& data, ValueHandle lhs, ValueHandle rhs) +{ + handle_mul_overflow(data, lhs, rhs); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_mul_overflow_abort(OverflowData& data, ValueHandle lhs, ValueHandle rhs) +{ + handle_mul_overflow(data, lhs, rhs); + ABORT_ALWAYS(); +} -void __ubsan_handle_shift_out_of_bounds(ShiftOutOfBoundsData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used)); -void __ubsan_handle_shift_out_of_bounds(ShiftOutOfBoundsData& data, ValueHandle, ValueHandle) +static void handle_shift_out_of_bounds(ShiftOutOfBoundsData& data, ValueHandle, ValueHandle) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -166,9 +259,18 @@ void __ubsan_handle_shift_out_of_bounds(ShiftOutOfBoundsData& data, ValueHandle, WARNLN_AND_DBGLN("UBSAN: shift out of bounds, {} ({}-bit) shifted by {} ({}-bit)", data.lhs_type.name(), data.lhs_type.bit_width(), data.rhs_type.name(), data.rhs_type.bit_width()); print_location(location); } +[[gnu::used]] void __ubsan_handle_shift_out_of_bounds(ShiftOutOfBoundsData& data, ValueHandle lhs, ValueHandle rhs) +{ + handle_shift_out_of_bounds(data, lhs, rhs); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_shift_out_of_bounds_abort(ShiftOutOfBoundsData& data, ValueHandle lhs, ValueHandle rhs) +{ + handle_shift_out_of_bounds(data, lhs, rhs); + ABORT_ALWAYS(); +} -void __ubsan_handle_divrem_overflow(OverflowData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used)); -void __ubsan_handle_divrem_overflow(OverflowData& data, ValueHandle, ValueHandle) +static void handle_divrem_overflow(OverflowData& data, ValueHandle, ValueHandle) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -176,9 +278,18 @@ void __ubsan_handle_divrem_overflow(OverflowData& data, ValueHandle, ValueHandle WARNLN_AND_DBGLN("UBSAN: divrem overflow, {} ({}-bit)", data.type.name(), data.type.bit_width()); print_location(location); } +[[gnu::used]] void __ubsan_handle_divrem_overflow(OverflowData& data, ValueHandle lhs, ValueHandle rhs) +{ + handle_divrem_overflow(data, lhs, rhs); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_divrem_overflow_abort(OverflowData& data, ValueHandle lhs, ValueHandle rhs) +{ + handle_divrem_overflow(data, lhs, rhs); + ABORT_ALWAYS(); +} -void __ubsan_handle_out_of_bounds(OutOfBoundsData&, ValueHandle) __attribute__((used)); -void __ubsan_handle_out_of_bounds(OutOfBoundsData& data, ValueHandle) +static void handle_out_of_bounds(OutOfBoundsData& data, ValueHandle) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -186,9 +297,18 @@ void __ubsan_handle_out_of_bounds(OutOfBoundsData& data, ValueHandle) WARNLN_AND_DBGLN("UBSAN: out of bounds access into array of {} ({}-bit), index type {} ({}-bit)", data.array_type.name(), data.array_type.bit_width(), data.index_type.name(), data.index_type.bit_width()); print_location(location); } +[[gnu::used]] void __ubsan_handle_out_of_bounds(OutOfBoundsData& data, ValueHandle index) +{ + handle_out_of_bounds(data, index); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_out_of_bounds_abort(OutOfBoundsData& data, ValueHandle index) +{ + handle_out_of_bounds(data, index); + ABORT_ALWAYS(); +} -void __ubsan_handle_type_mismatch_v1(TypeMismatchData&, ValueHandle) __attribute__((used)); -void __ubsan_handle_type_mismatch_v1(TypeMismatchData& data, ValueHandle ptr) +static void handle_type_mismatch_v1(TypeMismatchData& data, ValueHandle ptr) { constexpr StringView kinds[] = { "load of"sv, @@ -222,9 +342,18 @@ void __ubsan_handle_type_mismatch_v1(TypeMismatchData& data, ValueHandle ptr) print_location(location); } +[[gnu::used]] void __ubsan_handle_type_mismatch_v1(TypeMismatchData& data, ValueHandle ptr) +{ + handle_type_mismatch_v1(data, ptr); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_type_mismatch_v1_abort(TypeMismatchData& data, ValueHandle ptr) +{ + handle_type_mismatch_v1(data, ptr); + ABORT_ALWAYS(); +} -void __ubsan_handle_alignment_assumption(AlignmentAssumptionData&, ValueHandle, ValueHandle, ValueHandle) __attribute__((used)); -void __ubsan_handle_alignment_assumption(AlignmentAssumptionData& data, ValueHandle pointer, ValueHandle alignment, ValueHandle offset) +static void handle_alignment_assumption(AlignmentAssumptionData& data, ValueHandle pointer, ValueHandle alignment, ValueHandle offset) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -242,6 +371,16 @@ void __ubsan_handle_alignment_assumption(AlignmentAssumptionData& data, ValueHan print_location(location); } +[[gnu::used]] void __ubsan_handle_alignment_assumption(AlignmentAssumptionData& data, ValueHandle pointer, ValueHandle alignment, ValueHandle offset) +{ + handle_alignment_assumption(data, pointer, alignment, offset); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_alignment_assumption_abort(AlignmentAssumptionData& data, ValueHandle pointer, ValueHandle alignment, ValueHandle offset) +{ + handle_alignment_assumption(data, pointer, alignment, offset); + ABORT_ALWAYS(); +} [[gnu::used, noreturn]] void __ubsan_handle_builtin_unreachable(UnreachableData& data) { @@ -257,8 +396,7 @@ void __ubsan_handle_alignment_assumption(AlignmentAssumptionData& data, ValueHan ABORT_ALWAYS(); } -void __ubsan_handle_implicit_conversion(ImplicitConversionData&, ValueHandle, ValueHandle) __attribute__((used)); -void __ubsan_handle_implicit_conversion(ImplicitConversionData& data, ValueHandle, ValueHandle) +static void handle_implicit_conversion(ImplicitConversionData& data, ValueHandle, ValueHandle) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -269,9 +407,18 @@ void __ubsan_handle_implicit_conversion(ImplicitConversionData& data, ValueHandl data.from_type.name(), data.from_type.bit_width(), src_signed, data.to_type.name(), data.to_type.bit_width(), dst_signed); print_location(location); } +[[gnu::used]] void __ubsan_handle_implicit_conversion(ImplicitConversionData& data, ValueHandle from, ValueHandle to) +{ + handle_implicit_conversion(data, from, to); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_implicit_conversion_abort(ImplicitConversionData& data, ValueHandle from, ValueHandle to) +{ + handle_implicit_conversion(data, from, to); + ABORT_ALWAYS(); +} -void __ubsan_handle_invalid_builtin(InvalidBuiltinData&) __attribute__((used)); -void __ubsan_handle_invalid_builtin(InvalidBuiltinData& data) +static void handle_invalid_builtin(InvalidBuiltinData& data) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -279,9 +426,18 @@ void __ubsan_handle_invalid_builtin(InvalidBuiltinData& data) WARNLN_AND_DBGLN("UBSAN: passing invalid argument"); print_location(location); } +[[gnu::used]] void __ubsan_handle_invalid_builtin(InvalidBuiltinData& data) +{ + handle_invalid_builtin(data); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_invalid_builtin_abort(InvalidBuiltinData& data) +{ + handle_invalid_builtin(data); + ABORT_ALWAYS(); +} -void __ubsan_handle_pointer_overflow(PointerOverflowData&, ValueHandle, ValueHandle) __attribute__((used)); -void __ubsan_handle_pointer_overflow(PointerOverflowData& data, ValueHandle base, ValueHandle result) +static void handle_pointer_overflow(PointerOverflowData& data, ValueHandle base, ValueHandle result) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -297,9 +453,18 @@ void __ubsan_handle_pointer_overflow(PointerOverflowData& data, ValueHandle base } print_location(location); } +[[gnu::used]] void __ubsan_handle_pointer_overflow(PointerOverflowData& data, ValueHandle base, ValueHandle result) +{ + handle_pointer_overflow(data, base, result); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_pointer_overflow_abort(PointerOverflowData& data, ValueHandle base, ValueHandle result) +{ + handle_pointer_overflow(data, base, result); + ABORT_ALWAYS(); +} -void __ubsan_handle_float_cast_overflow(FloatCastOverflowData&, ValueHandle) __attribute__((used)); -void __ubsan_handle_float_cast_overflow(FloatCastOverflowData& data, ValueHandle) +static void handle_float_cast_overflow(FloatCastOverflowData& data, ValueHandle) { auto location = data.location.permanently_clear(); if (!location.needs_logging()) @@ -307,5 +472,14 @@ void __ubsan_handle_float_cast_overflow(FloatCastOverflowData& data, ValueHandle WARNLN_AND_DBGLN("UBSAN: overflow when casting from {} to {}", data.from_type.name(), data.to_type.name()); print_location(location); } - +[[gnu::used]] void __ubsan_handle_float_cast_overflow(FloatCastOverflowData& data, ValueHandle value) +{ + handle_float_cast_overflow(data, value); + ABORT_IF_DEADLY(); +} +[[gnu::used, noreturn]] void __ubsan_handle_float_cast_overflow_abort(FloatCastOverflowData& data, ValueHandle value) +{ + handle_float_cast_overflow(data, value); + ABORT_ALWAYS(); +} } // extern "C"