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

LeakSanitizer, ThreadSanitizer, AddressSanitizer and MemorySanitizer support #38699

Merged
merged 8 commits into from
Feb 9, 2017

Conversation

japaric
Copy link
Member

@japaric japaric commented Dec 30, 2016

Examples

LeakSanitizer

$ cargo new --bin leak && cd $_

$ edit Cargo.toml && tail -n3 $_
# LeakSanitizer doesn't work reliably with opt-level=0
[profile.dev]
opt-level = 1
$ edit src/main.rs && cat $_
use std::mem;

fn main() {
    let xs = vec![0, 1, 2, 3];
    mem::forget(xs);
}
$ RUSTFLAGS="-Z sanitizer=leak" cargo run --target x86_64-unknown-linux-gnu; echo $?
    Finished dev [optimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/leak`

=================================================================
==10848==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 16 byte(s) in 1 object(s) allocated from:
    #0 0x557c3488db1f in __interceptor_malloc /shared/rust/checkouts/lsan/src/compiler-rt/lib/lsan/lsan_interceptors.cc:55
    #1 0x557c34888aaa in alloc::heap::exchange_malloc::h68f3f8b376a0da42 /shared/rust/checkouts/lsan/src/liballoc/heap.rs:138
    #2 0x557c34888afc in leak::main::hc56ab767de6d653a $PWD/src/main.rs:4
    #3 0x557c348c0806 in __rust_maybe_catch_panic ($PWD/target/debug/leak+0x3d806)

SUMMARY: LeakSanitizer: 16 byte(s) leaked in 1 allocation(s).
23

ThreadSanitizer

$ cargo new --bin racy && cd $_

$ edit src/main.rs && cat $_
use std::thread;

static mut ANSWER: i32 = 0;

fn main() {
    let t1 = thread::spawn(|| unsafe { ANSWER = 42 });
    unsafe {
        ANSWER = 24;
    }
    t1.join().ok();
}
$ RUSTFLAGS="-Z sanitizer=thread" cargo run --target x86_64-unknown-linux-gnu; echo $?
==================
WARNING: ThreadSanitizer: data race (pid=12019)
  Write of size 4 at 0x562105989bb4 by thread T1:
    #0 racy::main::_$u7b$$u7b$closure$u7d$$u7d$::hbe13ea9e8ac73f7e $PWD/src/main.rs:6 (racy+0x000000010e3f)
    #1 _$LT$std..panic..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once::h2e466a92accacc78 /shared/rust/checkouts/lsan/src/libstd/panic.rs:296 (racy+0x000000010cc5)
    #2 std::panicking::try::do_call::h7f4d2b38069e4042 /shared/rust/checkouts/lsan/src/libstd/panicking.rs:460 (racy+0x00000000c8f2)
    #3 __rust_maybe_catch_panic <null> (racy+0x0000000b4e56)
    #4 std::panic::catch_unwind::h31ca45621ad66d5a /shared/rust/checkouts/lsan/src/libstd/panic.rs:361 (racy+0x00000000b517)
    #5 std::thread::Builder::spawn::_$u7b$$u7b$closure$u7d$$u7d$::hccfc37175dea0b01 /shared/rust/checkouts/lsan/src/libstd/thread/mod.rs:357 (racy+0x00000000c226)
    #6 _$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::hd880bbf91561e033 /shared/rust/checkouts/lsan/src/liballoc/boxed.rs:605 (racy+0x00000000f27e)
    #7 std::sys::imp::thread::Thread::new::thread_start::hebdfc4b3d17afc85 <null> (racy+0x0000000abd40)

  Previous write of size 4 at 0x562105989bb4 by main thread:
    #0 racy::main::h23e6e5ca46d085c3 $PWD/src/main.rs:8 (racy+0x000000010d7c)
    #1 __rust_maybe_catch_panic <null> (racy+0x0000000b4e56)
    #2 __libc_start_main <null> (libc.so.6+0x000000020290)

  Location is global 'racy::ANSWER::h543d2b139f819b19' of size 4 at 0x562105989bb4 (racy+0x0000002f8bb4)

  Thread T1 (tid=12028, running) created by main thread at:
    #0 pthread_create /shared/rust/checkouts/lsan/src/compiler-rt/lib/tsan/rtl/tsan_interceptors.cc:902 (racy+0x00000001aedb)
    #1 std::sys::imp::thread::Thread::new::hce44187bf4a36222 <null> (racy+0x0000000ab9ae)
    #2 std::thread::spawn::he382608373eb667e /shared/rust/checkouts/lsan/src/libstd/thread/mod.rs:412 (racy+0x00000000b5aa)
    #3 racy::main::h23e6e5ca46d085c3 $PWD/src/main.rs:6 (racy+0x000000010d5c)
    #4 __rust_maybe_catch_panic <null> (racy+0x0000000b4e56)
    #5 __libc_start_main <null> (libc.so.6+0x000000020290)

SUMMARY: ThreadSanitizer: data race $PWD/src/main.rs:6 in racy::main::_$u7b$$u7b$closure$u7d$$u7d$::hbe13ea9e8ac73f7e
==================
ThreadSanitizer: reported 1 warnings
66

AddressSanitizer

$ cargo new --bin oob && cd $_

$ edit src/main.rs && cat $_
fn main() {
    let xs = [0, 1, 2, 3];
    let y = unsafe { *xs.as_ptr().offset(4) };
}
$ RUSTFLAGS="-Z sanitizer=address" cargo run --target x86_64-unknown-linux-gnu; echo $?
=================================================================
==13328==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff29f3ecd0 at pc 0x55802dc6bf7e bp 0x7fff29f3ec90 sp 0x7fff29f3ec88
READ of size 4 at 0x7fff29f3ecd0 thread T0
    #0 0x55802dc6bf7d in oob::main::h0adc7b67e5feb2e7 $PWD/src/main.rs:3
    #1 0x55802dd60426 in __rust_maybe_catch_panic ($PWD/target/debug/oob+0xfe426)
    #2 0x55802dd58dd9 in std::rt::lang_start::hb2951fc8a59d62a7 ($PWD/target/debug/oob+0xf6dd9)
    #3 0x55802dc6c002 in main ($PWD/target/debug/oob+0xa002)
    #4 0x7fad8c3b3290 in __libc_start_main (/usr/lib/libc.so.6+0x20290)
    #5 0x55802dc6b719 in _start ($PWD/target/debug/oob+0x9719)

Address 0x7fff29f3ecd0 is located in stack of thread T0 at offset 48 in frame
    #0 0x55802dc6bd5f in oob::main::h0adc7b67e5feb2e7 $PWD/src/main.rs:1

  This frame has 1 object(s):
    [32, 48) 'xs' <== Memory access at offset 48 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow $PWD/src/main.rs:3 in oob::main::h0adc7b67e5feb2e7
Shadow bytes around the buggy address:
  0x1000653dfd40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfd50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfd60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfd70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x1000653dfd90: 00 00 00 00 f1 f1 f1 f1 00 00[f3]f3 00 00 00 00
  0x1000653dfda0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfdb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfdc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfdd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfde0: 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
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==13328==ABORTING
1

MemorySanitizer

$ cargo new --bin uninit && cd $_

$ edit src/main.rs && cat $_
use std::mem;

fn main() {
    let xs: [u8; 4] = unsafe { mem::uninitialized() };
    let y = xs[0] + xs[1];
}
$ RUSTFLAGS="-Z sanitizer=memory" cargo run; echo $?
==30198==WARNING: MemorySanitizer: use-of-uninitialized-value
    #0 0x563f4b6867da in uninit::main::hc2731cd4f2ed48f8 $PWD/src/main.rs:5
    #1 0x563f4b7033b6 in __rust_maybe_catch_panic ($PWD/target/debug/uninit+0x873b6)
    #2 0x563f4b6fbd69 in std::rt::lang_start::hb2951fc8a59d62a7 ($PWD/target/debug/uninit+0x7fd69)
    #3 0x563f4b6868a9 in main ($PWD/target/debug/uninit+0xa8a9)
    #4 0x7fe844354290 in __libc_start_main (/usr/lib/libc.so.6+0x20290)
    #5 0x563f4b6864f9 in _start ($PWD/target/debug/uninit+0xa4f9)

SUMMARY: MemorySanitizer: use-of-uninitialized-value $PWD/src/main.rs:5 in uninit::main::hc2731cd4f2ed48f8
Exiting
77

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @nikomatsakis (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see the contribution instructions for more information.

@japaric
Copy link
Member Author

japaric commented Dec 30, 2016

This is WIP revival of #31605. Right now, this only contains the Leak Sanitizer
as I'm getting familiar with the previous implementation and with sanitizers, in
general.

I'm bringing this back to life because one concern brought up in the original PR
was that std had to be recompiled with some extra LLVM instrumentation pass to
be able to use the sanitizers but such mechanism didn't exist.
Today, Xargo can recompile std (and all
its dependencies) with custom compilation flags so it could make sanitizer
support feasible.

Comments

  • "LeakSanitizer is only supported on x86_64 Linux."

  • The above example doesn't always work, as in it sometimes doesn't detect the
    memory leak. And by sometimes, I mean that out of 10_000 runs it doesn't
    detect the leak 4_991 times. I don't know if this expected or not, or who's
    to blame for this.

  • Unlike [WIP] Sanitizers support. #31605, the leak sanitizer runtime has been packaged into a crate,
    rustc_lsan, and is built using Cargo instead of using bootstrap. Also, the
    rustc_lsan crate forces the executable to use the alloc_system allocator
    because it was reported back in [WIP] Sanitizers support. #31605 that the sanitizers don't work well
    with jemalloc.

  • Building the sanitizer runtimes uses cmake (the runtimes are part of
    the compiler-rt project). As a result, rustc_lsan builds on Arch Linux but
    not on Ubuntu :-/. This is because Ubuntu's llvm-dev package installs some
    cmakefiles in a directory different from where compiler-rt cmake build system
    expects them.

  • The leak sanitizer does NOT require (re)compiling stuff with an extra
    LLVM instrumentation pass; it only requires linking the runtime. The other
    sanitizers do require the extra LLVM pass though.

  • As you see in the example, the sanitizer runtime is explicitly linked via
    extern crate rustc_lsan. I think it would make more sense to implicitly link
    it using -C sanitize=leak like the original PR did. -C sanitize=foo seems
    like it would make more compatible with the "build this crate with an extra
    LLVM pass" requirement that the other sanitizers have.

Concerns

  • If we continue using compiler-rt's build system (cmake), the one crate per
    sanitizer approach will result in building N runtimes N times because, AFAICT,
    compiler-rt build system provides no way to build a single runtime; it always
    builds all of them. Unless there some way to have many crates share the output
    directory of a single build script (workspaces?)

TODO

  • Does this work with LTO? Yes

  • Does this work with the test runner (#[test])? Yes

  • Can we build the sanitizers (C code) manually without relying on cmake?

cc @alexcrichton @brson @tmiasko

@@ -23,6 +23,9 @@ compiler_builtins = { path = "../libcompiler_builtins" }
std_unicode = { path = "../libstd_unicode" }
unwind = { path = "../libunwind" }

[target.x86_64-unknown-linux-gnu.dependencies]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@japaric japaric Dec 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also a hack to place the rustc_lsan crate in the sysroot. (because std doesn't actually depend on rustc_lsan)

@@ -0,0 +1,6 @@
#![cfg_attr(not(stage0), feature(sanitizer_runtime))]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose this crate should also contain the -lpthread -lrt -lm linker flags that runtimes supossedly depend on. But I'm not sure what runtime depends on what library.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh that's ok b/c they're all pulled in through libstd anyway

@bors
Copy link
Contributor

bors commented Dec 30, 2016

☔ The latest upstream changes (presumably #38697) made this pull request unmergeable. Please resolve the merge conflicts.

@nikomatsakis
Copy link
Contributor

@japaric I'm excited to hear that xargo can compile std with custom flags. However, I feel like there is an RFC wanting to be written here. But I'm not quite sure what it is. =)

At minimum, surely a feature gate of some kind would be required.

@alexcrichton
Copy link
Member

Neat!

How come we need to specially recognize the asan crate? Does it need to go in a special place on the linker command line?

I'm not really sure what the best way to expose this will be long-term. For now, though, having it be optional and behind a feature gate seems ok.

@japaric
Copy link
Member Author

japaric commented Dec 31, 2016

How come we need to specially recognize the asan crate? Does it need to go in a special place on the linker command line?

(a) It needs to be linked using -Wl,--whole-archive and (b) it provides malloc et al. symbols that must override the real ones (to intercept them) so it needs to be linked before the object/crate/library that provides the real e.g. malloc implementation.

@alexcrichton
Copy link
Member

I think the ordering is guaranteed via extern crate alloc_system that it shows up before that crate, but it could show up on the far left which means the symbols wouldn't get pulled in presumably as there'd be no references to them (this is what -Wl,--whole-archive fixes).

@japaric
Copy link
Member Author

japaric commented Dec 31, 2016

@alexcrichton That is, indeed, the case. It doesn't work without -Wl,--whole-archive but rustc_lsan does appear on the "far left" so at least I can remove that part of the logic.

@sanxiyn
Copy link
Member

sanxiyn commented Jan 1, 2017

Travis failure seems legit to me: "llvm-config not found".

@japaric
Copy link
Member Author

japaric commented Jan 5, 2017

OK. Added ThreadSanitizer support and added an unstable -Z sanitizer flag to
enable either sanitizer.

Using LeakSanitizer is as simple as cargo rustc -- -Z sanitizer=leak && target/debug/app and using ThreadSanitizer is as simple as RUSTFLAGS="-Z sanitizer=thread" xargo run (*). See the PR description for actual examples.

(*) Actually, first you have patch src/libstd to workaround
#36501. Here's the patch

--- a/src/libstd/Cargo.toml
+++ b/src/libstd/Cargo.toml
@@ -7,7 +7,6 @@ build = "build.rs"
 [lib]
 name = "std"
 path = "lib.rs"
-crate-type = ["dylib", "rlib"]

 [dependencies]
 alloc = { path = "../liballoc" }

Some comments:

LeakSanitizer only requires linking a runtime so, IMO, it makes sense to
distribute librustc_lsan with the std component of the
x86_64-unknown-linux-gnu target, which is the only target supported by lsan.
That's how the rustbuild is configured right now.

ThreadSanitizer requires recompiling the std facade with an extra LLVM pass
and linking to the tsan runtime. The former requirement is taken care of by
Xargo. But, because Xargo overrides the default sysroot, rustc_tsan must be
available in that sysroot. What I'm doing now is compiling rustc_tsan as
part of Xargo's sysroot. It would be better if one could just use a rustc_tsan
that's distributed as part of the std component though as compiling the
runtime requires having LLVM installed in the host system and the runtime
doesn't compile on Ubuntu because of how it has laid out its LLVM installation.

rust-lang/compiler-rt needs to be patched to make tsan work with PIE
executables. I'll send the patch (it's a backport) in a bit.

@japaric japaric changed the title leak sanitizer support LeakSanitizer and ThreadSanitizer support Jan 5, 2017
@@ -432,3 +432,4 @@ impl Drop for OperandBundleDef {
mod llvmdeps {
include! { env!("CFG_LLVM_LINKAGE_FILE") }
}
#[link(name = "ffi")] extern {}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO remove. (Sometimes I forget to not commit this -- workaround for #34486)

@japaric
Copy link
Member Author

japaric commented Jan 5, 2017

Pushed AddressSanitizer support and added an example to the PR description.

Interestingly, an empty main functions trips up asan's "ODR violation" check:

$ cat src/main.rs
fn main() {}

$ RUSTFLAGS="-Z sanitizer=address" xargo run
   Compiling oob v0.1.0 (file:///home/japaric/tmp/oob)
    Finished debug [unoptimized + debuginfo] target(s) in 0.53 secs
     Running `target/debug/oob`
=================================================================
==5051==ERROR: AddressSanitizer: odr-violation (0x5579099b23a0):
  [1] size=0 'ref.1O' core.cgu-0.rs
  [2] size=0 'ref.19' std.cgu-0.rs
These globals were registered at these points:
  [1]:
    #0 0x5579098c66e0 in __asan_register_globals.part.10 /shared/rust/checkouts/lsan/src/compiler-rt/lib/asan/asan_globals.cc:309
    #1 0x5579099b0dfb in asan.module_ctor (/home/japaric/tmp/oob/target/debug/oob+0x188dfb)

  [2]:
    #0 0x5579098c66e0 in __asan_register_globals.part.10 /shared/rust/checkouts/lsan/src/compiler-rt/lib/asan/asan_globals.cc:309
    #1 0x5579098a8b5b in asan.module_ctor (/home/japaric/tmp/oob/target/debug/oob+0x80b5b)

==5051==HINT: if you don't care about these errors you may set ASAN_OPTIONS=detect_odr_violation=0
SUMMARY: AddressSanitizer: odr-violation: global 'ref.1O' at core.cgu-0.rs
==5051==ABORTING

@japaric japaric changed the title LeakSanitizer and ThreadSanitizer support LeakSanitizer, ThreadSanitizer and AddressSanitizer support Jan 5, 2017
@nikomatsakis
Copy link
Contributor

@japaric

travis says:

-- Configuring incomplete, errors occurred!

See also "/checkout/obj/build/x86_64-unknown-linux-gnu/stage0-std/x86_64-unknown-linux-gnu/release/build/rustc_lsan-809bddb3df858e87/out/build/CMakeFiles/CMakeOutput.log".

--- stderr

CMake Error at CMakeLists.txt:46 (message):

  llvm-config not found: specify LLVM_CONFIG_PATH

not sure what's up w/ that

@japaric
Copy link
Member Author

japaric commented Jan 5, 2017

@nikomatsakis

Quoting myself

the runtime doesn't compile on Ubuntu because of how it has laid out its LLVM installation.

Travis is testing on Ubuntu. We should build rustc_lsan against the LLVM that rustbuild builds instead of against system LLVM but that's more tricky.

MemorySanitizer is now working (example in the PR description) but requires the latest compiler-rt release. We could backport some stuff to make our fork work (but we would have to first figure out what needs to be backported) or we could simply upgrade our fork to something more recent (easier).

@japaric japaric changed the title LeakSanitizer, ThreadSanitizer and AddressSanitizer support LeakSanitizer, ThreadSanitizer, AddressSanitizer and MemorySanitizer support Jan 5, 2017
@nikomatsakis
Copy link
Contributor

So, @alexcrichton -- I feel more-or-less about experimenting in this direction without an RFC, but it seems like we should have some docs. Maybe a tracking issue? I feel like I would not want this interface to stabilize without an RFC. Not sure what team has jurisdiction here, probably @rust-lang/compiler or @rust-lang/tools.

@alexcrichton
Copy link
Member

Yeah I'm fine experimenting here as well. I want to make sure that everything is thoroughly feature gated, but beyond that it seems worthwhile to enable supporting this at all

@alexcrichton
Copy link
Member

I wonder though if we can experiment here with less of this support in-tree. @japaric do you have an idea of how we might minimize basically the size of this PR? Ideally we wouldn't maintain all of this in tree because development would be slowed down, and hopefully one day these crates can all be on crates.io anyway.

I'm slightly hesitant here as this would be one of the first "linux only" features we support in-tree, so if we can keep it external that would be nice.

// We must link the sanitizer runtime using -Wl,--whole-archive but since
// it's packed in a .rlib, it contains stuff that are not objects that will
// make the linker error. So we must remove those bits from the .rlib before
// linking it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does it need --whole-archive?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the dependency graph (the sanitizer runtime gets injected as a direct dependency of the "top" crate) the runtime rlib ends up being one of the first linker arguments. Because linkers are lazy, if we don't use whole-archive, the linker will end up dropping most / all of the symbols in it (because the preceding linker argument didn't directly need the symbols). whole-archive prevents that; it forces the linker to keep all the symbols around as it keeps looking at the next linker arguments.

@japaric
Copy link
Member Author

japaric commented Jan 5, 2017

@alexcrichton Initial testing with rustc_lsan shows that the sanitizer runtimes can live out of tree. I'll update the PR with that in mind.

@vadimcn
Copy link
Contributor

vadimcn commented Jan 5, 2017

Great work, @japaric!

Some thoughts on integration into the compiler:

  • ISTM, that the extra LLVM pass would fit well with Rust-specific target-feature's. This allows having a custom version of Rust runtime libs, compiled with special flags/passes/etc, which is exactly what we want here.
  • We already build the "builtins" part of the compiler-rt using a custom build script instead of cmake. I wonder if extending this approach to sanitizer runtimes would be more reliable cross platform-wise than cmake?
  • Could we fold sanitizer runtimes into compiler-rt? (guarded by #cfg[target-feature="?san"]). Compiler-rt is already special for the compiler, which always puts it on the command line after any user libraries.
  • Similarly, could we fold delegation of malloc to the *san runtime into alloc_system (again, guarded by an appropriate #cfg[...])?

@ghost
Copy link

ghost commented Jan 5, 2017

@japaric It is great to see more work on sanitizer support!

Regarding the question how much of this should be in-tree or not, I think the
runtime libraries should be in-tree for the simple reason that LLVM sanitizer
passes are already in-tree and distributed along with rust compiler. Moreover,
those passes depend on specific version of sanitizer runtime libraries, and
separating the two creates avenue for problematic mismatches (while working on
previous reincarnation of sanitizer support #31605, I hit exactly this problem;
it wasn't exactly trivial to debug).

@japaric
Copy link
Member Author

japaric commented Feb 9, 2017

@bors r=alexcrichton

@bors
Copy link
Contributor

bors commented Feb 9, 2017

📌 Commit e180dd5 has been approved by alexcrichton

frewsxcv added a commit to frewsxcv/rust that referenced this pull request Feb 9, 2017
LeakSanitizer, ThreadSanitizer, AddressSanitizer and MemorySanitizer support

```
$ cargo new --bin leak && cd $_

$ edit Cargo.toml && tail -n3 $_
```

``` toml
[profile.dev]
opt-level = 1
```

```
$ edit src/main.rs && cat $_
```

``` rust
use std::mem;

fn main() {
    let xs = vec![0, 1, 2, 3];
    mem::forget(xs);
}
```

```
$ RUSTFLAGS="-Z sanitizer=leak" cargo run --target x86_64-unknown-linux-gnu; echo $?
    Finished dev [optimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/leak`

=================================================================
==10848==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 16 byte(s) in 1 object(s) allocated from:
    #0 0x557c3488db1f in __interceptor_malloc /shared/rust/checkouts/lsan/src/compiler-rt/lib/lsan/lsan_interceptors.cc:55
    #1 0x557c34888aaa in alloc::heap::exchange_malloc::h68f3f8b376a0da42 /shared/rust/checkouts/lsan/src/liballoc/heap.rs:138
    rust-lang#2 0x557c34888afc in leak::main::hc56ab767de6d653a $PWD/src/main.rs:4
    rust-lang#3 0x557c348c0806 in __rust_maybe_catch_panic ($PWD/target/debug/leak+0x3d806)

SUMMARY: LeakSanitizer: 16 byte(s) leaked in 1 allocation(s).
23
```

```
$ cargo new --bin racy && cd $_

$ edit src/main.rs && cat $_
```

``` rust
use std::thread;

static mut ANSWER: i32 = 0;

fn main() {
    let t1 = thread::spawn(|| unsafe { ANSWER = 42 });
    unsafe {
        ANSWER = 24;
    }
    t1.join().ok();
}
```

```
$ RUSTFLAGS="-Z sanitizer=thread" cargo run --target x86_64-unknown-linux-gnu; echo $?
==================
WARNING: ThreadSanitizer: data race (pid=12019)
  Write of size 4 at 0x562105989bb4 by thread T1:
    #0 racy::main::_$u7b$$u7b$closure$u7d$$u7d$::hbe13ea9e8ac73f7e $PWD/src/main.rs:6 (racy+0x000000010e3f)
    #1 _$LT$std..panic..AssertUnwindSafe$LT$F$GT$$u20$as$u20$core..ops..FnOnce$LT$$LP$$RP$$GT$$GT$::call_once::h2e466a92accacc78 /shared/rust/checkouts/lsan/src/libstd/panic.rs:296 (racy+0x000000010cc5)
    rust-lang#2 std::panicking::try::do_call::h7f4d2b38069e4042 /shared/rust/checkouts/lsan/src/libstd/panicking.rs:460 (racy+0x00000000c8f2)
    rust-lang#3 __rust_maybe_catch_panic <null> (racy+0x0000000b4e56)
    rust-lang#4 std::panic::catch_unwind::h31ca45621ad66d5a /shared/rust/checkouts/lsan/src/libstd/panic.rs:361 (racy+0x00000000b517)
    rust-lang#5 std::thread::Builder::spawn::_$u7b$$u7b$closure$u7d$$u7d$::hccfc37175dea0b01 /shared/rust/checkouts/lsan/src/libstd/thread/mod.rs:357 (racy+0x00000000c226)
    rust-lang#6 _$LT$F$u20$as$u20$alloc..boxed..FnBox$LT$A$GT$$GT$::call_box::hd880bbf91561e033 /shared/rust/checkouts/lsan/src/liballoc/boxed.rs:605 (racy+0x00000000f27e)
    rust-lang#7 std::sys::imp::thread::Thread::new::thread_start::hebdfc4b3d17afc85 <null> (racy+0x0000000abd40)

  Previous write of size 4 at 0x562105989bb4 by main thread:
    #0 racy::main::h23e6e5ca46d085c3 $PWD/src/main.rs:8 (racy+0x000000010d7c)
    #1 __rust_maybe_catch_panic <null> (racy+0x0000000b4e56)
    rust-lang#2 __libc_start_main <null> (libc.so.6+0x000000020290)

  Location is global 'racy::ANSWER::h543d2b139f819b19' of size 4 at 0x562105989bb4 (racy+0x0000002f8bb4)

  Thread T1 (tid=12028, running) created by main thread at:
    #0 pthread_create /shared/rust/checkouts/lsan/src/compiler-rt/lib/tsan/rtl/tsan_interceptors.cc:902 (racy+0x00000001aedb)
    #1 std::sys::imp::thread::Thread::new::hce44187bf4a36222 <null> (racy+0x0000000ab9ae)
    rust-lang#2 std::thread::spawn::he382608373eb667e /shared/rust/checkouts/lsan/src/libstd/thread/mod.rs:412 (racy+0x00000000b5aa)
    rust-lang#3 racy::main::h23e6e5ca46d085c3 $PWD/src/main.rs:6 (racy+0x000000010d5c)
    rust-lang#4 __rust_maybe_catch_panic <null> (racy+0x0000000b4e56)
    rust-lang#5 __libc_start_main <null> (libc.so.6+0x000000020290)

SUMMARY: ThreadSanitizer: data race $PWD/src/main.rs:6 in racy::main::_$u7b$$u7b$closure$u7d$$u7d$::hbe13ea9e8ac73f7e
==================
ThreadSanitizer: reported 1 warnings
66
```

```
$ cargo new --bin oob && cd $_

$ edit src/main.rs && cat $_
```

``` rust
fn main() {
    let xs = [0, 1, 2, 3];
    let y = unsafe { *xs.as_ptr().offset(4) };
}
```

```
$ RUSTFLAGS="-Z sanitizer=address" cargo run --target x86_64-unknown-linux-gnu; echo $?
=================================================================
==13328==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff29f3ecd0 at pc 0x55802dc6bf7e bp 0x7fff29f3ec90 sp 0x7fff29f3ec88
READ of size 4 at 0x7fff29f3ecd0 thread T0
    #0 0x55802dc6bf7d in oob::main::h0adc7b67e5feb2e7 $PWD/src/main.rs:3
    #1 0x55802dd60426 in __rust_maybe_catch_panic ($PWD/target/debug/oob+0xfe426)
    rust-lang#2 0x55802dd58dd9 in std::rt::lang_start::hb2951fc8a59d62a7 ($PWD/target/debug/oob+0xf6dd9)
    rust-lang#3 0x55802dc6c002 in main ($PWD/target/debug/oob+0xa002)
    rust-lang#4 0x7fad8c3b3290 in __libc_start_main (/usr/lib/libc.so.6+0x20290)
    rust-lang#5 0x55802dc6b719 in _start ($PWD/target/debug/oob+0x9719)

Address 0x7fff29f3ecd0 is located in stack of thread T0 at offset 48 in frame
    #0 0x55802dc6bd5f in oob::main::h0adc7b67e5feb2e7 $PWD/src/main.rs:1

  This frame has 1 object(s):
    [32, 48) 'xs' <== Memory access at offset 48 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow $PWD/src/main.rs:3 in oob::main::h0adc7b67e5feb2e7
Shadow bytes around the buggy address:
  0x1000653dfd40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfd50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfd60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfd70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x1000653dfd90: 00 00 00 00 f1 f1 f1 f1 00 00[f3]f3 00 00 00 00
  0x1000653dfda0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfdb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfdc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfdd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000653dfde0: 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
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==13328==ABORTING
1
```

```
$ cargo new --bin uninit && cd $_

$ edit src/main.rs && cat $_
```

``` rust
use std::mem;

fn main() {
    let xs: [u8; 4] = unsafe { mem::uninitialized() };
    let y = xs[0] + xs[1];
}
```

```
$ RUSTFLAGS="-Z sanitizer=memory" cargo run; echo $?
==30198==WARNING: MemorySanitizer: use-of-uninitialized-value
    #0 0x563f4b6867da in uninit::main::hc2731cd4f2ed48f8 $PWD/src/main.rs:5
    #1 0x563f4b7033b6 in __rust_maybe_catch_panic ($PWD/target/debug/uninit+0x873b6)
    rust-lang#2 0x563f4b6fbd69 in std::rt::lang_start::hb2951fc8a59d62a7 ($PWD/target/debug/uninit+0x7fd69)
    rust-lang#3 0x563f4b6868a9 in main ($PWD/target/debug/uninit+0xa8a9)
    rust-lang#4 0x7fe844354290 in __libc_start_main (/usr/lib/libc.so.6+0x20290)
    rust-lang#5 0x563f4b6864f9 in _start ($PWD/target/debug/uninit+0xa4f9)

SUMMARY: MemorySanitizer: use-of-uninitialized-value $PWD/src/main.rs:5 in uninit::main::hc2731cd4f2ed48f8
Exiting
77
```
bors added a commit that referenced this pull request Feb 9, 2017
Rollup of 9 pull requests

- Successful merges: #37928, #38699, #39589, #39598, #39599, #39641, #39649, #39653, #39671
- Failed merges:
@bors bors merged commit e180dd5 into rust-lang:master Feb 9, 2017
@malbarbo
Copy link
Contributor

malbarbo commented Feb 9, 2017

Should this be marked with relnotes tag?

@japaric japaric deleted the lsan branch February 9, 2017 14:36
@alexcrichton
Copy link
Member

@malbarbo currently this is a nightly-only feature, but we'll be sure to feature it in the relnotes once it's stable!

@japaric that reminds me, can you file a tracking issue for stabilizing these?

@japaric
Copy link
Member Author

japaric commented Feb 9, 2017

@alexcrichton Done. #39699

@androm3da
Copy link
Contributor

Thanks, @japaric , what a great feature!

@whitequark
Copy link
Member

@japaric @alexcrichton This PR integrates with rustbuild in a really weird way. It adds some always-enabled 'optional' std features, lsan/msan/asan, and then omits actually producing any code for them if the LLVM_CONFIG environment variable is absent; the LLVM_CONFIG variable is only added if --enable-sanitizers is passed.

Shouldn't there be more sanity in this? Actually enabling the feature only if the config flag is on?

@alexcrichton
Copy link
Member

@whitequark sounds reasonable to me, yeah. We may also be able to remove the features now as they were from a previous iteration as well. @japaric what do you think?

@japaric
Copy link
Member Author

japaric commented Feb 15, 2017

Yeah, seems fine to remove the Cargo features. I'll prepare a PR.

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

Successfully merging this pull request may close these issues.