Index: clang/docs/UndefinedBehaviorSanitizer.rst =================================================================== --- clang/docs/UndefinedBehaviorSanitizer.rst +++ clang/docs/UndefinedBehaviorSanitizer.rst @@ -205,8 +205,8 @@ There is a minimal UBSan runtime available suitable for use in production environments. This runtime has a small attack surface. It only provides very -basic issue logging and deduplication, and does not support ``-fsanitize=vptr`` -checking. +basic issue logging and deduplication, and does not support +``-fsanitize=function`` and ``-fsanitize=vptr`` checking. To use the minimal runtime, add ``-fsanitize-minimal-runtime`` to the clang command line options. For example, if you're used to compiling with Index: clang/lib/CodeGen/CGExpr.cpp =================================================================== --- clang/lib/CodeGen/CGExpr.cpp +++ clang/lib/CodeGen/CGExpr.cpp @@ -2927,7 +2927,7 @@ static CheckRecoverableKind getRecoverableKind(SanitizerMask Kind) { assert(Kind.countPopulation() == 1); - if (Kind == SanitizerKind::Vptr) + if (Kind == SanitizerKind::Function || Kind == SanitizerKind::Vptr) return CheckRecoverableKind::AlwaysRecoverable; else if (Kind == SanitizerKind::Return || Kind == SanitizerKind::Unreachable) return CheckRecoverableKind::Unrecoverable; Index: clang/lib/CodeGen/CodeGenFunction.h =================================================================== --- clang/lib/CodeGen/CodeGenFunction.h +++ clang/lib/CodeGen/CodeGenFunction.h @@ -114,7 +114,7 @@ SANITIZER_CHECK(DivremOverflow, divrem_overflow, 0) \ SANITIZER_CHECK(DynamicTypeCacheMiss, dynamic_type_cache_miss, 0) \ SANITIZER_CHECK(FloatCastOverflow, float_cast_overflow, 0) \ - SANITIZER_CHECK(FunctionTypeMismatch, function_type_mismatch, 0) \ + SANITIZER_CHECK(FunctionTypeMismatch, function_type_mismatch, 1) \ SANITIZER_CHECK(ImplicitConversion, implicit_conversion, 0) \ SANITIZER_CHECK(InvalidBuiltin, invalid_builtin, 0) \ SANITIZER_CHECK(LoadInvalidValue, load_invalid_value, 0) \ Index: clang/lib/Driver/SanitizerArgs.cpp =================================================================== --- clang/lib/Driver/SanitizerArgs.cpp +++ clang/lib/Driver/SanitizerArgs.cpp @@ -31,7 +31,8 @@ static const SanitizerMask NeedsUbsanCxxRt = SanitizerKind::Vptr | SanitizerKind::CFI; static const SanitizerMask NotAllowedWithTrap = SanitizerKind::Vptr; -static const SanitizerMask NotAllowedWithMinimalRuntime = SanitizerKind::Vptr; +static const SanitizerMask NotAllowedWithMinimalRuntime = + SanitizerKind::Function | SanitizerKind::Vptr; static const SanitizerMask RequiresPIE = SanitizerKind::DataFlow | SanitizerKind::HWAddress | SanitizerKind::Scudo; static const SanitizerMask NeedsUnwindTables = Index: clang/test/CodeGen/ubsan-function.cpp =================================================================== --- /dev/null +++ clang/test/CodeGen/ubsan-function.cpp @@ -0,0 +1,22 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -o - %s -fsanitize=function -fno-sanitize-recover=all | FileCheck %s + +// CHECK-LABEL: define void @_Z3funv() #0 prologue <{ i32, i32 }> <{ i32 846595819, i32 trunc (i64 sub (i64 ptrtoint (i8** @0 to i64), i64 ptrtoint (void ()* @_Z3funv to i64)) to i32) }> { +void fun() {} + +// CHECK-LABEL: define void @_Z6callerPFvvE(void ()* %f) +// CHECK: getelementptr <{ i32, i32 }>, <{ i32, i32 }>* {{.*}}, i32 0, i32 0, !nosanitize +// CHECK: load i32, i32* {{.*}}, align {{.*}}, !nosanitize +// CHECK: icmp eq i32 {{.*}}, 846595819, !nosanitize +// CHECK: br i1 {{.*}}, label %[[LABEL1:.*]], label %[[LABEL4:.*]], !nosanitize +// CHECK: [[LABEL1]]: +// CHECK: getelementptr <{ i32, i32 }>, <{ i32, i32 }>* {{.*}}, i32 0, i32 1, !nosanitize +// CHECK: load i32, i32* {{.*}}, align {{.*}}, !nosanitize +// CHECK: icmp eq i8* {{.*}}, bitcast ({ i8*, i8* }* @_ZTIFvvE to i8*), !nosanitize +// CHECK: br i1 {{.*}}, label %[[LABEL3:.*]], label %[[LABEL2:[^,]*]], {{.*}}!nosanitize +// CHECK: [[LABEL2]]: +// CHECK: call void @__ubsan_handle_function_type_mismatch_v1_abort(i8* {{.*}}, i64 {{.*}}, i64 {{.*}}, i64 {{.*}}) #{{.*}}, !nosanitize +// CHECK-NOT: unreachable +// CHECK: br label %[[LABEL3]], !nosanitize +// CHECK: [[LABEL3]]: +// CHECK: br label %[[LABEL4]], !nosanitize +void caller(void (*f)()) { f(); } Index: clang/test/Driver/fsanitize.c =================================================================== --- clang/test/Driver/fsanitize.c +++ clang/test/Driver/fsanitize.c @@ -749,9 +749,12 @@ // CHECK-TSAN-MINIMAL: error: invalid argument '-fsanitize-minimal-runtime' not allowed with '-fsanitize=thread' // RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UBSAN-MINIMAL -// CHECK-UBSAN-MINIMAL: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute|function),?){18}"}} +// CHECK-UBSAN-MINIMAL: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|shift-base|shift-exponent|unreachable|return|vla-bound|alignment|null|pointer-overflow|float-cast-overflow|array-bounds|enum|bool|builtin|returns-nonnull-attribute|nonnull-attribute),?){17}"}} // CHECK-UBSAN-MINIMAL: "-fsanitize-minimal-runtime" +// RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fsanitize=function -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UBSAN-FUNCTION-MINIMAL +// CHECK-UBSAN-FUNCTION-MINIMAL: error: invalid argument '-fsanitize=function' not allowed with '-fsanitize-minimal-runtime' + // RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fsanitize=vptr -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UBSAN-VPTR-MINIMAL // CHECK-UBSAN-VPTR-MINIMAL: error: invalid argument '-fsanitize=vptr' not allowed with '-fsanitize-minimal-runtime' Index: compiler-rt/lib/ubsan/ubsan_handlers_cxx.h =================================================================== --- compiler-rt/lib/ubsan/ubsan_handlers_cxx.h +++ compiler-rt/lib/ubsan/ubsan_handlers_cxx.h @@ -40,14 +40,15 @@ }; extern "C" SANITIZER_INTERFACE_ATTRIBUTE void -__ubsan_handle_function_type_mismatch(FunctionTypeMismatchData *Data, - ValueHandle Val, ValueHandle calleeRTTI, - ValueHandle fnRTTI); +__ubsan_handle_function_type_mismatch_v1(FunctionTypeMismatchData *Data, + ValueHandle Val, + ValueHandle calleeRTTI, + ValueHandle fnRTTI); extern "C" SANITIZER_INTERFACE_ATTRIBUTE void -__ubsan_handle_function_type_mismatch_abort(FunctionTypeMismatchData *Data, - ValueHandle Val, - ValueHandle calleeRTTI, - ValueHandle fnRTTI); +__ubsan_handle_function_type_mismatch_v1_abort(FunctionTypeMismatchData *Data, + ValueHandle Val, + ValueHandle calleeRTTI, + ValueHandle fnRTTI); } #endif // UBSAN_HANDLERS_H Index: compiler-rt/lib/ubsan/ubsan_handlers_cxx.cc =================================================================== --- compiler-rt/lib/ubsan/ubsan_handlers_cxx.cc +++ compiler-rt/lib/ubsan/ubsan_handlers_cxx.cc @@ -185,18 +185,17 @@ return true; } -void __ubsan_handle_function_type_mismatch(FunctionTypeMismatchData *Data, - ValueHandle Function, - ValueHandle calleeRTTI, - ValueHandle fnRTTI) { +void __ubsan_handle_function_type_mismatch_v1(FunctionTypeMismatchData *Data, + ValueHandle Function, + ValueHandle calleeRTTI, + ValueHandle fnRTTI) { GET_REPORT_OPTIONS(false); handleFunctionTypeMismatch(Data, Function, calleeRTTI, fnRTTI, Opts); } -void __ubsan_handle_function_type_mismatch_abort(FunctionTypeMismatchData *Data, - ValueHandle Function, - ValueHandle calleeRTTI, - ValueHandle fnRTTI) { +void __ubsan_handle_function_type_mismatch_v1_abort( + FunctionTypeMismatchData *Data, ValueHandle Function, + ValueHandle calleeRTTI, ValueHandle fnRTTI) { GET_REPORT_OPTIONS(true); if (handleFunctionTypeMismatch(Data, Function, calleeRTTI, fnRTTI, Opts)) Die(); Index: compiler-rt/lib/ubsan/ubsan_interface.inc =================================================================== --- compiler-rt/lib/ubsan/ubsan_interface.inc +++ compiler-rt/lib/ubsan/ubsan_interface.inc @@ -21,8 +21,8 @@ INTERFACE_FUNCTION(__ubsan_handle_dynamic_type_cache_miss_abort) INTERFACE_FUNCTION(__ubsan_handle_float_cast_overflow) INTERFACE_FUNCTION(__ubsan_handle_float_cast_overflow_abort) -INTERFACE_FUNCTION(__ubsan_handle_function_type_mismatch) -INTERFACE_FUNCTION(__ubsan_handle_function_type_mismatch_abort) +INTERFACE_FUNCTION(__ubsan_handle_function_type_mismatch_v1) +INTERFACE_FUNCTION(__ubsan_handle_function_type_mismatch_v1_abort) INTERFACE_FUNCTION(__ubsan_handle_implicit_conversion) INTERFACE_FUNCTION(__ubsan_handle_implicit_conversion_abort) INTERFACE_FUNCTION(__ubsan_handle_invalid_builtin) Index: compiler-rt/test/ubsan/TestCases/TypeCheck/Function/function.cpp =================================================================== --- compiler-rt/test/ubsan/TestCases/TypeCheck/Function/function.cpp +++ compiler-rt/test/ubsan/TestCases/TypeCheck/Function/function.cpp @@ -1,11 +1,53 @@ -// RUN: %clangxx -std=c++17 -fsanitize=function %s -O3 -g -o %t -// RUN: %run %t 2>&1 | FileCheck %s +// RUN: %clangxx -DDETERMINE_UNIQUE %s -o %t-unique +// RUN: %clangxx -std=c++17 -fsanitize=function %s -O3 -g -DSHARED_LIB -fPIC -shared -o %t-so.so +// RUN: %clangxx -std=c++17 -fsanitize=function %s -O3 -g -o %t %t-so.so +// RUN: %run %t 2>&1 | FileCheck %s --check-prefix=CHECK $(%run %t-unique UNIQUE) // Verify that we can disable symbolization if needed: -// RUN: %env_ubsan_opts=symbolize=0 %run %t 2>&1 | FileCheck %s --check-prefix=NOSYM +// RUN: %env_ubsan_opts=symbolize=0 %run %t 2>&1 | FileCheck %s --check-prefix=NOSYM $(%run %t-unique NOSYM-UNIQUE) // XFAIL: windows-msvc // Unsupported function flag // UNSUPPORTED: openbsd +#ifdef DETERMINE_UNIQUE + +#include + +#include "../../../../../lib/sanitizer_common/sanitizer_platform.h" + +int main(int, char **argv) { + if (!SANITIZER_NON_UNIQUE_TYPEINFO) + std::cout << "--check-prefix=" << argv[1]; +} + +#else + +struct Shared {}; +using FnShared = void (*)(Shared *); +FnShared getShared(); + +struct __attribute__((visibility("hidden"))) Hidden {}; +using FnHidden = void (*)(Hidden *); +FnHidden getHidden(); + +namespace { +struct Private {}; +} // namespace +using FnPrivate = void (*)(void *); +FnPrivate getPrivate(); + +#ifdef SHARED_LIB + +void fnShared(Shared *) {} +FnShared getShared() { return fnShared; } + +void fnHidden(Hidden *) {} +FnHidden getHidden() { return fnHidden; } + +void fnPrivate(Private *) {} +FnPrivate getPrivate() { return reinterpret_cast(fnPrivate); } + +#else + #include void f() {} @@ -64,12 +106,31 @@ p2(0); } +void check_cross_dso() { + getShared()(nullptr); + + // UNIQUE: function.cpp:[[@LINE+2]]:3: runtime error: call to function fnHidden(Hidden*) through pointer to incorrect function type 'void (*)(Hidden *)' + // NOSYM-UNIQUE: function.cpp:[[@LINE+1]]:3: runtime error: call to function (unknown) through pointer to incorrect function type 'void (*)(Hidden *)' + getHidden()(nullptr); + + // TODO: Unlike GCC, Clang fails to prefix the typeinfo name for the function + // type with "*", so this erroneously only fails for "*UNIQUE": + // UNIQUE: function.cpp:[[@LINE+2]]:3: runtime error: call to function fnPrivate((anonymous namespace)::Private*) through pointer to incorrect function type 'void (*)((anonymous namespace)::Private *)' + // NOSYM-UNIQUE: function.cpp:[[@LINE+1]]:3: runtime error: call to function (unknown) through pointer to incorrect function type 'void (*)((anonymous namespace)::Private *)' + reinterpret_cast(getPrivate())(nullptr); +} + int main(void) { make_valid_call(); make_invalid_call(); check_noexcept_calls(); + check_cross_dso(); // Check that no more errors will be printed. // CHECK-NOT: runtime error: call to function // NOSYM-NOT: runtime error: call to function make_invalid_call(); } + +#endif + +#endif