[libunwind] Support stack unwind in CET environment
Control-flow Enforcement Technology (CET), published by Intel,
introduces shadow stack feature aiming to ensure a return from
a function is directed to where the function was called.
In a CET enabled system, each function call will push return
address into normal stack and shadow stack, when the function
returns, the address stored in shadow stack will be popped and
compared with the return address, program will fail if the 2
addresses don't match.
In exception handling, the control flow may skip some stack frames
and we must adjust shadow stack to avoid violating CET restriction.
In order to achieve this, we count the number of stack frames skipped
and adjust shadow stack by this number before jumping to landing pad.
Reviewed By: hjl.tools, compnerd, MaskRay
Differential Revision: https://reviews.llvm.org/D105968
Signed-off-by: gejin <ge.jin@intel.com>
NOKEYCHECK=True
GitOrigin-RevId: 21b25a1fb32ecd2e1f336123c2715f8ef1a49f97
diff --git a/src/UnwindLevel1.c b/src/UnwindLevel1.c
index 8b8797f..9203ac7 100644
--- a/src/UnwindLevel1.c
+++ b/src/UnwindLevel1.c
@@ -25,6 +25,7 @@
#include <stdio.h>
#include <string.h>
+#include "cet_unwind.h"
#include "config.h"
#include "libunwind.h"
#include "libunwind_ext.h"
@@ -34,6 +35,38 @@
#ifndef _LIBUNWIND_SUPPORT_SEH_UNWIND
+// When CET is enabled, each "call" instruction will push return address to
+// CET shadow stack, each "ret" instruction will pop current CET shadow stack
+// top and compare it with target address which program will return.
+// In exception handing, some stack frames will be skipped before jumping to
+// landing pad and we must adjust CET shadow stack accordingly.
+// _LIBUNWIND_POP_CET_SSP is used to adjust CET shadow stack pointer and we
+// directly jump to __libunwind_Registerts_x86/x86_64_jumpto instead of using
+// a regular function call to avoid pushing to CET shadow stack again.
+#if !defined(_LIBUNWIND_USE_CET)
+#define __unw_phase2_resume(cursor, fn) __unw_resume((cursor))
+#elif defined(_LIBUNWIND_TARGET_I386)
+#define __unw_phase2_resume(cursor, fn) \
+ do { \
+ _LIBUNWIND_POP_CET_SSP((fn)); \
+ void *cetRegContext = __libunwind_cet_get_registers((cursor)); \
+ void *cetJumpAddress = __libunwind_cet_get_jump_target(); \
+ __asm__ volatile("push %%edi\n\t" \
+ "sub $4, %%esp\n\t" \
+ "jmp *%%edx\n\t" :: "D"(cetRegContext), \
+ "d"(cetJumpAddress)); \
+ } while (0)
+#elif defined(_LIBUNWIND_TARGET_X86_64)
+#define __unw_phase2_resume(cursor, fn) \
+ do { \
+ _LIBUNWIND_POP_CET_SSP((fn)); \
+ void *cetRegContext = __libunwind_cet_get_registers((cursor)); \
+ void *cetJumpAddress = __libunwind_cet_get_jump_target(); \
+ __asm__ volatile("jmpq *%%rdx\n\t" :: "D"(cetRegContext), \
+ "d"(cetJumpAddress)); \
+ } while (0)
+#endif
+
static _Unwind_Reason_Code
unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *exception_object) {
__unw_init_local(cursor, uc);
@@ -137,6 +170,9 @@
_LIBUNWIND_TRACE_UNWINDING("unwind_phase2(ex_ojb=%p)",
(void *)exception_object);
+ // uc is initialized by __unw_getcontext in the parent frame. The first stack
+ // frame walked is unwind_phase2.
+ unsigned framesWalked = 1;
// Walk each frame until we reach where search phase said to stop.
while (true) {
@@ -188,6 +224,7 @@
}
#endif
+ ++framesWalked;
// If there is a personality routine, tell it we are unwinding.
if (frameInfo.handler != 0) {
_Unwind_Personality_Fn p =
@@ -227,8 +264,9 @@
", sp=0x%" PRIxPTR,
(void *)exception_object, pc, sp);
}
- __unw_resume(cursor);
- // __unw_resume() only returns if there was an error.
+
+ __unw_phase2_resume(cursor, framesWalked);
+ // __unw_phase2_resume() only returns if there was an error.
return _URC_FATAL_PHASE2_ERROR;
default:
// Personality routine returned an unknown result code.
@@ -250,6 +288,9 @@
_Unwind_Stop_Fn stop, void *stop_parameter) {
__unw_init_local(cursor, uc);
+ // uc is initialized by __unw_getcontext in the parent frame. The first stack
+ // frame walked is unwind_phase2_forced.
+ unsigned framesWalked = 1;
// Walk each frame until we reach where search phase said to stop
while (__unw_step(cursor) > 0) {
@@ -296,6 +337,7 @@
return _URC_FATAL_PHASE2_ERROR;
}
+ ++framesWalked;
// If there is a personality routine, tell it we are unwinding.
if (frameInfo.handler != 0) {
_Unwind_Personality_Fn p =
@@ -320,7 +362,7 @@
"_URC_INSTALL_CONTEXT",
(void *)exception_object);
// We may get control back if landing pad calls _Unwind_Resume().
- __unw_resume(cursor);
+ __unw_phase2_resume(cursor, framesWalked);
break;
default:
// Personality routine returned an unknown result code.