Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [RFC PATCH 0/6] Protect VMM from speculative attacks using x86 PKRU
@ 2026-08-18 20:44 Jacky Li
  2026-08-18 20:44 ` [RFC PATCH 1/6] x86: Introduce basic PKRU hardware wrappers Jacky Li
                   ` (5 more replies)
  0 siblings, 6 replies; 9+ messages in thread
From: Jacky Li @ 2026-08-18 20:44 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Zhao Liu, Richard Henderson,
	Philippe Mathieu-Daudé, Peter Xu, kvm, James Houghton,
	Mingwei Zhang, Dave Hansen, Brendan Jackman, Reiji Watanabe,
	Jacky Li

Hi,

This RFC patch series introduces a hardware-assisted memory protection
mechanism in QEMU using x86 Protection Keys for Userspace (PKRU / MPK)
to complete the Address Space Isolation ecosystem by protecting the VMM
from guest-to-host speculative execution attacks.

In a guest-to-host speculative side-channel attack against QEMU, a
malicious guest mistrains the VMM branch predictor to execute a
speculative gadget in host mode. This gadget speculatively reads a
host secret and uses that secret as an index to touch guest RAMBlocks,
leaving an observable microarchitectural footprint in the CPU cache.
Existing mitigations like issuing IBPB on every VM-exit incur high
overhead, while guest_memfd cannot protect memory that must stay mapped.

To break this covert channel, we protect guest RAMBlocks using a
dedicated pkey (pkey_mprotect).

- During normal QEMU host userspace execution (I/O handling, timers,
  event loops), we lock this pkey by setting PKEY_DISABLE_ACCESS in
  the PKRU register via WRPKRU. Because CPU pipelines natively enforce
  PKRU restrictions during out-of-order execution, any speculative
  gadget in host mode attempting to touch guest RAMBlocks is blocked by
  the MMU at the hardware level, leaving zero footprint in the cache.

- Before entering KVM (KVM_RUN), we temporarily unlock the pkey, and
  immediately re-lock it upon returning to QEMU userspace.

- To gracefully handle legitimate host accesses to guest RAMBlocks
  when locked, a SEGV_PKUERR signal handler unlocks the pkey in the
  interrupted thread's xstate and issues an IBPB (PR_SPEC_INDIRECT_BRANCH)
  to prevent speculative leakage before resuming execution.

Comments and feedback on this approach are very welcome!

To: qemu-devel@nongnu.org
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Zhao Liu <zhao1.liu@intel.com>
Cc: Richard Henderson <richard.henderson@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@mailo.com>
Cc: Peter Xu <peterx@redhat.com>
Cc: kvm@vger.kernel.org
Cc: James Houghton <jthoughton@google.com>
Cc: Mingwei Zhang <mizhang@google.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Brendan Jackman <jackmanb@google.com>
Cc: Reiji Watanabe <reijiw@google.com>
Cc: Jacky Li <jackyli@google.com>

Signed-off-by: Jacky Li <jackyli@google.com>
---
Jacky Li (6):
      [RFC PATCH 1/6] x86: Introduce basic PKRU hardware wrappers
      [RFC PATCH 2/6] kvm: Add guest memory Pkey initialization
      [RFC PATCH 3/6] physmem: Tag guest RAMBlocks with Protection Key
      [RFC PATCH 4/6] kvm: Lock guest RAMBlocks via PKRU during host userspace execution
      [RFC PATCH 5/6] x86: Add xstate parsing and PKRU offset detection
      [RFC PATCH 6/6] kvm: Implement SIGSEGV sentinel for Pkey recovery

 MAINTAINERS               |   1 +
 accel/kvm/kvm-all.c       |   7 +-
 include/exec/cpu-common.h |   4 +
 include/qemu/mmap-alloc.h |   5 +
 system/physmem.c          |  22 ++-
 system/vl.c               |   1 +
 util/meson.build          |   2 +-
 util/mmap-alloc.c         |   3 +-
 util/pkey.c               | 386 ++++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 424 insertions(+), 7 deletions(-)
---
base-commit: fa19879df1658f96ac07365fca8835b7decd6995
change-id: 20260729-feature-pkey-dev-c8a026db85b4

Best regards,
-- 
Jacky Li <jackyli@google.com>


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [RFC PATCH 1/6] x86: Introduce basic PKRU hardware wrappers
  2026-08-18 20:44 [RFC PATCH 0/6] Protect VMM from speculative attacks using x86 PKRU Jacky Li
@ 2026-08-18 20:44 ` Jacky Li
  2026-08-18 20:44 ` [RFC PATCH 2/6] kvm: Add guest memory Pkey initialization Jacky Li
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 9+ messages in thread
From: Jacky Li @ 2026-08-18 20:44 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Zhao Liu, Richard Henderson,
	Philippe Mathieu-Daudé, Peter Xu, kvm, James Houghton,
	Mingwei Zhang, Dave Hansen, Brendan Jackman, Reiji Watanabe,
	Jacky Li

Introduce basic hardware instruction wrappers and helper functions for
interacting with x86 Protection Keys for Userspace (PKRU / MPK) in QEMU.

Signed-off-by: Jacky Li <jackyli@google.com>
---
 MAINTAINERS |  1 +
 util/pkey.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 97 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index b51f5c3e60..5a31c45963 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -156,6 +156,7 @@ F: target/i386/meson.build
 F: tools/i386/
 F: tests/functional/i386/
 F: tests/functional/x86_64/
