If CET shadow stack is enabled, we will count the number of stack frames skipped and adjust CET shadow stack based on the number in libunwind unwind_phase2. When unwinding stack, we can compare the return address in normal stack against counterpart in CET shadow stack, if they don't match, it means the return address stored in normal stack has been corrupted, we will return _URC_FATAL_PHASE2_ERROR in that case. We don't check for non-catchable exception since such exceptions will lead to process termination.
Details
- Reviewers
hjl.tools MaskRay - Group Reviewers
Restricted Project - Commits
- rG4db687155bc1: [libunwind] Check corrupted return address in unwind_phase2 when CET is enabled.
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
Hi, @hjl.tools
Could you take a look at this patch, we aim to align with libgcc's behavior in CET shadow stack unwinding process in order to enhance security.
Thanks very much.
Is there any way to validate correctness without a CET-capable machine?
Is there any emulator
Hi, @MaskRay
I am afraid we don't have anyway to validate correctness without a CET-enable machine since the intrinisc "_get_ssp" will return 0 in machine without CET and all the check based on comparison between normal stack ret addr and CET shadow stack ret addr will be skipped.
Currently, I set up a CET enabled machine and ran all libunwind, libcxx, libcxxabi tests in CET mode to validate this patch. In testing, we need to also build libcxx, libcxxabi and corresponding test with CET options on.
Thanks very much.
Is this check a libgcc behavior? Is it intended for just C++ exceptions (regular phase2) or also for _Unwind_ForcedUnwind?
Can you craft an example to demonstrate a failure caught by the new check? It is going to be brittle so I can understand that a test is likely inappropriate.
For C++ exceptions exception_object->exception_class != 0 is always true while for _Unwind_ForcedUnwind exception_class is typically zero, so I am not sure having the code in unwind_phase2_forced is useful...
libunwind/src/UnwindLevel1.c | ||
---|---|---|
317 | #ifdef |
In libgcc, the same macro is used for both _Unwind_RaiseException_Phase2 and _Unwind_ForcedUnwind_Phase2.
_Unwind_ForcedUnwind usages in glibc always terminate and don't unwind stack. We may not need to check
return address on shadow stack in this case.
libunwind/src/UnwindLevel1.c | ||
---|---|---|
317 |
|
Hi, @MaskRay
If so, I will remove unnecessary check in unwind_phase2_forced and where can I find any document or description about
"For C++ exceptions exception_object->exception_class != 0 is always true while for _Unwind_ForcedUnwind exception_class is typically zero"?
Thanks very much.
Thanks very much.
libunwind/src/UnwindLevel1.c | ||
---|---|---|
317 | Done. |
Hi, @MaskRay
Unnecessary check in forced unwind is removed. This patch is to align with libgcc's behavior in stack unwinding process on CET enabled platform. libgcc added check in stacking unwinding phase2, we need to go through the stacks to count how many stack frames to skip when CET is enabled and we can enhance security by comparing return addr in normal stack against CET shadow stack at the same time. If return addr in the 2 stacks don't match, it means the normal stack has been corrupted by someone and we can report fatal error in advance.
We also ran libunwind, libcxx, libcxxabi tests with CET enabled with latest patch, all tests passed.
Thanks very much.
#ifdef