AK+LibSanitizer: Add method to zero out a UBSAN SourceLocation

This is the same strategy that LLVM's compiler-rt uses to make sure that
each UBSAN error is only reported once, when UBSAN is *not* deadly.

Otherwise, each time we head through a UB codepath, we will log the same
error over and over. That behavior just adds noise to the logs and makes
it nearly impossible to run binaires that have some common code path
with flagged UB in them.

compiler-rt goes the extra step to make sure the "clear" action is
atomic, but we don't really have that many multi-threaded apps gettting
tested with UBSAN yet, so we can add that later.
This commit is contained in:
Andrew Kaster 2021-11-22 22:59:16 -07:00 committed by Brian Gianforcaro
parent 3e9fb9f23c
commit db33cdbc2f
2 changed files with 174 additions and 82 deletions

View file

@ -6,6 +6,8 @@
#pragma once
#include "AK/Noncopyable.h"
#include "AK/StdLibExtras.h"
#include <AK/Types.h>
namespace AK::UBSanitizer {
@ -15,15 +17,43 @@ extern bool g_ubsan_is_deadly;
typedef void* ValueHandle;
class SourceLocation {
AK_MAKE_NONCOPYABLE(SourceLocation);
public:
const char* filename() const { return m_filename; }
u32 line() const { return m_line; }
u32 column() const { return m_column; }
// Replace the location information in the .data segment with one that won't be logged in the future
// Using this method prevents log spam when sanitizers are not deadly by not logging the exact same
// code paths multiple times.
SourceLocation permanently_clear() { return move(*this); }
bool needs_logging() const { return !(m_filename == nullptr); }
SourceLocation() = default;
SourceLocation(SourceLocation&& other)
: m_filename(other.m_filename)
, m_line(other.m_line)
, m_column(other.m_column)
{
other = {};
}
SourceLocation& operator=(SourceLocation&& other)
{
if (this != &other) {
m_filename = exchange(other.m_filename, nullptr);
m_line = exchange(other.m_line, 0);
m_column = exchange(other.m_column, 0);
}
return *this;
}
private:
const char* m_filename;
u32 m_line;
u32 m_column;
const char* m_filename { nullptr };
u32 m_line { 0 };
u32 m_column { 0 };
};
enum TypeKind : u16 {

View file

@ -46,102 +46,138 @@ static void print_location(const SourceLocation& location)
}
}
void __ubsan_handle_load_invalid_value(const InvalidValueData&, ValueHandle) __attribute__((used));
void __ubsan_handle_load_invalid_value(const InvalidValueData& data, ValueHandle)
void __ubsan_handle_load_invalid_value(InvalidValueData&, ValueHandle) __attribute__((used));
void __ubsan_handle_load_invalid_value(InvalidValueData& data, ValueHandle)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: load-invalid-value: {} ({}-bit)", data.type.name(), data.type.bit_width());
print_location(data.location);
}
void __ubsan_handle_nonnull_arg(const NonnullArgData&) __attribute__((used));
void __ubsan_handle_nonnull_arg(const NonnullArgData& data)
{
WARNLN_AND_DBGLN("UBSAN: null pointer passed as argument {}, which is declared to never be null", data.argument_index);
print_location(data.location);
}
void __ubsan_handle_nullability_arg(const NonnullArgData&) __attribute__((used));
void __ubsan_handle_nullability_arg(const NonnullArgData& data)
{
WARNLN_AND_DBGLN("UBSAN: null pointer passed as argument {}, which is declared to never be null", data.argument_index);
print_location(data.location);
}
void __ubsan_handle_nonnull_return_v1(const NonnullReturnData&, const SourceLocation&) __attribute__((used));
void __ubsan_handle_nonnull_return_v1(const NonnullReturnData&, const SourceLocation& location)
{
WARNLN_AND_DBGLN("UBSAN: null pointer return from function declared to never return null");
print_location(location);
}
void __ubsan_handle_nullability_return_v1(const NonnullReturnData& data, const SourceLocation& location) __attribute__((used));
void __ubsan_handle_nullability_return_v1(const NonnullReturnData&, const SourceLocation& location)
void __ubsan_handle_nonnull_arg(NonnullArgData&) __attribute__((used));
void __ubsan_handle_nonnull_arg(NonnullArgData& data)
{
WARNLN_AND_DBGLN("UBSAN: null pointer return from function declared to never return null");
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: null pointer passed as argument {}, which is declared to never be null", data.argument_index);
print_location(location);
}
void __ubsan_handle_vla_bound_not_positive(const VLABoundData&, ValueHandle) __attribute__((used));
void __ubsan_handle_vla_bound_not_positive(const VLABoundData& data, ValueHandle)
void __ubsan_handle_nullability_arg(NonnullArgData&) __attribute__((used));
void __ubsan_handle_nullability_arg(NonnullArgData& data)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: null pointer passed as argument {}, which is declared to never be null", data.argument_index);
print_location(location);
}
void __ubsan_handle_nonnull_return_v1(const NonnullReturnData&, SourceLocation&) __attribute__((used));
void __ubsan_handle_nonnull_return_v1(const NonnullReturnData&, SourceLocation& location)
{
auto loc = location.permanently_clear();
if (!loc.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: null pointer return from function declared to never return null");
print_location(loc);
}
void __ubsan_handle_nullability_return_v1(const NonnullReturnData& data, SourceLocation& location) __attribute__((used));
void __ubsan_handle_nullability_return_v1(const NonnullReturnData&, SourceLocation& location)
{
auto loc = location.permanently_clear();
if (!loc.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: null pointer return from function declared to never return null");
print_location(loc);
}
void __ubsan_handle_vla_bound_not_positive(VLABoundData&, ValueHandle) __attribute__((used));
void __ubsan_handle_vla_bound_not_positive(VLABoundData& data, ValueHandle)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: VLA bound not positive {} ({}-bit)", data.type.name(), data.type.bit_width());
print_location(data.location);
print_location(location);
}
void __ubsan_handle_add_overflow(const OverflowData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used));
void __ubsan_handle_add_overflow(const OverflowData& data, ValueHandle, ValueHandle)
void __ubsan_handle_add_overflow(OverflowData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used));
void __ubsan_handle_add_overflow(OverflowData& data, ValueHandle, ValueHandle)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: addition overflow, {} ({}-bit)", data.type.name(), data.type.bit_width());
print_location(data.location);
print_location(location);
}
void __ubsan_handle_sub_overflow(const OverflowData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used));
void __ubsan_handle_sub_overflow(const OverflowData& data, ValueHandle, ValueHandle)
void __ubsan_handle_sub_overflow(OverflowData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used));
void __ubsan_handle_sub_overflow(OverflowData& data, ValueHandle, ValueHandle)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: subtraction overflow, {} ({}-bit)", data.type.name(), data.type.bit_width());
print_location(data.location);
print_location(location);
}
void __ubsan_handle_negate_overflow(const OverflowData&, ValueHandle) __attribute__((used));
void __ubsan_handle_negate_overflow(const OverflowData& data, ValueHandle)
void __ubsan_handle_negate_overflow(OverflowData&, ValueHandle) __attribute__((used));
void __ubsan_handle_negate_overflow(OverflowData& data, ValueHandle)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: negation overflow, {} ({}-bit)", data.type.name(), data.type.bit_width());
print_location(data.location);
print_location(location);
}
void __ubsan_handle_mul_overflow(const OverflowData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used));
void __ubsan_handle_mul_overflow(const OverflowData& data, ValueHandle, ValueHandle)
void __ubsan_handle_mul_overflow(OverflowData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used));
void __ubsan_handle_mul_overflow(OverflowData& data, ValueHandle, ValueHandle)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: multiplication overflow, {} ({}-bit)", data.type.name(), data.type.bit_width());
print_location(data.location);
print_location(location);
}
void __ubsan_handle_shift_out_of_bounds(const ShiftOutOfBoundsData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used));
void __ubsan_handle_shift_out_of_bounds(const ShiftOutOfBoundsData& data, ValueHandle, ValueHandle)
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)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
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(data.location);
print_location(location);
}
void __ubsan_handle_divrem_overflow(const OverflowData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used));
void __ubsan_handle_divrem_overflow(const OverflowData& data, ValueHandle, ValueHandle)
void __ubsan_handle_divrem_overflow(OverflowData&, ValueHandle lhs, ValueHandle rhs) __attribute__((used));
void __ubsan_handle_divrem_overflow(OverflowData& data, ValueHandle, ValueHandle)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: divrem overflow, {} ({}-bit)", data.type.name(), data.type.bit_width());
print_location(data.location);
print_location(location);
}
void __ubsan_handle_out_of_bounds(const OutOfBoundsData&, ValueHandle) __attribute__((used));
void __ubsan_handle_out_of_bounds(const OutOfBoundsData& data, ValueHandle)
void __ubsan_handle_out_of_bounds(OutOfBoundsData&, ValueHandle) __attribute__((used));
void __ubsan_handle_out_of_bounds(OutOfBoundsData& data, ValueHandle)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
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(data.location);
print_location(location);
}
void __ubsan_handle_type_mismatch_v1(const TypeMismatchData&, ValueHandle) __attribute__((used));
void __ubsan_handle_type_mismatch_v1(const TypeMismatchData& data, ValueHandle ptr)
void __ubsan_handle_type_mismatch_v1(TypeMismatchData&, ValueHandle) __attribute__((used));
void __ubsan_handle_type_mismatch_v1(TypeMismatchData& data, ValueHandle ptr)
{
constexpr StringView kinds[] = {
"load of",
@ -158,23 +194,30 @@ void __ubsan_handle_type_mismatch_v1(const TypeMismatchData& data, ValueHandle p
"dynamic operation on"
};
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
FlatPtr alignment = (FlatPtr)1 << data.log_alignment;
auto kind = kinds[data.type_check_kind];
if (!ptr) {
WARNLN_AND_DBGLN("UBSAN: {} null pointer of type {}", kind, data.type.name());
} else if ((FlatPtr)ptr & (alignment - 1)) {
WARNLN_AND_DBGLN("UBSAN: {} misaligned address {:p} of type {}", kind, ptr, data.type.name());
WARNLN_AND_DBGLN("UBSAN: {} misaligned address {:p} of type {} which requires {} byte alignment", kind, ptr, data.type.name(), alignment);
} else {
WARNLN_AND_DBGLN("UBSAN: {} address {:p} with insufficient space for type {}", kind, ptr, data.type.name());
}
print_location(data.location);
print_location(location);
}
void __ubsan_handle_alignment_assumption(const AlignmentAssumptionData&, ValueHandle, ValueHandle, ValueHandle) __attribute__((used));
void __ubsan_handle_alignment_assumption(const AlignmentAssumptionData& data, ValueHandle pointer, ValueHandle alignment, ValueHandle offset)
void __ubsan_handle_alignment_assumption(AlignmentAssumptionData&, ValueHandle, ValueHandle, ValueHandle) __attribute__((used));
void __ubsan_handle_alignment_assumption(AlignmentAssumptionData& data, ValueHandle pointer, ValueHandle alignment, ValueHandle offset)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
if (offset) {
WARNLN_AND_DBGLN(
"UBSAN: assumption of {:p} byte alignment (with offset of {:p} byte) for pointer {:p}"
@ -186,43 +229,58 @@ void __ubsan_handle_alignment_assumption(const AlignmentAssumptionData& data, Va
alignment, pointer, data.type.name());
}
print_location(data.location);
print_location(location);
}
void __ubsan_handle_builtin_unreachable(const UnreachableData&) __attribute__((used));
void __ubsan_handle_builtin_unreachable(const UnreachableData& data)
void __ubsan_handle_builtin_unreachable(UnreachableData&) __attribute__((used));
void __ubsan_handle_builtin_unreachable(UnreachableData& data)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: execution reached an unreachable program point");
print_location(data.location);
print_location(location);
}
void __ubsan_handle_missing_return(const UnreachableData&) __attribute__((used));
void __ubsan_handle_missing_return(const UnreachableData& data)
void __ubsan_handle_missing_return(UnreachableData&) __attribute__((used));
void __ubsan_handle_missing_return(UnreachableData& data)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: execution reached the end of a value-returning function without returning a value");
print_location(data.location);
print_location(location);
}
void __ubsan_handle_implicit_conversion(const ImplicitConversionData&, ValueHandle, ValueHandle) __attribute__((used));
void __ubsan_handle_implicit_conversion(const ImplicitConversionData& data, ValueHandle, ValueHandle)
void __ubsan_handle_implicit_conversion(ImplicitConversionData&, ValueHandle, ValueHandle) __attribute__((used));
void __ubsan_handle_implicit_conversion(ImplicitConversionData& data, ValueHandle, ValueHandle)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
const char* src_signed = data.from_type.is_signed() ? "" : "un";
const char* dst_signed = data.to_type.is_signed() ? "" : "un";
WARNLN_AND_DBGLN("UBSAN: implicit conversion from type {} ({}-bit, {}signed) to type {} ({}-bit, {}signed)",
data.from_type.name(), data.from_type.bit_width(), src_signed, data.to_type.name(), data.to_type.bit_width(), dst_signed);
print_location(data.location);
print_location(location);
}
void __ubsan_handle_invalid_builtin(const InvalidBuiltinData) __attribute__((used));
void __ubsan_handle_invalid_builtin(const InvalidBuiltinData data)
void __ubsan_handle_invalid_builtin(InvalidBuiltinData&) __attribute__((used));
void __ubsan_handle_invalid_builtin(InvalidBuiltinData& data)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: passing invalid argument");
print_location(data.location);
print_location(location);
}
void __ubsan_handle_pointer_overflow(const PointerOverflowData&, ValueHandle, ValueHandle) __attribute__((used));
void __ubsan_handle_pointer_overflow(const PointerOverflowData& data, ValueHandle base, ValueHandle result)
void __ubsan_handle_pointer_overflow(PointerOverflowData&, ValueHandle, ValueHandle) __attribute__((used));
void __ubsan_handle_pointer_overflow(PointerOverflowData& data, ValueHandle base, ValueHandle result)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
if (base == 0 && result == 0) {
WARNLN_AND_DBGLN("UBSAN: applied zero offset to nullptr");
} else if (base == 0 && result != 0) {
@ -232,13 +290,17 @@ void __ubsan_handle_pointer_overflow(const PointerOverflowData& data, ValueHandl
} else {
WARNLN_AND_DBGLN("UBSAN: addition of unsigned offset to {:p} overflowed to {:p}", base, result);
}
print_location(data.location);
print_location(location);
}
void __ubsan_handle_float_cast_overflow(const FloatCastOverflowData&, ValueHandle) __attribute__((used));
void __ubsan_handle_float_cast_overflow(const FloatCastOverflowData& data, ValueHandle)
void __ubsan_handle_float_cast_overflow(FloatCastOverflowData&, ValueHandle) __attribute__((used));
void __ubsan_handle_float_cast_overflow(FloatCastOverflowData& data, ValueHandle)
{
auto location = data.location.permanently_clear();
if (!location.needs_logging())
return;
WARNLN_AND_DBGLN("UBSAN: overflow when casting from {} to {}", data.from_type.name(), data.to_type.name());
print_location(data.location);
}
print_location(location);
}
} // extern "C"