This is an archive of the discontinued LLVM Phabricator instance.

[GWP-ASan] Add generic unwinders and structure backtrace output.
ClosedPublic

Authored by hctim on Jun 26 2019, 1:43 PM.

Details

Summary

Adds two flavours of generic unwinder and all the supporting cruft. If the
supporting allocator is okay with bringing in sanitizer_common, they can use
the fast frame-pointer based unwinder from sanitizer_common. Otherwise, we also
provide the backtrace() libc-based unwinder as well. Of course, the allocator
can always specify its own unwinder and unwinder-symbolizer.

The slightly changed output format is exemplified in the first comment on this
patch. It now better incorporates backtrace information, and displays
allocation details on the second line.

Event Timeline

hctim created this revision.Jun 26 2019, 1:43 PM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptJun 26 2019, 1:43 PM
Herald added subscribers: llvm-commits, Restricted Project, cryptoad and 2 others. · View Herald Transcript
hctim added a comment.Jun 26 2019, 1:45 PM

The new output format using the libc based unwinder is below. The report can be symbolized properly by using addr2line, but we don't really want to fork/exec during crash handling for obvious reasons. The libc generic unwinder provides either a function offset or a binary offset, so they can be happily symbolized offline.

The sanitizer unwinder does a better job of symbolizing online.

*** GWP-ASan detected a memory error ***
Use after free at 0xf7f22ff0 (0 bytes into a 10-byte allocation at 0xf7f22ff0) by thread 171092 here:
  #0 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(_ZN8gwp_asan20GuardedPoolAllocator19reportErrorInternalEjNS0_5ErrorE+0x3c8) [0x5660a4b8]
  #1 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(_ZN8gwp_asan20GuardedPoolAllocator11reportErrorEjNS0_5ErrorE+0x2a) [0x5660a03a]
  #2 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(+0x1877e) [0x5660977e]
  #3 linux-gate.so.1(__kernel_rt_sigreturn+0) [0xf7f4b0a0]
  #4 /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf1) [0xf7a8d4f1]

0xf7f22ff0 was allocated by thread 171092 here:
  #0 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(_ZN8gwp_asan20GuardedPoolAllocator8allocateEj+0x1b9) [0x56609ce9]
  #1 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(+0x27c8e) [0x56618c8e]
  #2 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(+0x278ed) [0x566188ed]
  #3 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(malloc+0x20) [0x5661bee0]
  #4 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(main+0x2b) [0x5661de6b]
  #5 /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf1) [0xf7a8d4f1]

0xf7f22ff0 was deallocated by thread 171092 here:
  #0 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(_ZN8gwp_asan20GuardedPoolAllocator10deallocateEPv+0xb6) [0x56609f16]
  #1 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(+0x27f91) [0x56618f91]
  #2 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(+0x27e1e) [0x56618e1e]
  #3 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(cfree+0x1f) [0x5661beaf]
  #4 ./projects/compiler-rt/test/gwp_asan/I386LinuxConfig/Output/use_after_free.cpp.tmp(main+0x65) [0x5661dea5]
  #5 /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf1) [0xf7a8d4f1]

*** End GWP-ASan report ***

I'm not very familiar with the compiler-rt build-side of this change, eugenis can speak to that better than I can.

compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp
123

Instead of doing if (PrintBacktrace) {} else {} checks everywhere why not just set it to the default value here if it's null?

compiler-rt/lib/gwp_asan/optional/backtrace_sanitizer_common.cpp
33

Why not std::min() instead of assert(), otherwise all backtrace implementations are beholden to __sanitizer::kStackTraceMax

We will need something different for out-of-process unwinding, which is AFAIK a requirement both in android and chrome.

compiler-rt/lib/gwp_asan/CMakeLists.txt
125

That's a lot of libraries. We need at most two - one standalone implementation for testing, and one "plugin" for the actual users.

compiler-rt/lib/gwp_asan/guarded_pool_allocator.h
43–44

update the comment

hctim marked 4 inline comments as done.Jun 28 2019, 10:41 AM

We will need something different for out-of-process unwinding, which is AFAIK a requirement both in android and chrome.

It looks like malloc_debug in Android uses in-process unwinding, based on libunwindstack. My assumption was that we would happily do it in-process there. Chromium also unwinds in-process AFAICT.

Out-of-process crash handling will be platform dependent, but we will likely have to provide some hooks at a later date to support this.

compiler-rt/lib/gwp_asan/CMakeLists.txt
125

The only problem here is that I'd expect people to mix-and-match these.

For example, Scudo is currently using the options parser, and the libc unwinder. Soon enough they'll be using the libc unwinder and providing their own options parser. I think we need to keep them decoupled to allow people to pick what they need (hence the optional dir).

compiler-rt/lib/gwp_asan/optional/backtrace_sanitizer_common.cpp
33

Done, with the caveat that we don't have c++ headers so have to do this ourselves ;)

hctim updated this revision to Diff 207097.Jun 28 2019, 10:42 AM
hctim marked an inline comment as done.
  • Updated with Vlad and Evgenii's comments.
eugenis added inline comments.Jun 28 2019, 10:57 AM
compiler-rt/lib/gwp_asan/CMakeLists.txt
125

Right, but they don't need libclang_rt.* libraries. Those are for the driver to link with user binaries. Scudo would link RTGwpAsanxxxx statically.

hctim marked 4 inline comments as done.Jun 28 2019, 11:08 AM
hctim added inline comments.
compiler-rt/lib/gwp_asan/CMakeLists.txt
125

Ah, I see. Have removed the libclang_rt varaints for most of these and made a unified sanitizer_common-based support library.

hctim updated this revision to Diff 207109.Jun 28 2019, 11:08 AM
hctim marked an inline comment as done.
  • Removed lots of libclang_rt libs and added a unified support library.
eugenis added inline comments.Jun 28 2019, 12:31 PM
compiler-rt/lib/gwp_asan/CMakeLists.txt
125

The library should be used in tests. Add at least a sanity test for stack trace printing.

compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp
371

Let's avoid printing incomplete lines. Android logcat, for example, adds a line break after every print.

hctim updated this revision to Diff 207154.Jun 28 2019, 3:07 PM
hctim marked 3 inline comments as done.
  • Added proper backtrace death unittests with unwinding checks.
hctim added inline comments.Jun 28 2019, 3:07 PM
compiler-rt/lib/gwp_asan/CMakeLists.txt
125

I've added a sanity test for this. Slight caveat - we can't really link against libclang_rt runtimes without adding -fsanitize=gwp_asan to the clang driver (which is how ASan checks itself against libclang_rt.ASan), which I'd rather not do as this mostly for tests only. Instead, I link the unittests against RTGwpAsan/RTGwpAsanOptionsParser/RTGwpAsanBacktraceSanitizerCommon which should provide sufficient coverage and ensure that backtracing is correct for the sanitizer common variant.

hctim updated this revision to Diff 207181.Jun 28 2019, 5:25 PM
  • Use intermediate strings and individual buffers (with snprintf()) instead of sprintf().
  • Flip order of deallocation/allocation stack traces.
eugenis accepted this revision.Jun 28 2019, 5:51 PM

LGTM

This revision is now accepted and ready to land.Jun 28 2019, 5:51 PM
This revision was automatically updated to reflect the committed changes.