entry: enable SYSCALL on the UEFI hand-off path

The 32-bit entry sets EFER.SCE on its way into long mode. entry64uefi
skips that bootstrap entirely, because firmware has already done the mode
switch -- and with it skipped the one bit in there that is not about
paging. UEFI has no reason to set SCE and does not, so the kernel came up
with SYSCALL disabled.

Nothing noticed until the UEFI path first got far enough to run a user
program, at which point the first sysretq raised #UD: "unexpected trap 6
... rip <syscall_trapret>" in pid 1, at zero ticks.

Read-modify-write, because LME and LMA are already set by firmware and
have to stay.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-08-09 21:51:16 +02:00
commit e4df779048

View file

@ -163,6 +163,23 @@ gdt64_end:
*/ */
.global entry64uefi .global entry64uefi
entry64uefi: entry64uefi:
/* Enable SYSCALL/SYSRET.
*
* The 32-bit entry above sets EFER.SCE on its way into long mode. This path
* skips all of that, because firmware has already done the mode switch -- and
* with it skips the one bit that is not about paging. UEFI has no reason to
* set SCE and does not, so the kernel came up with SYSCALL disabled and the
* first sysretq back to user space raised #UD: "unexpected trap 6 ... rip
* <syscall_trapret>" in pid 1, at zero ticks. It stayed hidden until this
* path first got far enough to run a user program.
*
* Read-modify-write: LME and LMA are already set by firmware and must stay.
*/
mov $MSR_EFER, %ecx
rdmsr
bts $0, %eax
wrmsr
lgdt (gdtr64 - mboot_header + mboot_load_addr) lgdt (gdtr64 - mboot_header + mboot_load_addr)
movq $(entry64low - mboot_header + mboot_load_addr), %rax movq $(entry64low - mboot_header + mboot_load_addr), %rax
pushq $8 /* the 64-bit code selector in gdt64 */ pushq $8 /* the 64-bit code selector in gdt64 */