[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