Skip to content

Commit 90e0933

Browse files
[ASan] Do not instrument catch block parameters on Windows (#159618)
**Mitigation for:** google/sanitizers#749 **Disclosure:** I'm not an ASan compiler expert yet (I'm trying to learn!), I primarily work in the runtime. Some of this PR was developed with the help of AI tools (primarily as a "fuzzy `grep` engine"), but I've manually refined and tested the output, and can speak for every line. In general, I used it only to orient myself and for "rubberducking". **Context:** The msvc ASan team (👋 ) has received an internal request to improve clang's exception handling under ASan for Windows. Namely, we're interested in **mitigating** this bug: google/sanitizers#749 To summarize, today, clang + ASan produces a false-positive error for this program: ```C++ #include <cstdio> #include <exception> int main() { try { throw std::exception("test"); }catch (const std::exception& ex){ puts(ex.what()); } return 0; } ``` The error reads as such: ``` C:\Users\dajusto\source\repros\upstream>type main.cpp #include <cstdio> #include <exception> int main() { try { throw std::exception("test"); }catch (const std::exception& ex){ puts(ex.what()); } return 0; } C:\Users\dajusto\source\repros\upstream>"C:\Users\dajusto\source\repos\llvm-project\build.runtimes\bin\clang.exe" -fsanitize=address -g -O0 main.cpp C:\Users\dajusto\source\repros\upstream>a.exe ================================================================= ==19112==ERROR: AddressSanitizer: access-violation on unknown address 0x000000000000 (pc 0x7ff72c7c11d9 bp 0x0080000ff960 sp 0x0080000fcf50 T0) ==19112==The signal is caused by a READ memory access. ==19112==Hint: address points to the zero page. #0 0x7ff72c7c11d8 in main C:\Users\dajusto\source\repros\upstream\main.cpp:8 #1 0x7ff72c7d479f in _CallSettingFrame C:\repos\msvc\src\vctools\crt\vcruntime\src\eh\amd64\handlers.asm:49 #2 0x7ff72c7c8944 in __FrameHandler3::CxxCallCatchBlock(struct _EXCEPTION_RECORD *) C:\repos\msvc\src\vctools\crt\vcruntime\src\eh\frame.cpp:1567 #3 0x7ffb4a90e3e5 (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18012e3e5) #4 0x7ff72c7c1128 in main C:\Users\dajusto\source\repros\upstream\main.cpp:6 #5 0x7ff72c7c33db in invoke_main C:\repos\msvc\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78 #6 0x7ff72c7c33db in __scrt_common_main_seh C:\repos\msvc\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288 #7 0x7ffb49b05c06 (C:\WINDOWS\System32\KERNEL32.DLL+0x180035c06) #8 0x7ffb4a8455ef (C:\WINDOWS\SYSTEM32\ntdll.dll+0x1800655ef) ==19112==Register values: rax = 0 rbx = 80000ff8e0 rcx = 27d76d00000 rdx = 80000ff8e0 rdi = 80000fdd50 rsi = 80000ff6a0 rbp = 80000ff960 rsp = 80000fcf50 r8 = 100 r9 = 19930520 r10 = 8000503a90 r11 = 80000fd540 r12 = 80000fd020 r13 = 0 r14 = 80000fdeb8 r15 = 0 AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: access-violation C:\Users\dajusto\source\repros\upstream\main.cpp:8 in main ==19112==ABORTING ``` The root of the issue _appears to be_ that ASan's instrumentation is incompatible with Window's assumptions for instantiating `catch`-block's parameters (`ex` in the snippet above). The nitty gritty details are lost on me, but I understand that to make this work without loss of ASan coverage, a "serious" refactoring is needed. In the meantime, users risk false positive errors when pairing ASan + catch-block parameters on Windows. **To mitigate this** I think we should avoid instrumenting catch-block parameters on Windows. It appears to me this is as "simple" as marking catch block parameters as "uninteresting" in `AddressSanitizer::isInterestingAlloca`. My manual tests seem to confirm this. I believe this is strictly better than today's status quo, where the runtime generates false positives. Although we're now explicitly choosing to instrument less, the benefit is that now more programs can run with ASan without _funky_ macros that disable ASan on exception blocks. **This PR:** implements the mitigation above, and creates a simple new test for it. _Thanks!_ --------- Co-authored-by: Antonio Frighetto <[email protected]>
1 parent 7a73a8b commit 90e0933

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// RUN: %clangxx_asan %s -o %t
2+
// RUN: %run %t | FileCheck %s
3+
4+
// This test tests that declaring a parameter in a catch-block does not produce a false positive
5+
// ASan error on Windows.
6+
7+
// This code is based on the repro in https://github.com/google/sanitizers/issues/749
8+
#include <cstdio>
9+
#include <exception>
10+
11+
void throwInFunction() { throw std::exception("test2"); }
12+
13+
int main() {
14+
// case 1: direct throw
15+
try {
16+
throw std::exception("test1");
17+
} catch (const std::exception &ex) {
18+
puts(ex.what());
19+
// CHECK: test1
20+
}
21+
22+
// case 2: throw in function
23+
try {
24+
throwInFunction();
25+
} catch (const std::exception &ex) {
26+
puts(ex.what());
27+
// CHECK: test2
28+
}
29+
30+
printf("Success!\n");
31+
// CHECK: Success!
32+
return 0;
33+
}

llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,7 @@ struct AddressSanitizer {
844844
bool maybeInsertAsanInitAtFunctionEntry(Function &F);
845845
bool maybeInsertDynamicShadowAtFunctionEntry(Function &F);
846846
void markEscapedLocalAllocas(Function &F);
847+
void markCatchParametersAsUninteresting(Function &F);
847848

848849
private:
849850
friend struct FunctionStackPoisoner;
@@ -2997,6 +2998,22 @@ void AddressSanitizer::markEscapedLocalAllocas(Function &F) {
29972998
}
29982999
}
29993000
}
3001+
// Mitigation for https://github.com/google/sanitizers/issues/749
3002+
// We don't instrument Windows catch-block parameters to avoid
3003+
// interfering with exception handling assumptions.
3004+
void AddressSanitizer::markCatchParametersAsUninteresting(Function &F) {
3005+
for (BasicBlock &BB : F) {
3006+
for (Instruction &I : BB) {
3007+
if (auto *CatchPad = dyn_cast<CatchPadInst>(&I)) {
3008+
// Mark the parameters to a catch-block as uninteresting to avoid
3009+
// instrumenting them.
3010+
for (Value *Operand : CatchPad->arg_operands())
3011+
if (auto *AI = dyn_cast<AllocaInst>(Operand))
3012+
ProcessedAllocas[AI] = false;
3013+
}
3014+
}
3015+
}
3016+
}
30003017

