Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use @llvm.lifetime.start/end for more precise stack use-after-scope detection #83

Open
ramosian-glider opened this issue Aug 31, 2015 · 2 comments

Comments

@ramosian-glider
Copy link
Member

@ramosian-glider ramosian-glider commented Aug 31, 2015

Originally reported on Google Code with ID 83

LLVM has a pair of intrinsics @llvm.lifetime.start/end
that indicate the lifetime of a memory object including stack objects. 

We could poison the object at function entry, 
unpoison it on lifetime.start and poison it again on lifetime.end. 
Need to be careful with performance costs. 

Currently, @llvm.lifetime is emitted by LLVM inliner, but not by clang.
Clang part will have to be implemented for better coverage. 

Example of @llvm.lifetime.start/end generated by the LLVM inliner: 

void foo(int *a);
void bar() {
  int aaa[10];
  foo(aaa);
}

void rab() {
  int bbb[20];
  foo(bbb);
}

void zoo(int x) {
  if (x > 10) bar();
  if (x < 20) rab();
}

% clang++ -S -o - -emit-llvm -O2 scope.cc

define void @_Z3zooi(i32 %x) uwtable {
entry:
  %bbb.i = alloca [20 x i32], align 16          <<<<<<<<<<<<<<<<<<<<<<<
  %aaa.i = alloca [10 x i32], align 16          <<<<<<<<<<<<<<<<<<<<<<<
  %cmp = icmp sgt i32 %x, 10
  br i1 %cmp, label %if.end, label %if.then2

if.end:                                           ; preds = %entry
  %0 = bitcast [10 x i32]* %aaa.i to i8*
  call void @llvm.lifetime.start(i64 -1, i8* %0)           <<<<<<<<<<<<<<<<<<<<<<<
  %arraydecay.i = getelementptr inbounds [10 x i32]* %aaa.i, i64 0, i64 0
  call void @_Z3fooPi(i32* %arraydecay.i)
  call void @llvm.lifetime.end(i64 -1, i8* %0)             <<<<<<<<<<<<<<<<<<<<<<<
  %cmp1 = icmp slt i32 %x, 20
  br i1 %cmp1, label %if.then2, label %if.end3

if.then2:                                         ; preds = %entry, %if.end
  %1 = bitcast [20 x i32]* %bbb.i to i8*
  call void @llvm.lifetime.start(i64 -1, i8* %1)           <<<<<<<<<<<<<<<<<<<<<<<
  %arraydecay.i4 = getelementptr inbounds [20 x i32]* %bbb.i, i64 0, i64 0
  call void @_Z3fooPi(i32* %arraydecay.i4)                 <<<<<<<<<<<<<<<<<<<<<<<
  call void @llvm.lifetime.end(i64 -1, i8* %1)
  br label %if.end3

Reported by konstantin.s.serebryany on 2012-06-25 05:27:39

@ramosian-glider
Copy link
Member Author

@ramosian-glider ramosian-glider commented Aug 31, 2015

With a little bit of hackery I can see smth like this:
$ cat b.cc 
int main() {
  int *p = 0;
  {
    int x = 0;
    p = &x;
  }
  return *p;
}
$ ./clang++ b.cc
$ ./a.out
$ ./clang++ b.cc -fsanitize=address -mllvm -asan-use-lifetime=1
$ ./a.out
=================================================================
==29643== ERROR: AddressSanitizer: use-after-poison on address 0x7fff861f4f80 at pc
0x403ed6 bp 0x7fff861f4e50 sp 0x7fff861f4e48
READ of size 4 at 0x7fff861f4f80 thread T0
    #0 0x403ed5 (/usr/local/google/llvm_cmake_clang/tmp/lifetime/a.out+0x403ed5)
    #1 0x7f7a40ec7c4c (/lib/libc-2.11.1.so+0x1ec4c)
Address 0x7fff861f4f80 is located at offset 160 in frame <main> of T0's stack:
  This frame has 4 object(s):
    [32, 36) ''
    [96, 104) 'p'
    [160, 164) 'x'
    [224, 228) ''
HINT: this may be a false positive if your program uses some custom stack unwind mechanism
      (longjmp and C++ exceptions *are* supported)
Shadow byte and word:
  0x1ffff0c3e9f0: f7
  0x1ffff0c3e9f0: f7 f4 f4 f4 f2 f2 f2 f2
More shadow bytes:
  0x1ffff0c3e9d0: 00 00 00 00 00 00 00 00
  0x1ffff0c3e9d8: 00 00 00 00 f1 f1 f1 f1
  0x1ffff0c3e9e0: 04 f4 f4 f4 f2 f2 f2 f2
  0x1ffff0c3e9e8: 00 f4 f4 f4 f2 f2 f2 f2
=>0x1ffff0c3e9f0: f7 f4 f4 f4 f2 f2 f2 f2
  0x1ffff0c3e9f8: 04 f4 f4 f4 f3 f3 f3 f3
  0x1ffff0c3ea00: 00 00 00 00 00 00 00 00
  0x1ffff0c3ea08: 00 00 00 00 00 00 00 00
  0x1ffff0c3ea10: 00 00 00 00 00 00 00 00
Stats: 0M malloced (0M for red zones) by 0 calls
Stats: 0M realloced by 0 calls
Stats: 0M freed by 0 calls
Stats: 0M really freed by 0 calls
Stats: 0M (0 full pages) mmaped in 0 calls
  mmaps   by size class: 
  mallocs by size class: 
  frees   by size class: 
  rfrees  by size class: 
Stats: malloc large: 0 small slow: 0
==29643== ABORTING

Certainly, I see no crash with -O1/-O2 :)

We need:
  1) make Clang emit llvm.lifetime.start intrinsic for a local variable declaration
(insert it between alloca for the variable and its initialization).
  2) make Clang emit llvm.lifetime.end intrinsic when variable goes out of scope.

Changes (1)-(2) seem to be pretty intrusive. We may enable them only for use-after-scope
sanitizer for now (expose this "sanitizer" as an optional part of -fsanitize=address,
support it in Clang driver etc).

  3) properly handle lifetime intrinsics in ASan pass. As a starting point, we may
just emit calls (or do equivalent of) __asan_poison_memory_region() for llvm.lifetime.end
and __asan_unpoison_memory_region() for llvm.lifetime.start AND before every return
instruction. There is much to do here to reduce redundant operations.

Reported by samsonov@google.com on 2012-11-22 14:55:04

  • Status changed: Started
@ramosian-glider
Copy link
Member Author

@ramosian-glider ramosian-glider commented Aug 31, 2015

Note: you can work on the asan part in parallel with the clang part: just use the tests
with inlining as in the original bug report. 

Can we also add -fsanitize=use-after-scope and make the reports look like 
ERROR: AddressSanitizer: use-after-scope
? 

Reported by konstantin.s.serebryany on 2012-11-22 17:46:53

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants