Skip to content

Commit b6c06dc

Browse files
committed
[Sanitizers] UBSan unreachable incompatible with ASan in the presence of noreturn calls
Summary: UBSan wants to detect when unreachable code is actually reached, so it adds instrumentation before every unreachable instruction. However, the optimizer will remove code after calls to functions marked with noreturn. To avoid this UBSan removes noreturn from both the call instruction as well as from the function itself. Unfortunately, ASan relies on this annotation to unpoison the stack by inserting calls to _asan_handle_no_return before noreturn functions. This is important for functions that do not return but access the the stack memory, e.g., unwinder functions *like* longjmp (longjmp itself is actually "double-proofed" via its interceptor). The result is that when ASan and UBSan are combined, the noreturn attributes are missing and ASan cannot unpoison the stack, so it has false positives when stack unwinding is used. Changes: Clang-CodeGen now directly insert calls to `__asan_handle_no_return` when a call to a noreturn function is encountered and both UBsan-unreachable and ASan are enabled. This allows UBSan to continue removing the noreturn attribute from functions without any changes to the ASan pass. Previously generated code: ``` call void @longjmp call void @__asan_handle_no_return call void @__ubsan_handle_builtin_unreachable ``` Generated code (for now): ``` call void @__asan_handle_no_return call void @longjmp call void @__asan_handle_no_return call void @__ubsan_handle_builtin_unreachable ``` rdar://problem/40723397 Reviewers: delcypher, eugenis, vsk Differential Revision: https://reviews.llvm.org/D57278 > llvm-svn: 352690 llvm-svn: 352829
1 parent 7cc0753 commit b6c06dc

File tree

4 files changed

+57
-21
lines changed

4 files changed

+57
-21
lines changed

clang/lib/CodeGen/CGCall.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4398,10 +4398,23 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
43984398

43994399
// Strip away the noreturn attribute to better diagnose unreachable UB.
44004400
if (SanOpts.has(SanitizerKind::Unreachable)) {
4401+
// Also remove from function since CI->hasFnAttr(..) also checks attributes
4402+
// of the called function.
44014403
if (auto *F = CI->getCalledFunction())
44024404
F->removeFnAttr(llvm::Attribute::NoReturn);
44034405
CI->removeAttribute(llvm::AttributeList::FunctionIndex,
44044406
llvm::Attribute::NoReturn);
4407+
4408+
// Avoid incompatibility with ASan which relies on the `noreturn`
4409+
// attribute to insert handler calls.
4410+
if (SanOpts.has(SanitizerKind::Address)) {
4411+
SanitizerScope SanScope(this);
4412+
llvm::IRBuilder<>::InsertPointGuard IPGuard(Builder);
4413+
Builder.SetInsertPoint(CI);
4414+
auto *FnType = llvm::FunctionType::get(CGM.VoidTy, /*isVarArg=*/false);
4415+
auto *Fn = CGM.CreateRuntimeFunction(FnType, "__asan_handle_no_return");
4416+
EmitNounwindRuntimeCall(Fn);
4417+
}
44054418
}
44064419

44074420
EmitUnreachable(Loc);

clang/lib/CodeGen/CodeGenFunction.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4084,8 +4084,8 @@ class CodeGenFunction : public CodeGenTypeCache {
40844084
/// passing to a runtime sanitizer handler.
40854085
llvm::Constant *EmitCheckSourceLocation(SourceLocation Loc);
40864086

4087-
/// Create a basic block that will call a handler function in a
4088-
/// sanitizer runtime with the provided arguments, and create a conditional
4087+
/// Create a basic block that will either trap or call a handler function in
4088+
/// the UBSan runtime with the provided arguments, and create a conditional
40894089
/// branch to it.
40904090
void EmitCheck(ArrayRef<std::pair<llvm::Value *, SanitizerMask>> Checked,
40914091
SanitizerHandler Check, ArrayRef<llvm::Constant *> StaticArgs,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Ensure compatiblity of UBSan unreachable with ASan in the presence of
2+
// noreturn functions.
3+
// RUN: %clang_cc1 -fsanitize=unreachable,address -triple x86_64-linux -emit-llvm -o - %s | FileCheck %s
4+
5+
void my_longjmp(void) __attribute__((noreturn));
6+
7+
// CHECK-LABEL: define void @calls_noreturn()
8+
void calls_noreturn() {
9+
my_longjmp();
10+
// CHECK: @__asan_handle_no_return{{.*}} !nosanitize
11+
// CHECK-NEXT: @my_longjmp(){{[^#]*}}
12+
// CHECK: @__asan_handle_no_return()
13+
// CHECK-NEXT: @__ubsan_handle_builtin_unreachable{{.*}} !nosanitize
14+
// CHECK-NEXT: unreachable
15+
}
16+
17+
// CHECK: declare void @my_longjmp() [[FN_ATTR:#[0-9]+]]
18+
// CHECK: declare void @__asan_handle_no_return()
19+
20+
// CHECK-LABEL: attributes
21+
// CHECK-NOT: [[FN_ATTR]] = { {{.*noreturn.*}} }
Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,61 @@
11
// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -emit-llvm -o - %s -fsanitize=unreachable | FileCheck %s
22

3-
extern void __attribute__((noreturn)) abort();
3+
void abort() __attribute__((noreturn));
44

5-
// CHECK-LABEL: define void @_Z14calls_noreturnv
5+
// CHECK-LABEL: define void @_Z14calls_noreturnv()
66
void calls_noreturn() {
7+
// Check absence ([^#]*) of call site attributes (including noreturn)
8+
// CHECK: call void @_Z5abortv(){{[^#]*}}
79
abort();
810

9-
// Check that there are no attributes on the call site.
10-
// CHECK-NOT: call void @_Z5abortv{{.*}}#
11-
1211
// CHECK: __ubsan_handle_builtin_unreachable
1312
// CHECK: unreachable
1413
}
1514

1615
struct A {
17-
// CHECK: declare void @_Z5abortv{{.*}} [[ABORT_ATTR:#[0-9]+]]
16+
// CHECK: declare void @_Z5abortv() [[EXTERN_FN_ATTR:#[0-9]+]]
1817

1918
// CHECK-LABEL: define linkonce_odr void @_ZN1A5call1Ev
2019
void call1() {
21-
// CHECK-NOT: call void @_ZN1A16does_not_return2Ev{{.*}}#
20+
// CHECK: call void @_ZN1A16does_not_return2Ev({{.*}}){{[^#]*}}
2221
does_not_return2();
2322

2423
// CHECK: __ubsan_handle_builtin_unreachable
2524
// CHECK: unreachable
2625
}
2726

28-
// Test static members.
29-
static void __attribute__((noreturn)) does_not_return1() {
30-
// CHECK-NOT: call void @_Z5abortv{{.*}}#
27+
// Test static members. Checks are below after `struct A` scope ends.
28+
static void does_not_return1() __attribute__((noreturn)) {
3129
abort();
3230
}
3331

3432
// CHECK-LABEL: define linkonce_odr void @_ZN1A5call2Ev
3533
void call2() {
36-
// CHECK-NOT: call void @_ZN1A16does_not_return1Ev{{.*}}#
34+
// CHECK: call void @_ZN1A16does_not_return1Ev(){{[^#]*}}
3735
does_not_return1();
3836

3937
// CHECK: __ubsan_handle_builtin_unreachable
4038
// CHECK: unreachable
4139
}
4240

4341
// Test calls through pointers to non-static member functions.
44-
typedef void __attribute__((noreturn)) (A::*MemFn)();
42+
typedef void (A::*MemFn)() __attribute__((noreturn));
4543

4644
// CHECK-LABEL: define linkonce_odr void @_ZN1A5call3Ev
4745
void call3() {
4846
MemFn MF = &A::does_not_return2;
47+
// CHECK: call void %{{[0-9]+\(.*}}){{[^#]*}}
4948
(this->*MF)();
5049

51-
// CHECK-NOT: call void %{{.*}}#
5250
// CHECK: __ubsan_handle_builtin_unreachable
5351
// CHECK: unreachable
5452
}
5553

5654
// Test regular members.
5755
// CHECK-LABEL: define linkonce_odr void @_ZN1A16does_not_return2Ev({{.*}})
58-
// CHECK-SAME: [[DOES_NOT_RETURN_ATTR:#[0-9]+]]
59-
void __attribute__((noreturn)) does_not_return2() {
60-
// CHECK-NOT: call void @_Z5abortv(){{.*}}#
56+
// CHECK-SAME: [[USER_FN_ATTR:#[0-9]+]]
57+
void does_not_return2() __attribute__((noreturn)) {
58+
// CHECK: call void @_Z5abortv(){{[^#]*}}
6159
abort();
6260

6361
// CHECK: call void @__ubsan_handle_builtin_unreachable
@@ -68,7 +66,9 @@ struct A {
6866
}
6967
};
7068

71-
// CHECK: define linkonce_odr void @_ZN1A16does_not_return1Ev() [[DOES_NOT_RETURN_ATTR]]
69+
// CHECK-LABEL: define linkonce_odr void @_ZN1A16does_not_return1Ev()
70+
// CHECK-SAME: [[USER_FN_ATTR]]
71+
// CHECK: call void @_Z5abortv(){{[^#]*}}
7272

7373
void force_irgen() {
7474
A a;
@@ -77,5 +77,7 @@ void force_irgen() {
7777
a.call3();
7878
}
7979

80-
// CHECK-NOT: [[ABORT_ATTR]] = {{[^}]+}}noreturn
81-
// CHECK-NOT: [[DOES_NOT_RETURN_ATTR]] = {{[^}]+}}noreturn
80+
// `noreturn` should be removed from functions and call sites
81+
// CHECK-LABEL: attributes
82+
// CHECK-NOT: [[USER_FN_ATTR]] = { {{.*noreturn.*}} }
83+
// CHECK-NOT: [[EXTERN_FN_ATTR]] = { {{.*noreturn.*}} }

0 commit comments

Comments
 (0)