Shadow stacks contain little more than return addresses, and they in
particular allow precise call traces also without FRAME_POINTER.
Signed-off-by: Jan Beulich <jbeulich@suse.com>
---
While the 'E' for exception frames is probably okay, I'm not overly
happy with the 'C' (for CET). I would have preferred 'S' (for shadow),
but we use that character already.
As an alternative to suppressing output for the top level exception
frame, adding the new code ahead of the 'R' output line (and then also
ahead of the stack top read) could be considered.
Perhaps having a printk() for the PV entry case is meaningless, for
- no frame being pushed when entered from CPL=3 (64-bit PV),
- no entry possible from CPL<3 (32-bit PV disabled when CET is active)?
In which case the comment probably should just be "Bogus." and the code
merely be "break;".
Quite likely a number of other uses of is_active_kernel_text() also want
amending with in_stub().
---
v2: IS_ENABLED() -> #ifdef. Re-base.
--- a/xen/arch/x86/traps.c
+++ b/xen/arch/x86/traps.c
@@ -48,6 +48,7 @@
#include <asm/shared.h>
#include <asm/shstk.h>
#include <asm/smp.h>
+#include <asm/stubs.h>
#include <asm/system.h>
#include <asm/traps.h>
#include <asm/uaccess.h>
@@ -705,6 +706,13 @@ unsigned long get_stack_dump_bottom(unsi
}
}
+#ifdef CONFIG_XEN_SHSTK
+static bool in_stub(unsigned long addr)
+{
+ return !((this_cpu(stubs.addr) ^ addr) >> STUB_BUF_SHIFT);
+}
+#endif
+
#if !defined(CONFIG_FRAME_POINTER)
/*
@@ -797,6 +805,52 @@ static void show_trace(const struct cpu_
!is_active_kernel_text(tos) )
printk(" [<%p>] R %pS\n", _p(regs->rip), _p(regs->rip));
+#ifdef CONFIG_XEN_SHSTK
+ if ( rdssp() != SSP_NO_SHSTK )
+ {
+ const unsigned long *ptr = _p(regs->entry_ssp);
+ unsigned int n;
+
+ for ( n = 0; (unsigned long)ptr & (PAGE_SIZE - sizeof(*ptr)); ++n )
+ {
+ unsigned long val = *ptr;
+
+ if ( is_active_kernel_text(val) || in_stub(val) )
+ {
+ /* Normal return address entry. */
+ printk(" [<%p>] C %pS\n", _p(val), _p(val));
+ ++ptr;
+ }
+ else if ( !((val ^ *ptr) >> (PAGE_SHIFT + STACK_ORDER)) )
+ {
+ if ( val & (sizeof(val) - 1) )
+ {
+ /* Most likely a supervisor token. */
+ break;
+ }
+
+ /*
+ * Ought to be a hypervisor interruption frame. But don't
+ * (re)log the current frame's %rip.
+ */
+ if ( n || ptr[1] != regs->rip )
+ printk(" [<%p>] E %pS\n", _p(ptr[1]), _p(ptr[1]));
+ ptr = _p(val);
+ }
+ else
+ {
+ /* Ought to be a PV guest hypercall/interruption frame. */
+ printk(" %04lx:[<%p>] E\n", ptr[2], _p(ptr[1]));
+ ptr = 0;
+ }
+ }
+
+ /* Fall back to legacy stack trace if nothing was logged at all. */
+ if ( n )
+ return;
+ }
+#endif /* CONFIG_XEN_SHSTK */
+
if ( fault )
{
printk(" [Fault on access]\n");
On 08/04/2026 1:23 pm, Jan Beulich wrote: > Shadow stacks contain little more than return addresses, and they in > particular allow precise call traces also without FRAME_POINTER. Do you have an example of what such a backtrace now looks like ? > Signed-off-by: Jan Beulich <jbeulich@suse.com> > --- > While the 'E' for exception frames is probably okay, I'm not overly > happy with the 'C' (for CET). I would have preferred 'S' (for shadow), > but we use that character already. > > As an alternative to suppressing output for the top level exception > frame, adding the new code ahead of the 'R' output line (and then also > ahead of the stack top read) could be considered. > > Perhaps having a printk() for the PV entry case is meaningless, for > - no frame being pushed when entered from CPL=3 (64-bit PV), > - no entry possible from CPL<3 (32-bit PV disabled when CET is active)? > In which case the comment probably should just be "Bogus." and the code > merely be "break;". Yes, PV32 doesn't exist when CET-SS is active, and PV64 doesn't push a frame. regs->ssp will point to the supervisor token (IDT delivery) or on the boundary with the regular stack (FRED). > Quite likely a number of other uses of is_active_kernel_text() also want > amending with in_stub(). There are very few things which can exist on a shadow stack. 1) Tokens (supervisor, restore or prev) 2) Return address 3) Old-SSP 4) Old-CS Intel recommend not allowing code or stacks to be in the bottom 64k of the address space to prevent type confusion between Old-CS and the other values. Xen matches this expectation, but it might be wise to check for it explicitly. Notably, we cannot ever get a value matching in_stub() (outside of general memory corruption). On SYSCALL/SYSENTER, SSP is set to 0, and we don't re-establish a proper SSP until the SETSSBSY after leaving the stub. Similarly on SYSRET, the CLRSSBSY sets SSP to 0 too. An NMI hitting these paths should find regs->ssp pointing at it's own shadow stack, with an Old-SSP of 0. ~Andrew
On 08.04.2026 19:53, Andrew Cooper wrote: > On 08/04/2026 1:23 pm, Jan Beulich wrote: >> Shadow stacks contain little more than return addresses, and they in >> particular allow precise call traces also without FRAME_POINTER. > > Do you have an example of what such a backtrace now looks like ? (XEN) Xen call trace: (XEN) [<ffff82d04032d730>] R extable.c#search_one_extable+0x70/0x73 (XEN) [<ffff82d04032d802>] C search_exception_table+0xc2/0x177 (XEN) [<ffff82d040358378>] C traps.c#extable_fixup.isra.0+0x18/0x6c (XEN) [<ffff82d040358e3b>] C do_invalid_op+0xab/0x106 (XEN) [<ffff82d040201d98>] C x86_64/entry.S#handle_exception_saved+0x88/0xf4 (XEN) [<ffff82d07fffe044>] E ffff82d07fffe044 (XEN) [<ffff82d040412db0>] C stub_selftest+0xd0/0x168 (XEN) [<ffff82d0403508d6>] C setup.c#init_done+0x116/0x15a Note how both the stub entry and stub_selftest() as its caller are present here. The stub entry is missing when FRAME_POINTER=n (albeit we could teach that code to recognize it), while the stub_selftest() entry is missing when FRAME_POINTER=y (and we can't do anything about this unless we wanted to add frame setup to stub generation). Jan
On 08.04.2026 19:53, Andrew Cooper wrote: > On 08/04/2026 1:23 pm, Jan Beulich wrote: >> Shadow stacks contain little more than return addresses, and they in >> particular allow precise call traces also without FRAME_POINTER. > > Do you have an example of what such a backtrace now looks like ? I don't have one to hand, but I'll add an example for v3. >> --- >> While the 'E' for exception frames is probably okay, I'm not overly >> happy with the 'C' (for CET). I would have preferred 'S' (for shadow), >> but we use that character already. >> >> As an alternative to suppressing output for the top level exception >> frame, adding the new code ahead of the 'R' output line (and then also >> ahead of the stack top read) could be considered. >> >> Perhaps having a printk() for the PV entry case is meaningless, for >> - no frame being pushed when entered from CPL=3 (64-bit PV), >> - no entry possible from CPL<3 (32-bit PV disabled when CET is active)? >> In which case the comment probably should just be "Bogus." and the code >> merely be "break;". > > Yes, PV32 doesn't exist when CET-SS is active, and PV64 doesn't push a > frame. regs->ssp will point to the supervisor token (IDT delivery) or > on the boundary with the regular stack (FRED). Okay, I'll change that then as indicated. >> Quite likely a number of other uses of is_active_kernel_text() also want >> amending with in_stub(). > > There are very few things which can exist on a shadow stack. > > 1) Tokens (supervisor, restore or prev) > 2) Return address > 3) Old-SSP > 4) Old-CS > > Intel recommend not allowing code or stacks to be in the bottom 64k of > the address space to prevent type confusion between Old-CS and the other > values. Xen matches this expectation, but it might be wise to check for > it explicitly. What exactly do you mean here? Neither is_active_kernel_text() nor in_stub() nor the further "!((val ^ *ptr) >> (PAGE_SHIFT + STACK_ORDER))" (which I only now notice can't be quite right, as val was read from *ptr; I think (unsigned long)ptr is meant instead) would yield true there. If, as per above, in the remaining else we'll have just "break", what would such a separate check be good for? Jan
© 2016 - 2026 Red Hat, Inc.