This is an archive of the discontinued LLVM Phabricator instance.

[libunwind] Use process_vm_readv to avoid potential segfaults
ClosedPublic

Authored by smeenai on May 24 2022, 4:23 PM.

Details

Summary

We've observed segfaults in libunwind when attempting to check for the
Linux aarch64 sigreturn frame, presumably because of bad unwind info
leading to an incorrect PC that we attempt to read from. Use
process_vm_readv to read the memory safely instead.

The s390x code path should likely follow suit, but I don't have the
hardware to be able to test that, so I didn't modify it here either.

Diff Detail

Event Timeline

smeenai created this revision.May 24 2022, 4:23 PM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptMay 24 2022, 4:23 PM
Herald added a reviewer: Restricted Project. · View Herald Transcript
smeenai requested review of this revision.May 24 2022, 4:23 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 24 2022, 4:23 PM
smeenai updated this revision to Diff 431840.May 24 2022, 4:51 PM

Use sizeof instead of raw number

smeenai updated this revision to Diff 431884.May 24 2022, 10:49 PM

XFAIL with MSAN, similar to other tests

smeenai updated this revision to Diff 431887.May 24 2022, 11:00 PM

Better MSAN suppression

MaskRay accepted this revision.May 24 2022, 11:02 PM

LGTM, but hope @rprichard can verify this.

libunwind/test/bad_unwind_info.pass.cpp
55

only supported on aarch64 and x86-64.

#error does not need a quoted argument.

70

Use __attribute__((naked)) with basic asm (https://gcc.gnu.org/onlinedocs/gcc/Basic-Asm.html), then get rid of the inline assembly language at file scope.

This revision is now accepted and ready to land.May 24 2022, 11:02 PM
MaskRay added inline comments.May 24 2022, 11:05 PM
libunwind/test/bad_unwind_info.pass.cpp
24

move aarch64 before x86_64 for an alphabetical order.

smeenai added inline comments.May 24 2022, 11:07 PM
libunwind/test/bad_unwind_info.pass.cpp
70

I wanted to do this originally, but GCC doesn't support __attribute__((naked)) on aarch64: https://godbolt.org/z/15rbv6GEG. I'm not sure if we need to support gcc in the test suite, but I thought at least libc++ tried to.

smeenai added inline comments.May 24 2022, 11:23 PM
libunwind/test/bad_unwind_info.pass.cpp
70

That being said, it looks like GCC isn't performing any stack manipulation in this case anyway, just adding a nop and a ret, which should be harmless. Not sure if we can rely on that in general though.

smeenai updated this revision to Diff 432100.May 25 2022, 1:09 PM
smeenai marked 3 inline comments as done.

Address review comments

libunwind/test/bad_unwind_info.pass.cpp
70

I ended up just unsupporting the test on gcc, because __attribute__((naked)) makes it much cleaner. Thanks for the suggestion :)

rprichard accepted this revision.May 25 2022, 8:58 PM

The fix and the test both look good to me.

rprichard accepted this revision.May 25 2022, 10:01 PM

The s390x code path should likely follow suit, but I don't have the
hardware to be able to test that, so I didn't modify it here either.

Thanks for the heads-up! It look me a while to get around to it, but I've now fixed the bug on s390x here: https://reviews.llvm.org/D129856

The Chrome sandbox disallows process_vm_readv() (I think most seccomp sandboxes would) so this causes crashes when trying to collect backtraces at runtime.

What would happen if I caused process_vm_read() to return EPERM here? Would the unwinder still be able to unwind past the sigreturn trampoline with heuristics?

If not, is it possible to introduce a fallback that reads from the address directly, or possibly uses mincore() to check if the address is valid (a small race is possible)?

The Chrome sandbox disallows process_vm_readv() (I think most seccomp sandboxes would) so this causes crashes when trying to collect backtraces at runtime.

What would happen if I caused process_vm_read() to return EPERM here? Would the unwinder still be able to unwind past the sigreturn trampoline with heuristics?

If not, is it possible to introduce a fallback that reads from the address directly, or possibly uses mincore() to check if the address is valid (a small race is possible)?

With the code as currently written, I believe we'd just fail to unwind past the sigreturn frame if process_vm_readv failed.

How would mincore work in this scenario? It tells you if a page is resident, but a non-resident page could still be accessible, right? On the flip side, ENOMEM would tell you if the page was unmapped, but an execute-only page would be mapped but still unreadable.

With the code as currently written, I believe we'd just fail to unwind past the sigreturn frame if process_vm_readv failed.

Ah, too bad.

How would mincore work in this scenario? It tells you if a page is resident, but a non-resident page could still be accessible, right? On the flip side, ENOMEM would tell you if the page was unmapped, but an execute-only page would be mapped but still unreadable.

I didn't realize ARM supported execute-only pages. I was thinking that if mincore returned EFAULT this would work to detect if an address was readable. It looks like a "write-to-pipe" works for checking if an address is readable: https://github.com/libunwind/libunwind/blob/0e9119698dfad47f8afb88c2a7ee3d36f9efd568/src/mi/Gaddress_validator.c#L97