[BUG] x86/entry: ORC stack unwinding error on error_entry

From: Chen Zhongjin
Date: Sat Sep 02 2023 - 08:28:40 EST


My KASAN reported a stack-out-of-bounds BUG in unwind_next_frame
I found there was a wrong stack unwinding on error_entry and the
stack was like:

<NMI>
...
exc_nmi
end_repeat_nmi
RIP: 0010:error_entry+0x17
RSP: 0018:ffff88de81889928 EFLAGS: 00000082
RAX: 0000000000000000 RBX: ffff88de81889a98 RCX: ffffffff9cec64a9
RDX: dffffc0000000000 RSI: ffffffff9e400cfa RDI: ffffffff9e400d01
RBP: 1ffff11bd031133c R08: ffffffff9cec6406 R09: ffff88de81889acc
R10: fffffbfff40bb517 R11: 0000000000000001 R12: 0000000000000001
R13: ffff88de81889ae0 R14: ffffffff9f2ee640 R15: 0000000000000000
</NMI>
<IRQ>
asm_sysvec_apic_timer_interrupt+0x11
RIP: 9e2d0a66:unwind_next_frame+0x0
RSP: 81889ab8:ffff88de81889ab8 EFLAGS: ffff88b11727fd48 ORIG_RAX: ffffffff9eecdff0
RAX: ffff88b11727fd48 RBX: 0000000000000018 RCX: ffff88de81889acd
RDX: ffffffff9e400d02 RSI: ffff88b11727fd40 RDI: 0000000041b58ab3
RBP: ffff88de818899a0 R08: ffffffff9e400d02 R09: ffff88de81889ae8
R10: ffff88de81889ad0 R11: ffffffff9cec64bf R12: 0000000000000282
R13: 0000000000000010 R14: ffffffff9cec6150 R15: ffffffffffffffff
(orc unwinding stopped here)

It's because in error_entry the return address is changed. When asm_sysvec_apic_timer_interrupt calls error_entry, the stack looks like:

asm_sysvec_apic_timer_interrupt+0xa
ffffffffffffffff
(IRET_REGS) <- idtentry unwind stack by IRET_REGS

Then error_entry enters PUSH_AND_CLEAR_REGS save_ret=1, which will
change the return address:

pushq %rsi
movq 8(%rsp), %rsi
movq %rdi, 8(%rsp)
(push other regs)
UNWIND_HINT_REGS

And the stack is changed to:

(other regs)
%rsi
%rdi (asm_sysvec_apic_timer_interrupt+0x11, in this case)
ffffffffffffffff
(IRET_REGS)

If there is a NMI happens before all regs are pushed and unwinding hint
changed to UNWIND_HINT_REGS, the unwinding in error_entry is wrong.
It find %rdi as the return address and search the next orc_entry,
which makes unwinding fail.

I guess this can be fixed if it keeps the pt_regs->di slot as return address until all regs are pushed to stack and unwind hint changed to UNWIND_HINT_REGS, then overwrite the return address to %rdi.

UNWIND_HINT_REGS

.if \save_ret
movq 0x70(%rsp), %rsi
movq %rdi, 0x70(%rsp)
pushq %rsi
.endif

However I'm not sure if it works or there is any side affect (at lease it looks not so graceful). Does anyone has some advise for this bug or this fix?
I'll test this after I can reproduce the bug stably.

Thanks.