From acf4d11a35d8bb546e205fe05349f60cfefbff76 Mon Sep 17 00:00:00 2001 From: Tycho Andersen Date: Thu, 16 Apr 2026 16:23:23 -0700 Subject: [PATCH 01/16] crypto/ccp: hoist kernel part of SNP_PLATFORM_STATUS ...to its own function. This way it can be used when the kernel needs access to the platform status regardless of the INIT state of the firmware. No functional change intended. Cc: Herbert Xu Signed-off-by: Tycho Andersen (AMD) Acked-by: Herbert Xu Reviewed-by: Tom Lendacky Tested-by: Tycho Andersen (AMD) Link: https://patch.msgid.link/20260416232329.3408497-2-seanjc@google.com Signed-off-by: Sean Christopherson --- drivers/crypto/ccp/sev-dev.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c index d1e9e0ac63b6..22bc4ef27a63 100644 --- a/drivers/crypto/ccp/sev-dev.c +++ b/drivers/crypto/ccp/sev-dev.c @@ -2381,7 +2381,8 @@ static int sev_ioctl_do_pdh_export(struct sev_issue_cmd *argp, bool writable) return ret; } -static int sev_ioctl_do_snp_platform_status(struct sev_issue_cmd *argp) +static int __sev_do_snp_platform_status(struct sev_user_data_snp_status *status, + int *error) { struct sev_device *sev = psp_master->sev_data; struct sev_data_snp_addr buf; @@ -2389,9 +2390,6 @@ static int sev_ioctl_do_snp_platform_status(struct sev_issue_cmd *argp) void *data; int ret; - if (!argp->data) - return -EINVAL; - status_page = alloc_page(GFP_KERNEL_ACCOUNT); if (!status_page) return -ENOMEM; @@ -2414,7 +2412,7 @@ static int sev_ioctl_do_snp_platform_status(struct sev_issue_cmd *argp) } buf.address = __psp_pa(data); - ret = __sev_do_cmd_locked(SEV_CMD_SNP_PLATFORM_STATUS, &buf, &argp->error); + ret = __sev_do_cmd_locked(SEV_CMD_SNP_PLATFORM_STATUS, &buf, error); if (sev->snp_initialized) { /* @@ -2429,15 +2427,32 @@ static int sev_ioctl_do_snp_platform_status(struct sev_issue_cmd *argp) if (ret) goto cleanup; - if (copy_to_user((void __user *)argp->data, data, - sizeof(struct sev_user_data_snp_status))) - ret = -EFAULT; + memcpy(status, data, sizeof(*status)); cleanup: __free_pages(status_page, 0); return ret; } +static int sev_ioctl_do_snp_platform_status(struct sev_issue_cmd *argp) +{ + struct sev_user_data_snp_status status; + int ret; + + if (!argp->data) + return -EINVAL; + + ret = __sev_do_snp_platform_status(&status, &argp->error); + if (ret < 0) + return ret; + + if (copy_to_user((void __user *)argp->data, &status, + sizeof(struct sev_user_data_snp_status))) + ret = -EFAULT; + + return ret; +} + static int sev_ioctl_do_snp_commit(struct sev_issue_cmd *argp) { struct sev_device *sev = psp_master->sev_data; From 4b28f0846ef6521b47ee95ea7d77c9d40a7baf29 Mon Sep 17 00:00:00 2001 From: Tycho Andersen Date: Thu, 16 Apr 2026 16:23:24 -0700 Subject: [PATCH 02/16] crypto/ccp: export firmware supported vm types In some configurations, the firmware does not support all VM types. The SEV firmware has an entry in the TCB_VERSION structure referred to as the Security Version Number in the SEV-SNP firmware specification and referred to as the "SPL" in SEV firmware release notes. The SEV firmware release notes say: On every SEV firmware release where a security mitigation has been added, the SNP SPL gets increased by 1. This is to let users know that it is important to update to this version. The SEV firmware release that fixed CVE-2025-48514 by disabling SEV-ES support on vulnerable platforms has this SVN increased to reflect the fix. The SVN is platform-specific, as is the structure of TCB_VERSION. Check CURRENT_TCB instead of REPORTED_TCB, since the firmware behaves with the CURRENT_TCB SVN level and will reject SEV-ES VMs accordingly. Parse the SVN, and mask off the SEV_ES supported VM type from the list of supported types if it is above the per-platform threshold for the relevant platforms. Signed-off-by: Tycho Andersen (AMD) Acked-by: Herbert Xu Reviewed-by: Tom Lendacky Tested-by: Tycho Andersen (AMD) Link: https://patch.msgid.link/20260416232329.3408497-3-seanjc@google.com Signed-off-by: Sean Christopherson --- drivers/crypto/ccp/sev-dev.c | 70 ++++++++++++++++++++++++++++++++++++ include/linux/psp-sev.h | 37 +++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c index 22bc4ef27a63..7cd6cd6fdb10 100644 --- a/drivers/crypto/ccp/sev-dev.c +++ b/drivers/crypto/ccp/sev-dev.c @@ -2954,3 +2954,73 @@ void sev_pci_exit(void) sev_firmware_shutdown(sev); } + +static int get_v1_svn(struct sev_device *sev) +{ + struct sev_snp_tcb_version_genoa_milan *tcb; + struct sev_user_data_snp_status status; + int ret, error = 0; + + mutex_lock(&sev_cmd_mutex); + ret = __sev_do_snp_platform_status(&status, &error); + mutex_unlock(&sev_cmd_mutex); + if (ret < 0) + return ret; + + tcb = (struct sev_snp_tcb_version_genoa_milan *)&status + .current_tcb_version; + return tcb->snp; +} + +static int get_v2_svn(struct sev_device *sev) +{ + struct sev_user_data_snp_status status; + struct sev_snp_tcb_version_turin *tcb; + int ret, error = 0; + + mutex_lock(&sev_cmd_mutex); + ret = __sev_do_snp_platform_status(&status, &error); + mutex_unlock(&sev_cmd_mutex); + if (ret < 0) + return ret; + + tcb = (struct sev_snp_tcb_version_turin *)&status + .current_tcb_version; + return tcb->snp; +} + +static bool sev_firmware_allows_es(struct sev_device *sev) +{ + /* Documented in AMD-SB-3023 */ + if (boot_cpu_has(X86_FEATURE_ZEN4) || boot_cpu_has(X86_FEATURE_ZEN3)) + return get_v1_svn(sev) < 0x1b; + else if (boot_cpu_has(X86_FEATURE_ZEN5)) + return get_v2_svn(sev) < 0x4; + else + return true; +} + +int sev_firmware_supported_vm_types(void) +{ + int supported_vm_types = 0; + struct sev_device *sev; + + if (!psp_master || !psp_master->sev_data) + return supported_vm_types; + sev = psp_master->sev_data; + + supported_vm_types |= BIT(KVM_X86_SEV_VM); + supported_vm_types |= BIT(KVM_X86_SEV_ES_VM); + + if (!sev->snp_initialized) + return supported_vm_types; + + supported_vm_types |= BIT(KVM_X86_SNP_VM); + + if (!sev_firmware_allows_es(sev)) + supported_vm_types &= ~BIT(KVM_X86_SEV_ES_VM); + + return supported_vm_types; + +} +EXPORT_SYMBOL_FOR_MODULES(sev_firmware_supported_vm_types, "kvm-amd"); diff --git a/include/linux/psp-sev.h b/include/linux/psp-sev.h index d5099a2baca5..ce16bbc0b308 100644 --- a/include/linux/psp-sev.h +++ b/include/linux/psp-sev.h @@ -902,6 +902,42 @@ struct snp_feature_info { /* Feature bits in EBX */ #define SNP_SEV_TIO_SUPPORTED BIT(1) +/** + * struct sev_snp_tcb_version_genoa_milan + * + * @boot_loader: SVN of PSP bootloader + * @tee: SVN of PSP operating system + * @reserved: reserved + * @snp: SVN of SNP firmware + * @microcode: Lowest current patch level of all cores + */ +struct sev_snp_tcb_version_genoa_milan { + u8 boot_loader; + u8 tee; + u8 reserved[4]; + u8 snp; + u8 microcode; +}; + +/** + * struct sev_snp_tcb_version_turin + * + * @fmc: SVN of FMC firmware + * @boot_loader: SVN of PSP bootloader + * @tee: SVN of PSP operating system + * @snp: SVN of SNP firmware + * @reserved: reserved + * @microcode: Lowest current patch level of all cores + */ +struct sev_snp_tcb_version_turin { + u8 fmc; + u8 boot_loader; + u8 tee; + u8 snp; + u8 reserved[3]; + u8 microcode; +}; + #ifdef CONFIG_CRYPTO_DEV_SP_PSP /** @@ -1048,6 +1084,7 @@ void snp_free_firmware_page(void *addr); void sev_platform_shutdown(void); bool sev_is_snp_ciphertext_hiding_supported(void); u64 sev_get_snp_policy_bits(void); +int sev_firmware_supported_vm_types(void); #else /* !CONFIG_CRYPTO_DEV_SP_PSP */ From c2a02db765af244a9a221aac386245ed4513fb81 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 16 Apr 2026 16:23:25 -0700 Subject: [PATCH 03/16] KVM: SEV: Set supported SEV+ VM types during sev_hardware_setup() Set the supported SEV+ VM types during sev_hardware_setup() instead of waiting until sev_set_cpu_caps(). This will using the set of *fully* supported VM types to print the enabled/unusable/disabled messaged. For all intents and purposes, no functional change intended. Reviewed-by: Tom Lendacky Tested-by: Tycho Andersen (AMD) Link: https://patch.msgid.link/20260416232329.3408497-4-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 940b97d4a852..e5e7d6a99220 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -3013,18 +3013,14 @@ void sev_vm_destroy(struct kvm *kvm) void __init sev_set_cpu_caps(void) { - if (sev_enabled) { + if (sev_enabled) kvm_cpu_cap_set(X86_FEATURE_SEV); - kvm_caps.supported_vm_types |= BIT(KVM_X86_SEV_VM); - } - if (sev_es_enabled) { + + if (sev_es_enabled) kvm_cpu_cap_set(X86_FEATURE_SEV_ES); - kvm_caps.supported_vm_types |= BIT(KVM_X86_SEV_ES_VM); - } - if (sev_snp_enabled) { + + if (sev_snp_enabled) kvm_cpu_cap_set(X86_FEATURE_SEV_SNP); - kvm_caps.supported_vm_types |= BIT(KVM_X86_SNP_VM); - } } static bool is_sev_snp_initialized(void) @@ -3194,6 +3190,13 @@ void __init sev_hardware_setup(void) } } + if (sev_supported) + kvm_caps.supported_vm_types |= BIT(KVM_X86_SEV_VM); + if (sev_es_supported) + kvm_caps.supported_vm_types |= BIT(KVM_X86_SEV_ES_VM); + if (sev_snp_supported) + kvm_caps.supported_vm_types |= BIT(KVM_X86_SNP_VM); + if (boot_cpu_has(X86_FEATURE_SEV)) pr_info("SEV %s (ASIDs %u - %u)\n", sev_supported ? min_sev_asid <= max_sev_asid ? "enabled" : From 82bf8282444c03a8a61b718b3ba3582b0481e970 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 16 Apr 2026 16:23:26 -0700 Subject: [PATCH 04/16] KVM: SEV: Consolidate logic for printing state of SEV{,-ES,-SNP} enabling Add a helper to print enabled/unusable/disabled for SEV+ VM types in anticipation of SNP also being subjecting to "unusable" logic. No functional change intended. Reviewed-by: Tom Lendacky Tested-by: Tycho Andersen (AMD) Link: https://patch.msgid.link/20260416232329.3408497-5-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index e5e7d6a99220..c4bbe49bb8c1 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -3050,6 +3050,11 @@ static bool is_sev_snp_initialized(void) return initialized; } +static const char * __init sev_str_feature_state(bool is_supported, bool is_usable) +{ + return is_supported ? is_usable ? "enabled" : "unusable" : "disabled"; +} + void __init sev_hardware_setup(void) { unsigned int eax, ebx, ecx, edx, sev_asid_count, sev_es_asid_count; @@ -3199,19 +3204,15 @@ void __init sev_hardware_setup(void) if (boot_cpu_has(X86_FEATURE_SEV)) pr_info("SEV %s (ASIDs %u - %u)\n", - sev_supported ? min_sev_asid <= max_sev_asid ? "enabled" : - "unusable" : - "disabled", + sev_str_feature_state(sev_supported, min_sev_asid <= max_sev_asid), min_sev_asid, max_sev_asid); if (boot_cpu_has(X86_FEATURE_SEV_ES)) pr_info("SEV-ES %s (ASIDs %u - %u)\n", - sev_es_supported ? min_sev_es_asid <= max_sev_es_asid ? "enabled" : - "unusable" : - "disabled", + sev_str_feature_state(sev_es_supported, min_sev_es_asid <= max_sev_es_asid), min_sev_es_asid, max_sev_es_asid); if (boot_cpu_has(X86_FEATURE_SEV_SNP)) pr_info("SEV-SNP %s (ASIDs %u - %u)\n", - str_enabled_disabled(sev_snp_supported), + sev_str_feature_state(sev_snp_supported, true), min_snp_asid, max_snp_asid); sev_enabled = sev_supported; From 93d1a486e1d4f05db65b36db5fe2bca3d0257bb0 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Thu, 16 Apr 2026 16:23:27 -0700 Subject: [PATCH 05/16] KVM: SEV: Don't advertise support for unusable VM types Commit 0aa6b90ef9d7 ("KVM: SVM: Add support for allowing zero SEV ASIDs") made it possible to make it impossible to use SEV VMs by not allocating them any ASIDs. Commit 6c7c620585c6 ("KVM: SEV: Add SEV-SNP CipherTextHiding support") did the same thing for SEV-ES. Do not export KVM_X86_SEV(_ES)_VM as supported types if in either of these situations, so that userspace can use them to determine what is actually supported by the current kernel configuration. Also move the buildup to a local variable so it is easier to add additional masking in future patches. Link: https://lore.kernel.org/all/aZyLIWtffvEnmtYh@google.com/ Suggested-by: Sean Christopherson Signed-off-by: Tycho Andersen (AMD) [sean: land code in sev_hardware_setup()] Reviewed-by: Tom Lendacky Tested-by: Tycho Andersen (AMD) Link: https://patch.msgid.link/20260416232329.3408497-6-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index c4bbe49bb8c1..105d95034cae 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -3062,6 +3062,7 @@ void __init sev_hardware_setup(void) bool sev_snp_supported = false; bool sev_es_supported = false; bool sev_supported = false; + u32 vm_types = 0; if (!sev_enabled || !npt_enabled || !nrips) goto out; @@ -3195,24 +3196,26 @@ void __init sev_hardware_setup(void) } } - if (sev_supported) - kvm_caps.supported_vm_types |= BIT(KVM_X86_SEV_VM); - if (sev_es_supported) - kvm_caps.supported_vm_types |= BIT(KVM_X86_SEV_ES_VM); + if (sev_supported && min_sev_asid <= max_sev_asid) + vm_types |= BIT(KVM_X86_SEV_VM); + if (sev_es_supported && min_sev_es_asid <= max_sev_es_asid) + vm_types |= BIT(KVM_X86_SEV_ES_VM); if (sev_snp_supported) - kvm_caps.supported_vm_types |= BIT(KVM_X86_SNP_VM); + vm_types |= BIT(KVM_X86_SNP_VM); + + kvm_caps.supported_vm_types |= vm_types; if (boot_cpu_has(X86_FEATURE_SEV)) pr_info("SEV %s (ASIDs %u - %u)\n", - sev_str_feature_state(sev_supported, min_sev_asid <= max_sev_asid), + sev_str_feature_state(sev_supported, vm_types & BIT(KVM_X86_SEV_VM)), min_sev_asid, max_sev_asid); if (boot_cpu_has(X86_FEATURE_SEV_ES)) pr_info("SEV-ES %s (ASIDs %u - %u)\n", - sev_str_feature_state(sev_es_supported, min_sev_es_asid <= max_sev_es_asid), + sev_str_feature_state(sev_es_supported, vm_types & BIT(KVM_X86_SEV_ES_VM)), min_sev_es_asid, max_sev_es_asid); if (boot_cpu_has(X86_FEATURE_SEV_SNP)) pr_info("SEV-SNP %s (ASIDs %u - %u)\n", - sev_str_feature_state(sev_snp_supported, true), + sev_str_feature_state(sev_snp_supported, vm_types & BIT(KVM_X86_SNP_VM)), min_snp_asid, max_snp_asid); sev_enabled = sev_supported; From d8355a92df1f016bcb2fdb0cc9fc7bd13b6588dc Mon Sep 17 00:00:00 2001 From: Tycho Andersen Date: Thu, 16 Apr 2026 16:23:28 -0700 Subject: [PATCH 06/16] KVM: SEV: Don't advertise VM types that are disabled by firmware As called out in a footnote for a recent SNP vulnerability[1], it is possible for a specific flavor of SEV+ to be disabled by the firmware even when the flavor is fully supported by the CPU and platform: Applying mitigation CVE-2025-48514 will result in disabling SEV-ES when SEV-SNP is enabled. Restrict KVM's set of supported VM types based on the VM types that are fully supported by firmware to avoid over-reporting what KVM can actually support. Like KVM's handling of ASID space exhaustion, don't modify KVM's CPUID capabilities, as the CPU/platform still supports the underlying technology and clearing e.g. SEV_ES while advertising SEV_SNP would confuse KVM and userspace. Link: https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3023.html [1] Link: https://lore.kernel.org/all/aZyLIWtffvEnmtYh@google.com Suggested-by: Sean Christopherson Signed-off-by: Tycho Andersen (AMD) [sean: rewrite changelog to provide details on why/how this can happen] Reviewed-by: Tom Lendacky Tested-by: Tycho Andersen (AMD) Link: https://patch.msgid.link/20260416232329.3408497-7-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 105d95034cae..145d0c54d955 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -3202,6 +3202,7 @@ void __init sev_hardware_setup(void) vm_types |= BIT(KVM_X86_SEV_ES_VM); if (sev_snp_supported) vm_types |= BIT(KVM_X86_SNP_VM); + vm_types &= sev_firmware_supported_vm_types(); kvm_caps.supported_vm_types |= vm_types; From accb7f3a63846e6685dc7cdce0307c645515db98 Mon Sep 17 00:00:00 2001 From: Tycho Andersen Date: Thu, 16 Apr 2026 16:23:29 -0700 Subject: [PATCH 07/16] KVM: selftests: Teach sev_*_test about revoking VM types Instead of using CPUID, use the VM type bit to determine support, since those now reflect the correct status of support by the kernel and firmware configurations. Suggested-by: Sean Christopherson Signed-off-by: Tycho Andersen (AMD) Tested-by: Tycho Andersen (AMD) Link: https://patch.msgid.link/20260416232329.3408497-8-seanjc@google.com Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/x86/sev_init2_tests.c | 14 ++++++-------- .../testing/selftests/kvm/x86/sev_migrate_tests.c | 2 +- tools/testing/selftests/kvm/x86/sev_smoke_test.c | 4 ++-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/tools/testing/selftests/kvm/x86/sev_init2_tests.c b/tools/testing/selftests/kvm/x86/sev_init2_tests.c index 8eeba2327c7c..8db88c355f16 100644 --- a/tools/testing/selftests/kvm/x86/sev_init2_tests.c +++ b/tools/testing/selftests/kvm/x86/sev_init2_tests.c @@ -136,16 +136,14 @@ int main(int argc, char *argv[]) kvm_check_cap(KVM_CAP_VM_TYPES), 1 << KVM_X86_SEV_VM); TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_VM)); - have_sev_es = kvm_cpu_has(X86_FEATURE_SEV_ES); + have_sev_es = kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_ES_VM); - TEST_ASSERT(have_sev_es == !!(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_ES_VM)), - "sev-es: KVM_CAP_VM_TYPES (%x) does not match cpuid (checking %x)", - kvm_check_cap(KVM_CAP_VM_TYPES), 1 << KVM_X86_SEV_ES_VM); + TEST_ASSERT(!have_sev_es || kvm_cpu_has(X86_FEATURE_SEV_ES), + "sev-es: SEV_ES_VM supported without SEV_ES in CPUID"); - have_snp = kvm_cpu_has(X86_FEATURE_SEV_SNP); - TEST_ASSERT(have_snp == !!(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SNP_VM)), - "sev-snp: KVM_CAP_VM_TYPES (%x) indicates SNP support (bit %d), but CPUID does not", - kvm_check_cap(KVM_CAP_VM_TYPES), KVM_X86_SNP_VM); + have_snp = kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SNP_VM); + TEST_ASSERT(!have_snp || kvm_cpu_has(X86_FEATURE_SEV_SNP), + "sev-snp: SNP_VM supported without SEV_SNP in CPUID"); test_vm_types(); diff --git a/tools/testing/selftests/kvm/x86/sev_migrate_tests.c b/tools/testing/selftests/kvm/x86/sev_migrate_tests.c index 6b0928e69051..42bc023d5193 100644 --- a/tools/testing/selftests/kvm/x86/sev_migrate_tests.c +++ b/tools/testing/selftests/kvm/x86/sev_migrate_tests.c @@ -374,7 +374,7 @@ int main(int argc, char *argv[]) TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SEV)); - have_sev_es = kvm_cpu_has(X86_FEATURE_SEV_ES); + have_sev_es = kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_ES_VM); if (kvm_has_cap(KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM)) { test_sev_migrate_from(/* es= */ false); diff --git a/tools/testing/selftests/kvm/x86/sev_smoke_test.c b/tools/testing/selftests/kvm/x86/sev_smoke_test.c index 1a49ee391586..6b2cbe2a90b7 100644 --- a/tools/testing/selftests/kvm/x86/sev_smoke_test.c +++ b/tools/testing/selftests/kvm/x86/sev_smoke_test.c @@ -249,10 +249,10 @@ int main(int argc, char *argv[]) test_sev_smoke(guest_sev_code, KVM_X86_SEV_VM, 0); - if (kvm_cpu_has(X86_FEATURE_SEV_ES)) + if (kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SEV_ES_VM)) test_sev_smoke(guest_sev_es_code, KVM_X86_SEV_ES_VM, SEV_POLICY_ES); - if (kvm_cpu_has(X86_FEATURE_SEV_SNP)) + if (kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SNP_VM)) test_sev_smoke(guest_snp_code, KVM_X86_SNP_VM, snp_default_policy()); return 0; From 78ee2d50185a037b3d2452a97f3dad69c3f7f389 Mon Sep 17 00:00:00 2001 From: Ashutosh Desai Date: Fri, 1 May 2026 13:35:32 -0700 Subject: [PATCH 08/16] KVM: SVM: Fix page overflow in sev_dbg_crypt() for ENCRYPT path In sev_dbg_crypt(), the per-iteration transfer length is bounded by the source page offset (PAGE_SIZE - s_off) but not by the destination page offset (PAGE_SIZE - d_off). When d_off > s_off, the encrypt path (__sev_dbg_encrypt_user) performs a read-modify-write using a single-page intermediate buffer (dst_tpage): 1. __sev_dbg_decrypt() expands the size to round_up(len + (d_off & 15), 16) before issuing the PSP command. If len + (d_off & 15) > PAGE_SIZE, the PSP writes beyond the end of the 4096-byte dst_tpage allocation. 2. The subsequent memcpy()/copy_from_user() into page_address(dst_tpage) + (d_off & 15) of 'len' bytes overflows by up to 15 bytes under the same condition. Trigger example: s_off = 0, d_off = 1, debug.len = PAGE_SIZE - the PSP is instructed to write round_up(4097, 16) = 4112 bytes to a 4096-byte buffer. Fix by also bounding len by (PAGE_SIZE - d_off), the same check that sev_send_update_data() already performs for its single-page guest region. ================================================================== BUG: KASAN: slab-use-after-free in sev_dbg_crypt+0x993/0xd10 [kvm_amd] Write of size 4095 at addr ff110062293bb009 by task sev_dbg_test/228214 CPU: 96 UID: 0 PID: 228214 Comm: sev_dbg_test Tainted: G U W 7.0.0-smp--5ce9b0c48211-dbg #156 PREEMPTLAZY Tainted: [U]=USER, [W]=WARN Hardware name: Google Astoria/astoria, BIOS 0.20250817.1-0 08/25/2025 Call Trace: dump_stack_lvl+0x54/0x70 print_report+0xbc/0x260 kasan_report+0xa2/0xd0 kasan_check_range+0x25f/0x2c0 __asan_memcpy+0x40/0x70 sev_dbg_crypt+0x993/0xd10 [kvm_amd] sev_mem_enc_ioctl+0x33c/0x450 [kvm_amd] kvm_vm_ioctl+0x65d/0x6d0 [kvm] __se_sys_ioctl+0xb2/0x100 do_syscall_64+0xe8/0x870 entry_SYSCALL_64_after_hwframe+0x4b/0x53 The buggy address belongs to the physical page: page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x7fe72b6a0 pfn:0x62293bb memcg:ff11000112827d82 flags: 0x1400000000000000(node=1|zone=1) raw: 1400000000000000 0000000000000000 dead000000000122 0000000000000000 raw: 00000007fe72b6a0 0000000000000000 00000001ffffffff ff11000112827d82 page dumped because: kasan: bad access detected Memory state around the buggy address: ff110062293bbf00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff110062293bbf80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >ff110062293bc000: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ^ ff110062293bc080: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ff110062293bc100: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ================================================================== Disabling lock debugging due to kernel taint Fixes: 24f41fb23a39 ("KVM: SVM: Add support for SEV DEBUG_DECRYPT command") Fixes: 7d1594f5d94b ("KVM: SVM: Add support for SEV DEBUG_ENCRYPT command") Cc: stable@vger.kernel.org Signed-off-by: Ashutosh Desai [sean: add sample KASAN splat, Fixes, and stable@] Link: https://patch.msgid.link/20260501203537.2120074-2-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 145d0c54d955..00d3bab091d2 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -1396,6 +1396,7 @@ static int sev_dbg_crypt(struct kvm *kvm, struct kvm_sev_cmd *argp, bool dec) s_off = vaddr & ~PAGE_MASK; d_off = dst_vaddr & ~PAGE_MASK; len = min_t(size_t, (PAGE_SIZE - s_off), size); + len = min_t(size_t, len, PAGE_SIZE - d_off); if (dec) ret = __sev_dbg_decrypt_user(kvm, From 6edd35e77a42ee3e72405619049a45bf368f9ab2 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 1 May 2026 13:35:33 -0700 Subject: [PATCH 09/16] KVM: selftests: Add a test to verify SEV {en,de}crypt debug ioctls Add a selftest to verify KVM's handling of {de,en}crypt debug ioctls, specifically focusing on edge cases around the chunk (16 bytes) and page (4096) sizes, where KVM had multiple bugs. E.g. KVM would fail to handle small sizes that aren't naturally aligned and sized, would buffer overflow if the destination was unaligned but the source was not, etc. Attempt to strike a balance between an exhaustive test and a reasonable runtime. On a system with both SEV and SEV-ES support, the current runtime is under 45 seconds. Which isn't great, but it's tolerable, and it's not obvious which of the combinations are "better" than the others. Link: https://patch.msgid.link/20260501203537.2120074-3-seanjc@google.com Signed-off-by: Sean Christopherson --- tools/testing/selftests/kvm/Makefile.kvm | 1 + tools/testing/selftests/kvm/include/x86/sev.h | 24 ++++ .../testing/selftests/kvm/x86/sev_dbg_test.c | 118 ++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 tools/testing/selftests/kvm/x86/sev_dbg_test.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 9118a5a51b89..82fa943b9503 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -140,6 +140,7 @@ TEST_GEN_PROGS_x86 += x86/tsc_msrs_test TEST_GEN_PROGS_x86 += x86/vmx_pmu_caps_test TEST_GEN_PROGS_x86 += x86/xen_shinfo_test TEST_GEN_PROGS_x86 += x86/xen_vmcall_test +TEST_GEN_PROGS_x86 += x86/sev_dbg_test TEST_GEN_PROGS_x86 += x86/sev_init2_tests TEST_GEN_PROGS_x86 += x86/sev_migrate_tests TEST_GEN_PROGS_x86 += x86/sev_smoke_test diff --git a/tools/testing/selftests/kvm/include/x86/sev.h b/tools/testing/selftests/kvm/include/x86/sev.h index 1af44c151d60..dec383e59a47 100644 --- a/tools/testing/selftests/kvm/include/x86/sev.h +++ b/tools/testing/selftests/kvm/include/x86/sev.h @@ -144,4 +144,28 @@ static inline void snp_launch_update_data(struct kvm_vm *vm, gpa_t gpa, vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_UPDATE, &update_data); } +static inline void sev_dbg_crypt_memory(struct kvm_vm *vm, unsigned int cmd, + void *dst, void *src, unsigned int len) +{ + struct kvm_sev_dbg dbg = { + .src_uaddr = (unsigned long)src, + .dst_uaddr = (unsigned long)dst, + .len = len, + }; + + vm_sev_ioctl(vm, cmd, &dbg); +} + +static inline void sev_decrypt_memory(struct kvm_vm *vm, void *dst, void *src, + unsigned int len) +{ + sev_dbg_crypt_memory(vm, KVM_SEV_DBG_DECRYPT, dst, src, len); +} + +static inline void sev_encrypt_memory(struct kvm_vm *vm, void *dst, void *src, + unsigned int len) +{ + sev_dbg_crypt_memory(vm, KVM_SEV_DBG_ENCRYPT, dst, src, len); +} + #endif /* SELFTEST_KVM_SEV_H */ diff --git a/tools/testing/selftests/kvm/x86/sev_dbg_test.c b/tools/testing/selftests/kvm/x86/sev_dbg_test.c new file mode 100644 index 000000000000..a9d8e4c059f9 --- /dev/null +++ b/tools/testing/selftests/kvm/x86/sev_dbg_test.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include + +#include "test_util.h" +#include "kvm_util.h" +#include "processor.h" +#include "sev.h" + +#define BUFFER_SIZE (PAGE_SIZE * 2) + +static u8 *data; +static u8 src[BUFFER_SIZE] __aligned(PAGE_SIZE); +static u8 dst[BUFFER_SIZE] __aligned(PAGE_SIZE); + +static void validate_dst(int i, int nr_bytes, u8 pattern) +{ + for ( ; i < nr_bytes; i++) + TEST_ASSERT(dst[i] == pattern, + "Expected 0x%x at byte %u, got 0x%x", + pattern, i, dst[i]); +} + +static void validate_buffers(void) +{ + int i; + + for (i = 0; i < BUFFER_SIZE; i++) + TEST_ASSERT(src[i] == dst[i], + "Expected src[%u] (0x%x) == dst[%u] (0x%x)", + i, src[i], i, dst[i]); +} + +static void ____test_sev_dbg(struct kvm_vm *vm, int i, int j, int nr_bytes) +{ + u8 pattern = guest_random_u32(&guest_rng); + + if (i + nr_bytes > BUFFER_SIZE || j + nr_bytes > BUFFER_SIZE) + return; + + memset(&src[i], pattern, nr_bytes); + sev_encrypt_memory(vm, &data[j], &src[i], nr_bytes); + sev_decrypt_memory(vm, &dst[i], &data[j], nr_bytes); + validate_buffers(); + validate_dst(i, nr_bytes, pattern); +} + +static void __test_sev_dbg(struct kvm_vm *vm, int nr_bytes) +{ + /* + * In a perfect world, all sizes at all combinations within the buffers + * would be tested. In reality, even this much testing is quite slow. + * Target sizes and offsets around the chunk (16 bytes) and page (4096 + * bytes) sizes. + */ + int x[] = { 1, 8, 15, 16, 23 }; + int p = PAGE_SIZE - 24; + int i, j; + + ____test_sev_dbg(vm, 0, 0, nr_bytes); + + for (i = 0; i < ARRAY_SIZE(x); i++) { + for (j = 0; j < ARRAY_SIZE(x); j++) { + ____test_sev_dbg(vm, x[i], x[j], nr_bytes); + ____test_sev_dbg(vm, x[i], p + x[j], nr_bytes); + ____test_sev_dbg(vm, p + x[i], x[j], nr_bytes); + ____test_sev_dbg(vm, p + x[i], p + x[j], nr_bytes); + } + } +} + +static void test_sev_dbg(u32 type, u64 policy) +{ + int sizes[] = { 1, 8, 15, 16, 17, 32, 33 }; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + int i; + + if (!(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(type))) + return; + + vm = vm_sev_create_with_one_vcpu(type, NULL, &vcpu); + + data = addr_gva2hva(vm, vm_alloc(vm, BUFFER_SIZE, KVM_UTIL_MIN_VADDR)); + memset(data, 0xaa, BUFFER_SIZE); + + vm_sev_launch(vm, policy, NULL); + + sev_decrypt_memory(vm, dst, data, BUFFER_SIZE); + validate_dst(0, BUFFER_SIZE, 0xaa); + + memset(src, 0x55, BUFFER_SIZE); + sev_encrypt_memory(vm, data, src, BUFFER_SIZE); + sev_decrypt_memory(vm, dst, data, BUFFER_SIZE); + validate_dst(0, BUFFER_SIZE, 0x55); + + __test_sev_dbg(vm, PAGE_SIZE); + + for (i = 0; i < ARRAY_SIZE(sizes); i++) { + __test_sev_dbg(vm, sizes[i]); + __test_sev_dbg(vm, PAGE_SIZE - sizes[i]); + __test_sev_dbg(vm, PAGE_SIZE + sizes[i]); + __test_sev_dbg(vm, BUFFER_SIZE - sizes[i]); + } + + kvm_vm_free(vm); +} + +int main(int argc, char *argv[]) +{ + TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SEV)); + + /* Note, KVM doesn't support {de,en}crypt commands for SNP. */ + test_sev_dbg(KVM_X86_SEV_VM, 0); + test_sev_dbg(KVM_X86_SEV_ES_VM, SEV_POLICY_ES); + return 0; +} From cb32e895546bf45ebdd0e76ae7a7d783eed2c304 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 1 May 2026 13:35:34 -0700 Subject: [PATCH 10/16] KVM: SEV: Explicitly validate the dst buffer for debug operations When encrypting/decrypting guest memory, explicitly check that the destination is non-NULL and doesn't wrap instead of subtly relying on sev_pin_memory() to perform the check. This will allow adding and using a more focused single-page pinning helper. Link: https://patch.msgid.link/20260501203537.2120074-4-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 00d3bab091d2..9e9b8ca2e9f7 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -1357,9 +1357,11 @@ static int sev_dbg_crypt(struct kvm *kvm, struct kvm_sev_cmd *argp, bool dec) if (copy_from_user(&debug, u64_to_user_ptr(argp->data), sizeof(debug))) return -EFAULT; - if (!debug.len || debug.src_uaddr + debug.len < debug.src_uaddr) + if (!debug.len || !debug.src_uaddr || !debug.dst_uaddr) return -EINVAL; - if (!debug.dst_uaddr) + + if (debug.src_uaddr + debug.len < debug.src_uaddr || + debug.dst_uaddr + debug.len < debug.dst_uaddr) return -EINVAL; vaddr = debug.src_uaddr; From 99694add4af382a8fd594e3fc8d02342b1afde02 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 1 May 2026 13:35:35 -0700 Subject: [PATCH 11/16] KVM: SEV: Add helper function to pin/unpin a single page Add helpers to pin/unpin a single page, and use it in all flows that pin exactly one page. None of the single-page users actually check that the correct number of pages was pinned, which is functionally ok, but visually jarring, especially in the decrypt/encrypt flow, which separately pins two pages, but uses a single variable to track how pages were pinned each time. Again, it's functionally ok since core mm guarantees exactly one page will be pinned on success, but it's ugly. Opportunistically use page_to_phys() instead of open coding an equivalent via page_to_pfn(). Note, all users of the single-page helper pre-validate the address and length, i.e. don't rely on the sanity check in sev_pin_memory(). No functional change intended. Link: https://patch.msgid.link/20260501203537.2120074-5-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 145 +++++++++++++++++++++++++---------------- 1 file changed, 90 insertions(+), 55 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 9e9b8ca2e9f7..2d0f5a802ae1 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -720,14 +720,50 @@ static int sev_launch_start(struct kvm *kvm, struct kvm_sev_cmd *argp) return ret; } +static int sev_check_pin_count(struct kvm *kvm, unsigned long npages) +{ + unsigned long total_npages, lock_limit; + + total_npages = to_kvm_sev_info(kvm)->pages_locked + npages; + if (total_npages > totalram_pages()) + return -EINVAL; + + lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; + if (total_npages > lock_limit && !capable(CAP_IPC_LOCK)) { + pr_err_ratelimited("SEV: %lu total pages would exceed the lock limit of %lu.\n", + total_npages, lock_limit); + return -ENOMEM; + } + + return 0; +} + +static int sev_pin_user_pages(struct kvm *kvm, unsigned long addr, int npages, + unsigned int gup_flags, struct page **pages) +{ + int npinned; + + lockdep_assert_held(&kvm->lock); + + npinned = pin_user_pages_fast(addr, npages, gup_flags, pages); + if (npinned != npages) { + if (npinned > 0) + unpin_user_pages(pages, npinned); + pr_err_ratelimited("SEV: Failure locking %u pages.\n", npages); + return -ENOMEM; + } + + to_kvm_sev_info(kvm)->pages_locked += npages; + return 0; +} + static struct page **sev_pin_memory(struct kvm *kvm, unsigned long uaddr, unsigned long ulen, unsigned long *n, unsigned int flags) { - struct kvm_sev_info *sev = to_kvm_sev_info(kvm); - unsigned long npages, total_npages, lock_limit; + unsigned long npages; struct page **pages; - int npinned, ret; + int ret; lockdep_assert_held(&kvm->lock); @@ -743,16 +779,9 @@ static struct page **sev_pin_memory(struct kvm *kvm, unsigned long uaddr, if (npages > INT_MAX) return ERR_PTR(-EINVAL); - total_npages = sev->pages_locked + npages; - if (total_npages > totalram_pages()) - return ERR_PTR(-EINVAL); - - lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; - if (total_npages > lock_limit && !capable(CAP_IPC_LOCK)) { - pr_err("SEV: %lu total pages would exceed the lock limit of %lu.\n", - total_npages, lock_limit); - return ERR_PTR(-ENOMEM); - } + ret = sev_check_pin_count(kvm, npages); + if (ret) + return ERR_PTR(ret); /* * Don't WARN if the kernel (rightly) thinks the total size is absurd, @@ -764,25 +793,14 @@ static struct page **sev_pin_memory(struct kvm *kvm, unsigned long uaddr, if (!pages) return ERR_PTR(-ENOMEM); - /* Pin the user virtual address. */ - npinned = pin_user_pages_fast(uaddr, npages, flags, pages); - if (npinned != npages) { - pr_err("SEV: Failure locking %lu pages.\n", npages); - ret = -ENOMEM; - goto err; + ret = sev_pin_user_pages(kvm, uaddr, npages, flags, pages); + if (ret) { + kvfree(pages); + return ERR_PTR(ret); } *n = npages; - sev->pages_locked = total_npages; - return pages; - -err: - if (npinned > 0) - unpin_user_pages(pages, npinned); - - kvfree(pages); - return ERR_PTR(ret); } static void sev_unpin_memory(struct kvm *kvm, struct page **pages, @@ -793,6 +811,29 @@ static void sev_unpin_memory(struct kvm *kvm, struct page **pages, to_kvm_sev_info(kvm)->pages_locked -= npages; } +static struct page *sev_pin_page(struct kvm *kvm, unsigned long addr, + unsigned int flags) +{ + struct page *page; + int r; + + r = sev_check_pin_count(kvm, 1); + if (r) + return ERR_PTR(r); + + r = sev_pin_user_pages(kvm, addr, 1, flags, &page); + if (r) + return ERR_PTR(r); + + return page; +} + +static void sev_unpin_page(struct kvm *kvm, struct page *page) +{ + unpin_user_pages(&page, 1); + to_kvm_sev_info(kvm)->pages_locked -= 1; +} + static void sev_clflush_pages(struct page *pages[], unsigned long npages) { uint8_t *page_virtual; @@ -1345,9 +1386,8 @@ static int sev_dbg_crypt(struct kvm *kvm, struct kvm_sev_cmd *argp, bool dec) { unsigned long vaddr, vaddr_end, next_vaddr; unsigned long dst_vaddr; - struct page **src_p, **dst_p; + struct page *src_p, *dst_p; struct kvm_sev_dbg debug; - unsigned long n; unsigned int size; int ret; @@ -1373,13 +1413,13 @@ static int sev_dbg_crypt(struct kvm *kvm, struct kvm_sev_cmd *argp, bool dec) int len, s_off, d_off; /* lock userspace source and destination page */ - src_p = sev_pin_memory(kvm, vaddr & PAGE_MASK, PAGE_SIZE, &n, 0); + src_p = sev_pin_page(kvm, vaddr & PAGE_MASK, 0); if (IS_ERR(src_p)) return PTR_ERR(src_p); - dst_p = sev_pin_memory(kvm, dst_vaddr & PAGE_MASK, PAGE_SIZE, &n, FOLL_WRITE); + dst_p = sev_pin_page(kvm, dst_vaddr & PAGE_MASK, FOLL_WRITE); if (IS_ERR(dst_p)) { - sev_unpin_memory(kvm, src_p, n); + sev_unpin_page(kvm, src_p); return PTR_ERR(dst_p); } @@ -1388,8 +1428,8 @@ static int sev_dbg_crypt(struct kvm *kvm, struct kvm_sev_cmd *argp, bool dec) * the pages; flush the destination too so that future accesses do not * see stale data. */ - sev_clflush_pages(src_p, 1); - sev_clflush_pages(dst_p, 1); + sev_clflush_pages(&src_p, 1); + sev_clflush_pages(&dst_p, 1); /* * Since user buffer may not be page aligned, calculate the @@ -1402,20 +1442,20 @@ static int sev_dbg_crypt(struct kvm *kvm, struct kvm_sev_cmd *argp, bool dec) if (dec) ret = __sev_dbg_decrypt_user(kvm, - __sme_page_pa(src_p[0]) + s_off, + __sme_page_pa(src_p) + s_off, (void __user *)dst_vaddr, - __sme_page_pa(dst_p[0]) + d_off, + __sme_page_pa(dst_p) + d_off, len, &argp->error); else ret = __sev_dbg_encrypt_user(kvm, - __sme_page_pa(src_p[0]) + s_off, + __sme_page_pa(src_p) + s_off, (void __user *)vaddr, - __sme_page_pa(dst_p[0]) + d_off, + __sme_page_pa(dst_p) + d_off, (void __user *)dst_vaddr, len, &argp->error); - sev_unpin_memory(kvm, src_p, n); - sev_unpin_memory(kvm, dst_p, n); + sev_unpin_page(kvm, src_p); + sev_unpin_page(kvm, dst_p); if (ret) goto err; @@ -1698,8 +1738,7 @@ static int sev_send_update_data(struct kvm *kvm, struct kvm_sev_cmd *argp) struct sev_data_send_update_data data; struct kvm_sev_send_update_data params; void *hdr, *trans_data; - struct page **guest_page; - unsigned long n; + struct page *guest_page; int ret, offset; if (!sev_guest(kvm)) @@ -1723,8 +1762,7 @@ static int sev_send_update_data(struct kvm *kvm, struct kvm_sev_cmd *argp) return -EINVAL; /* Pin guest memory */ - guest_page = sev_pin_memory(kvm, params.guest_uaddr & PAGE_MASK, - PAGE_SIZE, &n, 0); + guest_page = sev_pin_page(kvm, params.guest_uaddr & PAGE_MASK, 0); if (IS_ERR(guest_page)) return PTR_ERR(guest_page); @@ -1745,7 +1783,7 @@ static int sev_send_update_data(struct kvm *kvm, struct kvm_sev_cmd *argp) data.trans_len = params.trans_len; /* The SEND_UPDATE_DATA command requires C-bit to be always set. */ - data.guest_address = (page_to_pfn(guest_page[0]) << PAGE_SHIFT) + offset; + data.guest_address = page_to_phys(guest_page) + offset; data.guest_address |= sev_me_mask; data.guest_len = params.guest_len; data.handle = to_kvm_sev_info(kvm)->handle; @@ -1772,8 +1810,7 @@ static int sev_send_update_data(struct kvm *kvm, struct kvm_sev_cmd *argp) e_free_hdr: kfree(hdr); e_unpin: - sev_unpin_memory(kvm, guest_page, n); - + sev_unpin_page(kvm, guest_page); return ret; } @@ -1878,8 +1915,7 @@ static int sev_receive_update_data(struct kvm *kvm, struct kvm_sev_cmd *argp) struct kvm_sev_receive_update_data params; struct sev_data_receive_update_data data; void *hdr = NULL, *trans = NULL; - struct page **guest_page; - unsigned long n; + struct page *guest_page; int ret, offset; if (!sev_guest(kvm)) @@ -1916,8 +1952,7 @@ static int sev_receive_update_data(struct kvm *kvm, struct kvm_sev_cmd *argp) data.trans_len = params.trans_len; /* Pin guest memory */ - guest_page = sev_pin_memory(kvm, params.guest_uaddr & PAGE_MASK, - PAGE_SIZE, &n, FOLL_WRITE); + guest_page = sev_pin_page(kvm, params.guest_uaddr & PAGE_MASK, FOLL_WRITE); if (IS_ERR(guest_page)) { ret = PTR_ERR(guest_page); goto e_free_trans; @@ -1928,10 +1963,10 @@ static int sev_receive_update_data(struct kvm *kvm, struct kvm_sev_cmd *argp) * encrypts the written data with the guest's key, and the cache may * contain dirty, unencrypted data. */ - sev_clflush_pages(guest_page, n); + sev_clflush_pages(&guest_page, 1); /* The RECEIVE_UPDATE_DATA command requires C-bit to be always set. */ - data.guest_address = (page_to_pfn(guest_page[0]) << PAGE_SHIFT) + offset; + data.guest_address = page_to_phys(guest_page) + offset; data.guest_address |= sev_me_mask; data.guest_len = params.guest_len; data.handle = to_kvm_sev_info(kvm)->handle; @@ -1939,7 +1974,7 @@ static int sev_receive_update_data(struct kvm *kvm, struct kvm_sev_cmd *argp) ret = sev_issue_cmd(kvm, SEV_CMD_RECEIVE_UPDATE_DATA, &data, &argp->error); - sev_unpin_memory(kvm, guest_page, n); + sev_unpin_page(kvm, guest_page); e_free_trans: kfree(trans); From 8d6297a3e73e08f86aebdeeafb968816175ce39a Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 1 May 2026 13:35:36 -0700 Subject: [PATCH 12/16] KVM: SEV: Rewrite logic to {de,en}crypt memory for debug Wholesale rewrite the guts of the debug {de,en}crypt flows, as the existing code is broken, e.g. doesn't handle cases where the access isn't naturally sized and aligned, and is so wildly flawed that attempting to salvage the current code in an iterative fashion would be more risky than a rewrite. E.g. when encrypting 9 bytes at offset 8, KVM needs to _decrypt_ destination[31:0] into a temporary buffer, buffer[31:0], then copy 9 bytes from source[8:0] to buffer[16:8], then encrypt buffer[31:0] back into destination[31:0]. The current code only ever copies 16 bytes, and bizarrely uses a temporary buffer for the source as well. To keep the code easier to read and maintain, send the unaligned cases down dedicated "slow" paths instead of trying to mix and match the possible combinations in one helper. For now, preserve the basic approach of the current code, e.g. allocate an entire page for the temporary buffer, to minimize unwanted changes in functionality. Link: https://patch.msgid.link/20260501203537.2120074-6-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 324 +++++++++++++++++++---------------------- 1 file changed, 148 insertions(+), 176 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 2d0f5a802ae1..756878a258a4 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -1237,159 +1237,140 @@ static int sev_guest_status(struct kvm *kvm, struct kvm_sev_cmd *argp) return ret; } -static int __sev_issue_dbg_cmd(struct kvm *kvm, unsigned long src, - unsigned long dst, int size, - int *error, bool enc) +static int sev_issue_dbg_cmd(struct kvm *kvm, unsigned long src_pa, + unsigned long dst_pa, unsigned int size, + unsigned int ioctl, int *error) { - struct sev_data_dbg data; + int cmd = ioctl == KVM_SEV_DBG_DECRYPT ? SEV_CMD_DBG_DECRYPT : + SEV_CMD_DBG_ENCRYPT; + struct sev_data_dbg data = { + .handle = to_kvm_sev_info(kvm)->handle, + .dst_addr = dst_pa, + .src_addr = src_pa, + .len = size, + }; - data.reserved = 0; - data.handle = to_kvm_sev_info(kvm)->handle; - data.dst_addr = dst; - data.src_addr = src; - data.len = size; - - return sev_issue_cmd(kvm, - enc ? SEV_CMD_DBG_ENCRYPT : SEV_CMD_DBG_DECRYPT, - &data, error); + return sev_issue_cmd(kvm, cmd, &data, error); } -static int __sev_dbg_decrypt(struct kvm *kvm, unsigned long src_paddr, - unsigned long dst_paddr, int sz, int *err) +static struct page *sev_alloc_dbg_buffer(void **buf) { - int offset; + struct page *buf_p; + + buf_p = alloc_page(GFP_KERNEL); + if (!buf_p) + return NULL; + + *buf = kmap_local_page(buf_p); + return buf_p; +} + +static void sev_free_dbg_buffer(struct page *buf_p, void *buf) +{ + kunmap_local(buf); + __free_page(buf_p); +} + +static unsigned int sev_dbg_crypt_slow_addr_and_size(struct page *page, + unsigned long __va, + unsigned int len, + unsigned long *pa) +{ + /* The number of bytes to {de,en}crypt must be 16-byte aligned. */ + unsigned int nr_bytes = round_up(len, 16); + unsigned long va = ALIGN_DOWN(__va, 16); /* - * Its safe to read more than we are asked, caller should ensure that - * destination has enough space. + * Increase the number of bytes to {de,en}crypt by one chunk (16 bytes) + * if the aligned address and length doesn't cover the unaligned range, + * e.g. if the address is unaligned _and_ the access will split a chunk + * at the tail. */ - offset = src_paddr & 15; - src_paddr = round_down(src_paddr, 16); - sz = round_up(sz + offset, 16); + if (va + nr_bytes < __va + len) + nr_bytes += 16; - return __sev_issue_dbg_cmd(kvm, src_paddr, dst_paddr, sz, err, false); -} - -static int __sev_dbg_decrypt_user(struct kvm *kvm, unsigned long paddr, - void __user *dst_uaddr, - unsigned long dst_paddr, - int size, int *err) -{ - struct page *tpage = NULL; - int ret, offset; - - /* if inputs are not 16-byte then use intermediate buffer */ - if (!IS_ALIGNED(dst_paddr, 16) || - !IS_ALIGNED(paddr, 16) || - !IS_ALIGNED(size, 16)) { - tpage = (void *)alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO); - if (!tpage) - return -ENOMEM; - - dst_paddr = __sme_page_pa(tpage); - } - - ret = __sev_dbg_decrypt(kvm, paddr, dst_paddr, size, err); - if (ret) - goto e_free; - - if (tpage) { - offset = paddr & 15; - if (copy_to_user(dst_uaddr, page_address(tpage) + offset, size)) - ret = -EFAULT; - } - -e_free: - if (tpage) - __free_page(tpage); - - return ret; -} - -static int __sev_dbg_encrypt_user(struct kvm *kvm, unsigned long paddr, - void __user *vaddr, - unsigned long dst_paddr, - void __user *dst_vaddr, - int size, int *error) -{ - struct page *src_tpage = NULL; - struct page *dst_tpage = NULL; - int ret, len = size; - - /* If source buffer is not aligned then use an intermediate buffer */ - if (!IS_ALIGNED((unsigned long)vaddr, 16)) { - src_tpage = alloc_page(GFP_KERNEL_ACCOUNT); - if (!src_tpage) - return -ENOMEM; - - if (copy_from_user(page_address(src_tpage), vaddr, size)) { - __free_page(src_tpage); - return -EFAULT; - } - - paddr = __sme_page_pa(src_tpage); - } + *pa = __sme_page_pa(page) + (va & ~PAGE_MASK); /* - * If destination buffer or length is not aligned then do read-modify-write: - * - decrypt destination in an intermediate buffer - * - copy the source buffer in an intermediate buffer - * - use the intermediate buffer as source buffer + * Sanity check that the new access won't split a page. This should + * never happen; just squash the access and let the firmware command + * fail. */ - if (!IS_ALIGNED((unsigned long)dst_vaddr, 16) || !IS_ALIGNED(size, 16)) { - int dst_offset; + if (WARN_ON_ONCE((*pa & PAGE_MASK) != ((*pa + nr_bytes - 1) & PAGE_MASK))) + return 0; - dst_tpage = alloc_page(GFP_KERNEL_ACCOUNT); - if (!dst_tpage) { - ret = -ENOMEM; - goto e_free; - } - - ret = __sev_dbg_decrypt(kvm, dst_paddr, - __sme_page_pa(dst_tpage), size, error); - if (ret) - goto e_free; - - /* - * If source is kernel buffer then use memcpy() otherwise - * copy_from_user(). - */ - dst_offset = dst_paddr & 15; - - if (src_tpage) - memcpy(page_address(dst_tpage) + dst_offset, - page_address(src_tpage), size); - else { - if (copy_from_user(page_address(dst_tpage) + dst_offset, - vaddr, size)) { - ret = -EFAULT; - goto e_free; - } - } - - paddr = __sme_page_pa(dst_tpage); - dst_paddr = round_down(dst_paddr, 16); - len = round_up(size, 16); - } - - ret = __sev_issue_dbg_cmd(kvm, paddr, dst_paddr, len, error, true); - -e_free: - if (src_tpage) - __free_page(src_tpage); - if (dst_tpage) - __free_page(dst_tpage); - return ret; + return nr_bytes; } -static int sev_dbg_crypt(struct kvm *kvm, struct kvm_sev_cmd *argp, bool dec) +static int sev_dbg_decrypt_slow(struct kvm *kvm, unsigned long src, + struct page *src_p, unsigned long dst, + unsigned int len, int *err) +{ + unsigned int nr_bytes; + unsigned long src_pa; + struct page *buf_p; + void *buf; + int r; + + buf_p = sev_alloc_dbg_buffer(&buf); + if (!buf_p) + return -ENOMEM; + + nr_bytes = sev_dbg_crypt_slow_addr_and_size(src_p, src, len, &src_pa); + + r = sev_issue_dbg_cmd(kvm, src_pa, __sme_page_pa(buf_p), + nr_bytes, KVM_SEV_DBG_DECRYPT, err); + if (r) + goto out; + + if (copy_to_user((void __user *)dst, buf + (src & 15), len)) + r = -EFAULT; +out: + sev_free_dbg_buffer(buf_p, buf); + return r; +} + +static int sev_dbg_encrypt_slow(struct kvm *kvm, unsigned long src, + unsigned long dst, struct page *dst_p, + unsigned int len, int *err) +{ + unsigned int nr_bytes; + unsigned long dst_pa; + struct page *buf_p; + void *buf; + int r; + + buf_p = sev_alloc_dbg_buffer(&buf); + if (!buf_p) + return -ENOMEM; + + /* Decrypt the _destination_ to do a RMW on plaintext. */ + nr_bytes = sev_dbg_crypt_slow_addr_and_size(dst_p, dst, len, &dst_pa); + + r = sev_issue_dbg_cmd(kvm, dst_pa, __sme_page_pa(buf_p), + nr_bytes, KVM_SEV_DBG_DECRYPT, err); + if (r) + goto out; + + /* + * Copy from the source into the intermediate buffer, and then + * re-encrypt the buffer into the destination. + */ + if (copy_from_user(buf + (dst & 15), (void __user *)src, len)) + r = -EFAULT; + else + r = sev_issue_dbg_cmd(kvm, __sme_page_pa(buf_p), dst_pa, + nr_bytes, KVM_SEV_DBG_ENCRYPT, err); +out: + sev_free_dbg_buffer(buf_p, buf); + return r; +} + +static int sev_dbg_crypt(struct kvm *kvm, struct kvm_sev_cmd *argp, + unsigned int cmd) { - unsigned long vaddr, vaddr_end, next_vaddr; - unsigned long dst_vaddr; - struct page *src_p, *dst_p; struct kvm_sev_dbg debug; - unsigned int size; - int ret; + unsigned int i, len; if (!sev_guest(kvm)) return -ENOTTY; @@ -1404,20 +1385,29 @@ static int sev_dbg_crypt(struct kvm *kvm, struct kvm_sev_cmd *argp, bool dec) debug.dst_uaddr + debug.len < debug.dst_uaddr) return -EINVAL; - vaddr = debug.src_uaddr; - size = debug.len; - vaddr_end = vaddr + size; - dst_vaddr = debug.dst_uaddr; + for (i = 0; i < debug.len; i += len) { + unsigned long src = debug.src_uaddr + i; + unsigned long dst = debug.dst_uaddr + i; + unsigned long s_off = src & ~PAGE_MASK; + unsigned long d_off = dst & ~PAGE_MASK; + struct page *src_p, *dst_p; + int ret; - for (; vaddr < vaddr_end; vaddr = next_vaddr) { - int len, s_off, d_off; + /* + * Copy as many remaining bytes as possible while staying in a + * single page for both the source and destination. + */ + len = min3(debug.len - i, PAGE_SIZE - s_off, PAGE_SIZE - d_off); - /* lock userspace source and destination page */ - src_p = sev_pin_page(kvm, vaddr & PAGE_MASK, 0); + /* + * Pin the source and destination pages; firmware operates on + * physical addresses. + */ + src_p = sev_pin_page(kvm, src & PAGE_MASK, 0); if (IS_ERR(src_p)) return PTR_ERR(src_p); - dst_p = sev_pin_page(kvm, dst_vaddr & PAGE_MASK, FOLL_WRITE); + dst_p = sev_pin_page(kvm, dst & PAGE_MASK, FOLL_WRITE); if (IS_ERR(dst_p)) { sev_unpin_page(kvm, src_p); return PTR_ERR(dst_p); @@ -1431,41 +1421,25 @@ static int sev_dbg_crypt(struct kvm *kvm, struct kvm_sev_cmd *argp, bool dec) sev_clflush_pages(&src_p, 1); sev_clflush_pages(&dst_p, 1); - /* - * Since user buffer may not be page aligned, calculate the - * offset within the page. - */ - s_off = vaddr & ~PAGE_MASK; - d_off = dst_vaddr & ~PAGE_MASK; - len = min_t(size_t, (PAGE_SIZE - s_off), size); - len = min_t(size_t, len, PAGE_SIZE - d_off); - - if (dec) - ret = __sev_dbg_decrypt_user(kvm, - __sme_page_pa(src_p) + s_off, - (void __user *)dst_vaddr, - __sme_page_pa(dst_p) + d_off, - len, &argp->error); + if (IS_ALIGNED(src, 16) && IS_ALIGNED(dst, 16) && IS_ALIGNED(len, 16)) + ret = sev_issue_dbg_cmd(kvm, + __sme_page_pa(src_p) + s_off, + __sme_page_pa(dst_p) + d_off, + len, cmd, &argp->error); + else if (cmd == KVM_SEV_DBG_DECRYPT) + ret = sev_dbg_decrypt_slow(kvm, src, src_p, dst, + len, &argp->error); else - ret = __sev_dbg_encrypt_user(kvm, - __sme_page_pa(src_p) + s_off, - (void __user *)vaddr, - __sme_page_pa(dst_p) + d_off, - (void __user *)dst_vaddr, - len, &argp->error); + ret = sev_dbg_encrypt_slow(kvm, src, dst, dst_p, + len, &argp->error); sev_unpin_page(kvm, src_p); sev_unpin_page(kvm, dst_p); if (ret) - goto err; - - next_vaddr = vaddr + len; - dst_vaddr = dst_vaddr + len; - size -= len; + return ret; } -err: - return ret; + return 0; } static int sev_launch_secret(struct kvm *kvm, struct kvm_sev_cmd *argp) @@ -2727,10 +2701,8 @@ int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp) r = sev_guest_status(kvm, &sev_cmd); break; case KVM_SEV_DBG_DECRYPT: - r = sev_dbg_crypt(kvm, &sev_cmd, true); - break; case KVM_SEV_DBG_ENCRYPT: - r = sev_dbg_crypt(kvm, &sev_cmd, false); + r = sev_dbg_crypt(kvm, &sev_cmd, sev_cmd.id); break; case KVM_SEV_LAUNCH_SECRET: r = sev_launch_secret(kvm, &sev_cmd); From 4c735bf1bc22fd6ee66ab4bffe1d7599c3964781 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 1 May 2026 13:35:37 -0700 Subject: [PATCH 13/16] KVM: SEV: Allocate only as many bytes as needed for temp crypt buffers When using a temporary buffer to {de,en}crypt unaligned memory for debug, allocate only the number of bytes that are needed instead of allocating an entire page. The most common case for unaligned accesses will be reading or writing less than 16 bytes. Link: https://patch.msgid.link/20260501203537.2120074-7-seanjc@google.com Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 69 ++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 756878a258a4..37d4cfa5d980 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -1253,53 +1253,34 @@ static int sev_issue_dbg_cmd(struct kvm *kvm, unsigned long src_pa, return sev_issue_cmd(kvm, cmd, &data, error); } -static struct page *sev_alloc_dbg_buffer(void **buf) +static void *sev_dbg_crypt_slow_alloc(struct page *page, unsigned long __va, + unsigned int len, unsigned long *pa, + unsigned int *nr_bytes) { - struct page *buf_p; - - buf_p = alloc_page(GFP_KERNEL); - if (!buf_p) - return NULL; - - *buf = kmap_local_page(buf_p); - return buf_p; -} - -static void sev_free_dbg_buffer(struct page *buf_p, void *buf) -{ - kunmap_local(buf); - __free_page(buf_p); -} - -static unsigned int sev_dbg_crypt_slow_addr_and_size(struct page *page, - unsigned long __va, - unsigned int len, - unsigned long *pa) -{ - /* The number of bytes to {de,en}crypt must be 16-byte aligned. */ - unsigned int nr_bytes = round_up(len, 16); unsigned long va = ALIGN_DOWN(__va, 16); + /* The number of bytes to {de,en}crypt must be 16-byte aligned. */ + *nr_bytes = round_up(len, 16); + /* * Increase the number of bytes to {de,en}crypt by one chunk (16 bytes) * if the aligned address and length doesn't cover the unaligned range, * e.g. if the address is unaligned _and_ the access will split a chunk * at the tail. */ - if (va + nr_bytes < __va + len) - nr_bytes += 16; + if (va + *nr_bytes < __va + len) + *nr_bytes += 16; *pa = __sme_page_pa(page) + (va & ~PAGE_MASK); /* * Sanity check that the new access won't split a page. This should - * never happen; just squash the access and let the firmware command - * fail. + * never happen; just pretend the allocation failed. */ - if (WARN_ON_ONCE((*pa & PAGE_MASK) != ((*pa + nr_bytes - 1) & PAGE_MASK))) - return 0; + if (WARN_ON_ONCE((*pa & PAGE_MASK) != ((*pa + *nr_bytes - 1) & PAGE_MASK))) + return NULL; - return nr_bytes; + return kmalloc(*nr_bytes, GFP_KERNEL); } static int sev_dbg_decrypt_slow(struct kvm *kvm, unsigned long src, @@ -1308,17 +1289,14 @@ static int sev_dbg_decrypt_slow(struct kvm *kvm, unsigned long src, { unsigned int nr_bytes; unsigned long src_pa; - struct page *buf_p; void *buf; int r; - buf_p = sev_alloc_dbg_buffer(&buf); - if (!buf_p) + buf = sev_dbg_crypt_slow_alloc(src_p, src, len, &src_pa, &nr_bytes); + if (!buf) return -ENOMEM; - nr_bytes = sev_dbg_crypt_slow_addr_and_size(src_p, src, len, &src_pa); - - r = sev_issue_dbg_cmd(kvm, src_pa, __sme_page_pa(buf_p), + r = sev_issue_dbg_cmd(kvm, src_pa, __sme_set(__pa(buf)), nr_bytes, KVM_SEV_DBG_DECRYPT, err); if (r) goto out; @@ -1326,7 +1304,7 @@ static int sev_dbg_decrypt_slow(struct kvm *kvm, unsigned long src, if (copy_to_user((void __user *)dst, buf + (src & 15), len)) r = -EFAULT; out: - sev_free_dbg_buffer(buf_p, buf); + kfree(buf); return r; } @@ -1336,18 +1314,15 @@ static int sev_dbg_encrypt_slow(struct kvm *kvm, unsigned long src, { unsigned int nr_bytes; unsigned long dst_pa; - struct page *buf_p; void *buf; int r; - buf_p = sev_alloc_dbg_buffer(&buf); - if (!buf_p) + /* Decrypt the _destination_ to do a RMW on plaintext. */ + buf = sev_dbg_crypt_slow_alloc(dst_p, dst, len, &dst_pa, &nr_bytes); + if (!buf) return -ENOMEM; - /* Decrypt the _destination_ to do a RMW on plaintext. */ - nr_bytes = sev_dbg_crypt_slow_addr_and_size(dst_p, dst, len, &dst_pa); - - r = sev_issue_dbg_cmd(kvm, dst_pa, __sme_page_pa(buf_p), + r = sev_issue_dbg_cmd(kvm, dst_pa, __sme_set(__pa(buf)), nr_bytes, KVM_SEV_DBG_DECRYPT, err); if (r) goto out; @@ -1359,10 +1334,10 @@ static int sev_dbg_encrypt_slow(struct kvm *kvm, unsigned long src, if (copy_from_user(buf + (dst & 15), (void __user *)src, len)) r = -EFAULT; else - r = sev_issue_dbg_cmd(kvm, __sme_page_pa(buf_p), dst_pa, + r = sev_issue_dbg_cmd(kvm, __sme_set(__pa(buf)), dst_pa, nr_bytes, KVM_SEV_DBG_ENCRYPT, err); out: - sev_free_dbg_buffer(buf_p, buf); + kfree(buf); return r; } From f13e900599089b10113ceb36013423f0837c6792 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Fri, 22 May 2026 15:46:06 -0700 Subject: [PATCH 14/16] KVM: SEV: Pin source page for write when adding CPUID data for SNP guest When populating a guest_memfd instance with the initial CPUID data for an SNP guest, acquire a writable pin on the source page as KVM will write back the "correct" CPUID information if the userspace provided data is rejected by trusted firmware. Because KVM writes to the source page using a kernel mapping, pinning for read could result in KVM clobbering read-only memory. Note, well-behaved VMMs are unlikely to be affected, as CPUID information is almost always dynamically generated by userspace, i.e. it's unlikely for the CPUID information to be backed by a read-only mapping. Fixes: 2a62345b30529 ("KVM: guest_memfd: GUP source pages prior to populating guest memory") Cc: stable@vger.kernel.org Signed-off-by: Ackerley Tng Link: https://patch.msgid.link/20260522-fix-sev-gmem-post-populate-v2-1-3f196bfad5a1@google.com [sean: rewrite shortlog and changelog, tag for stable@] Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 1 + arch/x86/kvm/vmx/tdx.c | 2 +- include/linux/kvm_host.h | 3 ++- virt/kvm/guest_memfd.c | 6 ++++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 37d4cfa5d980..c73c028d72c1 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -2456,6 +2456,7 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp) sev_populate_args.type = params.type; count = kvm_gmem_populate(kvm, params.gfn_start, src, npages, + params.type == KVM_SEV_SNP_PAGE_TYPE_CPUID, sev_gmem_post_populate, &sev_populate_args); if (count < 0) { argp->error = sev_populate_args.fw_error; diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index b8c3d3d8bbfe..00dcfcbc47f6 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -3185,7 +3185,7 @@ static int tdx_vcpu_init_mem_region(struct kvm_vcpu *vcpu, struct kvm_tdx_cmd *c }; gmem_ret = kvm_gmem_populate(kvm, gpa_to_gfn(region.gpa), u64_to_user_ptr(region.source_addr), - 1, tdx_gmem_post_populate, &arg); + 1, false, tdx_gmem_post_populate, &arg); if (gmem_ret < 0) { ret = gmem_ret; break; diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 4c14aee1fb06..2c5ad9a6d5ce 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -2596,7 +2596,8 @@ int kvm_arch_gmem_prepare(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn, int max_ord typedef int (*kvm_gmem_populate_cb)(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn, struct page *page, void *opaque); -long kvm_gmem_populate(struct kvm *kvm, gfn_t gfn, void __user *src, long npages, +long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src, + long npages, bool may_writeback_src, kvm_gmem_populate_cb post_populate, void *opaque); #endif diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c index 69c9d6d546b2..07d8db344872 100644 --- a/virt/kvm/guest_memfd.c +++ b/virt/kvm/guest_memfd.c @@ -858,7 +858,8 @@ static long __kvm_gmem_populate(struct kvm *kvm, struct kvm_memory_slot *slot, return ret; } -long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src, long npages, +long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src, + long npages, bool may_writeback_src, kvm_gmem_populate_cb post_populate, void *opaque) { struct kvm_memory_slot *slot; @@ -892,8 +893,9 @@ long kvm_gmem_populate(struct kvm *kvm, gfn_t start_gfn, void __user *src, long if (src) { unsigned long uaddr = (unsigned long)src + i * PAGE_SIZE; + unsigned int flags = may_writeback_src ? FOLL_WRITE : 0; - ret = get_user_pages_fast(uaddr, 1, 0, &src_page); + ret = get_user_pages_fast(uaddr, 1, flags, &src_page); if (ret < 0) break; if (ret != 1) { From 138f5f9cbe374ff87084f1d32181d2a4a2d924e8 Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Fri, 22 May 2026 15:46:09 -0700 Subject: [PATCH 15/16] KVM: SEV: Unmap local kmaps in LIFO order, per highmem requirements Per highmem.h, local kernel mappings must be unmapped in the reserve order they were acquired, following a LIFO (last-in, first-out) stack-based approach, and that failure to do so "is invalid and causes malfunction". Swap the kunmap_local() calls in SNP post-populate flow to ensure the mappings are released in the correct order. Note, because SNP is 64-bit only, the bugs are benign as there are no highmem mappings to unwind. Fixes: 2a62345b3052 ("KVM: guest_memfd: GUP source pages prior to populating guest memory") Signed-off-by: Ackerley Tng Link: https://patch.msgid.link/20260522-fix-sev-gmem-post-populate-v2-4-3f196bfad5a1@google.com [sean: call out that the bug is benign] Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index c73c028d72c1..0e7f8b5cd4cb 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -2347,8 +2347,8 @@ static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn, memcpy(dst_vaddr, src_vaddr, PAGE_SIZE); - kunmap_local(src_vaddr); kunmap_local(dst_vaddr); + kunmap_local(src_vaddr); } ret = rmp_make_private(pfn, gfn << PAGE_SHIFT, PG_LEVEL_4K, @@ -2383,8 +2383,8 @@ static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn, memcpy(src_vaddr, dst_vaddr, PAGE_SIZE); - kunmap_local(src_vaddr); kunmap_local(dst_vaddr); + kunmap_local(src_vaddr); } out: From 97cd21d57e9bd2da79845178d9250cfd19289cd4 Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Fri, 22 May 2026 15:46:10 -0700 Subject: [PATCH 16/16] KVM: SEV: Mark source page dirty when writing back CPUID data on failure When writing back CPUID data (provided by trusted firmware) to the source page on failure, mark the page/folio as dirty so that the data isn't lost in the unlikely scenario the page is reclaimed before its read by userspace. Fixes: 2a62345b3052 ("KVM: guest_memfd: GUP source pages prior to populating guest memory") Signed-off-by: Ackerley Tng Link: https://patch.msgid.link/20260522-fix-sev-gmem-post-populate-v2-5-3f196bfad5a1@google.com [sean: use set_page_dirty(), massage changelog] Signed-off-by: Sean Christopherson --- arch/x86/kvm/svm/sev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 0e7f8b5cd4cb..1d513778d88d 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -2382,6 +2382,7 @@ static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn, void *dst_vaddr = kmap_local_pfn(pfn); memcpy(src_vaddr, dst_vaddr, PAGE_SIZE); + set_page_dirty(src_page); kunmap_local(dst_vaddr); kunmap_local(src_vaddr);