[llvm-bugs] [Bug 41481] New: AddressSanitizer stack-use-after-scope false positive after r357452
via llvm-bugs
llvm-bugs at lists.llvm.org
Fri Apr 12 04:35:23 PDT 2019
https://bugs.llvm.org/show_bug.cgi?id=41481
Bug ID: 41481
Summary: AddressSanitizer stack-use-after-scope false positive
after r357452
Product: new-bugs
Version: unspecified
Hardware: PC
OS: Linux
Status: NEW
Severity: enhancement
Priority: P
Component: new bugs
Assignee: unassignedbugs at nondot.org
Reporter: hans at chromium.org
CC: htmldeveloper at gmail.com, llvm-bugs at lists.llvm.org
The underlying issue is that AddressSanitizer stack-use-after-scope doesn't
"fail safe" if it can't trace back lifetime intrinsics to an alloca.
This was discovered after r357452 caused false positives in a V8 test
(https://bugs.chromium.org/p/v8/issues/detail?id=9086) and in internal Google
testing (b/129905821).
Reduced repro:
x.cc
struct S {
~S();
};
void baz(S);
struct T {
T(int p1);
S ad();
int i;
};
int bar(int);
static T t(int i) {
return T(bar(i));
}
void foo(bool *bs, int n) {
for (int i = 0; i < n; i++) {
baz(bs[i] ? t(0).ad() : t(1).ad());
}
}
y.cc
struct S {
~S();
};
S::~S() {}
void baz(S) {}
struct T {
T(int p1);
S ad();
int i;
};
T::T(int) {}
S T::ad() { return S(); }
int bar(int x) { return x; }
extern void foo(bool *bs, int n);
int main() {
bool b;
foo(&b, 1);
return 0;
}
With Clang built at r357452:
$ clang -fsanitize=address -fsanitize-address-use-after-scope -O2
-fno-exceptions -fno-rtti /tmp/x.cc /tmp/y.cc -g && ./a.out
=================================================================
==239661==ERROR: AddressSanitizer: stack-use-after-scope on address
0x7fffc1b474f0 at pc 0x0000004edd69 bp 0x7fffc1b47490 sp 0x7fffc1b47488
WRITE of size 4 at 0x7fffc1b474f0 thread T0
#0 0x4edd68 (/work/llvm.monorepo/build.release/a.out+0x4edd68)
#1 0x4edec8 (/work/llvm.monorepo/build.release/a.out+0x4edec8)
#2 0x7f424a3b12b0 (/lib/x86_64-linux-gnu/libc.so.6+0x202b0)
#3 0x41d7d9 (/work/llvm.monorepo/build.release/a.out+0x41d7d9)
Address 0x7fffc1b474f0 is located in stack of thread T0 at offset 80 in frame
#0 0x4edb3f (/work/llvm.monorepo/build.release/a.out+0x4edb3f)
This frame has 5 object(s):
[32, 36) 'retval.i11'
[48, 52) 'retval.i'
[64, 65) 'agg.tmp'
[80, 84) 'ref.tmp' (line 19) <== Memory access at offset 80 is inside this
variable
[96, 100) 'ref.tmp1' (line 19)
HINT: this may be a false positive if your program uses some custom stack
unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope
(/work/llvm.monorepo/build.release/a.out+0x4edd68)
Shadow bytes around the buggy address:
0x100078360e40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100078360e50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100078360e60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100078360e70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100078360e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x100078360e90: 00 00 00 00 f1 f1 f1 f1 04 f2 04 f2 01 f2[f8]f2
0x100078360ea0: f8 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
0x100078360eb0: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
0x100078360ec0: 01 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
0x100078360ed0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100078360ee0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==239661==ABORTING
The IR looks like this before instrumentation:
*** IR Dump Before AddressSanitizerFunctionPass ***
; Function Attrs: nounwind sanitize_address uwtable
define dso_local void @_Z3fooPbi(i8* nocapture readonly %bs, i32 %n)
local_unnamed_addr #0 {
entry:
%retval.i11 = alloca %struct.T, align 4
%retval.i = alloca %struct.T, align 4
%agg.tmp = alloca %struct.S, align 1
%ref.tmp = alloca %struct.T, align 4
%ref.tmp1 = alloca %struct.T, align 4
%cmp14 = icmp sgt i32 %n, 0
br i1 %cmp14, label %for.body.lr.ph, label %for.cond.cleanup
for.body.lr.ph: ; preds = %entry
%0 = bitcast %struct.T* %ref.tmp to i8*
%coerce.dive.i = getelementptr inbounds %struct.T, %struct.T* %retval.i, i64
0, i32 0
%coerce.dive = getelementptr inbounds %struct.T, %struct.T* %ref.tmp, i64 0,
i32 0
%1 = bitcast %struct.T* %ref.tmp1 to i8*
%coerce.dive.i13 = getelementptr inbounds %struct.T, %struct.T* %retval.i11,
i64 0, i32 0
%coerce.dive4 = getelementptr inbounds %struct.T, %struct.T* %ref.tmp1, i64
0, i32 0
%wide.trip.count = zext i32 %n to i64
br label %for.body
for.cond.cleanup: ; preds = %for.inc, %entry
ret void
for.body: ; preds = %for.inc,
%for.body.lr.ph
%indvars.iv = phi i64 [ 0, %for.body.lr.ph ], [ %indvars.iv.next, %for.inc ]
%arrayidx = getelementptr inbounds i8, i8* %bs, i64 %indvars.iv
%2 = load i8, i8* %arrayidx, align 1, !tbaa !2, !range !6
%tobool = icmp eq i8 %2, 0
%. = select i1 %tobool, i8* %1, i8* %0
%.19.v = select i1 %tobool, %struct.T* %retval.i11, %struct.T* %retval.i
%.19 = bitcast %struct.T* %.19.v to i8*
%3 = xor i8 %2, 1
%4 = zext i8 %3 to i32
%retval.i11.retval.i = select i1 %tobool, %struct.T* %retval.i11, %struct.T*
%retval.i
%coerce.dive4.coerce.dive = select i1 %tobool, i32* %coerce.dive4, i32*
%coerce.dive
%ref.tmp1.ref.tmp = select i1 %tobool, %struct.T* %ref.tmp1, %struct.T*
%ref.tmp
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %.) #4
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %.19)
%call.i12 = call i32 @_Z3bari(i32 %4) #4
call void @_ZN1TC1Ei(%struct.T* nonnull %retval.i11.retval.i, i32 %call.i12)
#4
%coerce.dive.i13.val = load i32, i32* %coerce.dive.i13, align 4
%coerce.dive.i.val = load i32, i32* %coerce.dive.i, align 4
%5 = select i1 %tobool, i32 %coerce.dive.i13.val, i32 %coerce.dive.i.val
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %.19)
store i32 %5, i32* %coerce.dive4.coerce.dive, align 4
call void @_ZN1T2adEv(%struct.S* nonnull sret %agg.tmp, %struct.T* nonnull
%ref.tmp1.ref.tmp) #4
call void @_Z3baz1S(%struct.S* nonnull %agg.tmp) #4
call void @_ZN1SD1Ev(%struct.S* nonnull %agg.tmp) #4
br i1 %tobool, label %cleanup.action, label %cleanup.action6
cleanup.action: ; preds = %for.body
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %1) #4
br label %for.inc
cleanup.action6: ; preds = %for.body
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %0) #4
br label %for.inc
for.inc: ; preds = %cleanup.action,
%cleanup.action6
%indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
%exitcond = icmp eq i64 %indvars.iv.next, %wide.trip.count
br i1 %exitcond, label %for.cond.cleanup, label %for.body
}
The problem is that FunctionStackPoisoner::visitIntrinsicInst() is able to
trace the lifetime.end intrinsics back to the %ref.tmp and %ref.tmp1 allocas,
but it is unable to trace the lifetime.start intrinsic back, because it looks
like this:
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %.)
since r357452 has caused us to sink two separate lifetime.start intrinsics into
one with a select instruction as argument.
The code that handles this, in FunctionStackPoisoner::findAllocaForValue()
emits a "Alloca search canceled on unknown instruction" message in debug mode,
so it's aware of the situation but doesn't handle it.
I think the asan instrumentation needs to handle the situation when it can't
trace all the lifetime intrinsics. Perhaps when an alloca has a known
lifetime.end intrinsics but unknown lifetime.start it should be assumed to be
in-scope at the start of the function.
--
You are receiving this mail because:
You are on the CC list for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-bugs/attachments/20190412/b3e38570/attachment-0001.html>
More information about the llvm-bugs
mailing list