From 7bdcf557057097ce4661238e672a514ae1e74936 Mon Sep 17 00:00:00 2001 From: Marcos Oduardo Date: Fri, 15 May 2026 18:33:21 +0200 Subject: [PATCH] lib: sbi: add UBSan support UBSan (Undefined Behavior Sanitizer) is a tool implemented using compiler instrumentation at runtime that allows checking for statements whose output is not deterministic or defined by the C standard. Compiling and running OpenSBI with UBSan instrumentation will print a message in the console if any sentence performs such an action. Support involves two main components: 1. The UBSan implementation hooks (derived from NetBSD), used by the compiler to handle the check output. 2. A test suite integrated with the SBI unit test framework to verify correct operation at runtime. Usage: make UBSAN=y PLATFORM=generic ... The test suite is built when both UBSAN=y and CONFIG_SBIUNIT=y are enabled. When UBSan is enabled, FW_PAYLOAD_OFFSET may need to be increased due to the size increase added by the instrumentation. A value of 0x400000 has been tested. UBSan adds runtime overhead and is intended for development builds only, not for production. Note: This patch marks __stack_chk_guard in sbi_init.c as a weak symbol to prevent multiple definition errors at compile time with UBSan instrumentation enabled. This resolves the conflict between the .globl definitions in sbi_init.c and test_head.S. Signed-off-by: Marcos Oduardo Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260515163321.2038366-1-marcos.oduardo@gmail.com Signed-off-by: Anup Patel --- Makefile | 17 + include/sbi/sbi_ubsan.h | 14 + lib/sbi/objects.mk | 3 + lib/sbi/sbi_init.c | 2 +- lib/sbi/sbi_ubsan.c | 924 +++++++++++++++++++++++++++++++++ lib/sbi/tests/objects.mk | 5 + lib/sbi/tests/sbi_ubsan_test.c | 114 ++++ 7 files changed, 1078 insertions(+), 1 deletion(-) create mode 100644 include/sbi/sbi_ubsan.h create mode 100644 lib/sbi/sbi_ubsan.c create mode 100644 lib/sbi/tests/sbi_ubsan_test.c diff --git a/Makefile b/Makefile index 46541063..54eb15bc 100644 --- a/Makefile +++ b/Makefile @@ -455,6 +455,23 @@ else CFLAGS += -O2 endif +ifeq ($(UBSAN),y) +UBSAN_CC_FLAGS := -fsanitize=undefined +UBSAN_CC_FLAGS += -DUBSAN_ENABLED +UBSAN_CC_FLAGS += -fno-sanitize=vptr +UBSAN_CC_FLAGS += -fno-sanitize=float-cast-overflow +UBSAN_CC_FLAGS += -fno-sanitize=float-divide-by-zero +UBSAN_CC_FLAGS += -fsanitize-recover=undefined +UBSAN_CC_FLAGS += -fsanitize=pointer-overflow +UBSAN_CC_FLAGS += -fsanitize=alignment +UBSAN_CC_FLAGS += -fno-sanitize-recover=alignment +UBSAN_CC_FLAGS += -fno-stack-protector +ifeq ($(LLVM), y) +UBSAN_CC_FLAGS += -fno-sanitize-link-runtime +endif +CFLAGS += $(UBSAN_CC_FLAGS) +endif + ifeq ($(V), 1) ELFFLAGS += -Wl,--print-gc-sections endif diff --git a/include/sbi/sbi_ubsan.h b/include/sbi/sbi_ubsan.h new file mode 100644 index 00000000..f93215c4 --- /dev/null +++ b/include/sbi/sbi_ubsan.h @@ -0,0 +1,14 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Author: Marcos Oduardo + */ + +#ifndef __SBI_UBSAN_H__ +#define __SBI_UBSAN_H__ + +#include + +extern volatile unsigned long sbi_ubsan_report_count; + +#endif /* __SBI_UBSAN_H__ */ diff --git a/lib/sbi/objects.mk b/lib/sbi/objects.mk index a7b4a82f..c29c888f 100644 --- a/lib/sbi/objects.mk +++ b/lib/sbi/objects.mk @@ -100,6 +100,9 @@ libsbi-objs-y += sbi_tlb.o libsbi-objs-y += sbi_trap.o libsbi-objs-y += sbi_trap_ldst.o libsbi-objs-y += sbi_trap_v_ldst.o +ifeq ($(UBSAN), y) +libsbi-objs-y += sbi_ubsan.o +endif libsbi-objs-y += sbi_unpriv.o libsbi-objs-y += sbi_expected_trap.o libsbi-objs-y += sbi_cppc.o diff --git a/lib/sbi/sbi_init.c b/lib/sbi/sbi_init.c index b248e73f..4e34bbc5 100644 --- a/lib/sbi/sbi_init.c +++ b/lib/sbi/sbi_init.c @@ -218,7 +218,7 @@ static void wake_coldboot_harts(struct sbi_scratch *scratch) __smp_store_release(&coldboot_done, 1); } -unsigned long __stack_chk_guard = 0x95B5FF5A; +unsigned long __attribute__((weak)) __stack_chk_guard = 0x95B5FF5A; static unsigned long entry_count_offset; static unsigned long init_count_offset; diff --git a/lib/sbi/sbi_ubsan.c b/lib/sbi/sbi_ubsan.c new file mode 100644 index 00000000..271be7b0 --- /dev/null +++ b/lib/sbi/sbi_ubsan.c @@ -0,0 +1,924 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2018 The NetBSD Foundation, Inc. + * + * Author: Marcos Oduardo + */ + +#ifdef UBSAN_ENABLED + +#include +#include + +/* Undefined Behavior specific defines and structures defined by the compiler ABI */ + +#define KIND_INTEGER 0 +#define KIND_FLOAT 1 +#define KIND_UNKNOWN UINT16_MAX + +volatile unsigned long sbi_ubsan_report_count; + +struct CSourceLocation { + char *mFilename; + uint32_t mLine; + uint32_t mColumn; +}; + +struct CTypeDescriptor { + uint16_t mTypeKind; + uint16_t mTypeInfo; + uint8_t mTypeName[1]; +}; + +struct COverflowData { + struct CSourceLocation mLocation; + struct CTypeDescriptor *mType; +}; + +struct CUnreachableData { + struct CSourceLocation mLocation; +}; + +struct CCFICheckFailData { + uint8_t mCheckKind; + struct CSourceLocation mLocation; + struct CTypeDescriptor *mType; +}; + +struct CDynamicTypeCacheMissData { + struct CSourceLocation mLocation; + struct CTypeDescriptor *mType; + void *mTypeInfo; + uint8_t mTypeCheckKind; +}; + +struct CFunctionTypeMismatchData { + struct CSourceLocation mLocation; + struct CTypeDescriptor *mType; +}; + +struct CInvalidBuiltinData { + struct CSourceLocation mLocation; + uint8_t mKind; +}; + +struct CInvalidValueData { + struct CSourceLocation mLocation; + struct CTypeDescriptor *mType; +}; + +struct CNonNullArgData { + struct CSourceLocation mLocation; + struct CSourceLocation mAttributeLocation; + int mArgIndex; +}; + +struct CNonNullReturnData { + struct CSourceLocation mAttributeLocation; +}; + +struct COutOfBoundsData { + struct CSourceLocation mLocation; + struct CTypeDescriptor *mArrayType; + struct CTypeDescriptor *mIndexType; +}; + +struct CPointerOverflowData { + struct CSourceLocation mLocation; +}; + +struct CShiftOutOfBoundsData { + struct CSourceLocation mLocation; + struct CTypeDescriptor *mLHSType; + struct CTypeDescriptor *mRHSType; +}; + +struct CTypeMismatchData { + struct CSourceLocation mLocation; + struct CTypeDescriptor *mType; + unsigned long mLogAlignment; + uint8_t mTypeCheckKind; +}; + +struct CTypeMismatchData_v1 { + struct CSourceLocation mLocation; + struct CTypeDescriptor *mType; + uint8_t mLogAlignment; + uint8_t mTypeCheckKind; +}; + +struct CVLABoundData { + struct CSourceLocation mLocation; + struct CTypeDescriptor *mType; +}; + +struct CFloatCastOverflowData { + struct CSourceLocation + mLocation; /* This field exists in this struct since 2015 August 11th */ + struct CTypeDescriptor *mFromType; + struct CTypeDescriptor *mToType; +}; + +struct CImplicitConversionData { + struct CSourceLocation mLocation; + struct CTypeDescriptor *mFromType; + struct CTypeDescriptor *mToType; + uint8_t mKind; +}; + +struct CAlignmentAssumptionData { + struct CSourceLocation mLocation; + struct CSourceLocation mAssumptionLocation; + struct CTypeDescriptor *mType; +}; + +/* Public symbols used in the instrumentation of the code generation part */ +void __ubsan_handle_add_overflow(struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS); +void __ubsan_handle_add_overflow_abort(struct COverflowData *pData, + unsigned long ulLHS, + unsigned long ulRHS); +void __ubsan_handle_alignment_assumption(struct CAlignmentAssumptionData *pData, + unsigned long ulPointer, + unsigned long ulAlignment, + unsigned long ulOffset); +void __ubsan_handle_alignment_assumption_abort( + struct CAlignmentAssumptionData *pData, unsigned long ulPointer, + unsigned long ulAlignment, unsigned long ulOffset); +void __ubsan_handle_builtin_unreachable(struct CUnreachableData *pData); +void __ubsan_handle_divrem_overflow(struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS); +void __ubsan_handle_divrem_overflow_abort(struct COverflowData *pData, + unsigned long ulLHS, + unsigned long ulRHS); +void __ubsan_handle_function_type_mismatch( + struct CFunctionTypeMismatchData *pData, unsigned long ulFunction); +void __ubsan_handle_function_type_mismatch_abort( + struct CFunctionTypeMismatchData *pData, unsigned long ulFunction); +void __ubsan_handle_function_type_mismatch_v1( + struct CFunctionTypeMismatchData *pData, unsigned long ulFunction, + unsigned long ulCalleeRTTI, unsigned long ulFnRTTI); +void __ubsan_handle_function_type_mismatch_v1_abort( + struct CFunctionTypeMismatchData *pData, unsigned long ulFunction, + unsigned long ulCalleeRTTI, unsigned long ulFnRTTI); +void __ubsan_handle_invalid_builtin(struct CInvalidBuiltinData *pData); +void __ubsan_handle_invalid_builtin_abort(struct CInvalidBuiltinData *pData); +void __ubsan_handle_load_invalid_value(struct CInvalidValueData *pData, + unsigned long ulVal); +void __ubsan_handle_load_invalid_value_abort(struct CInvalidValueData *pData, + unsigned long ulVal); +void __ubsan_handle_missing_return(struct CUnreachableData *pData); +void __ubsan_handle_mul_overflow(struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS); +void __ubsan_handle_mul_overflow_abort(struct COverflowData *pData, + unsigned long ulLHS, + unsigned long ulRHS); +void __ubsan_handle_negate_overflow(struct COverflowData *pData, + unsigned long ulOldVal); +void __ubsan_handle_negate_overflow_abort(struct COverflowData *pData, + unsigned long ulOldVal); +void __ubsan_handle_nullability_arg(struct CNonNullArgData *pData); +void __ubsan_handle_nullability_arg_abort(struct CNonNullArgData *pData); +void __ubsan_handle_nullability_return_v1( + struct CNonNullReturnData *pData, + struct CSourceLocation *pLocationPointer); +void __ubsan_handle_nullability_return_v1_abort( + struct CNonNullReturnData *pData, + struct CSourceLocation *pLocationPointer); +void __ubsan_handle_out_of_bounds(struct COutOfBoundsData *pData, + unsigned long ulIndex); +void __ubsan_handle_out_of_bounds_abort(struct COutOfBoundsData *pData, + unsigned long ulIndex); +void __ubsan_handle_pointer_overflow(struct CPointerOverflowData *pData, + unsigned long ulBase, + unsigned long ulResult); +void __ubsan_handle_pointer_overflow_abort(struct CPointerOverflowData *pData, + unsigned long ulBase, + unsigned long ulResult); +void __ubsan_handle_shift_out_of_bounds(struct CShiftOutOfBoundsData *pData, + unsigned long ulLHS, + unsigned long ulRHS); +void __ubsan_handle_shift_out_of_bounds_abort( + struct CShiftOutOfBoundsData *pData, unsigned long ulLHS, + unsigned long ulRHS); +void __ubsan_handle_sub_overflow(struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS); +void __ubsan_handle_sub_overflow_abort(struct COverflowData *pData, + unsigned long ulLHS, + unsigned long ulRHS); +void __ubsan_handle_type_mismatch(struct CTypeMismatchData *pData, + unsigned long ulPointer); +void __ubsan_handle_type_mismatch_abort(struct CTypeMismatchData *pData, + unsigned long ulPointer); +void __ubsan_handle_type_mismatch_v1(struct CTypeMismatchData_v1 *pData, + unsigned long ulPointer); +void __ubsan_handle_type_mismatch_v1_abort(struct CTypeMismatchData_v1 *pData, + unsigned long ulPointer); +void __ubsan_handle_vla_bound_not_positive(struct CVLABoundData *pData, + unsigned long ulBound); +void __ubsan_handle_vla_bound_not_positive_abort(struct CVLABoundData *pData, + unsigned long ulBound); +void __ubsan_get_current_report_data(const char **ppOutIssueKind, + const char **ppOutMessage, + const char **ppOutFilename, + uint32_t *pOutLine, uint32_t *pOutCol, + char **ppOutMemoryAddr); +static void HandleOverflow(bool isFatal, struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS, + const char *szOperation); +static void HandleNegateOverflow(bool isFatal, struct COverflowData *pData, + unsigned long ulOldValue); +static void HandleBuiltinUnreachable(bool isFatal, + struct CUnreachableData *pData); +static void HandleTypeMismatch(bool isFatal, struct CSourceLocation *mLocation, + struct CTypeDescriptor *mType, + unsigned long mLogAlignment, + uint8_t mTypeCheckKind, unsigned long ulPointer); +static void HandleVlaBoundNotPositive(bool isFatal, struct CVLABoundData *pData, + unsigned long ulBound); +static void HandleOutOfBounds(bool isFatal, struct COutOfBoundsData *pData, + unsigned long ulIndex); +static void HandleShiftOutOfBounds(bool isFatal, + struct CShiftOutOfBoundsData *pData, + unsigned long ulLHS, unsigned long ulRHS); +static void HandleLoadInvalidValue(bool isFatal, + struct CInvalidValueData *pData, + unsigned long ulValue); +static void HandleInvalidBuiltin(bool isFatal, + struct CInvalidBuiltinData *pData); +static void HandleFunctionTypeMismatch(bool isFatal, + struct CFunctionTypeMismatchData *pData, + unsigned long ulFunction); +static void HandleMissingReturn(bool isFatal, struct CUnreachableData *pData); +static void HandlePointerOverflow(bool isFatal, + struct CPointerOverflowData *pData, + unsigned long ulBase, unsigned long ulResult); +static void HandleAlignmentAssumption(bool isFatal, + struct CAlignmentAssumptionData *pData, + unsigned long ulPointer, + unsigned long ulAlignment, + unsigned long ulOffset); + +#define NUMBER_SIGNED_BIT 1 +#define NUMBER_MAXLEN 128 +#define __arraycount(__a) (sizeof(__a) / sizeof(__a[0])) +#define __BIT(__n) (1UL << (__n)) +#define SEPARATOR sbi_printf("===========================================\n") +#define ACK_REPORTED (1U << 31) + +static bool isAlreadyReported(struct CSourceLocation *pLocation) +{ + uint32_t siOldValue; + volatile uint32_t *pLine; + + if (!pLocation) + return false; + + pLine = &pLocation->mLine; + + do { + siOldValue = *pLine; + } while (__sync_val_compare_and_swap(pLine, siOldValue, + siOldValue | ACK_REPORTED) != + siOldValue); + + if (!(siOldValue & ACK_REPORTED)) { + + sbi_ubsan_report_count++; + + return false; + } + + return true; +} + +static void HandleOverflow(bool isFatal, struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS, + const char *szOperation) +{ + if (!pData) { + return; + } + + if (isAlreadyReported(&pData->mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + pData->mLocation.mFilename, + pData->mLocation.mLine & ~ACK_REPORTED, + pData->mLocation.mColumn); + + bool is_signed = pData->mType->mTypeInfo & NUMBER_SIGNED_BIT; + + sbi_printf("UBSan: %s integer overflow: ", + is_signed ? "signed" : "unsigned"); + + if (is_signed) { + sbi_printf("%ld %s %ld ", (long)ulLHS, szOperation, + (long)ulRHS); + } else { + sbi_printf("%lu %s %lu ", ulLHS, szOperation, ulRHS); + } + + sbi_printf("cannot be represented in type %s\n", + pData->mType->mTypeName); + + SEPARATOR; +} + +static void HandleNegateOverflow(bool isFatal, struct COverflowData *pData, + unsigned long ulOldValue) +{ + if (!pData) { + return; + } + if (isAlreadyReported(&pData->mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + pData->mLocation.mFilename, + pData->mLocation.mLine & ~ACK_REPORTED, + pData->mLocation.mColumn); + + bool is_signed = pData->mType->mTypeInfo & NUMBER_SIGNED_BIT; + + sbi_printf("UBSan: Negation of "); + + if (is_signed) { + sbi_printf("%ld", (long)ulOldValue); + } else { + sbi_printf("%lu", ulOldValue); + } + + sbi_printf("cannot be represented in type %s\n", + pData->mType->mTypeName); + + SEPARATOR; +} + +static void HandleBuiltinUnreachable(bool isFatal, + struct CUnreachableData *pData) +{ + if (!pData) { + return; + } + if (isAlreadyReported(&pData->mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + pData->mLocation.mFilename, + pData->mLocation.mLine & ~ACK_REPORTED, + pData->mLocation.mColumn); + + SEPARATOR; +} + +const char *rgczTypeCheckKinds[] = { "load of", + "store to", + "reference binding to", + "member access within", + "member call on", + "constructor call on", + "downcast of", + "downcast of", + "upcast of", + "cast to virtual base of", + "_Nonnull binding to", + "dynamic operation on" }; + +static void HandleTypeMismatch(bool isFatal, struct CSourceLocation *mLocation, + struct CTypeDescriptor *mType, + unsigned long mLogAlignment, + uint8_t mTypeCheckKind, unsigned long ulPointer) +{ + + if ((!mLocation) || (!mType)) { + return; + } + + if (isAlreadyReported(mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + mLocation->mFilename, mLocation->mLine & ~ACK_REPORTED, + mLocation->mColumn); + + const char *kind = (mTypeCheckKind < __arraycount(rgczTypeCheckKinds)) + ? rgczTypeCheckKinds[mTypeCheckKind] + : "access to"; + + if (ulPointer == 0) { + sbi_printf("%s null pointer of type %s\n", kind, + mType->mTypeName); + } else if ( + (mLogAlignment - 1) & + ulPointer) { //mLogAlignment is converted on the wrapper function call + sbi_printf( + "%s misaligned address %p for type %s which requires %ld byte alignment\n", + kind, (void *)ulPointer, mType->mTypeName, + mLogAlignment); + } else { + sbi_printf( + "%s address %p with insufficient space for an object of type %s\n", + kind, (void *)ulPointer, mType->mTypeName); + } + SEPARATOR; +} + +static void HandleVlaBoundNotPositive(bool isFatal, struct CVLABoundData *pData, + unsigned long ulBound) +{ + if (!pData) { + return; + } + if (isAlreadyReported(&pData->mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + pData->mLocation.mFilename, + pData->mLocation.mLine & ~ACK_REPORTED, + pData->mLocation.mColumn); + + sbi_printf("variable length array bound value "); + + bool is_signed = pData->mType->mTypeInfo & NUMBER_SIGNED_BIT; + + if (is_signed) { + sbi_printf("%ld", (long)ulBound); + } else { + sbi_printf("%lu", ulBound); + } + + sbi_printf(" <= 0\n"); + + SEPARATOR; +} + +static void HandleOutOfBounds(bool isFatal, struct COutOfBoundsData *pData, + unsigned long ulIndex) +{ + if (!pData) { + return; + } + if (isAlreadyReported(&pData->mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + pData->mLocation.mFilename, + pData->mLocation.mLine & ~ACK_REPORTED, + pData->mLocation.mColumn); + + bool is_signed = pData->mIndexType->mTypeInfo & NUMBER_SIGNED_BIT; + + if (is_signed) { + sbi_printf("index %ld", (long)ulIndex); + } else { + sbi_printf("index %lu", ulIndex); + } + + sbi_printf(" is out of range for type %s\n", + pData->mArrayType->mTypeName); + + SEPARATOR; +} + +static bool isNegativeNumber(struct CTypeDescriptor *pType, unsigned long ulVal) +{ + if (!(pType->mTypeInfo & NUMBER_SIGNED_BIT)) { + return false; + } + + return (long)ulVal < 0; +} + +static size_t type_width(struct CTypeDescriptor *pType) +{ + return 1UL << (pType->mTypeInfo >> 1); +} + +static void HandleShiftOutOfBounds(bool isFatal, + struct CShiftOutOfBoundsData *pData, + unsigned long ulLHS, unsigned long ulRHS) +{ + if (!pData) { + return; + } + + if (isAlreadyReported(&pData->mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + pData->mLocation.mFilename, + pData->mLocation.mLine & ~ACK_REPORTED, + pData->mLocation.mColumn); + + if (isNegativeNumber(pData->mRHSType, ulRHS)) { + sbi_printf("shift exponent %ld is negative\n", (long)ulRHS); + } else if (ulRHS >= type_width(pData->mLHSType)) { + sbi_printf( + "shift exponent %lu is too large for %lu-bit type %s\n", + ulRHS, (unsigned long)type_width(pData->mLHSType), + pData->mLHSType->mTypeName); + + } else if (isNegativeNumber(pData->mLHSType, ulLHS)) { + sbi_printf("left shift of negative value %ld\n", (long)ulLHS); + } else { + sbi_printf( + "left shift of %lu by %lu places cannot be represented in type %s\n", + ulLHS, ulRHS, pData->mLHSType->mTypeName); + } + + SEPARATOR; +} + +static void HandleLoadInvalidValue(bool isFatal, + struct CInvalidValueData *pData, + unsigned long ulValue) +{ + if (!pData) { + return; + } + + if (isAlreadyReported(&pData->mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + pData->mLocation.mFilename, + pData->mLocation.mLine & ~ACK_REPORTED, + pData->mLocation.mColumn); + + bool is_signed = pData->mType->mTypeInfo & NUMBER_SIGNED_BIT; + + sbi_printf("load of value "); + + if (is_signed) { + sbi_printf("%ld ", (long)ulValue); + } else { + sbi_printf("%lu ", ulValue); + } + + sbi_printf("is not a valid value for type %s\n", + pData->mType->mTypeName); + + SEPARATOR; +} + +const char *rgczBuiltinCheckKinds[] = { "ctz()", "clz()" }; + +static void HandleInvalidBuiltin(bool isFatal, + struct CInvalidBuiltinData *pData) +{ + if (!pData) { + return; + } + + if (isAlreadyReported(&pData->mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + pData->mLocation.mFilename, + pData->mLocation.mLine & ~ACK_REPORTED, + pData->mLocation.mColumn); + + const char *builtin = + (pData->mKind < __arraycount(rgczBuiltinCheckKinds)) + ? rgczBuiltinCheckKinds[pData->mKind] + : "unknown builtin"; + + sbi_printf("passing zero to %s, which is not a valid argument\n", + builtin); + + SEPARATOR; +} + +static void HandleFunctionTypeMismatch(bool isFatal, + struct CFunctionTypeMismatchData *pData, + unsigned long ulFunction) +{ + if (!pData) { + return; + } + + if (isAlreadyReported(&pData->mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + pData->mLocation.mFilename, + pData->mLocation.mLine & ~ACK_REPORTED, + pData->mLocation.mColumn); + + sbi_printf( + "call to function %#lx through pointer to incorrect function type %s\n", + ulFunction, pData->mType->mTypeName); + + SEPARATOR; +} + +static void HandleMissingReturn(bool isFatal, struct CUnreachableData *pData) +{ + if (!pData) { + return; + } + + if (isAlreadyReported(&pData->mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + pData->mLocation.mFilename, + pData->mLocation.mLine & ~ACK_REPORTED, + pData->mLocation.mColumn); + + sbi_printf( + "execution reached the end of a value-returning function without returning a value\n"); + + SEPARATOR; +} + +static void HandlePointerOverflow(bool isFatal, + struct CPointerOverflowData *pData, + unsigned long ulBase, unsigned long ulResult) +{ + if (!pData) { + return; + } + + if (isAlreadyReported(&pData->mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + pData->mLocation.mFilename, + pData->mLocation.mLine & ~ACK_REPORTED, + pData->mLocation.mColumn); + + sbi_printf("pointer expression with base %#lx overflowed to %#lx\n", + ulBase, ulResult); + + SEPARATOR; +} + +static void HandleAlignmentAssumption(bool isFatal, + struct CAlignmentAssumptionData *pData, + unsigned long ulPointer, + unsigned long ulAlignment, + unsigned long ulOffset) +{ + + if (!pData) { + return; + } + + if (isAlreadyReported(&pData->mLocation)) { + return; + } + + SEPARATOR; + + sbi_printf("UBSan: Undefined Behavior in %s:%u:%u\n", + pData->mLocation.mFilename, + pData->mLocation.mLine & ~ACK_REPORTED, + pData->mLocation.mColumn); + + unsigned long ulRealPointer = ulPointer - ulOffset; + sbi_printf("alignment assumption of %lu for pointer %p (offset %p)", + ulAlignment, (void *)ulRealPointer, (void *)ulOffset); + + if (pData->mAssumptionLocation.mFilename != NULL) { + sbi_printf(", assumption made in %s:%u:%u", + pData->mAssumptionLocation.mFilename, + pData->mAssumptionLocation.mLine, + pData->mAssumptionLocation.mColumn); + } + + sbi_printf("\n"); + + SEPARATOR; +} + +/* Definions of public symbols emitted by the instrumentation code */ +void __ubsan_handle_add_overflow(struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS) +{ + HandleOverflow(false, pData, ulLHS, ulRHS, "+"); +} + +void __ubsan_handle_add_overflow_abort(struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS) +{ + HandleOverflow(true, pData, ulLHS, ulRHS, "+"); +} + +void __ubsan_handle_sub_overflow(struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS) +{ + HandleOverflow(false, pData, ulLHS, ulRHS, "-"); +} + +void __ubsan_handle_sub_overflow_abort(struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS) +{ + HandleOverflow(true, pData, ulLHS, ulRHS, "-"); +} + +void __ubsan_handle_mul_overflow(struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS) +{ + HandleOverflow(false, pData, ulLHS, ulRHS, "*"); +} + +void __ubsan_handle_mul_overflow_abort(struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS) +{ + HandleOverflow(true, pData, ulLHS, ulRHS, "*"); +} + +void __ubsan_handle_negate_overflow(struct COverflowData *pData, + unsigned long ulOldValue) +{ + HandleNegateOverflow(false, pData, ulOldValue); +} + +void __ubsan_handle_negate_overflow_abort(struct COverflowData *pData, + unsigned long ulOldValue) +{ + HandleNegateOverflow(true, pData, ulOldValue); +} + +void __ubsan_handle_divrem_overflow(struct COverflowData *pData, + unsigned long ulLHS, unsigned long ulRHS) +{ + HandleOverflow(false, pData, ulLHS, ulRHS, "divrem"); +} + +void __ubsan_handle_divrem_overflow_abort(struct COverflowData *pData, + unsigned long ulLHS, + unsigned long ulRHS) +{ + HandleOverflow(true, pData, ulLHS, ulRHS, "divrem"); +} + +void __ubsan_handle_type_mismatch_v1(struct CTypeMismatchData_v1 *pData, + unsigned long ulPointer) +{ + HandleTypeMismatch(false, &pData->mLocation, pData->mType, + __BIT(pData->mLogAlignment), pData->mTypeCheckKind, + ulPointer); +} + +void __ubsan_handle_type_mismatch_v1_abort(struct CTypeMismatchData_v1 *pData, + unsigned long ulPointer) +{ + HandleTypeMismatch(true, &pData->mLocation, pData->mType, + __BIT(pData->mLogAlignment), pData->mTypeCheckKind, + ulPointer); +} + +void __ubsan_handle_out_of_bounds(struct COutOfBoundsData *pData, + unsigned long ulIndex) +{ + HandleOutOfBounds(false, pData, ulIndex); +} + +void __ubsan_handle_out_of_bounds_abort(struct COutOfBoundsData *pData, + unsigned long ulIndex) +{ + HandleOutOfBounds(true, pData, ulIndex); +} + +void __ubsan_handle_shift_out_of_bounds(struct CShiftOutOfBoundsData *pData, + unsigned long ulLHS, + unsigned long ulRHS) +{ + HandleShiftOutOfBounds(false, pData, ulLHS, ulRHS); +} + +void __ubsan_handle_shift_out_of_bounds_abort( + struct CShiftOutOfBoundsData *pData, unsigned long ulLHS, + unsigned long ulRHS) +{ + HandleShiftOutOfBounds(true, pData, ulLHS, ulRHS); +} + +void __ubsan_handle_pointer_overflow(struct CPointerOverflowData *pData, + unsigned long ulBase, + unsigned long ulResult) +{ + HandlePointerOverflow(false, pData, ulBase, ulResult); +} + +void __ubsan_handle_pointer_overflow_abort(struct CPointerOverflowData *pData, + unsigned long ulBase, + unsigned long ulResult) +{ + HandlePointerOverflow(true, pData, ulBase, ulResult); +} + +void __ubsan_handle_alignment_assumption(struct CAlignmentAssumptionData *pData, + unsigned long ulPointer, + unsigned long ulAlignment, + unsigned long ulOffset) +{ + HandleAlignmentAssumption(false, pData, ulPointer, ulAlignment, + ulOffset); +} + +void __ubsan_handle_alignment_assumption_abort( + struct CAlignmentAssumptionData *pData, unsigned long ulPointer, + unsigned long ulAlignment, unsigned long ulOffset) +{ + HandleAlignmentAssumption(true, pData, ulPointer, ulAlignment, + ulOffset); +} + +void __ubsan_handle_builtin_unreachable(struct CUnreachableData *pData) +{ + HandleBuiltinUnreachable(true, pData); +} + +void __ubsan_handle_invalid_builtin(struct CInvalidBuiltinData *pData) +{ + HandleInvalidBuiltin(true, pData); +} + +void __ubsan_handle_invalid_builtin_abort(struct CInvalidBuiltinData *pData) +{ + HandleInvalidBuiltin(true, pData); +} + +void __ubsan_handle_load_invalid_value(struct CInvalidValueData *pData, + unsigned long ulValue) +{ + HandleLoadInvalidValue(false, pData, ulValue); +} + +void __ubsan_handle_load_invalid_value_abort(struct CInvalidValueData *pData, + unsigned long ulValue) +{ + HandleLoadInvalidValue(true, pData, ulValue); +} + +void __ubsan_handle_missing_return(struct CUnreachableData *pData) +{ + HandleMissingReturn(true, pData); +} + +void __ubsan_handle_vla_bound_not_positive(struct CVLABoundData *pData, + unsigned long ulBound) +{ + HandleVlaBoundNotPositive(false, pData, ulBound); +} + +void __ubsan_handle_vla_bound_not_positive_abort(struct CVLABoundData *pData, + unsigned long ulBound) +{ + HandleVlaBoundNotPositive(true, pData, ulBound); +} + +void __ubsan_handle_function_type_mismatch( + struct CFunctionTypeMismatchData *pData, unsigned long ulFunction) +{ + HandleFunctionTypeMismatch(false, pData, ulFunction); +} + +void __ubsan_handle_function_type_mismatch_abort( + struct CFunctionTypeMismatchData *pData, unsigned long ulFunction) +{ + HandleFunctionTypeMismatch(true, pData, ulFunction); +} +#endif diff --git a/lib/sbi/tests/objects.mk b/lib/sbi/tests/objects.mk index 3ee1c635..40c441e1 100644 --- a/lib/sbi/tests/objects.mk +++ b/lib/sbi/tests/objects.mk @@ -24,3 +24,8 @@ libsbi-objs-$(CONFIG_SBIUNIT) += tests/sbi_bitops_test.o carray-sbi_unit_tests-$(CONFIG_SBIUNIT) += string_test_suite libsbi-objs-$(CONFIG_SBIUNIT) += tests/sbi_string_test.o + +ifeq ($(UBSAN),y) +carray-sbi_unit_tests-$(CONFIG_SBIUNIT) += ubsan_test_suite +libsbi-objs-$(CONFIG_SBIUNIT) += tests/sbi_ubsan_test.o +endif diff --git a/lib/sbi/tests/sbi_ubsan_test.c b/lib/sbi/tests/sbi_ubsan_test.c new file mode 100644 index 00000000..23409cb1 --- /dev/null +++ b/lib/sbi/tests/sbi_ubsan_test.c @@ -0,0 +1,114 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Author: Marcos Oduardo + */ + +#include +#include +#include +#include + +#define UBSAN_EXPECT_FIRES(test, stmt) \ + do { \ + unsigned long _before = sbi_ubsan_report_count; \ + stmt; \ + SBIUNIT_EXPECT_NE(test, sbi_ubsan_report_count, _before); \ + } while (0) + +static void test_ubsan_add_overflow(struct sbiunit_test_case *test) +{ + volatile int a = 0x7FFFFFFF; //INT_MAx + volatile int b = 1; + volatile int c; + UBSAN_EXPECT_FIRES(test, c = a + b); + (void)c; +} + +static void test_ubsan_sub_overflow(struct sbiunit_test_case *test) +{ + volatile int a = 0x80000000; //INT_MIN + volatile int b = 1; + volatile int c; + UBSAN_EXPECT_FIRES(test, c = a - b); + (void)c; +} + +static void test_ubsan_mul_overflow(struct sbiunit_test_case *test) +{ + volatile int a = 0x7FFFFFFF; + volatile int b = 2; + volatile int c; + UBSAN_EXPECT_FIRES(test, c = a * b); + (void)c; +} + +static void test_ubsan_divrem(struct sbiunit_test_case *test) +{ + volatile int a = 10; + volatile int b = 0; + volatile int c; + UBSAN_EXPECT_FIRES(test, c = a / b); + (void)c; +} + +static void test_ubsan_oob(struct sbiunit_test_case *test) +{ + volatile int idx = 5; + int arr[3] = { 1, 2, 3 }; + volatile int val; + UBSAN_EXPECT_FIRES(test, val = arr[idx]); + (void)val; +} + +static void test_ubsan_shift_too_large(struct sbiunit_test_case *test) +{ + volatile unsigned long val = 1; + volatile int shift = 64; + volatile unsigned long res; + UBSAN_EXPECT_FIRES(test, res = val << shift); + (void)res; +} + +static void test_ubsan_shift_negative(struct sbiunit_test_case *test) +{ + volatile int val = 1; + volatile int shift = -1; + volatile int res; + + UBSAN_EXPECT_FIRES(test, res = val << shift); + (void)res; +} + +static void test_ubsan_load_invalid_bool(struct sbiunit_test_case *test) +{ + volatile char bool_val = 5; + volatile bool *b_ptr = (bool *)&bool_val; + volatile int taken = 0; + UBSAN_EXPECT_FIRES(test, if (*b_ptr) taken = 1); + (void)taken; +} + +static void test_ubsan_pointer_overflow(struct sbiunit_test_case *test) +{ + volatile uintptr_t base = 0xFFFFFFFFFFFFFFFEUL; + volatile char *ptr = (char *)base; + volatile char *res; + UBSAN_EXPECT_FIRES(test, res = ptr + 5); + (void)res; +} + +static struct sbiunit_test_case ubsan_tests[] = { + SBIUNIT_TEST_CASE(test_ubsan_add_overflow), + SBIUNIT_TEST_CASE(test_ubsan_sub_overflow), + SBIUNIT_TEST_CASE(test_ubsan_mul_overflow), + SBIUNIT_TEST_CASE(test_ubsan_divrem), + SBIUNIT_TEST_CASE(test_ubsan_oob), + SBIUNIT_TEST_CASE(test_ubsan_shift_too_large), + SBIUNIT_TEST_CASE(test_ubsan_shift_negative), + SBIUNIT_TEST_CASE(test_ubsan_load_invalid_bool), + SBIUNIT_TEST_CASE(test_ubsan_pointer_overflow), + SBIUNIT_END_CASE, +}; + +SBIUNIT_TEST_SUITE(ubsan_test_suite, ubsan_tests);