+F: util/pkey.c
 
 X86 VM file descriptor change on reset test
 M: Ani Sinha <anisinha@redhat.com>
diff --git a/util/pkey.c b/util/pkey.c
new file mode 100644
index 0000000000..4f14a72151
--- /dev/null
+++ b/util/pkey.c
@@ -0,0 +1,96 @@
+/*
+ * QEMU guest memory protection key helpers
+ *
+ * Copyright (c) 2026 Google LLC
+ *
+ * Author:
+ *  Jacky Li <jackyli@google.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "exec/cpu-common.h"
+#include "qemu/error-report.h"
+
+#if defined(HOST_X86_64) && defined(CONFIG_LINUX)
+#include <asm/unistd.h>
+#include <cpuid.h>
+#include <immintrin.h>
+#include <linux/kvm.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+
+#include "qemu/atomic.h"
+
+#ifndef PR_SET_SPECULATION_CTRL
+#define PR_SET_SPECULATION_CTRL 53
+#endif
+#ifndef PR_SPEC_INDIRECT_BRANCH
+#define PR_SPEC_INDIRECT_BRANCH 1
+#endif
+#ifndef PR_SPEC_DISABLE
+#define PR_SPEC_DISABLE (1UL << 2)
+#endif
+
+#ifndef PKEY_DISABLE_ACCESS
+#define PKEY_DISABLE_ACCESS 0x1
+#endif
+
+/* Each protection key occupies exactly 2 bits in the PKRU register. */
+#define BITS_PER_KEY 2
+/* The mask used to extract/write the 2-bit permission flags. */
+#define KEY_MASK 3U
+/* Max protection keys supported on x86_64 */
+#define KEY_COUNT 16
+
+static inline __attribute__((target("pku"), always_inline))
+uint32_t rdpkru(void)
+{
+    return _rdpkru_u32();
+}
+
+static inline __attribute__((target("pku"), always_inline)) void wrpkru(
+        uint32_t pkru)
+{
+    _wrpkru(pkru);
+}
+
+static inline __attribute__((target("pku"), always_inline)) int inline_pkey_get(
+        int pkey)
+{
+    uint32_t pkru = rdpkru();
+    return (pkru >> (pkey * BITS_PER_KEY)) & KEY_MASK;
+}
+
+static inline __attribute__((target("pku"), always_inline)) void
+inline_pkey_set(int pkey, unsigned int access_rights)
+{
+    uint32_t pkru = rdpkru();
+    pkru &= ~(KEY_MASK << (pkey * BITS_PER_KEY));
+    pkru |= ((access_rights & KEY_MASK) << (pkey * BITS_PER_KEY));
+    wrpkru(pkru);
+}
+
+static inline __attribute__((always_inline)) intptr_t local_syscall3(
+        intptr_t num, intptr_t arg1, intptr_t arg2, intptr_t arg3)
+{
+    intptr_t ret = num;
+    asm volatile("syscall\n"
+               : "+a"(ret)
+               : "D"(arg1), "S"(arg2), "d"(arg3)
+               : "rcx", "r11", "memory");
+    return ret;
+}
+
+#else
+/* Dummy implementations for all other configurations (non-x86_64 Linux, */
+/* Windows, macOS, etc.) */
+#if defined(CONFIG_LINUX)
+#include <linux/kvm.h>
+#include <sys/ioctl.h>
+#endif
+#endif

-- 
2.55.0.737.g08866a6d13-goog


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [RFC PATCH 2/6] kvm: Add guest memory Pkey initialization
  2026-08-18 20:44 [RFC PATCH 0/6] Protect VMM from speculative attacks using x86 PKRU Jacky Li
  2026-08-18 20:44 ` [RFC PATCH 1/6] x86: Introduce basic PKRU hardware wrappers Jacky Li
@ 2026-08-18 20:44 ` Jacky Li
  2026-08-18 22:03   ` Dave Hansen
  2026-08-18 20:44 ` [RFC PATCH 3/6] physmem: Tag guest RAMBlocks with Protection Key Jacky Li
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 9+ messages in thread
From: Jacky Li @ 2026-08-18 20:44 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Zhao Liu, Richard Henderson,
	Philippe Mathieu-Daudé, Peter Xu, kvm, James Houghton,
	Mingwei Zhang, Dave Hansen, Brendan Jackman, Reiji Watanabe,
	Jacky Li

Add initialization logic during early QEMU startup to allocate a
dedicated userspace protection key (Pkey) for protecting guest physical
memory when enabled via QEMU_ENABLE_PKEY_GUEST_MEMORY.

Signed-off-by: Jacky Li <jackyli@google.com>
---
 include/exec/cpu-common.h |  1 +
 system/vl.c               |  1 +
 util/meson.build          |  2 +-
 util/pkey.c               | 23 +++++++++++++++++++++++
 4 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/include/exec/cpu-common.h b/include/exec/cpu-common.h
index 6594f7fa1b..5ede0c65dc 100644
--- a/include/exec/cpu-common.h
+++ b/include/exec/cpu-common.h
@@ -112,4 +112,5 @@ static inline CPUState *env_cpu(CPUArchState *env)
     return (CPUState *)env_cpu_const(env);
 }
 
