ramosian-glider / sanitizer-issues

test
0 stars 0 forks source link

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

Open ramosian-glider opened 9 years ago

ramosian-glider commented 9 years ago

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 commented 9 years ago
See also: http://permalink.gmane.org/gmane.comp.compilers.llvm.cvs/120712

Reported by konstantin.s.serebryany on 2012-09-05 06:45:24

ramosian-glider commented 9 years ago
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

ramosian-glider commented 9 years ago
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

ramosian-glider commented 9 years ago

Reported by ramosian.glider on 2015-07-30 09:05:30

ramosian-glider commented 9 years ago
Adding Project:AddressSanitizer as part of GitHub migration.

Reported by ramosian.glider on 2015-07-30 09:06:54