30013018
bool AddressSanitizer::suppressInstrumentationSiteForDebug(int &Instrumented) {
30023019
bool ShouldInstrument =
@@ -3041,6 +3058,9 @@ bool AddressSanitizer::instrumentFunction(Function &F,
30413058
// can be passed to that intrinsic.
30423059
markEscapedLocalAllocas(F);
30433060

3061+
if (TargetTriple.isOSWindows())
3062+
markCatchParametersAsUninteresting(F);
3063+
30443064
// We want to instrument every address only once per basic block (unless there
30453065
// are calls between uses).
30463066
SmallPtrSet<Value *, 16> TempsToInstrument;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
; RUN: opt < %s -passes=asan -S | FileCheck %s
2+
; CHECK: %ex = alloca i32, align 4
3+
; CHECK: catchpad within %{{.*}} [ptr @"??_R0H@8", i32 0, ptr %ex]
4+
5+
; This test ensures that catch parameters are not instrumented on Windows.
6+
7+
; This file was generated using the following source
8+
;
9+
; ```C++
10+
; #include <exception>
11+
; #include <cstdio>
12+
;
13+
; int main() {
14+
; try {
15+
; throw 1;
16+
; } catch (const int ex) {
17+
; printf("%d\n", ex);
18+
; return -1;
19+
; }
20+
; return 0;
21+
; }
22+
;
23+
; ```
24+
; then running the following sequence of commands
25+
;
26+
; ```
27+
; clang.exe -g0 -O0 -emit-llvm -c main.cpp -o main.bc
28+
; llvm-extract.exe -func=main main.bc -o main_func.bc
29+
; llvm-dis.exe main_func.bc -o main_func_dis.ll
30+
; ```
31+
; and finally manually trimming the resulting `.ll` file to remove
32+
; unnecessary metadata, and manually adding the `sanitize_address` annotation;
33+
; needed for the ASan pass to run.
34+
35+
target triple = "x86_64-pc-windows-msvc"
36+
37+
@"??_R0H@8" = external global ptr
38+
39+
; Function Attrs: sanitize_address
40+
define i32 @main() sanitize_address personality ptr @__CxxFrameHandler3 {
41+
entry:
42+
%ex = alloca i32, align 4
43+
invoke void @throw()
44+
to label %unreachable unwind label %catch.dispatch
45+
46+
catch.dispatch: ; preds = %entry
47+
%0 = catchswitch within none [label %catch] unwind to caller
48+
49+
catch: ; preds = %catch.dispatch
50+
%1 = catchpad within %0 [ptr @"??_R0H@8", i32 0, ptr %ex]
51+
call void @opaque() [ "funclet"(token %1) ]
52+
catchret from %1 to label %return
53+
54+
return: ; preds = %catch
55+
ret i32 0
56+
57+
unreachable: ; preds = %entry
58+
unreachable
59+
}
60+
61+
declare void @throw() noreturn
62+
declare void @opaque()
63+
declare i32 @__CxxFrameHandler3(...)

0 commit comments

Comments
 (0)