[PATCH v2] x86/vsyscall: Avoid vsyscall emulation for unexpected page fault types

From: Sohil Mehta

Date: Tue Jul 21 2026 - 22:45:07 EST


Currently, vsyscall emulation is rejected for write faults and
kernel-privilege faults. It is assumed that other page fault error codes
such as X86_PF_PK and X86_PF_SHSTK will not be set in the vsyscall
context. From a defensive point of view, it is better to explicitly
reject these faults than assume that they will never happen. So, avoid
vsyscall emulation if any of those are present.

Note, X86_PF_SGX and X86_PF_RMP are also unlikely to occur in the
vsyscall context. They can probably be added to the reject-list in the
future to solidify this further. Also, X86_PF_RSVD is already handled in
do_user_addr_fault() before vsyscall emulation is attempted.

Similarly, on systems that support X86_FEATURE_NX, X86_PF_INSTR is
always expected to be set when an instruction fetch triggers a #PF. The
kernel issues a warning whenever that is not the case. Instead of
continuing when something is very wrong, return early and skip vsyscall
emulation.

Suggested-by: H. Peter Anvin (Intel) <hpa@xxxxxxxxx>
Signed-off-by: Sohil Mehta <sohil.mehta@xxxxxxxxx>
---
v2:
- Combined the two patches into one.
- Make the commit message concise.

I have excluded SGX and RMP because I wasn't 100% sure and I didn't get
any feedback on them in the last post. Let me know if someone wants them
to be added.
RFC-v1: https://lore.kernel.org/lkml/20260313192327.2089471-1-sohil.mehta@xxxxxxxxx/
---
arch/x86/entry/vsyscall/vsyscall_64.c | 10 ++++++----
arch/x86/mm/fault.c | 3 ---
2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/arch/x86/entry/vsyscall/vsyscall_64.c b/arch/x86/entry/vsyscall/vsyscall_64.c
index ea36de9fa864..5176860cef55 100644
--- a/arch/x86/entry/vsyscall/vsyscall_64.c
+++ b/arch/x86/entry/vsyscall/vsyscall_64.c
@@ -257,8 +257,9 @@ static bool __emulate_vsyscall(struct pt_regs *regs, unsigned long address)
bool emulate_vsyscall_pf(unsigned long error_code, struct pt_regs *regs,
unsigned long address)
{
- /* Write faults or kernel-privilege faults never get fixed up. */
- if ((error_code & (X86_PF_WRITE | X86_PF_USER)) != X86_PF_USER)
+ /* Only simple user read faults get fixed up (no write, pkey or shadow stack) */
+ if ((error_code & (X86_PF_WRITE | X86_PF_PK | X86_PF_SHSTK | X86_PF_USER)) !=
+ X86_PF_USER)
return false;

/*
@@ -282,8 +283,9 @@ bool emulate_vsyscall_pf(unsigned long error_code, struct pt_regs *regs,
* available, use it to double-check that the emulation code
* is only being used for instruction fetches:
*/
- if (cpu_feature_enabled(X86_FEATURE_NX))
- WARN_ON_ONCE(!(error_code & X86_PF_INSTR));
+ if (cpu_feature_enabled(X86_FEATURE_NX) &&
+ WARN_ON_ONCE(!(error_code & X86_PF_INSTR)))
+ return false;

return __emulate_vsyscall(regs, address);
}
diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c
index 45b99c3b1442..e0b645a55844 100644
--- a/arch/x86/mm/fault.c
+++ b/arch/x86/mm/fault.c
@@ -1318,9 +1318,6 @@ void do_user_addr_fault(struct pt_regs *regs,
*
* The vsyscall page does not have a "real" VMA, so do this
* emulation before we go searching for VMAs.
- *
- * PKRU never rejects instruction fetches, so we don't need
- * to consider the PF_PK bit.
*/
if (is_vsyscall_vaddr(address)) {
if (emulate_vsyscall_pf(error_code, regs, address))
--
2.43.0