+void qemu_init_guest_memory_pkey(void);
 #endif /* CPU_COMMON_H */
diff --git a/system/vl.c b/system/vl.c
index 061cbdf860..e49c6fed39 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -2907,6 +2907,7 @@ void qemu_init(int argc, char **argv)
     module_allow_arch(target_name());
 
     qemu_init_subsystems();
+    qemu_init_guest_memory_pkey();
 
     /* first pass of option parsing */
     optind = 1;
diff --git a/util/meson.build b/util/meson.build
index fa174c07a5..682a078a3d 100644
--- a/util/meson.build
+++ b/util/meson.build
@@ -9,7 +9,7 @@ if host_os != 'windows'
   util_ss.add(files('compatfd.c'))
   util_ss.add(files('event_notifier-posix.c'))
   if host_os != 'emscripten'
-    util_ss.add(files('mmap-alloc.c'))
+    util_ss.add(files('mmap-alloc.c', 'pkey.c'))
   endif
   freebsd_dep = []
   if host_os == 'freebsd'
diff --git a/util/pkey.c b/util/pkey.c
index 4f14a72151..249e36d508 100644
--- a/util/pkey.c
+++ b/util/pkey.c
@@ -40,6 +40,8 @@
 #define PKEY_DISABLE_ACCESS 0x1
 #endif
 
+static int guest_memory_pkey = -1;
+
 /* Each protection key occupies exactly 2 bits in the PKRU register. */
 #define BITS_PER_KEY 2
 /* The mask used to extract/write the 2-bit permission flags. */
@@ -86,6 +88,24 @@ static inline __attribute__((always_inline)) intptr_t local_syscall3(
     return ret;
 }
 
+__attribute__((target("pku"))) void qemu_init_guest_memory_pkey(void)
+{
+    if (guest_memory_pkey != -1) {
+        return;
+    }
+
+    const char *enable_pkey = getenv("QEMU_ENABLE_PKEY_GUEST_MEMORY");
+    if (enable_pkey && strcmp(enable_pkey, "1") == 0) {
+        int pkey = pkey_alloc(0, 0);
+        if (pkey == -1) {
+            error_report("pkey_alloc failed for guest memory: %s",
+                         strerror(errno));
+        } else {
+            guest_memory_pkey = pkey;
+        }
+    }
+}
+
 #else
 /* Dummy implementations for all other configurations (non-x86_64 Linux, */
 /* Windows, macOS, etc.) */
@@ -93,4 +113,7 @@ static inline __attribute__((always_inline)) intptr_t local_syscall3(
 #include <linux/kvm.h>
 #include <sys/ioctl.h>
 #endif
+
+void qemu_init_guest_memory_pkey(void)
+{}
 #endif

-- 
2.55.0.737.g08866a6d13-goog


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [RFC PATCH 3/6] physmem: Tag guest RAMBlocks with Protection Key
  2026-08-18 20:44 [RFC PATCH 0/6] Protect VMM from speculative attacks using x86 PKRU Jacky Li
  2026-08-18 20:44 ` [RFC PATCH 1/6] x86: Introduce basic PKRU hardware wrappers Jacky Li
  2026-08-18 20:44 ` [RFC PATCH 2/6] kvm: Add guest memory Pkey initialization Jacky Li
@ 2026-08-18 20:44 ` Jacky Li
  2026-08-18 20:44 ` [RFC PATCH 4/6] kvm: Lock guest RAMBlocks via PKRU during host userspace execution Jacky Li
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 9+ messages in thread
From: Jacky Li @ 2026-08-18 20:44 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Zhao Liu, Richard Henderson,
	Philippe Mathieu-Daudé, Peter Xu, kvm, James Houghton,
	Mingwei Zhang, Dave Hansen, Brendan Jackman, Reiji Watanabe,
	Jacky Li

Apply the allocated guest memory protection key to all guest RAMBlocks
using `pkey_mprotect()` to enforce hardware-assisted access control.

This ensures that guest physical RAM mappings are tagged with our
dedicated Pkey in the host page tables, allowing the PKRU register to
dynamically permit or block access to guest memory.

Signed-off-by: Jacky Li <jackyli@google.com>
---
 include/exec/cpu-common.h |  1 +
 include/qemu/mmap-alloc.h |  5 +++++
 system/physmem.c          | 16 ++++++++++++++--
 util/mmap-alloc.c         |  3 +--
 util/pkey.c               | 16 ++++++++++++++++
 5 files changed, 37 insertions(+), 4 deletions(-)

diff --git a/include/exec/cpu-common.h b/include/exec/cpu-common.h
index 5ede0c65dc..28399088f6 100644
--- a/include/exec/cpu-common.h
+++ b/include/exec/cpu-common.h
@@ -113,4 +113,5 @@ static inline CPUState *env_cpu(CPUArchState *env)
 }
 
 void qemu_init_guest_memory_pkey(void);
+int qemu_pkey_mprotect_guest_memory(void *addr, size_t len, int prot);
 #endif /* CPU_COMMON_H */
diff --git a/include/qemu/mmap-alloc.h b/include/qemu/mmap-alloc.h
index 8344daaa03..82fe7f0c3d 100644
--- a/include/qemu/mmap-alloc.h
+++ b/include/qemu/mmap-alloc.h
@@ -63,4 +63,9 @@ void qemu_ram_munmap(int fd, void *ptr, size_t size);
  */
 #define QEMU_MAP_NORESERVE  (1 << 3)
 
+static inline int qemu_map_flags_to_prot(uint32_t qemu_map_flags)
+{
+  return PROT_READ | ((qemu_map_flags & QEMU_MAP_READONLY) ? 0 : PROT_WRITE);
+}
+
 #endif
diff --git a/system/physmem.c b/system/physmem.c
index 362a00f76c..9f5a0f194c 100644
--- a/system/physmem.c
+++ b/system/physmem.c
@@ -2144,6 +2144,12 @@ static void dirty_memory_extend(ram_addr_t new_ram_size)
     ram_list.num_dirty_blocks = new_num_blocks;
 }
 
+static inline int ramblock_get_prot(const RAMBlock *rb)
+{
+  uint32_t map_flags = (rb->flags & RAM_READONLY) ? QEMU_MAP_READONLY : 0;
+  return qemu_map_flags_to_prot(map_flags);
+}
+
 static void ram_block_add(RAMBlock *new_block, Error **errp)
 {
     const bool noreserve = qemu_ram_is_noreserve(new_block);
@@ -2282,6 +2288,13 @@ static void ram_block_add(RAMBlock *new_block, Error **errp)
         }
         ram_block_notify_add(new_block->host, new_block->used_length,
                              new_block->max_length);
+        int prot = ramblock_get_prot(new_block);
+        int ret = qemu_pkey_mprotect_guest_memory(new_block->host,
+                                                  new_block->max_length, prot);
+        if (ret != 0) {
+            error_report("qemu_pkey_mprotect failed for guest RAMBlock: %s",
+                         strerror(errno));
+        }
     }
     return;
 
@@ -2624,8 +2637,7 @@ static int qemu_ram_remap_mmap(RAMBlock *block, uint64_t start, size_t length)
     flags = MAP_FIXED | MAP_ANONYMOUS;
     flags |= block->flags & RAM_SHARED ? MAP_SHARED : MAP_PRIVATE;
     flags |= block->flags & RAM_NORESERVE ? MAP_NORESERVE : 0;
-    prot = PROT_READ;
-    prot |= block->flags & RAM_READONLY ? 0 : PROT_WRITE;
+    prot = ramblock_get_prot(block);
     area = mmap(host_startaddr, length, prot, flags, -1, 0);
     return area != host_startaddr ? -errno : 0;
 }
diff --git a/util/mmap-alloc.c b/util/mmap-alloc.c
index ed14f9c64d..0dc8e8275d 100644
--- a/util/mmap-alloc.c
+++ b/util/mmap-alloc.c
@@ -185,10 +185,9 @@ static void *mmap_activate(void *ptr, size_t size, int fd,
                            uint32_t qemu_map_flags, off_t map_offset)
 {
     const bool noreserve = qemu_map_flags & QEMU_MAP_NORESERVE;
-    const bool readonly = qemu_map_flags & QEMU_MAP_READONLY;
     const bool shared = qemu_map_flags & QEMU_MAP_SHARED;
     const bool sync = qemu_map_flags & QEMU_MAP_SYNC;
-    const int prot = PROT_READ | (readonly ? 0 : PROT_WRITE);
+    const int prot = qemu_map_flags_to_prot(qemu_map_flags);
     int map_sync_flags = 0;
     int flags = MAP_FIXED;
     void *activated_ptr;
diff --git a/util/pkey.c b/util/pkey.c
index 249e36d508..0151714f32 100644
--- a/util/pkey.c
+++ b/util/pkey.c
@@ -106,6 +106,17 @@ __attribute__((target("pku"))) void qemu_init_guest_memory_pkey(void)
     }
 }
 
+__attribute__((target("pku"))) int qemu_pkey_mprotect_guest_memory(void *addr,
+                                                                   size_t len,
+                                                                   int prot)
+{
+    int pkey = guest_memory_pkey;
+    if (pkey == -1) {
+        return 0;
+    }
+    return pkey_mprotect(addr, len, prot, pkey);
+}
+
 #else
 /* Dummy implementations for all other configurations (non-x86_64 Linux, */
 /* Windows, macOS, etc.) */
@@ -116,4 +127,9 @@ __attribute__((target("pku"))) void qemu_init_guest_memory_pkey(void)
 
 void qemu_init_guest_memory_pkey(void)
 {}
+
+int qemu_pkey_mprotect_guest_memory(void *addr, size_t len, int prot)
+{
+    return 0;
+}
 #endif

-- 
2.55.0.737.g08866a6d13-goog


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [RFC PATCH 4/6] kvm: Lock guest RAMBlocks via PKRU during host userspace execution
  2026-08-18 20:44 [RFC PATCH 0/6] Protect VMM from speculative attacks using x86 PKRU Jacky Li
                   ` (2 preceding siblings ...)
  2026-08-18 20:44 ` [RFC PATCH 3/6] physmem: Tag guest RAMBlocks with Protection Key Jacky Li
@ 2026-08-18 20:44 ` Jacky Li
  2026-08-18 20:44 ` [RFC PATCH 5/6] x86: Add xstate parsing and PKRU offset detection Jacky Li
  2026-08-18 20:44 ` [RFC PATCH 6/6] kvm: Implement SIGSEGV sentinel for Pkey recovery Jacky Li
  5 siblings, 0 replies; 9+ messages in thread
From: Jacky Li @ 2026-08-18 20:44 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Zhao Liu, Richard Henderson,
	Philippe Mathieu-Daudé, Peter Xu, kvm, James Houghton,
	Mingwei Zhang, Dave Hansen, Brendan Jackman, Reiji Watanabe,
	Jacky Li

Protect guest RAMBlocks from host-side speculative execution attacks by
locking and unlocking the guest memory protection key before and after
`KVM_RUN`.

Signed-off-by: Jacky Li <jackyli@google.com>
---
 accel/kvm/kvm-all.c       |  7 ++++++-
 include/exec/cpu-common.h |  2 ++
 system/physmem.c          |  6 +++++-
 util/pkey.c               | 45 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 83cbd120a8..ae395cd374 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -19,6 +19,7 @@
 
 #include <linux/kvm.h>
 
+#include "exec/cpu-common.h"
 #include "qemu/atomic.h"
 #include "qemu/option.h"
 #include "qemu/config-file.h"
@@ -3666,7 +3667,11 @@ int kvm_vcpu_ioctl(CPUState *cpu, unsigned long type, ...)
 
     trace_kvm_vcpu_ioctl(cpu->cpu_index, type, arg);
     accel_cpu_ioctl_begin(cpu);
-    ret = ioctl(cpu->kvm_fd, type, arg);
+    if (type == KVM_RUN) {
+        ret = qemu_pkey_kvm_run(cpu->kvm_fd, arg);
+    } else {
+        ret = ioctl(cpu->kvm_fd, type, arg);
+    }
     accel_cpu_ioctl_end(cpu);
     if (ret == -1) {
         ret = -errno;
diff --git a/include/exec/cpu-common.h b/include/exec/cpu-common.h
index 28399088f6..90047128de 100644
--- a/include/exec/cpu-common.h
+++ b/include/exec/cpu-common.h
@@ -114,4 +114,6 @@ static inline CPUState *env_cpu(CPUArchState *env)
 
 void qemu_init_guest_memory_pkey(void);
 int qemu_pkey_mprotect_guest_memory(void *addr, size_t len, int prot);
+void qemu_reset_pkey_with_ibpb(void);
+int qemu_pkey_kvm_run(int fd, void *arg);
 #endif /* CPU_COMMON_H */
diff --git a/system/physmem.c b/system/physmem.c
index 9f5a0f194c..c7101f6841 100644
--- a/system/physmem.c
+++ b/system/physmem.c
@@ -2755,7 +2755,11 @@ static void *qemu_ram_ptr_length(RAMBlock *block, ram_addr_t addr,
                                     1, lock, is_write);
     }
 
-    return ramblock_ptr(block, addr);
+    void *ptr = ramblock_ptr(block, addr);
+    if (ptr) {
+        qemu_reset_pkey_with_ibpb();
+    }
+    return ptr;
 }
 
 /*
diff --git a/util/pkey.c b/util/pkey.c
index 0151714f32..2e42da7d9c 100644
--- a/util/pkey.c
+++ b/util/pkey.c
@@ -117,17 +117,62 @@ __attribute__((target("pku"))) int qemu_pkey_mprotect_guest_memory(void *addr,
     return pkey_mprotect(addr, len, prot, pkey);
 }
 
+__attribute__((target("pku"))) void qemu_reset_pkey_with_ibpb(void)
+{
+    int pkey = guest_memory_pkey;
+    if (pkey == -1) {
+        return;
+    }
+    if (inline_pkey_get(pkey) == 0) {
+        return;
+    }
+
+    /* IBPB */
+    prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_DISABLE, 0,
+                0);
+
+    inline_pkey_set(pkey, 0);
+}
+
+__attribute__((target("pku"))) int qemu_pkey_kvm_run(int fd, void *arg)
+{
+    int pkey = guest_memory_pkey;
+    if (pkey == -1) {
+        return ioctl(fd, KVM_RUN, arg);
+    }
+
+    assert(pkey >= 0 && pkey < KEY_COUNT);
+
+    inline_pkey_set(pkey, 0);
+
+    intptr_t ret = local_syscall3(__NR_ioctl, fd, KVM_RUN, (intptr_t)arg);
+
+    inline_pkey_set(pkey, PKEY_DISABLE_ACCESS);
+
+    if (ret < 0) {
+        errno = -ret;
+        ret = -1;
+    }
+    return (int)ret;
+}
+
 #else
 /* Dummy implementations for all other configurations (non-x86_64 Linux, */
 /* Windows, macOS, etc.) */
 #if defined(CONFIG_LINUX)
 #include <linux/kvm.h>
 #include <sys/ioctl.h>
+
+int qemu_pkey_kvm_run(int fd, void *arg)
+{ return ioctl(fd, KVM_RUN, arg); }
 #endif
 
 void qemu_init_guest_memory_pkey(void)
 {}
 
+void qemu_reset_pkey_with_ibpb(void)
+{}
+
 int qemu_pkey_mprotect_guest_memory(void *addr, size_t len, int prot)
 {
     return 0;

-- 
2.55.0.737.g08866a6d13-goog


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [RFC PATCH 5/6] x86: Add xstate parsing and PKRU offset detection
  2026-08-18 20:44 [RFC PATCH 0/6] Protect VMM from speculative attacks using x86 PKRU Jacky Li
                   ` (3 preceding siblings ...)
  2026-08-18 20:44 ` [RFC PATCH 4/6] kvm: Lock guest RAMBlocks via PKRU during host userspace execution Jacky Li
@ 2026-08-18 20:44 ` Jacky Li
  2026-08-18 20:44 ` [RFC PATCH 6/6] kvm: Implement SIGSEGV sentinel for Pkey recovery Jacky Li
  5 siblings, 0 replies; 9+ messages in thread
From: Jacky Li @ 2026-08-18 20:44 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Zhao Liu, Richard Henderson,
	Philippe Mathieu-Daudé, Peter Xu, kvm, James Houghton,
	Mingwei Zhang, Dave Hansen, Brendan Jackman, Reiji Watanabe,
	Jacky Li

Add data structures and CPUID detection routines to locate the PKRU
register inside the Linux x86_64 signal frame extended state (`xstate`).

Signed-off-by: Jacky Li <jackyli@google.com>
---
 util/pkey.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/util/pkey.c b/util/pkey.c
index 2e42da7d9c..21b41f928e 100644
--- a/util/pkey.c
+++ b/util/pkey.c
@@ -88,6 +88,59 @@ static inline __attribute__((always_inline)) intptr_t local_syscall3(
     return ret;
 }
 
+/*
+ * Custom structures to parse FPU xstate and extract the PKRU register.
+ * These match the Linux kernel x86_64 signal frame ABI.
+ */
+struct fpstate_64 {
+    uint16_t cwd;
+    uint16_t swd;
+    uint16_t twd;
+    uint16_t fop;
+    uint64_t rip;
+    uint64_t rdp;
+    uint32_t mxcsr;
+    uint32_t mxcsr_mask;
+    uint32_t st_space[32];  /*  8x  FP registers, 16 bytes each */
+    uint32_t xmm_space[64]; /* 16x XMM registers, 16 bytes each */
+    uint32_t reserved1[12];
+    union {
+        uint32_t reserved2[12];
+        struct {
+            uint32_t magic1;
+            uint32_t extended_size;
+            uint64_t xstate_bv;
+            uint32_t xstate_size;
+            uint32_t padding[7];
+        } sw_reserved; /* Extended state is encoded here */
+    };
+};
+
+#define FP_XSTATE_MAGIC1 0x46505853U
+#define FP_XSTATE_MAGIC2 0x46505845U
+#define XFEATURE_PKRU 9
+#define XSTATE_PKRU (1ULL << XFEATURE_PKRU)
+
+static int pkru_offset = -2; /* -2 means uninitialized */
+
+static int __attribute__((unused)) get_pkru_offset(void)
+{
+    uint32_t eax, ebx, ecx, edx;
+    __cpuid_count(0xd, XFEATURE_PKRU, eax, ebx, ecx, edx);
+    if (ebx == 0) {
+        return -1;
+    }
+    return (int)ebx;
+}
+
+static int __attribute__((unused)) qemu_get_pkru_offset(void)
+{
+    if (pkru_offset == -2) {
+        pkru_offset = get_pkru_offset();
+    }
+    return pkru_offset;
+}
+
 __attribute__((target("pku"))) void qemu_init_guest_memory_pkey(void)
 {
     if (guest_memory_pkey != -1) {

-- 
2.55.0.737.g08866a6d13-goog


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [RFC PATCH 6/6] kvm: Implement SIGSEGV sentinel for Pkey recovery
  2026-08-18 20:44 [RFC PATCH 0/6] Protect VMM from speculative attacks using x86 PKRU Jacky Li
                   ` (4 preceding siblings ...)
  2026-08-18 20:44 ` [RFC PATCH 5/6] x86: Add xstate parsing and PKRU offset detection Jacky Li
@ 2026-08-18 20:44 ` Jacky Li
  2026-08-18 21:55   ` Dave Hansen
  5 siblings, 1 reply; 9+ messages in thread
From: Jacky Li @ 2026-08-18 20:44 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Zhao Liu, Richard Henderson,
	Philippe Mathieu-Daudé, Peter Xu, kvm, James Houghton,
	Mingwei Zhang, Dave Hansen, Brendan Jackman, Reiji Watanabe,
	Jacky Li

Implement a fallback `SIGSEGV` signal handler
(`qemu_pkey_sigsegv_handler`) to recover from legitimate host accesses
to Pkey-protected guest memory when locked.

Signed-off-by: Jacky Li <jackyli@google.com>
---
 util/pkey.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 155 insertions(+), 2 deletions(-)

diff --git a/util/pkey.c b/util/pkey.c
index 21b41f928e..689fec8003 100644
--- a/util/pkey.c
+++ b/util/pkey.c
@@ -123,7 +123,7 @@ struct fpstate_64 {
 
 static int pkru_offset = -2; /* -2 means uninitialized */
 
-static int __attribute__((unused)) get_pkru_offset(void)
+static int get_pkru_offset(void)
 {
     uint32_t eax, ebx, ecx, edx;
     __cpuid_count(0xd, XFEATURE_PKRU, eax, ebx, ecx, edx);
@@ -133,7 +133,7 @@ static int __attribute__((unused)) get_pkru_offset(void)
     return (int)ebx;
 }
 
-static int __attribute__((unused)) qemu_get_pkru_offset(void)
+static int qemu_get_pkru_offset(void)
 {
     if (pkru_offset == -2) {
         pkru_offset = get_pkru_offset();
@@ -141,6 +141,155 @@ static int __attribute__((unused)) qemu_get_pkru_offset(void)
     return pkru_offset;
 }
 
+static __attribute__((target("pku"))) bool do_qemu_pkey_sigsegv_recovery(
+    const siginfo_t *si, ucontext_t *ucontext, int pkey)
+{
+    if (pkey < 0 || pkey >= KEY_COUNT) {
+        return false;
+    }
+
+    if (!ucontext) {
+        return false;
+    }
+
+    void *fpstate = ucontext->uc_mcontext.fpregs;
+    if (fpstate == NULL) {
+        return false;
+    }
+
+    struct fpstate_64 *fpstate_64 = (struct fpstate_64 *)fpstate;
+
+    if (fpstate_64->sw_reserved.magic1 != FP_XSTATE_MAGIC1) {
+        return false;
+    }
+
+    uint32_t *magic2 =
+            (uint32_t *)((char *)fpstate + fpstate_64->sw_reserved.xstate_size);
+    if (*magic2 != FP_XSTATE_MAGIC2) {
+        return false;
+    }
+
+    if ((fpstate_64->sw_reserved.xstate_bv & XSTATE_PKRU) == 0) {
+        return false;
+    }
+
+    int pkr_offset = qemu_get_pkru_offset();
+    if (pkr_offset < 0 || pkr_offset >= fpstate_64->sw_reserved.xstate_size) {
+        return false;
+    }
+
+    uint32_t *pkru = (uint32_t *)((char *)fpstate + pkr_offset);
+
+    uint32_t access_rights = (*pkru >> (pkey * BITS_PER_KEY)) & KEY_MASK;
+    if (access_rights == 0) {
+        return false;
+    }
+
+    /*
+     * Flush microarchitectural state (IBPB) before returning to avoid
+     * speculative execution.
+     */
+    prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_DISABLE, 0,
+                0);
+
+    /*
+     * Clear bits in the saved PKRU so that access is unrestricted upon
+     * returning from the signal handler.
+     */
+    *pkru &= ~(KEY_MASK << (pkey * BITS_PER_KEY));
+
+    return true;
+}
+
+static void (*old_sigaction_func)(int, siginfo_t *, void *);
+static void (*old_sighandler_func)(int);
+
+static int pkey_recovery_handler_installed;
+static int pkey_for_recovery = -1;
+
+static void qemu_pkey_sigsegv_handler(int si_signo, siginfo_t *si,
+                                      void *raw_ucontext)
+{
+    ucontext_t *const ucontext = (ucontext_t *)raw_ucontext;
+
+    if (si_signo == SIGSEGV && si != NULL && si->si_code == SEGV_PKUERR) {
+        int pkey = qatomic_read(&pkey_for_recovery);
+        if (pkey >= 0 && do_qemu_pkey_sigsegv_recovery(si, ucontext, pkey)) {
+            return;
+        }
+    }
+
+    void (*old_sigaction)(int, siginfo_t *, void *) =
+            qatomic_read(&old_sigaction_func);
+    if (old_sigaction != NULL) {
+        old_sigaction(si_signo, si, raw_ucontext);
+        return;
+    }
+
+    void (*old_sighandler)(int) = qatomic_read(&old_sighandler_func);
+    if (old_sighandler != NULL && old_sighandler != SIG_DFL &&
+            old_sighandler != SIG_IGN)
+{
+        old_sighandler(si_signo);
+        return;
+    }
+
+    /* Fallback: abort */
+    const char msg[] = "QEMU: Received unexpected Pkey SIGSEGV\n";
+    int unused __attribute__((unused)) =
+        write(STDERR_FILENO, msg, sizeof(msg) - 1);
+    abort();
+}
+
+static void qemu_register_pkey_recovery_handler(void)
+{
+    struct sigaction old_sigact = {0};
+    struct sigaction new_sigact = {0};
+
+    if (qatomic_xchg(&pkey_recovery_handler_installed, 1)) {
+        return; /* Already installed */
+    }
+
+    if (sigaction(SIGSEGV, NULL, &old_sigact) < 0) {
+        error_report("QEMU Pkey: Failed to get current SIGSEGV handler");
+        qatomic_set(&pkey_recovery_handler_installed, 0);
+        return;
+    }
+
+    if (old_sigact.sa_flags & SA_RESETHAND) {
+        error_report(
+            "QEMU Pkey: Incompatible SA_RESETHAND flags in old handler");
+        qatomic_set(&pkey_recovery_handler_installed, 0);
+        return;
+    }
+
+    if (old_sigact.sa_flags & SA_SIGINFO) {
+        qatomic_set(&old_sigaction_func, old_sigact.sa_sigaction);
+    } else {
+        qatomic_set(&old_sighandler_func, old_sigact.sa_handler);
+    }
+
+    new_sigact = old_sigact;
+    new_sigact.sa_flags |= SA_SIGINFO;
+    new_sigact.sa_sigaction = &qemu_pkey_sigsegv_handler;
+
+    if (sigaction(SIGSEGV, &new_sigact, &old_sigact) < 0) {
+        error_report("QEMU Pkey: Failed to register SIGSEGV handler");
+        qatomic_set(&pkey_recovery_handler_installed, 0);
+        return;
+    }
+}
+
+static void qemu_add_pkey_for_recovery(int pkey)
+{
+    int expected = -1;
+    /* We only support one recovery pkey at a time */
+    if (qatomic_cmpxchg(&pkey_for_recovery, expected, pkey) != expected) {
+        error_report("QEMU Pkey: Recovery Pkey already set to %d",
+                     pkey_for_recovery);
+    }
+}
+
 __attribute__((target("pku"))) void qemu_init_guest_memory_pkey(void)
 {
     if (guest_memory_pkey != -1) {
@@ -154,6 +303,10 @@ __attribute__((target("pku"))) void qemu_init_guest_memory_pkey(void)
             error_report("pkey_alloc failed for guest memory: %s",
                          strerror(errno));
         } else {
+            /* Register recovery signal handler and add pkey for recovery. */
+            qemu_register_pkey_recovery_handler();
+            qemu_add_pkey_for_recovery(pkey);
+
             guest_memory_pkey = pkey;
         }
     }

-- 
2.55.0.737.g08866a6d13-goog


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [RFC PATCH 6/6] kvm: Implement SIGSEGV sentinel for Pkey recovery
  2026-08-18 20:44 ` [RFC PATCH 6/6] kvm: Implement SIGSEGV sentinel for Pkey recovery Jacky Li
@ 2026-08-18 21:55   ` Dave Hansen
  0 siblings, 0 replies; 9+ messages in thread
From: Dave Hansen @ 2026-08-18 21:55 UTC (permalink / raw)
  To: Jacky Li, qemu-devel
  Cc: Paolo Bonzini, Zhao Liu, Richard Henderson,
	Philippe Mathieu-Daudé, Peter Xu, kvm, James Houghton,
	Mingwei Zhang, Dave Hansen, Brendan Jackman, Reiji Watanabe

On 8/18/26 13:44, Jacky Li wrote:
> +    if ((fpstate_64->sw_reserved.xstate_bv & XSTATE_PKRU) == 0) {
> +        return false;
> +    }
Cool stuff!

A few little nits on the XSAVE handling.

It's subtle, but this wouldn't quite work for pkru when in its init
state. I guess the argument is that pkru=0 wouldn't cause a SIGSEGV in
the first place so this code wouldn't ever get run. But, if it were me,
I'd probably warn or _something_ if I ended up with
(xstate_bv&XSTATE_PKRU)==0.

I'd probably also at least _check_ for the compacted format. Basically
make sure that XCOMP_BV==0, too. This isn't strictly necessary. But it
would help me win a bet with a colleague, so would be much appreciated! ;)



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [RFC PATCH 2/6] kvm: Add guest memory Pkey initialization
  2026-08-18 20:44 ` [RFC PATCH 2/6] kvm: Add guest memory Pkey initialization Jacky Li
@ 2026-08-18 22:03   ` Dave Hansen
  0 siblings, 0 replies; 9+ messages in thread
From: Dave Hansen @ 2026-08-18 22:03 UTC (permalink / raw)
  To: Jacky Li, qemu-devel
  Cc: Paolo Bonzini, Zhao Liu, Richard Henderson,
	Philippe Mathieu-Daudé, Peter Xu, kvm, James Houghton,
	Mingwei Zhang, Dave Hansen, Brendan Jackman, Reiji Watanabe

On 8/18/26 13:44, Jacky Li wrote:
> +__attribute__((target("pku"))) void qemu_init_guest_memory_pkey(void)
> +{
> +    if (guest_memory_pkey != -1) {
> +        return;
> +    }
> +
> +    const char *enable_pkey = getenv("QEMU_ENABLE_PKEY_GUEST_MEMORY");
> +    if (enable_pkey && strcmp(enable_pkey, "1") == 0) {
> +        int pkey = pkey_alloc(0, 0);
> +        if (pkey == -1) {
> +            error_report("pkey_alloc failed for guest memory: %s",
> +                         strerror(errno));
> +        } else {
> +            guest_memory_pkey = pkey;
> +        }
> +    }
> +}

Is an environment variable a normal way of enabling qemu features? It
seems a bit unusual to me.

I would also guess that you might want some kind of an enabling mode
that lets users opportunistically enable pkeys when running on
pkey-enabled hardware, but not complain too loudly if they are unavailable.

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-08-18 22:03 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-18 20:44 [RFC PATCH 0/6] Protect VMM from speculative attacks using x86 PKRU Jacky Li
2026-08-18 20:44 ` [RFC PATCH 1/6] x86: Introduce basic PKRU hardware wrappers Jacky Li
2026-08-18 20:44 ` [RFC PATCH 2/6] kvm: Add guest memory Pkey initialization Jacky Li
2026-08-18 22:03   ` Dave Hansen
2026-08-18 20:44 ` [RFC PATCH 3/6] physmem: Tag guest RAMBlocks with Protection Key Jacky Li
2026-08-18 20:44 ` [RFC PATCH 4/6] kvm: Lock guest RAMBlocks via PKRU during host userspace execution Jacky Li
2026-08-18 20:44 ` [RFC PATCH 5/6] x86: Add xstate parsing and PKRU offset detection Jacky Li
2026-08-18 20:44 ` [RFC PATCH 6/6] kvm: Implement SIGSEGV sentinel for Pkey recovery Jacky Li
2026-08-18 21:55   ` Dave Hansen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox