| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/08 13:02 | flow | patch-triage |
2mArgs:null Results: {
"EnableConfigs": [
"KVM",
"KVM_AMD",
"KVM_AMD_SEV",
"CRYPTO_DEV_SP_PSP",
"CRYPTO_DEV_CCP_DD",
"AMD_IOMMU"
],
"FocusSymbols": [
"svm_enable_vm_cap",
"sev_vcpu_mem_enc_ioctl",
"snp_get_vcpu_state",
"snp_set_vcpu_state",
"sev_snp_install_guest_vmsa",
"__sev_snp_reload_vmsa",
"sev_snp_get_apic_ids",
"snp_launch_update",
"snp_launch_update_vmsa",
"sev_vcpu_create",
"sev_free_vcpu"
],
"KMSANReasoning": "The patch adds support for direct VMSAs and guest APIC ID list queries in AMD SEV-SNP (including new vCPU ioctls KVM_SEV_SNP_GET_VCPU_STATE / KVM_SEV_SNP_SET_VCPU_STATE and VMGEXIT handling for SVM_VMGEXIT_GET_APIC_IDS).\n\nAll kernel structures returned to userspace or written to guest memory are fully initialized:\n- `struct kvm_sev_snp_vcpu_state` in `snp_get_vcpu_state` is initialized with `= {}`, consists solely of `__u64` fields with no padding holes, and is fully zeroed before `copy_to_user()`.\n- `sev_cmd` in `sev_vcpu_mem_enc_ioctl` is copied from userspace via `copy_from_user()` before being copied back.\n- `struct sev_apic_id_desc` in `sev_snp_get_apic_ids` is allocated with `kvzalloc()` and completely populated before `kvm_write_guest()`.\n\nAny memory safety risks introduced by the patch (such as memory mapping, guest page validation, or concurrency) fall under standard detectors (KASAN, LOCKDEP). There are no uninitialized memory reads or kernel info-leak risks requiring a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false,
"Reasoning": "The patch series introduces direct-VMSA support and APIC ID enumeration for AMD SEV-SNP guests in KVM. It adds new vCPU ioctl commands (KVM_SEV_SNP_GET_VCPU_STATE and KVM_SEV_SNP_SET_VCPU_STATE), implements VM capability enablement (KVM_CAP_SNP_DIRECT_VMSA) in KVM SVM, adds the SVM_VMGEXIT_GET_APIC_IDS exit handler, and refactors VMSA guest memory installation and MMU invalidation sequencing. Fuzzing is warranted to test new state transitions, validation checks, and locking invariants.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/08 13:02 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 760726df0f542fb0f0b066de39ad501e3c2c3594\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Sep 8 13:02:00 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/virt/kvm/x86/amd-memory-encryption.rst b/Documentation/virt/kvm/x86/amd-memory-encryption.rst\nindex bd04a908a8dbd..427b673c3832f 100644\n--- a/Documentation/virt/kvm/x86/amd-memory-encryption.rst\n+++ b/Documentation/virt/kvm/x86/amd-memory-encryption.rst\n@@ -536,6 +536,7 @@ Returns: 0 on success, \u003c 0 on error, -EAGAIN if caller should retry\n where the allowed values for page_type are #define'd as::\n \n KVM_SEV_SNP_PAGE_TYPE_NORMAL\n+ KVM_SEV_SNP_PAGE_TYPE_VMSA\n KVM_SEV_SNP_PAGE_TYPE_ZERO\n KVM_SEV_SNP_PAGE_TYPE_UNMEASURED\n KVM_SEV_SNP_PAGE_TYPE_SECRETS\n@@ -544,7 +545,68 @@ where the allowed values for page_type are #define'd as::\n See the SEV-SNP spec [snp-fw-abi]_ for further details on how each page type is\n used/measured.\n \n-20. KVM_SEV_SNP_LAUNCH_FINISH\n+``KVM_SEV_SNP_PAGE_TYPE_VMSA`` creates VMSA pages as part of the measured\n+initial image. A request must contain exactly one 4 KiB VMSA page, but the\n+command may be used multiple times. Creating a VMSA page does not associate it\n+with a vCPU; use ``KVM_SEV_SNP_SET_VCPU_STATE`` on the intended vCPU file\n+descriptor before launch finish to make that association. KVM treats the\n+VMSA contents as guest-owned data, but requires VMPL 0 and a ``sev_features``\n+value that matches the VM's configured VMSA features.\n+\n+20. KVM_SEV_SNP_GET_VCPU_STATE / KVM_SEV_SNP_SET_VCPU_STATE\n+------------------------------------------------------------\n+\n+These commands get or set the VMSA and GHCB addresses for the vCPU on whose\n+file descriptor the command is issued. Unlike the other SEV commands,\n+userspace must issue KVM_MEMORY_ENCRYPT_OP on a vCPU file descriptor. The\n+capability is reported as ``KVM_CAP_SNP_VCPU_STATE``.\n+\n+Parameters (in/out): struct kvm_sev_snp_vcpu_state\n+\n+Returns: 0 on success, -negative on error\n+\n+::\n+\n+ #define KVM_SEV_SNP_VCPU_STATE_VMSA_VALID _BITULL(0)\n+ #define KVM_SEV_SNP_VCPU_STATE_GHCB_VALID _BITULL(1)\n+\n+ struct kvm_sev_snp_vcpu_state {\n+ __u64 valid_fields;\n+ __u64 vmsa_gpa;\n+ __u64 ghcb_gpa;\n+ __u64 pad[5]; /* Must be zero */\n+ };\n+\n+``KVM_SEV_SNP_GET_VCPU_STATE`` returns the current addresses and sets the\n+corresponding bit in ``valid_fields`` for each valid address.\n+\n+``KVM_SEV_SNP_SET_VCPU_STATE`` sets addresses whose validity bits are present\n+and invalidates addresses whose bits are absent. The command must be issued\n+after launch start and before KVM_SEV_SNP_LAUNCH_FINISH, and the VM must have\n+enabled ``KVM_CAP_SNP_DIRECT_VMSA``. A valid VMSA GPA must be backed by\n+guest_memfd and populated. The GPA must be 4-KiB aligned. A valid GHCB\n+address is copied without inspecting its backing page. Nonzero reserved\n+fields or unknown validity bits are rejected.\n+\n+``KVM_CAP_SNP_DIRECT_VMSA`` is a VM-scoped capability that selects direct-VMSA\n+mode. Userspace enables it with ``KVM_ENABLE_CAP`` on an SNP VM before\n+creating any vCPUs. ``flags`` and all elements of ``args`` must be zero.\n+Enabling the capability on a non-SNP VM or after creating a vCPU is rejected.\n+\n+In direct-VMSA mode, KVM does not allocate a KVM-owned VMSA when a vCPU is\n+created and does not generate or measure one at launch finish. All launch\n+VMSAs are owned and supplied by userspace. Valid VMSAs selected with\n+``KVM_SEV_SNP_SET_VCPU_STATE`` are preserved, while vCPUs without a valid VMSA\n+have no runnable VMSA until the guest uses SNP AP creation to supply one. If\n+the capability is not enabled, launch finish retains the legacy behavior of\n+generating and measuring a KVM-owned VMSA for every vCPU. VMSAs that were\n+measured but not selected remain ordinary valid pages in the initial image.\n+\n+Direct VMSAs make the launch measurement independent of KVM's selected VMSA\n+GPA and of the configured vCPU count. This gives VMMs a stable launch\n+measurement across hypervisors.\n+\n+21. KVM_SEV_SNP_LAUNCH_FINISH\n -----------------------------\n \n After completion of the SNP guest launch flow, the KVM_SEV_SNP_LAUNCH_FINISH\n@@ -572,7 +634,7 @@ Returns: 0 on success, -negative on error\n See SNP_LAUNCH_FINISH in the SEV-SNP specification [snp-fw-abi]_ for further\n details on the input parameters in ``struct kvm_sev_snp_launch_finish``.\n \n-21. KVM_SEV_SNP_ENABLE_REQ_CERTS\n+22. KVM_SEV_SNP_ENABLE_REQ_CERTS\n --------------------------------\n \n The KVM_SEV_SNP_ENABLE_REQ_CERTS command will configure KVM to exit to\ndiff --git a/arch/x86/include/asm/kvm-x86-ops.h b/arch/x86/include/asm/kvm-x86-ops.h\nindex e213c9ae3e301..a48259696ca6e 100644\n--- a/arch/x86/include/asm/kvm-x86-ops.h\n+++ b/arch/x86/include/asm/kvm-x86-ops.h\n@@ -23,6 +23,7 @@ KVM_X86_OP(vcpu_after_set_cpuid)\n KVM_X86_OP(vm_init)\n KVM_X86_OP_OPTIONAL(vm_destroy)\n KVM_X86_OP_OPTIONAL(vm_pre_destroy)\n+KVM_X86_OP_OPTIONAL(enable_vm_cap)\n KVM_X86_OP_OPTIONAL_RET0(vcpu_precreate)\n KVM_X86_OP(vcpu_create)\n KVM_X86_OP(vcpu_free)\ndiff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h\nindex 683bb8bf43a94..3bfc02bb79984 100644\n--- a/arch/x86/include/asm/kvm_host.h\n+++ b/arch/x86/include/asm/kvm_host.h\n@@ -1519,6 +1519,7 @@ struct kvm_x86_ops {\n \tint (*vm_init)(struct kvm *kvm);\n \tvoid (*vm_destroy)(struct kvm *kvm);\n \tvoid (*vm_pre_destroy)(struct kvm *kvm);\n+\tint (*enable_vm_cap)(struct kvm *kvm, struct kvm_enable_cap *cap);\n \n \t/* Create, but do not attach this VCPU */\n \tint (*vcpu_precreate)(struct kvm *kvm);\ndiff --git a/arch/x86/include/asm/sev-common.h b/arch/x86/include/asm/sev-common.h\nindex 01a6e4dbe4235..c041f85dfd5c8 100644\n--- a/arch/x86/include/asm/sev-common.h\n+++ b/arch/x86/include/asm/sev-common.h\n@@ -136,6 +136,7 @@ enum psc_op {\n \n #define GHCB_HV_FT_SNP\t\t\tBIT_ULL(0)\n #define GHCB_HV_FT_SNP_AP_CREATION\tBIT_ULL(1)\n+#define GHCB_HV_FT_APIC_ID_LIST\t\tBIT_ULL(4)\n #define GHCB_HV_FT_SNP_MULTI_VMPL\tBIT_ULL(5)\n \n /*\ndiff --git a/arch/x86/include/uapi/asm/kvm.h b/arch/x86/include/uapi/asm/kvm.h\nindex 1585ec8040666..5ae041e617b82 100644\n--- a/arch/x86/include/uapi/asm/kvm.h\n+++ b/arch/x86/include/uapi/asm/kvm.h\n@@ -748,6 +748,8 @@ enum sev_cmd_id {\n \tKVM_SEV_SNP_LAUNCH_UPDATE,\n \tKVM_SEV_SNP_LAUNCH_FINISH,\n \tKVM_SEV_SNP_ENABLE_REQ_CERTS,\n+\tKVM_SEV_SNP_GET_VCPU_STATE,\n+\tKVM_SEV_SNP_SET_VCPU_STATE,\n \n \tKVM_SEV_NR_MAX,\n };\n@@ -887,6 +889,7 @@ struct kvm_sev_snp_launch_start {\n /* Kept in sync with firmware values for simplicity. */\n #define KVM_SEV_PAGE_TYPE_INVALID\t\t0x0\n #define KVM_SEV_SNP_PAGE_TYPE_NORMAL\t\t0x1\n+#define KVM_SEV_SNP_PAGE_TYPE_VMSA\t\t0x2\n #define KVM_SEV_SNP_PAGE_TYPE_ZERO\t\t0x3\n #define KVM_SEV_SNP_PAGE_TYPE_UNMEASURED\t0x4\n #define KVM_SEV_SNP_PAGE_TYPE_SECRETS\t\t0x5\n@@ -903,6 +906,16 @@ struct kvm_sev_snp_launch_update {\n \t__u64 pad2[4];\n };\n \n+#define KVM_SEV_SNP_VCPU_STATE_VMSA_VALID\t_BITULL(0)\n+#define KVM_SEV_SNP_VCPU_STATE_GHCB_VALID\t_BITULL(1)\n+\n+struct kvm_sev_snp_vcpu_state {\n+\t__u64 valid_fields;\n+\t__u64 vmsa_gpa;\n+\t__u64 ghcb_gpa;\n+\t__u64 pad[5];\n+};\n+\n #define KVM_SEV_SNP_ID_BLOCK_SIZE\t96\n #define KVM_SEV_SNP_ID_AUTH_SIZE\t4096\n #define KVM_SEV_SNP_FINISH_DATA_SIZE\t32\ndiff --git a/arch/x86/include/uapi/asm/svm.h b/arch/x86/include/uapi/asm/svm.h\nindex 010a45c9f6147..05e5e31e23e18 100644\n--- a/arch/x86/include/uapi/asm/svm.h\n+++ b/arch/x86/include/uapi/asm/svm.h\n@@ -117,6 +117,7 @@\n #define SVM_VMGEXIT_AP_CREATE_ON_INIT\t\t0\n #define SVM_VMGEXIT_AP_CREATE\t\t\t1\n #define SVM_VMGEXIT_AP_DESTROY\t\t\t2\n+#define SVM_VMGEXIT_GET_APIC_IDS\t\t0x80000017ull\n #define SVM_VMGEXIT_SNP_RUN_VMPL\t\t0x80000018ull\n #define SVM_VMGEXIT_SAVIC\t\t\t0x8000001aull\n #define SVM_VMGEXIT_SAVIC_REGISTER_GPA\t\t0\ndiff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c\nindex 5705723f1f412..3e0727e251a93 100644\n--- a/arch/x86/kvm/svm/sev.c\n+++ b/arch/x86/kvm/svm/sev.c\n@@ -40,7 +40,9 @@\n #define GHCB_VERSION_MAX\t2ULL\n #define GHCB_VERSION_MIN\t1ULL\n \n-#define GHCB_HV_FT_SUPPORTED\t(GHCB_HV_FT_SNP | GHCB_HV_FT_SNP_AP_CREATION)\n+#define GHCB_HV_FT_SUPPORTED\t(GHCB_HV_FT_SNP | \\\n+\t\t\t\t GHCB_HV_FT_SNP_AP_CREATION | \\\n+\t\t\t\t GHCB_HV_FT_APIC_ID_LIST)\n \n /*\n * The GHCB spec essentially states that all non-zero error codes other than\n@@ -2344,6 +2346,7 @@ struct sev_gmem_populate_args {\n \t__u8 type;\n \tint sev_fd;\n \tint fw_error;\n+\tbool vmsa_invalid;\n };\n \n static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,\n@@ -2367,11 +2370,20 @@ static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,\n \tif (src_page) {\n \t\tvoid *src_vaddr = kmap_local_page(src_page);\n \t\tvoid *dst_vaddr = kmap_local_pfn(pfn);\n+\t\tstruct sev_es_save_area *vmsa = dst_vaddr;\n \n \t\tmemcpy(dst_vaddr, src_vaddr, PAGE_SIZE);\n+\t\tif (sev_populate_args-\u003etype == KVM_SEV_SNP_PAGE_TYPE_VMSA \u0026\u0026\n+\t\t (vmsa-\u003evmpl || vmsa-\u003esev_features != sev-\u003evmsa_features)) {\n+\t\t\tsev_populate_args-\u003evmsa_invalid = true;\n+\t\t\tret = -EINVAL;\n+\t\t}\n \n \t\tkunmap_local(dst_vaddr);\n \t\tkunmap_local(src_vaddr);\n+\n+\t\tif (ret)\n+\t\t\tgoto out;\n \t}\n \n \tret = rmp_make_private(pfn, gfn \u003c\u003c PAGE_SHIFT, PG_LEVEL_4K,\n@@ -2437,7 +2449,10 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)\n \t\t params.gfn_start, params.len, params.type, params.flags);\n \n \tif (!params.len || !PAGE_ALIGNED(params.len) || params.flags ||\n+\t (params.type == KVM_SEV_SNP_PAGE_TYPE_VMSA \u0026\u0026\n+\t (!sev-\u003esnp_direct_vmsa || params.len != PAGE_SIZE)) ||\n \t (params.type != KVM_SEV_SNP_PAGE_TYPE_NORMAL \u0026\u0026\n+\t params.type != KVM_SEV_SNP_PAGE_TYPE_VMSA \u0026\u0026\n \t params.type != KVM_SEV_SNP_PAGE_TYPE_ZERO \u0026\u0026\n \t params.type != KVM_SEV_SNP_PAGE_TYPE_UNMEASURED \u0026\u0026\n \t params.type != KVM_SEV_SNP_PAGE_TYPE_SECRETS \u0026\u0026\n@@ -2485,6 +2500,9 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)\n \t\t\t\t params.type == KVM_SEV_SNP_PAGE_TYPE_CPUID,\n \t\t\t\t sev_gmem_post_populate, \u0026sev_populate_args);\n \tif (count \u003c 0) {\n+\t\tif (sev_populate_args.vmsa_invalid)\n+\t\t\treturn -EINVAL;\n+\n \t\targp-\u003eerror = sev_populate_args.fw_error;\n \t\tpr_debug(\"%s: kvm_gmem_populate failed, ret %ld (fw_error %d)\\n\",\n \t\t\t __func__, count, argp-\u003eerror);\n@@ -2502,6 +2520,8 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)\n \treturn 0;\n }\n \n+static int sev_snp_install_guest_vmsa(struct vcpu_svm *svm, gpa_t gpa);\n+\n static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)\n {\n \tstruct kvm_sev_info *sev = to_kvm_sev_info(kvm);\n@@ -2522,8 +2542,15 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)\n \n \tkvm_for_each_vcpu(i, vcpu, kvm) {\n \t\tstruct vcpu_svm *svm = to_svm(vcpu);\n-\t\tu64 pfn = __pa(svm-\u003esev_es.vmsa) \u003e\u003e PAGE_SHIFT;\n+\t\tu64 pfn;\n+\n+\t\tif (sev-\u003esnp_direct_vmsa) {\n+\t\t\tif (!svm-\u003esev_es.snp_has_guest_vmsa)\n+\t\t\t\tsvm-\u003evmcb-\u003econtrol.vmsa_pa = INVALID_PAGE;\n+\t\t\tgoto protect_vcpu;\n+\t\t}\n \n+\t\tpfn = __pa(svm-\u003esev_es.vmsa) \u003e\u003e PAGE_SHIFT;\n \t\tret = sev_es_sync_vmsa(svm);\n \t\tif (ret)\n \t\t\tgoto out;\n@@ -2543,6 +2570,7 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)\n \t\t\tgoto out;\n \t\t}\n \n+protect_vcpu:\n \t\tsvm-\u003evcpu.arch.guest_state_protected = true;\n \t\t/*\n \t\t * SEV-ES (and thus SNP) guest mandates LBR Virtualization to\n@@ -2559,6 +2587,89 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)\n \treturn ret;\n }\n \n+static int snp_get_vcpu_state(struct kvm_vcpu *vcpu,\n+\t\t\t struct kvm_sev_cmd *argp)\n+{\n+\tstruct vcpu_svm *svm = to_svm(vcpu);\n+\tstruct kvm *kvm = vcpu-\u003ekvm;\n+\tstruct kvm_sev_snp_vcpu_state state = {};\n+\n+\tif (!is_sev_snp_guest(vcpu))\n+\t\treturn -ENOTTY;\n+\tif (!to_kvm_sev_info(kvm)-\u003esnp_context)\n+\t\treturn -EINVAL;\n+\n+\tguard(mutex)(\u0026svm-\u003esev_es.snp_vmsa_mutex);\n+\n+\tif (VALID_PAGE(svm-\u003esev_es.snp_guest_vmsa_gpa) \u0026\u0026\n+\t VALID_PAGE(svm-\u003evmcb-\u003econtrol.vmsa_pa)) {\n+\t\tstate.vmsa_gpa = svm-\u003esev_es.snp_guest_vmsa_gpa;\n+\t\tstate.valid_fields |= KVM_SEV_SNP_VCPU_STATE_VMSA_VALID;\n+\t}\n+\n+\tif (VALID_PAGE(svm-\u003evmcb-\u003econtrol.ghcb_gpa)) {\n+\t\tstate.ghcb_gpa = svm-\u003evmcb-\u003econtrol.ghcb_gpa;\n+\t\tstate.valid_fields |= KVM_SEV_SNP_VCPU_STATE_GHCB_VALID;\n+\t}\n+\n+\tif (copy_to_user(u64_to_user_ptr(argp-\u003edata), \u0026state, sizeof(state)))\n+\t\treturn -EFAULT;\n+\n+\treturn 0;\n+}\n+\n+static int snp_set_vcpu_state(struct kvm_vcpu *vcpu,\n+\t\t\t struct kvm_sev_cmd *argp)\n+{\n+\tstruct vcpu_svm *svm = to_svm(vcpu);\n+\tstruct kvm *kvm = vcpu-\u003ekvm;\n+\tstruct kvm_sev_info *sev = to_kvm_sev_info(kvm);\n+\tstruct kvm_sev_snp_vcpu_state state;\n+\tint ret;\n+\n+\tif (!is_sev_snp_guest(vcpu))\n+\t\treturn -ENOTTY;\n+\tif (!sev-\u003esnp_direct_vmsa)\n+\t\treturn -EINVAL;\n+\tif (!sev-\u003esnp_context || kvm-\u003earch.pre_fault_allowed)\n+\t\treturn -EINVAL;\n+\n+\tif (copy_from_user(\u0026state, u64_to_user_ptr(argp-\u003edata), sizeof(state)))\n+\t\treturn -EFAULT;\n+\n+\tif (memchr_inv(state.pad, 0, sizeof(state.pad)) ||\n+\t state.valid_fields \u0026 ~(KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |\n+\t\t\t\t KVM_SEV_SNP_VCPU_STATE_GHCB_VALID))\n+\t\treturn -EINVAL;\n+\n+\tif (state.valid_fields \u0026 KVM_SEV_SNP_VCPU_STATE_VMSA_VALID) {\n+\t\tif (!PAGE_ALIGNED(state.vmsa_gpa) ||\n+\t\t !page_address_valid(vcpu, state.vmsa_gpa) ||\n+\t\t IS_ALIGNED(state.vmsa_gpa, PMD_SIZE))\n+\t\t\treturn -EINVAL;\n+\t}\n+\n+\tguard(mutex)(\u0026svm-\u003esev_es.snp_vmsa_mutex);\n+\n+\tif (state.valid_fields \u0026 KVM_SEV_SNP_VCPU_STATE_VMSA_VALID) {\n+\t\tret = sev_snp_install_guest_vmsa(svm, state.vmsa_gpa);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t} else {\n+\t\tsvm-\u003esev_es.snp_has_guest_vmsa = true;\n+\t\tsvm-\u003esev_es.snp_guest_vmsa_gpa = INVALID_PAGE;\n+\t\tsvm-\u003evmcb-\u003econtrol.vmsa_pa = INVALID_PAGE;\n+\t}\n+\n+\tif (state.valid_fields \u0026 KVM_SEV_SNP_VCPU_STATE_GHCB_VALID)\n+\t\tsvm-\u003evmcb-\u003econtrol.ghcb_gpa = state.ghcb_gpa;\n+\telse\n+\t\tsvm-\u003evmcb-\u003econtrol.ghcb_gpa = INVALID_PAGE;\n+\n+\tvmcb_mark_all_dirty(svm-\u003evmcb);\n+\treturn 0;\n+}\n+\n static int snp_launch_finish(struct kvm *kvm, struct kvm_sev_cmd *argp)\n {\n \tstruct kvm_sev_info *sev = to_kvm_sev_info(kvm);\n@@ -2755,6 +2866,35 @@ int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp)\n \treturn r;\n }\n \n+int sev_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp)\n+{\n+\tstruct kvm_sev_cmd sev_cmd;\n+\tint ret;\n+\n+\tif (!sev_enabled)\n+\t\treturn -ENOTTY;\n+\tif (!argp)\n+\t\treturn -EINVAL;\n+\tif (copy_from_user(\u0026sev_cmd, argp, sizeof(sev_cmd)))\n+\t\treturn -EFAULT;\n+\n+\tswitch (sev_cmd.id) {\n+\tcase KVM_SEV_SNP_GET_VCPU_STATE:\n+\t\tret = snp_get_vcpu_state(vcpu, \u0026sev_cmd);\n+\t\tbreak;\n+\tcase KVM_SEV_SNP_SET_VCPU_STATE:\n+\t\tret = snp_set_vcpu_state(vcpu, \u0026sev_cmd);\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (copy_to_user(argp, \u0026sev_cmd, sizeof(sev_cmd)))\n+\t\treturn -EFAULT;\n+\n+\treturn ret;\n+}\n+\n int sev_mem_enc_register_region(struct kvm *kvm,\n \t\t\t\tstruct kvm_enc_region *range)\n {\n@@ -3493,7 +3633,8 @@ static bool sev_es_are_required_ghcb_fields_valid(struct vcpu_svm *svm)\n \tcase SVM_VMGEXIT_AP_CREATION:\n \t\treturn kvm_ghcb_rax_is_valid(svm) ||\n \t\t lower_32_bits(control-\u003eexit_info_1) == SVM_VMGEXIT_AP_DESTROY;\n-\t\tbreak;\n+\tcase SVM_VMGEXIT_GET_APIC_IDS:\n+\t\treturn kvm_ghcb_rax_is_valid(svm);\n \tcase SVM_VMGEXIT_MMIO_READ:\n \tcase SVM_VMGEXIT_MMIO_WRITE:\n \tcase SVM_VMGEXIT_PSC:\n@@ -3556,6 +3697,9 @@ void sev_free_vcpu(struct kvm_vcpu *vcpu)\n \t * a guest-owned page. Transition the page to hypervisor state before\n \t * releasing it back to the system.\n \t */\n+\tif (!svm-\u003esev_es.vmsa)\n+\t\tgoto skip_vmsa_free;\n+\n \tif (is_sev_snp_guest(vcpu)) {\n \t\tu64 pfn = __pa(svm-\u003esev_es.vmsa) \u003e\u003e PAGE_SHIFT;\n \n@@ -4025,6 +4169,56 @@ static int snp_begin_psc(struct vcpu_svm *svm)\n \treturn snp_do_psc(svm);\n }\n \n+/*\n+ * Install a guest-owned VMSA. The caller must serialize against AP creation\n+ * and destruction with snp_vmsa_mutex.\n+ */\n+static int sev_snp_install_guest_vmsa(struct vcpu_svm *svm, gpa_t gpa)\n+{\n+\tstruct kvm *kvm = svm-\u003evcpu.kvm;\n+\tstruct kvm_memory_slot *slot;\n+\tunsigned long mmu_seq;\n+\tstruct page *page;\n+\tkvm_pfn_t pfn;\n+\tgfn_t gfn;\n+\tint idx;\n+\tint ret;\n+\n+\tlockdep_assert_held(\u0026svm-\u003esev_es.snp_vmsa_mutex);\n+\n+\tgfn = gpa_to_gfn(gpa);\n+\tidx = srcu_read_lock(\u0026kvm-\u003esrcu);\n+\tslot = gfn_to_memslot(kvm, gfn);\n+\tif (!slot) {\n+\t\tret = -EINVAL;\n+\t\tgoto out_unlock;\n+\t}\n+\n+\tmmu_seq = kvm-\u003emmu_invalidate_seq;\n+\t/* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */\n+\tsmp_rmb();\n+\n+\t/* Guest-owned VMSAs are backed by guest_memfd private memory. */\n+\tret = kvm_gmem_get_pfn(kvm, slot, gfn, \u0026pfn, \u0026page, NULL);\n+\tif (ret)\n+\t\tgoto out_unlock;\n+\n+\tread_lock(\u0026kvm-\u003emmu_lock);\n+\tif (mmu_invalidate_retry_gfn(kvm, mmu_seq, gfn)) {\n+\t\tret = -EAGAIN;\n+\t} else {\n+\t\tsvm-\u003esev_es.snp_has_guest_vmsa = true;\n+\t\tWRITE_ONCE(svm-\u003esev_es.snp_guest_vmsa_gpa, gpa);\n+\t\tsvm-\u003evmcb-\u003econtrol.vmsa_pa = pfn_to_hpa(pfn);\n+\t}\n+\tread_unlock(\u0026kvm-\u003emmu_lock);\n+\n+\tkvm_release_page_clean(page);\n+out_unlock:\n+\tsrcu_read_unlock(\u0026kvm-\u003esrcu, idx);\n+\treturn ret;\n+}\n+\n static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)\n {\n \tstruct vcpu_svm *svm = to_svm(vcpu);\n@@ -4034,6 +4228,7 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)\n \tunsigned long mmu_seq;\n \tstruct page *page;\n \tkvm_pfn_t pfn;\n+\tint idx;\n \n \tlockdep_assert_held(\u0026svm-\u003esev_es.snp_vmsa_mutex);\n \n@@ -4052,22 +4247,19 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)\n \tvmcb_mark_all_dirty(svm-\u003evmcb);\n \n \t/*\n-\t * From this point forward, the VMSA will always be a guest-mapped page\n-\t * rather than the initial one allocated by KVM in svm-\u003esev_es.vmsa. In\n-\t * theory, svm-\u003esev_es.vmsa could be free'd and cleaned up here, but\n-\t * that involves cleanups like flushing caches, which would ideally be\n-\t * handled during teardown rather than guest boot. Deferring that also\n-\t * allows the existing logic for SEV-ES VMSAs to be re-used with\n-\t * minimal SNP-specific changes.\n+\t * From this point forward, the VMSA will always be a guest-mapped page.\n+\t * If KVM allocated an initial VMSA, keep it until teardown to defer\n+\t * cache flushing and other cleanup out of the guest boot path.\n \t */\n \tsvm-\u003esev_es.snp_has_guest_vmsa = true;\n \n \tif (!VALID_PAGE(gpa))\n \t\treturn;\n \n-\tslot = gfn_to_memslot(vcpu-\u003ekvm, gfn);\n+\tidx = srcu_read_lock(\u0026kvm-\u003esrcu);\n+\tslot = gfn_to_memslot(kvm, gfn);\n \tif (!slot)\n-\t\treturn;\n+\t\tgoto out_unlock;\n \n \tmmu_seq = kvm-\u003emmu_invalidate_seq;\n \tsmp_rmb();\n@@ -4076,8 +4268,8 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)\n \t * The new VMSA will be private memory guest memory, so retrieve the\n \t * PFN from the gmem backend.\n \t */\n-\tif (kvm_gmem_get_pfn(vcpu-\u003ekvm, slot, gfn, \u0026pfn, \u0026page, NULL))\n-\t\treturn;\n+\tif (kvm_gmem_get_pfn(kvm, slot, gfn, \u0026pfn, \u0026page, NULL))\n+\t\tgoto out_unlock;\n \n \tread_lock(\u0026kvm-\u003emmu_lock);\n \t/*\n@@ -4094,6 +4286,8 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)\n \tread_unlock(\u0026kvm-\u003emmu_lock);\n \n \tkvm_release_page_clean(page);\n+out_unlock:\n+\tsrcu_read_unlock(\u0026kvm-\u003esrcu, idx);\n }\n \n /*\n@@ -4215,6 +4409,63 @@ static int sev_snp_ap_creation(struct vcpu_svm *svm)\n \treturn 0;\n }\n \n+struct sev_apic_id_desc {\n+\tu32 num_entries;\n+\tu32 apic_ids[];\n+};\n+\n+static int sev_snp_get_apic_ids(struct vcpu_svm *svm)\n+{\n+\tstruct kvm_vcpu *vcpu = \u0026svm-\u003evcpu;\n+\tstruct kvm *kvm = vcpu-\u003ekvm;\n+\tstruct sev_apic_id_desc *desc;\n+\tunsigned int nr_vcpus, size;\n+\tunsigned int i;\n+\tgpa_t gpa, end_gpa;\n+\tu64 pages;\n+\n+\tnr_vcpus = atomic_read(\u0026kvm-\u003eonline_vcpus);\n+\tsize = sizeof(*desc) + (nr_vcpus * sizeof(desc-\u003eapic_ids[0]));\n+\n+\tpages = vcpu-\u003earch.regs[VCPU_REGS_RAX];\n+\tif (pages \u003c PFN_UP(size)) {\n+\t\tvcpu-\u003earch.regs[VCPU_REGS_RAX] = PFN_UP(size);\n+\t\treturn 1;\n+\t}\n+\n+\tgpa = svm-\u003evmcb-\u003econtrol.exit_info_1;\n+\tif (!PAGE_ALIGNED(gpa) ||\n+\t check_add_overflow(gpa, size - 1, \u0026end_gpa) ||\n+\t !page_address_valid(vcpu, gpa) ||\n+\t !page_address_valid(vcpu, end_gpa \u0026 PAGE_MASK))\n+\t\tgoto invalid_buffer;\n+\n+\tdesc = kvzalloc(size, GFP_KERNEL_ACCOUNT);\n+\tif (!desc)\n+\t\treturn -ENOMEM;\n+\n+\tdesc-\u003enum_entries = nr_vcpus;\n+\tfor (i = 0; i \u003c nr_vcpus; i++) {\n+\t\tstruct kvm_vcpu *listed_vcpu = kvm_get_vcpu(kvm, i);\n+\n+\t\tif (WARN_ON_ONCE(!listed_vcpu))\n+\t\t\tgoto invalid_buffer_free;\n+\t\tdesc-\u003eapic_ids[i] = listed_vcpu-\u003evcpu_id;\n+\t}\n+\n+\tif (kvm_write_guest(kvm, gpa, desc, size))\n+\t\tgoto invalid_buffer_free;\n+\n+\tkvfree(desc);\n+\treturn 1;\n+\n+invalid_buffer_free:\n+\tkvfree(desc);\n+invalid_buffer:\n+\tsvm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);\n+\treturn 1;\n+}\n+\n static int snp_handle_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t resp_gpa)\n {\n \tstruct sev_data_snp_guest_request data = {0};\n@@ -4494,6 +4745,7 @@ static bool is_snp_only_vmgexit(u64 exit_code)\n {\n \tswitch (exit_code) {\n \tcase SVM_VMGEXIT_AP_CREATION:\n+\tcase SVM_VMGEXIT_GET_APIC_IDS:\n \tcase SVM_VMGEXIT_GUEST_REQUEST:\n \tcase SVM_VMGEXIT_EXT_GUEST_REQUEST:\n \tcase SVM_VMGEXIT_PSC:\n@@ -4665,6 +4917,8 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)\n \t\tif (sev_snp_ap_creation(svm))\n \t\t\tsvm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);\n \t\treturn 1;\n+\tcase SVM_VMGEXIT_GET_APIC_IDS:\n+\t\treturn sev_snp_get_apic_ids(svm);\n \tcase SVM_VMGEXIT_GUEST_REQUEST:\n \tcase SVM_VMGEXIT_EXT_GUEST_REQUEST:\n \t\tif (!PAGE_ALIGNED(control-\u003eexit_info_1) ||\n@@ -4862,12 +5116,15 @@ void sev_init_vmcb(struct vcpu_svm *svm, bool init_event)\n int sev_vcpu_create(struct kvm_vcpu *vcpu)\n {\n \tstruct vcpu_svm *svm = to_svm(vcpu);\n+\tstruct kvm_sev_info *sev = to_kvm_sev_info(vcpu-\u003ekvm);\n \tstruct page *vmsa_page;\n \n \tmutex_init(\u0026svm-\u003esev_es.snp_vmsa_mutex);\n \n \tif (!is_sev_es_guest(vcpu))\n \t\treturn 0;\n+\tif (is_sev_snp_guest(vcpu) \u0026\u0026 sev-\u003esnp_direct_vmsa)\n+\t\tgoto init_vmsa_state;\n \n \t/*\n \t * SEV-ES guests require a separate (from the VMCB) VMSA page used to\n@@ -4878,6 +5135,8 @@ int sev_vcpu_create(struct kvm_vcpu *vcpu)\n \t\treturn -ENOMEM;\n \n \tsvm-\u003esev_es.vmsa = page_address(vmsa_page);\n+\n+init_vmsa_state:\n \tsvm-\u003esev_es.snp_pending_vmsa_gpa = INVALID_PAGE;\n \tsvm-\u003esev_es.snp_guest_vmsa_gpa = INVALID_PAGE;\n \ndiff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c\nindex 7d59d301e1e54..378d94409134f 100644\n--- a/arch/x86/kvm/svm/svm.c\n+++ b/arch/x86/kvm/svm/svm.c\n@@ -5325,6 +5325,27 @@ static void *svm_alloc_apic_backing_page(struct kvm_vcpu *vcpu)\n \treturn page_address(page);\n }\n \n+static int svm_enable_vm_cap(struct kvm *kvm, struct kvm_enable_cap *cap)\n+{\n+\tswitch (cap-\u003ecap) {\n+#ifdef CONFIG_KVM_AMD_SEV\n+\tcase KVM_CAP_SNP_DIRECT_VMSA:\n+\t\tif (memchr_inv(cap-\u003eargs, 0, sizeof(cap-\u003eargs)) ||\n+\t\t kvm-\u003earch.vm_type != KVM_X86_SNP_VM)\n+\t\t\treturn -EINVAL;\n+\n+\t\tguard(mutex)(\u0026kvm-\u003elock);\n+\t\tif (kvm-\u003ecreated_vcpus)\n+\t\t\treturn -EINVAL;\n+\n+\t\tto_kvm_sev_info(kvm)-\u003esnp_direct_vmsa = true;\n+\t\treturn 0;\n+#endif\n+\tdefault:\n+\t\treturn -EINVAL;\n+\t}\n+}\n+\n struct kvm_x86_ops svm_x86_ops __initdata = {\n \t.name = KBUILD_MODNAME,\n \n@@ -5345,6 +5366,7 @@ struct kvm_x86_ops svm_x86_ops __initdata = {\n \t.vm_init = svm_vm_init,\n \t.vm_pre_destroy = avic_vm_pre_destroy,\n \t.vm_destroy = svm_vm_destroy,\n+\t.enable_vm_cap = svm_enable_vm_cap,\n \n \t.prepare_switch_to_guest = svm_prepare_switch_to_guest,\n \t.vcpu_load = svm_vcpu_load,\n@@ -5444,6 +5466,7 @@ struct kvm_x86_ops svm_x86_ops __initdata = {\n \t.vcpu_needs_initialization = sev_vcpu_needs_initialization,\n \t.dev_get_attr = sev_dev_get_attr,\n \t.mem_enc_ioctl = sev_mem_enc_ioctl,\n+\t.vcpu_mem_enc_ioctl = sev_vcpu_mem_enc_ioctl,\n \t.mem_enc_register_region = sev_mem_enc_register_region,\n \t.mem_enc_unregister_region = sev_mem_enc_unregister_region,\n \t.guest_memory_reclaimed = sev_guest_memory_reclaimed,\ndiff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h\nindex e958943b81627..c920c797b7ecf 100644\n--- a/arch/x86/kvm/svm/svm.h\n+++ b/arch/x86/kvm/svm/svm.h\n@@ -121,6 +121,7 @@ struct kvm_sev_info {\n \tstruct mutex guest_req_mutex; /* Must acquire before using bounce buffers */\n \tcpumask_var_t have_run_cpus; /* CPUs that have done VMRUN for this VM. */\n \tbool snp_certs_enabled;\t/* SNP certificate-fetching support. */\n+\tbool snp_direct_vmsa;\t/* Userspace provides and measures VMSA pages. */\n };\n #endif\n \n@@ -982,6 +983,7 @@ void sev_es_unmap_ghcb(struct vcpu_svm *svm);\n #ifdef CONFIG_KVM_AMD_SEV\n bool sev_vcpu_needs_initialization(struct kvm_vcpu *vcpu);\n int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp);\n+int sev_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp);\n int sev_mem_enc_register_region(struct kvm *kvm,\n \t\t\t\tstruct kvm_enc_region *range);\n int sev_mem_enc_unregister_region(struct kvm *kvm,\ndiff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c\nindex 79468ddfe4736..232507ae504a6 100644\n--- a/arch/x86/kvm/x86.c\n+++ b/arch/x86/kvm/x86.c\n@@ -2404,6 +2404,10 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)\n \tcase KVM_CAP_VM_TYPES:\n \t\tr = kvm_caps.supported_vm_types;\n \t\tbreak;\n+\tcase KVM_CAP_SNP_VCPU_STATE:\n+\tcase KVM_CAP_SNP_DIRECT_VMSA:\n+\t\tr = !!(kvm_caps.supported_vm_types \u0026 BIT(KVM_X86_SNP_VM));\n+\t\tbreak;\n \tcase KVM_CAP_READONLY_MEM:\n \t\tr = kvm ? kvm_arch_has_readonly_mem(kvm) : 1;\n \t\tbreak;\n@@ -4212,6 +4216,8 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,\n \t}\n \tdefault:\n \t\tr = -EINVAL;\n+\t\tif (kvm_x86_ops.enable_vm_cap)\n+\t\t\tr = kvm_x86_call(enable_vm_cap)(kvm, cap);\n \t\tbreak;\n \t}\n \treturn r;\ndiff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h\nindex ac2d77d149635..8c6765f78c1c1 100644\n--- a/include/uapi/linux/kvm.h\n+++ b/include/uapi/linux/kvm.h\n@@ -999,6 +999,8 @@ struct kvm_enable_cap {\n #define KVM_CAP_S390_HPAGE_2G 249\n #define KVM_CAP_PPC_COMPAT_CAPS 250\n #define KVM_CAP_ARM_PMU_V3_STRICT 251\n+#define KVM_CAP_SNP_DIRECT_VMSA 252\n+#define KVM_CAP_SNP_VCPU_STATE 253\n \n struct kvm_irq_routing_irqchip {\n \t__u32 irqchip;\ndiff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm\nindex 96bab7002d39e..4cf0a8297b041 100644\n--- a/tools/testing/selftests/kvm/Makefile.kvm\n+++ b/tools/testing/selftests/kvm/Makefile.kvm\n@@ -149,6 +149,8 @@ TEST_GEN_PROGS_x86 += x86/xen_vmcall_test\n TEST_GEN_PROGS_x86 += x86/sev_dbg_test\n TEST_GEN_PROGS_x86 += x86/sev_init2_tests\n TEST_GEN_PROGS_x86 += x86/sev_migrate_tests\n+TEST_GEN_PROGS_x86 += x86/sev_snp_apic_id_test\n+TEST_GEN_PROGS_x86 += x86/sev_snp_direct_vmsa_test\n TEST_GEN_PROGS_x86 += x86/sev_smoke_test\n TEST_GEN_PROGS_x86 += x86/amx_test\n TEST_GEN_PROGS_x86 += x86/max_vcpuid_cap_test\ndiff --git a/tools/testing/selftests/kvm/include/x86/sev.h b/tools/testing/selftests/kvm/include/x86/sev.h\nindex dec383e59a47e..0f6c92b802eea 100644\n--- a/tools/testing/selftests/kvm/include/x86/sev.h\n+++ b/tools/testing/selftests/kvm/include/x86/sev.h\n@@ -100,6 +100,30 @@ static inline u64 snp_default_policy(void)\n \t__TEST_ASSERT_VM_VCPU_IOCTL(!ret, #cmd,\tret, vm);\t\t\\\n })\n \n+static inline int __vcpu_sev_ioctl(struct kvm_vcpu *vcpu, u32 cmd, void *arg)\n+{\n+\tunion {\n+\t\tstruct kvm_sev_cmd c;\n+\t\tunsigned long raw;\n+\t} sev_cmd = { .c = {\n+\t\t.id = cmd,\n+\t\t.data = (u64)arg,\n+\t\t.sev_fd = vcpu-\u003evm-\u003earch.sev_fd,\n+\t} };\n+\tint ret;\n+\n+\tret = __vcpu_ioctl(vcpu, KVM_MEMORY_ENCRYPT_OP, \u0026sev_cmd.raw);\n+\treturn ret ?: sev_cmd.c.error;\n+}\n+\n+#define vcpu_sev_ioctl(vcpu, cmd, arg)\t\t\t\t\\\n+({\t\t\t\t\t\t\t\t\\\n+\tstruct kvm_vcpu *__vcpu = (vcpu);\t\t\t\t\\\n+\tint ret = __vcpu_sev_ioctl(__vcpu, cmd, arg);\t\t\\\n+\t\t\t\t\t\t\t\t\\\n+\t__TEST_ASSERT_VM_VCPU_IOCTL(!ret, #cmd, ret, __vcpu-\u003evm);\t\\\n+})\n+\n void sev_vm_init(struct kvm_vm *vm);\n void sev_es_vm_init(struct kvm_vm *vm);\n void snp_vm_init(struct kvm_vm *vm);\n@@ -144,6 +168,31 @@ static inline void snp_launch_update_data(struct kvm_vm *vm, gpa_t gpa,\n \tvm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_UPDATE, \u0026update_data);\n }\n \n+static inline void snp_launch_update_vmsa(struct kvm_vm *vm, gpa_t gpa,\n+\t\t\t\t\t void *vmsa)\n+{\n+\tvm_mem_set_private(vm, gpa, PAGE_SIZE);\n+\tsnp_launch_update_data(vm, gpa, (u64)vmsa, PAGE_SIZE,\n+\t\t\t KVM_SEV_SNP_PAGE_TYPE_VMSA);\n+}\n+\n+static inline void snp_get_vcpu_state(struct kvm_vcpu *vcpu,\n+\t\t\t\t struct kvm_sev_snp_vcpu_state *state)\n+{\n+\tvcpu_sev_ioctl(vcpu, KVM_SEV_SNP_GET_VCPU_STATE, state);\n+}\n+\n+static inline void snp_set_vcpu_state(struct kvm_vcpu *vcpu, gpa_t vmsa_gpa)\n+{\n+\tstruct kvm_sev_snp_vcpu_state state = {\n+\t\t.vmsa_gpa = vmsa_gpa,\n+\t\t.valid_fields = KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |\n+\t\t\t\tKVM_SEV_SNP_VCPU_STATE_GHCB_VALID,\n+\t};\n+\n+\tvcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, \u0026state);\n+}\n+\n static inline void sev_dbg_crypt_memory(struct kvm_vm *vm, unsigned int cmd,\n \t\t\t\t\tvoid *dst, void *src, unsigned int len)\n {\ndiff --git a/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c b/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c\nnew file mode 100644\nindex 0000000000000..8ad9c15091c2d\n--- /dev/null\n+++ b/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c\n@@ -0,0 +1,180 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+#include \u003cstddef.h\u003e\n+#include \u003cstdint.h\u003e\n+\n+#include \"kvm_util.h\"\n+#include \"processor.h\"\n+#include \"sev.h\"\n+#include \"svm_util.h\"\n+\n+#define GHCB_SAVE_RAX_OFFSET\t\t0x1f8\n+#define GHCB_SAVE_SW_EXIT_CODE_OFFSET\t0x390\n+#define GHCB_SAVE_SW_EXIT_INFO_1_OFFSET\t0x398\n+#define GHCB_SAVE_SW_EXIT_INFO_2_OFFSET\t0x3a0\n+#define GHCB_SAVE_VALID_BITMAP_OFFSET\t0x3f0\n+\n+#define GHCB_MSR_REG_GPA_REQ\t\t0x012\n+#define GHCB_MSR_REG_GPA_RESP\t\t0x013\n+#define GHCB_MSR_INFO_MASK\t\tGENMASK_ULL(11, 0)\n+\n+#define GHCB_HV_RESP_MALFORMED_INPUT\t2\n+#define GHCB_ERR_MISSING_INPUT\t\t4\n+#define GHCB_ERR_INVALID_INPUT\t\t5\n+\n+struct apic_id_desc {\n+\tu32 nr_entries;\n+\tu32 apic_ids[];\n+};\n+\n+struct apic_id_results {\n+\tu64 missing_info1;\n+\tu64 missing_info2;\n+\tu64 zero_info1;\n+\tu64 zero_info2;\n+\tu64 zero_rax;\n+\tu64 invalid_info1;\n+\tu64 invalid_info2;\n+\tu64 valid_info1;\n+\tu64 valid_info2;\n+\tu32 nr_entries;\n+\tu32 first_apic_id;\n+\tu32 last_apic_id;\n+};\n+\n+static void ghcb_set_field(void *ghcb, size_t offset, u64 value, bool valid)\n+{\n+\tu8 *valid_bitmap = ghcb + GHCB_SAVE_VALID_BITMAP_OFFSET;\n+\n+\t*(u64 *)(ghcb + offset) = value;\n+\tif (valid)\n+\t\tvalid_bitmap[(offset / sizeof(u64)) / 8] |=\n+\t\t\tBIT((offset / sizeof(u64)) % 8);\n+}\n+\n+static u64 ghcb_get_field(void *ghcb, size_t offset)\n+{\n+\treturn *(u64 *)(ghcb + offset);\n+}\n+\n+static void do_get_apic_ids(void *ghcb, gpa_t buffer_gpa, u64 pages,\n+\t\t\t bool rax_valid)\n+{\n+\tmemset(ghcb, 0, PAGE_SIZE);\n+\tghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_CODE_OFFSET,\n+\t\t SVM_VMGEXIT_GET_APIC_IDS, true);\n+\tghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET, buffer_gpa, true);\n+\tghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET, 0, true);\n+\tghcb_set_field(ghcb, GHCB_SAVE_RAX_OFFSET, pages, rax_valid);\n+\tvmgexit();\n+}\n+\n+static void guest_code(void *ghcb, gpa_t ghcb_gpa, void *list,\n+\t\t gpa_t list_gpa, struct apic_id_results *results,\n+\t\t u64 expected_vcpus)\n+{\n+\tstruct apic_id_desc *desc = list;\n+\tu64 msr;\n+\n+\twrmsr(MSR_AMD64_SEV_ES_GHCB,\n+\t (ghcb_gpa \u003e\u003e PAGE_SHIFT) \u003c\u003c PAGE_SHIFT | GHCB_MSR_REG_GPA_REQ);\n+\tvmgexit();\n+\tmsr = rdmsr(MSR_AMD64_SEV_ES_GHCB);\n+\tif ((msr \u0026 GHCB_MSR_INFO_MASK) != GHCB_MSR_REG_GPA_RESP)\n+\t\tgoto terminate;\n+\n+\twrmsr(MSR_AMD64_SEV_ES_GHCB, ghcb_gpa);\n+\n+\tdo_get_apic_ids(ghcb, list_gpa, 1, false);\n+\tresults-\u003emissing_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);\n+\tresults-\u003emissing_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);\n+\n+\tdo_get_apic_ids(ghcb, list_gpa, 0, true);\n+\tresults-\u003ezero_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);\n+\tresults-\u003ezero_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);\n+\tresults-\u003ezero_rax = ghcb_get_field(ghcb, GHCB_SAVE_RAX_OFFSET);\n+\n+\tdo_get_apic_ids(ghcb, BIT_ULL(52), 2, true);\n+\tresults-\u003einvalid_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);\n+\tresults-\u003einvalid_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);\n+\n+\tdo_get_apic_ids(ghcb, list_gpa, 2, true);\n+\tresults-\u003evalid_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);\n+\tresults-\u003evalid_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);\n+\tresults-\u003enr_entries = desc-\u003enr_entries;\n+\tresults-\u003efirst_apic_id = desc-\u003eapic_ids[0];\n+\tresults-\u003elast_apic_id = desc-\u003eapic_ids[expected_vcpus - 1];\n+\n+terminate:\n+\twrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);\n+\tvmgexit();\n+}\n+\n+static void run_apic_id_test(unsigned int nr_vcpus)\n+{\n+\tstruct apic_id_results *results;\n+\tstruct kvm_vcpu *vcpu;\n+\tstruct kvm_vm *vm;\n+\tgva_t ghcb_gva, list_gva, results_gva;\n+\tgpa_t ghcb_gpa, list_gpa;\n+\tunsigned int i;\n+\n+\tkvm_set_files_rlimit(nr_vcpus);\n+\tvm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_code, \u0026vcpu);\n+\tfor (i = 1; i \u003c nr_vcpus; i++)\n+\t\t__vm_vcpu_add(vm, i);\n+\n+\tghcb_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,\n+\t\t\t\t MEM_REGION_TEST_DATA);\n+\tlist_gva = vm_alloc_shared(vm, 2 * PAGE_SIZE, KVM_UTIL_MIN_VADDR,\n+\t\t\t\t MEM_REGION_TEST_DATA);\n+\tresults_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,\n+\t\t\t\t MEM_REGION_TEST_DATA);\n+\tghcb_gpa = addr_gva2gpa(vm, ghcb_gva);\n+\tlist_gpa = addr_gva2gpa(vm, list_gva);\n+\tresults = addr_gva2hva(vm, results_gva);\n+\n+\tvcpu_args_set(vcpu, 6, ghcb_gva, ghcb_gpa, list_gva, list_gpa,\n+\t\t results_gva, nr_vcpus);\n+\tmemset(addr_gva2hva(vm, ghcb_gva), 0, PAGE_SIZE);\n+\tmemset(addr_gva2hva(vm, list_gva), 0, 2 * PAGE_SIZE);\n+\tmemset(results, 0, PAGE_SIZE);\n+\tvm_sev_launch(vm, snp_default_policy(), NULL);\n+\n+\tvcpu_run(vcpu);\n+\tTEST_ASSERT_EQ(vcpu-\u003erun-\u003eexit_reason, KVM_EXIT_SYSTEM_EVENT);\n+\tTEST_ASSERT_EQ(vcpu-\u003erun-\u003esystem_event.type, KVM_SYSTEM_EVENT_SEV_TERM);\n+\n+\tTEST_ASSERT_EQ(results-\u003emissing_info1, GHCB_HV_RESP_MALFORMED_INPUT);\n+\tTEST_ASSERT_EQ(results-\u003emissing_info2, GHCB_ERR_MISSING_INPUT);\n+\tTEST_ASSERT_EQ(results-\u003ezero_info1, 0);\n+\tTEST_ASSERT_EQ(results-\u003ezero_info2, 0);\n+\tTEST_ASSERT_EQ(results-\u003ezero_rax,\n+\t\t DIV_ROUND_UP(sizeof(struct apic_id_desc) + nr_vcpus * sizeof(u32),\n+\t\t\t\t PAGE_SIZE));\n+\tTEST_ASSERT_EQ(results-\u003einvalid_info1, GHCB_HV_RESP_MALFORMED_INPUT);\n+\tTEST_ASSERT_EQ(results-\u003einvalid_info2, GHCB_ERR_INVALID_INPUT);\n+\tTEST_ASSERT_EQ(results-\u003evalid_info1, 0);\n+\tTEST_ASSERT_EQ(results-\u003evalid_info2, 0);\n+\tTEST_ASSERT_EQ(results-\u003enr_entries, nr_vcpus);\n+\tTEST_ASSERT_EQ(results-\u003efirst_apic_id, 0);\n+\tTEST_ASSERT_EQ(results-\u003elast_apic_id, nr_vcpus - 1);\n+\n+\tkvm_vm_free(vm);\n+}\n+\n+int main(int argc, char *argv[])\n+{\n+\tunsigned int max_vcpus;\n+\n+\tTEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) \u0026 BIT(KVM_X86_SNP_VM));\n+\trun_apic_id_test(2);\n+\n+\t/* 1024 IDs cross the one-page descriptor boundary. */\n+\tmax_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);\n+\tif (max_vcpus \u003e= 1024)\n+\t\trun_apic_id_test(1024);\n+\telse\n+\t\tpr_info(\"Skipping vCPU-count boundary test (max vCPUs: %u)\\n\", max_vcpus);\n+\n+\treturn 0;\n+}\ndiff --git a/tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c b/tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c\nnew file mode 100644\nindex 0000000000000..72eecf3853a32\n--- /dev/null\n+++ b/tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c\n@@ -0,0 +1,421 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+#include \u003cerrno.h\u003e\n+#include \u003cstdlib.h\u003e\n+\n+#include \"kvm_util.h\"\n+#include \"processor.h\"\n+#include \"sev.h\"\n+#include \"svm_util.h\"\n+\n+#define DIRECT_MARKER\t0x444952454354564dULL\n+#define LEGACY_MARKER\t0x4c4547414359564dULL\n+#define SNP_ACTIVE_SEV_FEATURE\tBIT_ULL(0)\n+#define VMSA_PMD_SIZE\tBIT_ULL(21)\n+#define VMSA_MIN_GPA\t(VMSA_PMD_SIZE + PAGE_SIZE)\n+\n+struct test_vmsa {\n+\tstruct vmcb_seg es, cs, ss, ds, fs, gs;\n+\tstruct vmcb_seg gdtr, ldtr, idtr, tr;\n+\tu64 pl0_ssp, pl1_ssp, pl2_ssp, pl3_ssp;\n+\tu64 u_cet;\n+\tu8 reserved_0xc8[2];\n+\tu8 vmpl;\n+\tu8 cpl;\n+\tu8 reserved_0xcc[4];\n+\tu64 efer;\n+\tu8 reserved_0xd8[104];\n+\tu64 xss;\n+\tu64 cr4, cr3, cr0, dr7, dr6, rflags, rip;\n+\tu64 dr0, dr1, dr2, dr3;\n+\tu64 dr0_addr_mask, dr1_addr_mask, dr2_addr_mask, dr3_addr_mask;\n+\tu8 reserved_0x1c0[24];\n+\tu64 rsp, s_cet, ssp, isst_addr, rax;\n+\tu64 star, lstar, cstar, sfmask, kernel_gs_base;\n+\tu64 sysenter_cs, sysenter_esp, sysenter_eip, cr2;\n+\tu8 reserved_0x248[32];\n+\tu64 g_pat, dbgctl, br_from, br_to, last_excp_from, last_excp_to;\n+\tu8 reserved_0x298[80];\n+\tu32 pkru, tsc_aux;\n+\tu64 tsc_scale, tsc_offset;\n+\tu8 reserved_0x300[8];\n+\tu64 rcx, rdx, rbx, reserved_0x320, rbp, rsi, rdi;\n+\tu64 r8, r9, r10, r11, r12, r13, r14, r15;\n+\tu8 reserved_0x380[16];\n+\tu64 guest_exit_info_1, guest_exit_info_2, guest_exit_int_info, guest_nrip;\n+\tu64 sev_features, vintr_ctrl, guest_exit_code, virtual_tom, tlb_id, pcpu_id;\n+\tu64 event_inj, xcr0;\n+\tu8 reserved_0x3f0[16];\n+\tu64 x87_dp;\n+\tu32 mxcsr;\n+\tu16 x87_ftw, x87_fsw, x87_fcw, x87_fop, x87_ds, x87_cs;\n+\tu64 x87_rip;\n+\tu8 fpreg_x87[80];\n+\tu8 fpreg_xmm[256];\n+\tu8 fpreg_ymm[256];\n+} __packed;\n+\n+static_assert(offsetof(struct test_vmsa, vmpl) == 0xca);\n+static_assert(offsetof(struct test_vmsa, rip) == 0x178);\n+static_assert(offsetof(struct test_vmsa, sev_features) == 0x3b0);\n+static_assert(offsetof(struct test_vmsa, xcr0) == 0x3e8);\n+\n+static void guest_direct_entry(u64 *marker)\n+{\n+\t*marker = DIRECT_MARKER;\n+\twrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);\n+\tvmgexit();\n+}\n+\n+static void guest_legacy_entry(u64 *marker)\n+{\n+\t*marker = LEGACY_MARKER;\n+\twrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);\n+\tvmgexit();\n+}\n+\n+static void copy_segment(struct vmcb_seg *dst, const struct kvm_segment *src)\n+{\n+\tdst-\u003eselector = src-\u003eselector;\n+\tdst-\u003ebase = src-\u003ebase;\n+\tdst-\u003elimit = src-\u003elimit;\n+\tdst-\u003eattrib = src-\u003etype |\n+\t\t(src-\u003es \u003c\u003c SVM_SELECTOR_S_SHIFT) |\n+\t\t(src-\u003edpl \u003c\u003c SVM_SELECTOR_DPL_SHIFT) |\n+\t\t((src-\u003epresent \u0026\u0026 !src-\u003eunusable) \u003c\u003c SVM_SELECTOR_P_SHIFT) |\n+\t\t(src-\u003eavl \u003c\u003c SVM_SELECTOR_AVL_SHIFT) |\n+\t\t(src-\u003el \u003c\u003c SVM_SELECTOR_L_SHIFT) |\n+\t\t(src-\u003edb \u003c\u003c SVM_SELECTOR_DB_SHIFT) |\n+\t\t(src-\u003eg \u003c\u003c SVM_SELECTOR_G_SHIFT);\n+}\n+\n+static void copy_dtable(struct vmcb_seg *dst, const struct kvm_dtable *src)\n+{\n+\tdst-\u003ebase = src-\u003ebase;\n+\tdst-\u003elimit = src-\u003elimit;\n+}\n+\n+static void prepare_vmsa(struct kvm_vcpu *vcpu, struct test_vmsa *vmsa,\n+\t\t\t void *entry)\n+{\n+\tstruct kvm_sregs sregs;\n+\tstruct kvm_regs regs;\n+\n+\tmemset(vmsa, 0, PAGE_SIZE);\n+\tvcpu_sregs_get(vcpu, \u0026sregs);\n+\tvcpu_regs_get(vcpu, \u0026regs);\n+\n+\tcopy_segment(\u0026vmsa-\u003ees, \u0026sregs.es);\n+\tcopy_segment(\u0026vmsa-\u003ecs, \u0026sregs.cs);\n+\tcopy_segment(\u0026vmsa-\u003ess, \u0026sregs.ss);\n+\tcopy_segment(\u0026vmsa-\u003eds, \u0026sregs.ds);\n+\tcopy_segment(\u0026vmsa-\u003efs, \u0026sregs.fs);\n+\tcopy_segment(\u0026vmsa-\u003egs, \u0026sregs.gs);\n+\tcopy_dtable(\u0026vmsa-\u003egdtr, \u0026sregs.gdt);\n+\tcopy_segment(\u0026vmsa-\u003eldtr, \u0026sregs.ldt);\n+\tcopy_dtable(\u0026vmsa-\u003eidtr, \u0026sregs.idt);\n+\tcopy_segment(\u0026vmsa-\u003etr, \u0026sregs.tr);\n+\n+\tvmsa-\u003ecpl = sregs.cs.dpl;\n+\t/*\n+\t * KVM_GET_SREGS exposes the guest-visible EFER and therefore omits\n+\t * SVME, which KVM normally adds to the hardware VMSA itself.\n+\t */\n+\tvmsa-\u003eefer = sregs.efer | EFER_SVME;\n+\tvmsa-\u003ecr4 = sregs.cr4;\n+\tvmsa-\u003ecr3 = sregs.cr3;\n+\tvmsa-\u003ecr0 = sregs.cr0;\n+\tvmsa-\u003edr7 = 0x400;\n+\tvmsa-\u003edr6 = 0xffff0ff0;\n+\tvmsa-\u003erflags = regs.rflags;\n+\tvmsa-\u003erip = (u64)entry;\n+\tvmsa-\u003ersp = regs.rsp;\n+\tvmsa-\u003erax = regs.rax;\n+\tvmsa-\u003ercx = regs.rcx;\n+\tvmsa-\u003erdx = regs.rdx;\n+\tvmsa-\u003erbx = regs.rbx;\n+\tvmsa-\u003erbp = regs.rbp;\n+\tvmsa-\u003ersi = regs.rsi;\n+\tvmsa-\u003erdi = regs.rdi;\n+\tvmsa-\u003er8 = regs.r8;\n+\tvmsa-\u003er9 = regs.r9;\n+\tvmsa-\u003er10 = regs.r10;\n+\tvmsa-\u003er11 = regs.r11;\n+\tvmsa-\u003er12 = regs.r12;\n+\tvmsa-\u003er13 = regs.r13;\n+\tvmsa-\u003er14 = regs.r14;\n+\tvmsa-\u003er15 = regs.r15;\n+\tvmsa-\u003eg_pat = 0x0007040600070406ULL;\n+\tvmsa-\u003esev_features = SNP_ACTIVE_SEV_FEATURE;\n+\tvmsa-\u003excr0 = 1;\n+\tvmsa-\u003emxcsr = 0x1f80;\n+\tvmsa-\u003ex87_fcw = 0x37f;\n+}\n+\n+static void expect_launch_update_vmsa_error(struct kvm_vm *vm, gpa_t gpa,\n+\t\t\t\t\t void *vmsa, u64 size)\n+{\n+\tstruct kvm_sev_snp_launch_update update = {\n+\t\t.gfn_start = gpa \u003e\u003e PAGE_SHIFT,\n+\t\t.uaddr = (u64)vmsa,\n+\t\t.len = size,\n+\t\t.type = KVM_SEV_SNP_PAGE_TYPE_VMSA,\n+\t};\n+\n+\terrno = 0;\n+\tTEST_ASSERT_EQ(__vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_UPDATE, \u0026update), -1);\n+\tTEST_ASSERT_EQ(errno, EINVAL);\n+}\n+\n+static void exclude_from_normal_launch(struct kvm_vm *vm, gpa_t gpa,\n+\t\t\t\t unsigned int npages)\n+{\n+\tstruct userspace_mem_region *region;\n+\n+\tregion = memslot2region(vm, vm-\u003ememslots[MEM_REGION_TEST_DATA]);\n+\tsparsebit_clear_num(region-\u003eprotected_phy_pages, gpa \u003e\u003e PAGE_SHIFT, npages);\n+}\n+\n+static void assert_vcpu_terminated(struct kvm_vcpu *vcpu)\n+{\n+\tvcpu_run(vcpu);\n+\tTEST_ASSERT_EQ(vcpu-\u003erun-\u003eexit_reason, KVM_EXIT_SYSTEM_EVENT);\n+\tTEST_ASSERT_EQ(vcpu-\u003erun-\u003esystem_event.type, KVM_SYSTEM_EVENT_SEV_TERM);\n+}\n+\n+static struct kvm_vm *create_direct_vmsa_vm(unsigned int nr_vcpus,\n+\t\t\t\t\t void *guest_code,\n+\t\t\t\t\t struct kvm_vcpu **vcpus)\n+{\n+\tstruct vm_shape shape = {\n+\t\t.mode = VM_MODE_DEFAULT,\n+\t\t.type = KVM_X86_SNP_VM,\n+\t};\n+\tstruct kvm_vm *vm;\n+\tunsigned int i;\n+\n+\tvm = __vm_create(shape, nr_vcpus, 0);\n+\tvm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0);\n+\tfor (i = 0; i \u003c nr_vcpus; i++)\n+\t\tvcpus[i] = vm_vcpu_add(vm, i, guest_code);\n+\tkvm_arch_vm_finalize_vcpus(vm);\n+\n+\treturn vm;\n+}\n+\n+static void test_direct_vmsa(void)\n+{\n+\tstruct test_vmsa *vmsas, *selected_vmsa;\n+\tstruct kvm_sev_snp_vcpu_state state = {};\n+\tstruct kvm_mp_state mp_state = {\n+\t\t.mp_state = KVM_MP_STATE_UNINITIALIZED,\n+\t};\n+\tstruct kvm_vcpu *vcpus[2];\n+\tstruct kvm_vcpu *vcpu, *ap;\n+\tstruct kvm_vm *vm;\n+\tgva_t marker_gva;\n+\tgpa_t vmsa_gpa;\n+\tu64 *marker;\n+\n+\tvm = create_direct_vmsa_vm(ARRAY_SIZE(vcpus), guest_legacy_entry, vcpus);\n+\tvcpu = vcpus[0];\n+\tap = vcpus[1];\n+\t/* Restore normal AP state after vm_vcpu_add() makes it runnable. */\n+\tvcpu_mp_state_set(ap, \u0026mp_state);\n+\tmarker_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,\n+\t\t\t\t MEM_REGION_TEST_DATA);\n+\tmarker = addr_gva2hva(vm, marker_gva);\n+\tvcpu_args_set(vcpu, 1, marker_gva);\n+\n+\tvmsa_gpa = vm_phy_pages_alloc(vm, 2, VMSA_MIN_GPA,\n+\t\t\t\t vm-\u003ememslots[MEM_REGION_TEST_DATA]);\n+\tTEST_ASSERT(vmsa_gpa \u0026 (VMSA_PMD_SIZE - 1), \"unsafe VMSA GPA\");\n+\tvmsas = aligned_alloc(PAGE_SIZE, 2 * PAGE_SIZE);\n+\tTEST_ASSERT(vmsas, \"Failed to allocate VMSA source pages\");\n+\tselected_vmsa = (void *)vmsas + PAGE_SIZE;\n+\tprepare_vmsa(vcpu, \u0026vmsas[0], guest_legacy_entry);\n+\tprepare_vmsa(vcpu, selected_vmsa, guest_direct_entry);\n+\n+\tsnp_vm_launch_start(vm, snp_default_policy());\n+\tvm_mem_set_private(vm, vmsa_gpa, 2 * PAGE_SIZE);\n+\texpect_launch_update_vmsa_error(vm, vmsa_gpa, vmsas, 2 * PAGE_SIZE);\n+\n+\tvmsas[0].vmpl = 1;\n+\texpect_launch_update_vmsa_error(vm, vmsa_gpa, vmsas, PAGE_SIZE);\n+\tvmsas[0].vmpl = 0;\n+\tvmsas[0].sev_features = 0;\n+\texpect_launch_update_vmsa_error(vm, vmsa_gpa, vmsas, PAGE_SIZE);\n+\tvmsas[0].sev_features = SNP_ACTIVE_SEV_FEATURE;\n+\n+\tsnp_launch_update_vmsa(vm, vmsa_gpa, vmsas);\n+\tsnp_launch_update_vmsa(vm, vmsa_gpa + PAGE_SIZE, selected_vmsa);\n+\texclude_from_normal_launch(vm, vmsa_gpa, 2);\n+\tsnp_vm_launch_update(vm);\n+\n+\t/* Rebinding is allowed; the second, selected VMSA must win. */\n+\tsnp_set_vcpu_state(vcpu, vmsa_gpa);\n+\tsnp_set_vcpu_state(vcpu, vmsa_gpa + PAGE_SIZE);\n+\tsnp_get_vcpu_state(vcpu, \u0026state);\n+\tTEST_ASSERT_EQ(state.valid_fields,\n+\t\t KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |\n+\t\t KVM_SEV_SNP_VCPU_STATE_GHCB_VALID);\n+\tTEST_ASSERT_EQ(state.vmsa_gpa, vmsa_gpa + PAGE_SIZE);\n+\tTEST_ASSERT_EQ(state.ghcb_gpa, 0);\n+\tsnp_vm_launch_finish(vm);\n+\n+\tvcpu_mp_state_get(ap, \u0026mp_state);\n+\tTEST_ASSERT_EQ(mp_state.mp_state, KVM_MP_STATE_UNINITIALIZED);\n+\t*marker = 0;\n+\tassert_vcpu_terminated(vcpu);\n+\tTEST_ASSERT_EQ(*marker, DIRECT_MARKER);\n+\n+\tstate = (struct kvm_sev_snp_vcpu_state) {\n+\t\t.vmsa_gpa = vmsa_gpa,\n+\t\t.valid_fields = KVM_SEV_SNP_VCPU_STATE_VMSA_VALID,\n+\t};\n+\terrno = 0;\n+\tTEST_ASSERT_EQ(__vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE,\n+\t\t\t\t\t\u0026state), -1);\n+\tTEST_ASSERT_EQ(errno, EINVAL);\n+\n+\tfree(vmsas);\n+\tkvm_vm_free(vm);\n+}\n+\n+static void expect_set_vcpu_state_error(struct kvm_vcpu *vcpu,\n+\t\t\t\t\tstruct kvm_sev_snp_vcpu_state *state,\n+\t\t\t\t\tint expected_errno)\n+{\n+\terrno = 0;\n+\tTEST_ASSERT_EQ(__vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, state), -1);\n+\tTEST_ASSERT_EQ(errno, expected_errno);\n+}\n+\n+static void test_invalid_requests(void)\n+{\n+\tstruct kvm_sev_snp_vcpu_state state = {\n+\t\t.valid_fields = KVM_SEV_SNP_VCPU_STATE_VMSA_VALID,\n+\t};\n+\tstruct kvm_vcpu *vcpu;\n+\tstruct kvm_vm *vm;\n+\tgva_t shared_gva;\n+\tgpa_t private_gpa;\n+\n+\tvm = vm_create_with_one_vcpu(\u0026vcpu, guest_legacy_entry);\n+\texpect_set_vcpu_state_error(vcpu, \u0026state, ENOTTY);\n+\tkvm_vm_free(vm);\n+\n+\tvm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_legacy_entry, \u0026vcpu);\n+\texpect_set_vcpu_state_error(vcpu, \u0026state, EINVAL);\n+\tTEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0), -1);\n+\tTEST_ASSERT_EQ(errno, EINVAL);\n+\tsnp_vm_launch_start(vm, snp_default_policy());\n+\texpect_set_vcpu_state_error(vcpu, \u0026state, EINVAL);\n+\tkvm_vm_free(vm);\n+\n+\tvm = create_direct_vmsa_vm(1, guest_legacy_entry, \u0026vcpu);\n+\texpect_set_vcpu_state_error(vcpu, \u0026state, EINVAL);\n+\tsnp_vm_launch_start(vm, snp_default_policy());\n+\n+\tstate.pad[4] = 1;\n+\texpect_set_vcpu_state_error(vcpu, \u0026state, EINVAL);\n+\tstate.pad[4] = 0;\n+\tstate.valid_fields |= BIT_ULL(2);\n+\texpect_set_vcpu_state_error(vcpu, \u0026state, EINVAL);\n+\tstate.valid_fields \u0026= ~BIT_ULL(2);\n+\tstate.vmsa_gpa = PAGE_SIZE + 1;\n+\texpect_set_vcpu_state_error(vcpu, \u0026state, EINVAL);\n+\tstate.vmsa_gpa = VMSA_PMD_SIZE;\n+\texpect_set_vcpu_state_error(vcpu, \u0026state, EINVAL);\n+\tstate.vmsa_gpa = BIT_ULL(40) + PAGE_SIZE;\n+\texpect_set_vcpu_state_error(vcpu, \u0026state, EINVAL);\n+\n+\tshared_gva = vm_alloc_shared(vm, 2 * PAGE_SIZE, KVM_UTIL_MIN_VADDR,\n+\t\t\t\t MEM_REGION_TEST_DATA);\n+\tstate.vmsa_gpa = addr_gva2gpa(vm, shared_gva);\n+\tif (!(state.vmsa_gpa \u0026 (VMSA_PMD_SIZE - 1)))\n+\t\tstate.vmsa_gpa += PAGE_SIZE;\n+\tstate.ghcb_gpa = BIT_ULL(40);\n+\tstate.valid_fields |= KVM_SEV_SNP_VCPU_STATE_GHCB_VALID;\n+\tvcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, \u0026state);\n+\n+\tprivate_gpa = vm_phy_page_alloc(vm, VMSA_MIN_GPA,\n+\t\t\t\t\tvm-\u003ememslots[MEM_REGION_TEST_DATA]);\n+\tvm_mem_set_private(vm, private_gpa, PAGE_SIZE);\n+\tstate.vmsa_gpa = private_gpa;\n+\tvcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, \u0026state);\n+\n+\tmemset(\u0026state, 0, sizeof(state));\n+\tsnp_get_vcpu_state(vcpu, \u0026state);\n+\tTEST_ASSERT_EQ(state.valid_fields,\n+\t\t KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |\n+\t\t KVM_SEV_SNP_VCPU_STATE_GHCB_VALID);\n+\tTEST_ASSERT_EQ(state.vmsa_gpa, private_gpa);\n+\tTEST_ASSERT_EQ(state.ghcb_gpa, BIT_ULL(40));\n+\n+\tkvm_vm_free(vm);\n+}\n+\n+static void test_direct_vmsa_capability(void)\n+{\n+\tstruct kvm_enable_cap cap = {\n+\t\t.cap = KVM_CAP_SNP_DIRECT_VMSA,\n+\t};\n+\tstruct vm_shape shape = {\n+\t\t.mode = VM_MODE_DEFAULT,\n+\t\t.type = KVM_X86_SNP_VM,\n+\t};\n+\tstruct kvm_vm *vm;\n+\n+\tvm = vm_create_barebones();\n+\tTEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0), -1);\n+\tTEST_ASSERT_EQ(errno, EINVAL);\n+\tkvm_vm_free(vm);\n+\n+\tvm = __vm_create(shape, 1, 0);\n+\tTEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 1), -1);\n+\tTEST_ASSERT_EQ(errno, EINVAL);\n+\tcap.args[3] = 1;\n+\tTEST_ASSERT_EQ(__vm_ioctl(vm, KVM_ENABLE_CAP, \u0026cap), -1);\n+\tTEST_ASSERT_EQ(errno, EINVAL);\n+\tcap.args[3] = 0;\n+\tcap.flags = 1;\n+\tTEST_ASSERT_EQ(__vm_ioctl(vm, KVM_ENABLE_CAP, \u0026cap), -1);\n+\tTEST_ASSERT_EQ(errno, EINVAL);\n+\tvm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0);\n+\tvm_vcpu_add(vm, 0, guest_legacy_entry);\n+\tTEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0), -1);\n+\tTEST_ASSERT_EQ(errno, EINVAL);\n+\tkvm_vm_free(vm);\n+}\n+\n+static void test_legacy_launch(void)\n+{\n+\tstruct kvm_vcpu *vcpu;\n+\tstruct kvm_vm *vm;\n+\tgva_t marker_gva;\n+\tu64 *marker;\n+\n+\tvm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_legacy_entry, \u0026vcpu);\n+\tmarker_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,\n+\t\t\t\t MEM_REGION_TEST_DATA);\n+\tmarker = addr_gva2hva(vm, marker_gva);\n+\tvcpu_args_set(vcpu, 1, marker_gva);\n+\tvm_sev_launch(vm, snp_default_policy(), NULL);\n+\t*marker = 0;\n+\tassert_vcpu_terminated(vcpu);\n+\tTEST_ASSERT_EQ(*marker, LEGACY_MARKER);\n+\tkvm_vm_free(vm);\n+}\n+\n+int main(int argc, char *argv[])\n+{\n+\tTEST_REQUIRE(kvm_check_cap(KVM_CAP_SNP_VCPU_STATE));\n+\tTEST_REQUIRE(kvm_check_cap(KVM_CAP_SNP_DIRECT_VMSA));\n+\tTEST_ASSERT(sizeof(struct test_vmsa) \u003c= PAGE_SIZE, \"VMSA structure is too large\");\n+\n+\ttest_direct_vmsa_capability();\n+\ttest_invalid_requests();\n+\ttest_direct_vmsa();\n+\ttest_legacy_launch();\n+\treturn 0;\n+}\n"
} |
| 2/1 | 2026/09/08 13:02 | agent | patch-evaluator |
2mModel:core Args: null Results: {
"EnableConfigs": [
"KVM",
"KVM_AMD",
"KVM_AMD_SEV",
"CRYPTO_DEV_SP_PSP",
"CRYPTO_DEV_CCP_DD",
"AMD_IOMMU"
],
"FocusSymbols": [
"svm_enable_vm_cap",
"sev_vcpu_mem_enc_ioctl",
"snp_get_vcpu_state",
"snp_set_vcpu_state",
"sev_snp_install_guest_vmsa",
"__sev_snp_reload_vmsa",
"sev_snp_get_apic_ids",
"snp_launch_update",
"snp_launch_update_vmsa",
"sev_vcpu_create",
"sev_free_vcpu"
],
"Reasoning": "The patch series introduces direct-VMSA support and APIC ID enumeration for AMD SEV-SNP guests in KVM. It adds new vCPU ioctl commands (KVM_SEV_SNP_GET_VCPU_STATE and KVM_SEV_SNP_SET_VCPU_STATE), implements VM capability enablement (KVM_CAP_SNP_DIRECT_VMSA) in KVM SVM, adds the SVM_VMGEXIT_GET_APIC_IDS exit handler, and refactors VMSA guest memory installation and MMU invalidation sequencing. Fuzzing is warranted to test new state transitions, validation checks, and locking invariants.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 760726df0f542fb0f0b066de39ad501e3c2c3594
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Sep 8 13:02:00 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/virt/kvm/x86/amd-memory-encryption.rst b/Documentation/virt/kvm/x86/amd-memory-encryption.rst
index bd04a908a8dbd..427b673c3832f 100644
--- a/Documentation/virt/kvm/x86/amd-memory-encryption.rst
+++ b/Documentation/virt/kvm/x86/amd-memory-encryption.rst
@@ -536,6 +536,7 @@ Returns: 0 on success, < 0 on error, -EAGAIN if caller should retry
where the allowed values for page_type are #define'd as::
KVM_SEV_SNP_PAGE_TYPE_NORMAL
+ KVM_SEV_SNP_PAGE_TYPE_VMSA
KVM_SEV_SNP_PAGE_TYPE_ZERO
KVM_SEV_SNP_PAGE_TYPE_UNMEASURED
KVM_SEV_SNP_PAGE_TYPE_SECRETS
@@ -544,7 +545,68 @@ where the allowed values for page_type are #define'd as::
See the SEV-SNP spec [snp-fw-abi]_ for further details on how each page type is
used/measured.
-20. KVM_SEV_SNP_LAUNCH_FINISH
+``KVM_SEV_SNP_PAGE_TYPE_VMSA`` creates VMSA pages as part of the measured
+initial image. A request must contain exactly one 4 KiB VMSA page, but the
+command may be used multiple times. Creating a VMSA page does not associate it
+with a vCPU; use ``KVM_SEV_SNP_SET_VCPU_STATE`` on the intended vCPU file
+descriptor before launch finish to make that association. KVM treats the
+VMSA contents as guest-owned data, but requires VMPL 0 and a ``sev_features``
+value that matches the VM's configured VMSA features.
+
+20. KVM_SEV_SNP_GET_VCPU_STATE / KVM_SEV_SNP_SET_VCPU_STATE
+------------------------------------------------------------
+
+These commands get or set the VMSA and GHCB addresses for the vCPU on whose
+file descriptor the command is issued. Unlike the other SEV commands,
+userspace must issue KVM_MEMORY_ENCRYPT_OP on a vCPU file descriptor. The
+capability is reported as ``KVM_CAP_SNP_VCPU_STATE``.
+
+Parameters (in/out): struct kvm_sev_snp_vcpu_state
+
+Returns: 0 on success, -negative on error
+
+::
+
+ #define KVM_SEV_SNP_VCPU_STATE_VMSA_VALID _BITULL(0)
+ #define KVM_SEV_SNP_VCPU_STATE_GHCB_VALID _BITULL(1)
+
+ struct kvm_sev_snp_vcpu_state {
+ __u64 valid_fields;
+ __u64 vmsa_gpa;
+ __u64 ghcb_gpa;
+ __u64 pad[5]; /* Must be zero */
+ };
+
+``KVM_SEV_SNP_GET_VCPU_STATE`` returns the current addresses and sets the
+corresponding bit in ``valid_fields`` for each valid address.
+
+``KVM_SEV_SNP_SET_VCPU_STATE`` sets addresses whose validity bits are present
+and invalidates addresses whose bits are absent. The command must be issued
+after launch start and before KVM_SEV_SNP_LAUNCH_FINISH, and the VM must have
+enabled ``KVM_CAP_SNP_DIRECT_VMSA``. A valid VMSA GPA must be backed by
+guest_memfd and populated. The GPA must be 4-KiB aligned. A valid GHCB
+address is copied without inspecting its backing page. Nonzero reserved
+fields or unknown validity bits are rejected.
+
+``KVM_CAP_SNP_DIRECT_VMSA`` is a VM-scoped capability that selects direct-VMSA
+mode. Userspace enables it with ``KVM_ENABLE_CAP`` on an SNP VM before
+creating any vCPUs. ``flags`` and all elements of ``args`` must be zero.
+Enabling the capability on a non-SNP VM or after creating a vCPU is rejected.
+
+In direct-VMSA mode, KVM does not allocate a KVM-owned VMSA when a vCPU is
+created and does not generate or measure one at launch finish. All launch
+VMSAs are owned and supplied by userspace. Valid VMSAs selected with
+``KVM_SEV_SNP_SET_VCPU_STATE`` are preserved, while vCPUs without a valid VMSA
+have no runnable VMSA until the guest uses SNP AP creation to supply one. If
+the capability is not enabled, launch finish retains the legacy behavior of
+generating and measuring a KVM-owned VMSA for every vCPU. VMSAs that were
+measured but not selected remain ordinary valid pages in the initial image.
+
+Direct VMSAs make the launch measurement independent of KVM's selected VMSA
+GPA and of the configured vCPU count. This gives VMMs a stable launch
+measurement across hypervisors.
+
+21. KVM_SEV_SNP_LAUNCH_FINISH
-----------------------------
After completion of the SNP guest launch flow, the KVM_SEV_SNP_LAUNCH_FINISH
@@ -572,7 +634,7 @@ Returns: 0 on success, -negative on error
See SNP_LAUNCH_FINISH in the SEV-SNP specification [snp-fw-abi]_ for further
details on the input parameters in ``struct kvm_sev_snp_launch_finish``.
-21. KVM_SEV_SNP_ENABLE_REQ_CERTS
+22. KVM_SEV_SNP_ENABLE_REQ_CERTS
--------------------------------
The KVM_SEV_SNP_ENABLE_REQ_CERTS command will configure KVM to exit to
diff --git a/arch/x86/include/asm/kvm-x86-ops.h b/arch/x86/include/asm/kvm-x86-ops.h
index e213c9ae3e301..a48259696ca6e 100644
--- a/arch/x86/include/asm/kvm-x86-ops.h
+++ b/arch/x86/include/asm/kvm-x86-ops.h
@@ -23,6 +23,7 @@ KVM_X86_OP(vcpu_after_set_cpuid)
KVM_X86_OP(vm_init)
KVM_X86_OP_OPTIONAL(vm_destroy)
KVM_X86_OP_OPTIONAL(vm_pre_destroy)
+KVM_X86_OP_OPTIONAL(enable_vm_cap)
KVM_X86_OP_OPTIONAL_RET0(vcpu_precreate)
KVM_X86_OP(vcpu_create)
KVM_X86_OP(vcpu_free)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 683bb8bf43a94..3bfc02bb79984 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1519,6 +1519,7 @@ struct kvm_x86_ops {
int (*vm_init)(struct kvm *kvm);
void (*vm_destroy)(struct kvm *kvm);
void (*vm_pre_destroy)(struct kvm *kvm);
+ int (*enable_vm_cap)(struct kvm *kvm, struct kvm_enable_cap *cap);
/* Create, but do not attach this VCPU */
int (*vcpu_precreate)(struct kvm *kvm);
diff --git a/arch/x86/include/asm/sev-common.h b/arch/x86/include/asm/sev-common.h
index 01a6e4dbe4235..c041f85dfd5c8 100644
--- a/arch/x86/include/asm/sev-common.h
+++ b/arch/x86/include/asm/sev-common.h
@@ -136,6 +136,7 @@ enum psc_op {
#define GHCB_HV_FT_SNP BIT_ULL(0)
#define GHCB_HV_FT_SNP_AP_CREATION BIT_ULL(1)
+#define GHCB_HV_FT_APIC_ID_LIST BIT_ULL(4)
#define GHCB_HV_FT_SNP_MULTI_VMPL BIT_ULL(5)
/*
diff --git a/arch/x86/include/uapi/asm/kvm.h b/arch/x86/include/uapi/asm/kvm.h
index 1585ec8040666..5ae041e617b82 100644
--- a/arch/x86/include/uapi/asm/kvm.h
+++ b/arch/x86/include/uapi/asm/kvm.h
@@ -748,6 +748,8 @@ enum sev_cmd_id {
KVM_SEV_SNP_LAUNCH_UPDATE,
KVM_SEV_SNP_LAUNCH_FINISH,
KVM_SEV_SNP_ENABLE_REQ_CERTS,
+ KVM_SEV_SNP_GET_VCPU_STATE,
+ KVM_SEV_SNP_SET_VCPU_STATE,
KVM_SEV_NR_MAX,
};
@@ -887,6 +889,7 @@ struct kvm_sev_snp_launch_start {
/* Kept in sync with firmware values for simplicity. */
#define KVM_SEV_PAGE_TYPE_INVALID 0x0
#define KVM_SEV_SNP_PAGE_TYPE_NORMAL 0x1
+#define KVM_SEV_SNP_PAGE_TYPE_VMSA 0x2
#define KVM_SEV_SNP_PAGE_TYPE_ZERO 0x3
#define KVM_SEV_SNP_PAGE_TYPE_UNMEASURED 0x4
#define KVM_SEV_SNP_PAGE_TYPE_SECRETS 0x5
@@ -903,6 +906,16 @@ struct kvm_sev_snp_launch_update {
__u64 pad2[4];
};
+#define KVM_SEV_SNP_VCPU_STATE_VMSA_VALID _BITULL(0)
+#define KVM_SEV_SNP_VCPU_STATE_GHCB_VALID _BITULL(1)
+
+struct kvm_sev_snp_vcpu_state {
+ __u64 valid_fields;
+ __u64 vmsa_gpa;
+ __u64 ghcb_gpa;
+ __u64 pad[5];
+};
+
#define KVM_SEV_SNP_ID_BLOCK_SIZE 96
#define KVM_SEV_SNP_ID_AUTH_SIZE 4096
#define KVM_SEV_SNP_FINISH_DATA_SIZE 32
diff --git a/arch/x86/include/uapi/asm/svm.h b/arch/x86/include/uapi/asm/svm.h
index 010a45c9f6147..05e5e31e23e18 100644
--- a/arch/x86/include/uapi/asm/svm.h
+++ b/arch/x86/include/uapi/asm/svm.h
@@ -117,6 +117,7 @@
#define SVM_VMGEXIT_AP_CREATE_ON_INIT 0
#define SVM_VMGEXIT_AP_CREATE 1
#define SVM_VMGEXIT_AP_DESTROY 2
+#define SVM_VMGEXIT_GET_APIC_IDS 0x80000017ull
#define SVM_VMGEXIT_SNP_RUN_VMPL 0x80000018ull
#define SVM_VMGEXIT_SAVIC 0x8000001aull
#define SVM_VMGEXIT_SAVIC_REGISTER_GPA 0
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 5705723f1f412..3e0727e251a93 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -40,7 +40,9 @@
#define GHCB_VERSION_MAX 2ULL
#define GHCB_VERSION_MIN 1ULL
-#define GHCB_HV_FT_SUPPORTED (GHCB_HV_FT_SNP | GHCB_HV_FT_SNP_AP_CREATION)
+#define GHCB_HV_FT_SUPPORTED (GHCB_HV_FT_SNP | \
+ GHCB_HV_FT_SNP_AP_CREATION | \
+ GHCB_HV_FT_APIC_ID_LIST)
/*
* The GHCB spec essentially states that all non-zero error codes other than
@@ -2344,6 +2346,7 @@ struct sev_gmem_populate_args {
__u8 type;
int sev_fd;
int fw_error;
+ bool vmsa_invalid;
};
static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
@@ -2367,11 +2370,20 @@ static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
if (src_page) {
void *src_vaddr = kmap_local_page(src_page);
void *dst_vaddr = kmap_local_pfn(pfn);
+ struct sev_es_save_area *vmsa = dst_vaddr;
memcpy(dst_vaddr, src_vaddr, PAGE_SIZE);
+ if (sev_populate_args->type == KVM_SEV_SNP_PAGE_TYPE_VMSA &&
+ (vmsa->vmpl || vmsa->sev_features != sev->vmsa_features)) {
+ sev_populate_args->vmsa_invalid = true;
+ ret = -EINVAL;
+ }
kunmap_local(dst_vaddr);
kunmap_local(src_vaddr);
+
+ if (ret)
+ goto out;
}
ret = rmp_make_private(pfn, gfn << PAGE_SHIFT, PG_LEVEL_4K,
@@ -2437,7 +2449,10 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
params.gfn_start, params.len, params.type, params.flags);
if (!params.len || !PAGE_ALIGNED(params.len) || params.flags ||
+ (params.type == KVM_SEV_SNP_PAGE_TYPE_VMSA &&
+ (!sev->snp_direct_vmsa || params.len != PAGE_SIZE)) ||
(params.type != KVM_SEV_SNP_PAGE_TYPE_NORMAL &&
+ params.type != KVM_SEV_SNP_PAGE_TYPE_VMSA &&
params.type != KVM_SEV_SNP_PAGE_TYPE_ZERO &&
params.type != KVM_SEV_SNP_PAGE_TYPE_UNMEASURED &&
params.type != KVM_SEV_SNP_PAGE_TYPE_SECRETS &&
@@ -2485,6 +2500,9 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
params.type == KVM_SEV_SNP_PAGE_TYPE_CPUID,
sev_gmem_post_populate, &sev_populate_args);
if (count < 0) {
+ if (sev_populate_args.vmsa_invalid)
+ return -EINVAL;
+
argp->error = sev_populate_args.fw_error;
pr_debug("%s: kvm_gmem_populate failed, ret %ld (fw_error %d)\n",
__func__, count, argp->error);
@@ -2502,6 +2520,8 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
return 0;
}
+static int sev_snp_install_guest_vmsa(struct vcpu_svm *svm, gpa_t gpa);
+
static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
{
struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
@@ -2522,8 +2542,15 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
kvm_for_each_vcpu(i, vcpu, kvm) {
struct vcpu_svm *svm = to_svm(vcpu);
- u64 pfn = __pa(svm->sev_es.vmsa) >> PAGE_SHIFT;
+ u64 pfn;
+
+ if (sev->snp_direct_vmsa) {
+ if (!svm->sev_es.snp_has_guest_vmsa)
+ svm->vmcb->control.vmsa_pa = INVALID_PAGE;
+ goto protect_vcpu;
+ }
+ pfn = __pa(svm->sev_es.vmsa) >> PAGE_SHIFT;
ret = sev_es_sync_vmsa(svm);
if (ret)
goto out;
@@ -2543,6 +2570,7 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
goto out;
}
+protect_vcpu:
svm->vcpu.arch.guest_state_protected = true;
/*
* SEV-ES (and thus SNP) guest mandates LBR Virtualization to
@@ -2559,6 +2587,89 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
return ret;
}
+static int snp_get_vcpu_state(struct kvm_vcpu *vcpu,
+ struct kvm_sev_cmd *argp)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+ struct kvm *kvm = vcpu->kvm;
+ struct kvm_sev_snp_vcpu_state state = {};
+
+ if (!is_sev_snp_guest(vcpu))
+ return -ENOTTY;
+ if (!to_kvm_sev_info(kvm)->snp_context)
+ return -EINVAL;
+
+ guard(mutex)(&svm->sev_es.snp_vmsa_mutex);
+
+ if (VALID_PAGE(svm->sev_es.snp_guest_vmsa_gpa) &&
+ VALID_PAGE(svm->vmcb->control.vmsa_pa)) {
+ state.vmsa_gpa = svm->sev_es.snp_guest_vmsa_gpa;
+ state.valid_fields |= KVM_SEV_SNP_VCPU_STATE_VMSA_VALID;
+ }
+
+ if (VALID_PAGE(svm->vmcb->control.ghcb_gpa)) {
+ state.ghcb_gpa = svm->vmcb->control.ghcb_gpa;
+ state.valid_fields |= KVM_SEV_SNP_VCPU_STATE_GHCB_VALID;
+ }
+
+ if (copy_to_user(u64_to_user_ptr(argp->data), &state, sizeof(state)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int snp_set_vcpu_state(struct kvm_vcpu *vcpu,
+ struct kvm_sev_cmd *argp)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+ struct kvm *kvm = vcpu->kvm;
+ struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
+ struct kvm_sev_snp_vcpu_state state;
+ int ret;
+
+ if (!is_sev_snp_guest(vcpu))
+ return -ENOTTY;
+ if (!sev->snp_direct_vmsa)
+ return -EINVAL;
+ if (!sev->snp_context || kvm->arch.pre_fault_allowed)
+ return -EINVAL;
+
+ if (copy_from_user(&state, u64_to_user_ptr(argp->data), sizeof(state)))
+ return -EFAULT;
+
+ if (memchr_inv(state.pad, 0, sizeof(state.pad)) ||
+ state.valid_fields & ~(KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |
+ KVM_SEV_SNP_VCPU_STATE_GHCB_VALID))
+ return -EINVAL;
+
+ if (state.valid_fields & KVM_SEV_SNP_VCPU_STATE_VMSA_VALID) {
+ if (!PAGE_ALIGNED(state.vmsa_gpa) ||
+ !page_address_valid(vcpu, state.vmsa_gpa) ||
+ IS_ALIGNED(state.vmsa_gpa, PMD_SIZE))
+ return -EINVAL;
+ }
+
+ guard(mutex)(&svm->sev_es.snp_vmsa_mutex);
+
+ if (state.valid_fields & KVM_SEV_SNP_VCPU_STATE_VMSA_VALID) {
+ ret = sev_snp_install_guest_vmsa(svm, state.vmsa_gpa);
+ if (ret)
+ return ret;
+ } else {
+ svm->sev_es.snp_has_guest_vmsa = true;
+ svm->sev_es.snp_guest_vmsa_gpa = INVALID_PAGE;
+ svm->vmcb->control.vmsa_pa = INVALID_PAGE;
+ }
+
+ if (state.valid_fields & KVM_SEV_SNP_VCPU_STATE_GHCB_VALID)
+ svm->vmcb->control.ghcb_gpa = state.ghcb_gpa;
+ else
+ svm->vmcb->control.ghcb_gpa = INVALID_PAGE;
+
+ vmcb_mark_all_dirty(svm->vmcb);
+ return 0;
+}
+
static int snp_launch_finish(struct kvm *kvm, struct kvm_sev_cmd *argp)
{
struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
@@ -2755,6 +2866,35 @@ int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
return r;
}
+int sev_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
+{
+ struct kvm_sev_cmd sev_cmd;
+ int ret;
+
+ if (!sev_enabled)
+ return -ENOTTY;
+ if (!argp)
+ return -EINVAL;
+ if (copy_from_user(&sev_cmd, argp, sizeof(sev_cmd)))
+ return -EFAULT;
+
+ switch (sev_cmd.id) {
+ case KVM_SEV_SNP_GET_VCPU_STATE:
+ ret = snp_get_vcpu_state(vcpu, &sev_cmd);
+ break;
+ case KVM_SEV_SNP_SET_VCPU_STATE:
+ ret = snp_set_vcpu_state(vcpu, &sev_cmd);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (copy_to_user(argp, &sev_cmd, sizeof(sev_cmd)))
+ return -EFAULT;
+
+ return ret;
+}
+
int sev_mem_enc_register_region(struct kvm *kvm,
struct kvm_enc_region *range)
{
@@ -3493,7 +3633,8 @@ static bool sev_es_are_required_ghcb_fields_valid(struct vcpu_svm *svm)
case SVM_VMGEXIT_AP_CREATION:
return kvm_ghcb_rax_is_valid(svm) ||
lower_32_bits(control->exit_info_1) == SVM_VMGEXIT_AP_DESTROY;
- break;
+ case SVM_VMGEXIT_GET_APIC_IDS:
+ return kvm_ghcb_rax_is_valid(svm);
case SVM_VMGEXIT_MMIO_READ:
case SVM_VMGEXIT_MMIO_WRITE:
case SVM_VMGEXIT_PSC:
@@ -3556,6 +3697,9 @@ void sev_free_vcpu(struct kvm_vcpu *vcpu)
* a guest-owned page. Transition the page to hypervisor state before
* releasing it back to the system.
*/
+ if (!svm->sev_es.vmsa)
+ goto skip_vmsa_free;
+
if (is_sev_snp_guest(vcpu)) {
u64 pfn = __pa(svm->sev_es.vmsa) >> PAGE_SHIFT;
@@ -4025,6 +4169,56 @@ static int snp_begin_psc(struct vcpu_svm *svm)
return snp_do_psc(svm);
}
+/*
+ * Install a guest-owned VMSA. The caller must serialize against AP creation
+ * and destruction with snp_vmsa_mutex.
+ */
+static int sev_snp_install_guest_vmsa(struct vcpu_svm *svm, gpa_t gpa)
+{
+ struct kvm *kvm = svm->vcpu.kvm;
+ struct kvm_memory_slot *slot;
+ unsigned long mmu_seq;
+ struct page *page;
+ kvm_pfn_t pfn;
+ gfn_t gfn;
+ int idx;
+ int ret;
+
+ lockdep_assert_held(&svm->sev_es.snp_vmsa_mutex);
+
+ gfn = gpa_to_gfn(gpa);
+ idx = srcu_read_lock(&kvm->srcu);
+ slot = gfn_to_memslot(kvm, gfn);
+ if (!slot) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ mmu_seq = kvm->mmu_invalidate_seq;
+ /* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */
+ smp_rmb();
+
+ /* Guest-owned VMSAs are backed by guest_memfd private memory. */
+ ret = kvm_gmem_get_pfn(kvm, slot, gfn, &pfn, &page, NULL);
+ if (ret)
+ goto out_unlock;
+
+ read_lock(&kvm->mmu_lock);
+ if (mmu_invalidate_retry_gfn(kvm, mmu_seq, gfn)) {
+ ret = -EAGAIN;
+ } else {
+ svm->sev_es.snp_has_guest_vmsa = true;
+ WRITE_ONCE(svm->sev_es.snp_guest_vmsa_gpa, gpa);
+ svm->vmcb->control.vmsa_pa = pfn_to_hpa(pfn);
+ }
+ read_unlock(&kvm->mmu_lock);
+
+ kvm_release_page_clean(page);
+out_unlock:
+ srcu_read_unlock(&kvm->srcu, idx);
+ return ret;
+}
+
static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
{
struct vcpu_svm *svm = to_svm(vcpu);
@@ -4034,6 +4228,7 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
unsigned long mmu_seq;
struct page *page;
kvm_pfn_t pfn;
+ int idx;
lockdep_assert_held(&svm->sev_es.snp_vmsa_mutex);
@@ -4052,22 +4247,19 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
vmcb_mark_all_dirty(svm->vmcb);
/*
- * From this point forward, the VMSA will always be a guest-mapped page
- * rather than the initial one allocated by KVM in svm->sev_es.vmsa. In
- * theory, svm->sev_es.vmsa could be free'd and cleaned up here, but
- * that involves cleanups like flushing caches, which would ideally be
- * handled during teardown rather than guest boot. Deferring that also
- * allows the existing logic for SEV-ES VMSAs to be re-used with
- * minimal SNP-specific changes.
+ * From this point forward, the VMSA will always be a guest-mapped page.
+ * If KVM allocated an initial VMSA, keep it until teardown to defer
+ * cache flushing and other cleanup out of the guest boot path.
*/
svm->sev_es.snp_has_guest_vmsa = true;
if (!VALID_PAGE(gpa))
return;
- slot = gfn_to_memslot(vcpu->kvm, gfn);
+ idx = srcu_read_lock(&kvm->srcu);
+ slot = gfn_to_memslot(kvm, gfn);
if (!slot)
- return;
+ goto out_unlock;
mmu_seq = kvm->mmu_invalidate_seq;
smp_rmb();
@@ -4076,8 +4268,8 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
* The new VMSA will be private memory guest memory, so retrieve the
* PFN from the gmem backend.
*/
- if (kvm_gmem_get_pfn(vcpu->kvm, slot, gfn, &pfn, &page, NULL))
- return;
+ if (kvm_gmem_get_pfn(kvm, slot, gfn, &pfn, &page, NULL))
+ goto out_unlock;
read_lock(&kvm->mmu_lock);
/*
@@ -4094,6 +4286,8 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
read_unlock(&kvm->mmu_lock);
kvm_release_page_clean(page);
+out_unlock:
+ srcu_read_unlock(&kvm->srcu, idx);
}
/*
@@ -4215,6 +4409,63 @@ static int sev_snp_ap_creation(struct vcpu_svm *svm)
return 0;
}
+struct sev_apic_id_desc {
+ u32 num_entries;
+ u32 apic_ids[];
+};
+
+static int sev_snp_get_apic_ids(struct vcpu_svm *svm)
+{
+ struct kvm_vcpu *vcpu = &svm->vcpu;
+ struct kvm *kvm = vcpu->kvm;
+ struct sev_apic_id_desc *desc;
+ unsigned int nr_vcpus, size;
+ unsigned int i;
+ gpa_t gpa, end_gpa;
+ u64 pages;
+
+ nr_vcpus = atomic_read(&kvm->online_vcpus);
+ size = sizeof(*desc) + (nr_vcpus * sizeof(desc->apic_ids[0]));
+
+ pages = vcpu->arch.regs[VCPU_REGS_RAX];
+ if (pages < PFN_UP(size)) {
+ vcpu->arch.regs[VCPU_REGS_RAX] = PFN_UP(size);
+ return 1;
+ }
+
+ gpa = svm->vmcb->control.exit_info_1;
+ if (!PAGE_ALIGNED(gpa) ||
+ check_add_overflow(gpa, size - 1, &end_gpa) ||
+ !page_address_valid(vcpu, gpa) ||
+ !page_address_valid(vcpu, end_gpa & PAGE_MASK))
+ goto invalid_buffer;
+
+ desc = kvzalloc(size, GFP_KERNEL_ACCOUNT);
+ if (!desc)
+ return -ENOMEM;
+
+ desc->num_entries = nr_vcpus;
+ for (i = 0; i < nr_vcpus; i++) {
+ struct kvm_vcpu *listed_vcpu = kvm_get_vcpu(kvm, i);
+
+ if (WARN_ON_ONCE(!listed_vcpu))
+ goto invalid_buffer_free;
+ desc->apic_ids[i] = listed_vcpu->vcpu_id;
+ }
+
+ if (kvm_write_guest(kvm, gpa, desc, size))
+ goto invalid_buffer_free;
+
+ kvfree(desc);
+ return 1;
+
+invalid_buffer_free:
+ kvfree(desc);
+invalid_buffer:
+ svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);
+ return 1;
+}
+
static int snp_handle_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t resp_gpa)
{
struct sev_data_snp_guest_request data = {0};
@@ -4494,6 +4745,7 @@ static bool is_snp_only_vmgexit(u64 exit_code)
{
switch (exit_code) {
case SVM_VMGEXIT_AP_CREATION:
+ case SVM_VMGEXIT_GET_APIC_IDS:
case SVM_VMGEXIT_GUEST_REQUEST:
case SVM_VMGEXIT_EXT_GUEST_REQUEST:
case SVM_VMGEXIT_PSC:
@@ -4665,6 +4917,8 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
if (sev_snp_ap_creation(svm))
svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);
return 1;
+ case SVM_VMGEXIT_GET_APIC_IDS:
+ return sev_snp_get_apic_ids(svm);
case SVM_VMGEXIT_GUEST_REQUEST:
case SVM_VMGEXIT_EXT_GUEST_REQUEST:
if (!PAGE_ALIGNED(control->exit_info_1) ||
@@ -4862,12 +5116,15 @@ void sev_init_vmcb(struct vcpu_svm *svm, bool init_event)
int sev_vcpu_create(struct kvm_vcpu *vcpu)
{
struct vcpu_svm *svm = to_svm(vcpu);
+ struct kvm_sev_info *sev = to_kvm_sev_info(vcpu->kvm);
struct page *vmsa_page;
mutex_init(&svm->sev_es.snp_vmsa_mutex);
if (!is_sev_es_guest(vcpu))
return 0;
+ if (is_sev_snp_guest(vcpu) && sev->snp_direct_vmsa)
+ goto init_vmsa_state;
/*
* SEV-ES guests require a separate (from the VMCB) VMSA page used to
@@ -4878,6 +5135,8 @@ int sev_vcpu_create(struct kvm_vcpu *vcpu)
return -ENOMEM;
svm->sev_es.vmsa = page_address(vmsa_page);
+
+init_vmsa_state:
svm->sev_es.snp_pending_vmsa_gpa = INVALID_PAGE;
svm->sev_es.snp_guest_vmsa_gpa = INVALID_PAGE;
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 7d59d301e1e54..378d94409134f 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5325,6 +5325,27 @@ static void *svm_alloc_apic_backing_page(struct kvm_vcpu *vcpu)
return page_address(page);
}
+static int svm_enable_vm_cap(struct kvm *kvm, struct kvm_enable_cap *cap)
+{
+ switch (cap->cap) {
+#ifdef CONFIG_KVM_AMD_SEV
+ case KVM_CAP_SNP_DIRECT_VMSA:
+ if (memchr_inv(cap->args, 0, sizeof(cap->args)) ||
+ kvm->arch.vm_type != KVM_X86_SNP_VM)
+ return -EINVAL;
+
+ guard(mutex)(&kvm->lock);
+ if (kvm->created_vcpus)
+ return -EINVAL;
+
+ to_kvm_sev_info(kvm)->snp_direct_vmsa = true;
+ return 0;
+#endif
+ default:
+ return -EINVAL;
+ }
+}
+
struct kvm_x86_ops svm_x86_ops __initdata = {
.name = KBUILD_MODNAME,
@@ -5345,6 +5366,7 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
.vm_init = svm_vm_init,
.vm_pre_destroy = avic_vm_pre_destroy,
.vm_destroy = svm_vm_destroy,
+ .enable_vm_cap = svm_enable_vm_cap,
.prepare_switch_to_guest = svm_prepare_switch_to_guest,
.vcpu_load = svm_vcpu_load,
@@ -5444,6 +5466,7 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
.vcpu_needs_initialization = sev_vcpu_needs_initialization,
.dev_get_attr = sev_dev_get_attr,
.mem_enc_ioctl = sev_mem_enc_ioctl,
+ .vcpu_mem_enc_ioctl = sev_vcpu_mem_enc_ioctl,
.mem_enc_register_region = sev_mem_enc_register_region,
.mem_enc_unregister_region = sev_mem_enc_unregister_region,
.guest_memory_reclaimed = sev_guest_memory_reclaimed,
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index e958943b81627..c920c797b7ecf 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -121,6 +121,7 @@ struct kvm_sev_info {
struct mutex guest_req_mutex; /* Must acquire before using bounce buffers */
cpumask_var_t have_run_cpus; /* CPUs that have done VMRUN for this VM. */
bool snp_certs_enabled; /* SNP certificate-fetching support. */
+ bool snp_direct_vmsa; /* Userspace provides and measures VMSA pages. */
};
#endif
@@ -982,6 +983,7 @@ void sev_es_unmap_ghcb(struct vcpu_svm *svm);
#ifdef CONFIG_KVM_AMD_SEV
bool sev_vcpu_needs_initialization(struct kvm_vcpu *vcpu);
int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp);
+int sev_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp);
int sev_mem_enc_register_region(struct kvm *kvm,
struct kvm_enc_region *range);
int sev_mem_enc_unregister_region(struct kvm *kvm,
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 79468ddfe4736..232507ae504a6 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2404,6 +2404,10 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_VM_TYPES:
r = kvm_caps.supported_vm_types;
break;
+ case KVM_CAP_SNP_VCPU_STATE:
+ case KVM_CAP_SNP_DIRECT_VMSA:
+ r = !!(kvm_caps.supported_vm_types & BIT(KVM_X86_SNP_VM));
+ break;
case KVM_CAP_READONLY_MEM:
r = kvm ? kvm_arch_has_readonly_mem(kvm) : 1;
break;
@@ -4212,6 +4216,8 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
}
default:
r = -EINVAL;
+ if (kvm_x86_ops.enable_vm_cap)
+ r = kvm_x86_call(enable_vm_cap)(kvm, cap);
break;
}
return r;
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index ac2d77d149635..8c6765f78c1c1 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -999,6 +999,8 @@ struct kvm_enable_cap {
#define KVM_CAP_S390_HPAGE_2G 249
#define KVM_CAP_PPC_COMPAT_CAPS 250
#define KVM_CAP_ARM_PMU_V3_STRICT 251
+#define KVM_CAP_SNP_DIRECT_VMSA 252
+#define KVM_CAP_SNP_VCPU_STATE 253
struct kvm_irq_routing_irqchip {
__u32 irqchip;
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39e..4cf0a8297b041 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -149,6 +149,8 @@ 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_snp_apic_id_test
+TEST_GEN_PROGS_x86 += x86/sev_snp_direct_vmsa_test
TEST_GEN_PROGS_x86 += x86/sev_smoke_test
TEST_GEN_PROGS_x86 += x86/amx_test
TEST_GEN_PROGS_x86 += x86/max_vcpuid_cap_test
diff --git a/tools/testing/selftests/kvm/include/x86/sev.h b/tools/testing/selftests/kvm/include/x86/sev.h
index dec383e59a47e..0f6c92b802eea 100644
--- a/tools/testing/selftests/kvm/include/x86/sev.h
+++ b/tools/testing/selftests/kvm/include/x86/sev.h
@@ -100,6 +100,30 @@ static inline u64 snp_default_policy(void)
__TEST_ASSERT_VM_VCPU_IOCTL(!ret, #cmd, ret, vm); \
})
+static inline int __vcpu_sev_ioctl(struct kvm_vcpu *vcpu, u32 cmd, void *arg)
+{
+ union {
+ struct kvm_sev_cmd c;
+ unsigned long raw;
+ } sev_cmd = { .c = {
+ .id = cmd,
+ .data = (u64)arg,
+ .sev_fd = vcpu->vm->arch.sev_fd,
+ } };
+ int ret;
+
+ ret = __vcpu_ioctl(vcpu, KVM_MEMORY_ENCRYPT_OP, &sev_cmd.raw);
+ return ret ?: sev_cmd.c.error;
+}
+
+#define vcpu_sev_ioctl(vcpu, cmd, arg) \
+({ \
+ struct kvm_vcpu *__vcpu = (vcpu); \
+ int ret = __vcpu_sev_ioctl(__vcpu, cmd, arg); \
+ \
+ __TEST_ASSERT_VM_VCPU_IOCTL(!ret, #cmd, ret, __vcpu->vm); \
+})
+
void sev_vm_init(struct kvm_vm *vm);
void sev_es_vm_init(struct kvm_vm *vm);
void snp_vm_init(struct kvm_vm *vm);
@@ -144,6 +168,31 @@ 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 snp_launch_update_vmsa(struct kvm_vm *vm, gpa_t gpa,
+ void *vmsa)
+{
+ vm_mem_set_private(vm, gpa, PAGE_SIZE);
+ snp_launch_update_data(vm, gpa, (u64)vmsa, PAGE_SIZE,
+ KVM_SEV_SNP_PAGE_TYPE_VMSA);
+}
+
+static inline void snp_get_vcpu_state(struct kvm_vcpu *vcpu,
+ struct kvm_sev_snp_vcpu_state *state)
+{
+ vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_GET_VCPU_STATE, state);
+}
+
+static inline void snp_set_vcpu_state(struct kvm_vcpu *vcpu, gpa_t vmsa_gpa)
+{
+ struct kvm_sev_snp_vcpu_state state = {
+ .vmsa_gpa = vmsa_gpa,
+ .valid_fields = KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |
+ KVM_SEV_SNP_VCPU_STATE_GHCB_VALID,
+ };
+
+ vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, &state);
+}
+
static inline void sev_dbg_crypt_memory(struct kvm_vm *vm, unsigned int cmd,
void *dst, void *src, unsigned int len)
{
diff --git a/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c b/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c
new file mode 100644
index 0000000000000..8ad9c15091c2d
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <stddef.h>
+#include <stdint.h>
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "sev.h"
+#include "svm_util.h"
+
+#define GHCB_SAVE_RAX_OFFSET 0x1f8
+#define GHCB_SAVE_SW_EXIT_CODE_OFFSET 0x390
+#define GHCB_SAVE_SW_EXIT_INFO_1_OFFSET 0x398
+#define GHCB_SAVE_SW_EXIT_INFO_2_OFFSET 0x3a0
+#define GHCB_SAVE_VALID_BITMAP_OFFSET 0x3f0
+
+#define GHCB_MSR_REG_GPA_REQ 0x012
+#define GHCB_MSR_REG_GPA_RESP 0x013
+#define GHCB_MSR_INFO_MASK GENMASK_ULL(11, 0)
+
+#define GHCB_HV_RESP_MALFORMED_INPUT 2
+#define GHCB_ERR_MISSING_INPUT 4
+#define GHCB_ERR_INVALID_INPUT 5
+
+struct apic_id_desc {
+ u32 nr_entries;
+ u32 apic_ids[];
+};
+
+struct apic_id_results {
+ u64 missing_info1;
+ u64 missing_info2;
+ u64 zero_info1;
+ u64 zero_info2;
+ u64 zero_rax;
+ u64 invalid_info1;
+ u64 invalid_info2;
+ u64 valid_info1;
+ u64 valid_info2;
+ u32 nr_entries;
+ u32 first_apic_id;
+ u32 last_apic_id;
+};
+
+static void ghcb_set_field(void *ghcb, size_t offset, u64 value, bool valid)
+{
+ u8 *valid_bitmap = ghcb + GHCB_SAVE_VALID_BITMAP_OFFSET;
+
+ *(u64 *)(ghcb + offset) = value;
+ if (valid)
+ valid_bitmap[(offset / sizeof(u64)) / 8] |=
+ BIT((offset / sizeof(u64)) % 8);
+}
+
+static u64 ghcb_get_field(void *ghcb, size_t offset)
+{
+ return *(u64 *)(ghcb + offset);
+}
+
+static void do_get_apic_ids(void *ghcb, gpa_t buffer_gpa, u64 pages,
+ bool rax_valid)
+{
+ memset(ghcb, 0, PAGE_SIZE);
+ ghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_CODE_OFFSET,
+ SVM_VMGEXIT_GET_APIC_IDS, true);
+ ghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET, buffer_gpa, true);
+ ghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET, 0, true);
+ ghcb_set_field(ghcb, GHCB_SAVE_RAX_OFFSET, pages, rax_valid);
+ vmgexit();
+}
+
+static void guest_code(void *ghcb, gpa_t ghcb_gpa, void *list,
+ gpa_t list_gpa, struct apic_id_results *results,
+ u64 expected_vcpus)
+{
+ struct apic_id_desc *desc = list;
+ u64 msr;
+
+ wrmsr(MSR_AMD64_SEV_ES_GHCB,
+ (ghcb_gpa >> PAGE_SHIFT) << PAGE_SHIFT | GHCB_MSR_REG_GPA_REQ);
+ vmgexit();
+ msr = rdmsr(MSR_AMD64_SEV_ES_GHCB);
+ if ((msr & GHCB_MSR_INFO_MASK) != GHCB_MSR_REG_GPA_RESP)
+ goto terminate;
+
+ wrmsr(MSR_AMD64_SEV_ES_GHCB, ghcb_gpa);
+
+ do_get_apic_ids(ghcb, list_gpa, 1, false);
+ results->missing_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+ results->missing_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+
+ do_get_apic_ids(ghcb, list_gpa, 0, true);
+ results->zero_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+ results->zero_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+ results->zero_rax = ghcb_get_field(ghcb, GHCB_SAVE_RAX_OFFSET);
+
+ do_get_apic_ids(ghcb, BIT_ULL(52), 2, true);
+ results->invalid_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+ results->invalid_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+
+ do_get_apic_ids(ghcb, list_gpa, 2, true);
+ results->valid_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+ results->valid_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+ results->nr_entries = desc->nr_entries;
+ results->first_apic_id = desc->apic_ids[0];
+ results->last_apic_id = desc->apic_ids[expected_vcpus - 1];
+
+terminate:
+ wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
+ vmgexit();
+}
+
+static void run_apic_id_test(unsigned int nr_vcpus)
+{
+ struct apic_id_results *results;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ gva_t ghcb_gva, list_gva, results_gva;
+ gpa_t ghcb_gpa, list_gpa;
+ unsigned int i;
+
+ kvm_set_files_rlimit(nr_vcpus);
+ vm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_code, &vcpu);
+ for (i = 1; i < nr_vcpus; i++)
+ __vm_vcpu_add(vm, i);
+
+ ghcb_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ list_gva = vm_alloc_shared(vm, 2 * PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ results_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ ghcb_gpa = addr_gva2gpa(vm, ghcb_gva);
+ list_gpa = addr_gva2gpa(vm, list_gva);
+ results = addr_gva2hva(vm, results_gva);
+
+ vcpu_args_set(vcpu, 6, ghcb_gva, ghcb_gpa, list_gva, list_gpa,
+ results_gva, nr_vcpus);
+ memset(addr_gva2hva(vm, ghcb_gva), 0, PAGE_SIZE);
+ memset(addr_gva2hva(vm, list_gva), 0, 2 * PAGE_SIZE);
+ memset(results, 0, PAGE_SIZE);
+ vm_sev_launch(vm, snp_default_policy(), NULL);
+
+ vcpu_run(vcpu);
+ TEST_ASSERT_EQ(vcpu->run->exit_reason, KVM_EXIT_SYSTEM_EVENT);
+ TEST_ASSERT_EQ(vcpu->run->system_event.type, KVM_SYSTEM_EVENT_SEV_TERM);
+
+ TEST_ASSERT_EQ(results->missing_info1, GHCB_HV_RESP_MALFORMED_INPUT);
+ TEST_ASSERT_EQ(results->missing_info2, GHCB_ERR_MISSING_INPUT);
+ TEST_ASSERT_EQ(results->zero_info1, 0);
+ TEST_ASSERT_EQ(results->zero_info2, 0);
+ TEST_ASSERT_EQ(results->zero_rax,
+ DIV_ROUND_UP(sizeof(struct apic_id_desc) + nr_vcpus * sizeof(u32),
+ PAGE_SIZE));
+ TEST_ASSERT_EQ(results->invalid_info1, GHCB_HV_RESP_MALFORMED_INPUT);
+ TEST_ASSERT_EQ(results->invalid_info2, GHCB_ERR_INVALID_INPUT);
+ TEST_ASSERT_EQ(results->valid_info1, 0);
+ TEST_ASSERT_EQ(results->valid_info2, 0);
+ TEST_ASSERT_EQ(results->nr_entries, nr_vcpus);
+ TEST_ASSERT_EQ(results->first_apic_id, 0);
+ TEST_ASSERT_EQ(results->last_apic_id, nr_vcpus - 1);
+
+ kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int max_vcpus;
+
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SNP_VM));
+ run_apic_id_test(2);
+
+ /* 1024 IDs cross the one-page descriptor boundary. */
+ max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);
+ if (max_vcpus >= 1024)
+ run_apic_id_test(1024);
+ else
+ pr_info("Skipping vCPU-count boundary test (max vCPUs: %u)\n", max_vcpus);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c b/tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c
new file mode 100644
index 0000000000000..72eecf3853a32
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <errno.h>
+#include <stdlib.h>
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "sev.h"
+#include "svm_util.h"
+
+#define DIRECT_MARKER 0x444952454354564dULL
+#define LEGACY_MARKER 0x4c4547414359564dULL
+#define SNP_ACTIVE_SEV_FEATURE BIT_ULL(0)
+#define VMSA_PMD_SIZE BIT_ULL(21)
+#define VMSA_MIN_GPA (VMSA_PMD_SIZE + PAGE_SIZE)
+
+struct test_vmsa {
+ struct vmcb_seg es, cs, ss, ds, fs, gs;
+ struct vmcb_seg gdtr, ldtr, idtr, tr;
+ u64 pl0_ssp, pl1_ssp, pl2_ssp, pl3_ssp;
+ u64 u_cet;
+ u8 reserved_0xc8[2];
+ u8 vmpl;
+ u8 cpl;
+ u8 reserved_0xcc[4];
+ u64 efer;
+ u8 reserved_0xd8[104];
+ u64 xss;
+ u64 cr4, cr3, cr0, dr7, dr6, rflags, rip;
+ u64 dr0, dr1, dr2, dr3;
+ u64 dr0_addr_mask, dr1_addr_mask, dr2_addr_mask, dr3_addr_mask;
+ u8 reserved_0x1c0[24];
+ u64 rsp, s_cet, ssp, isst_addr, rax;
+ u64 star, lstar, cstar, sfmask, kernel_gs_base;
+ u64 sysenter_cs, sysenter_esp, sysenter_eip, cr2;
+ u8 reserved_0x248[32];
+ u64 g_pat, dbgctl, br_from, br_to, last_excp_from, last_excp_to;
+ u8 reserved_0x298[80];
+ u32 pkru, tsc_aux;
+ u64 tsc_scale, tsc_offset;
+ u8 reserved_0x300[8];
+ u64 rcx, rdx, rbx, reserved_0x320, rbp, rsi, rdi;
+ u64 r8, r9, r10, r11, r12, r13, r14, r15;
+ u8 reserved_0x380[16];
+ u64 guest_exit_info_1, guest_exit_info_2, guest_exit_int_info, guest_nrip;
+ u64 sev_features, vintr_ctrl, guest_exit_code, virtual_tom, tlb_id, pcpu_id;
+ u64 event_inj, xcr0;
+ u8 reserved_0x3f0[16];
+ u64 x87_dp;
+ u32 mxcsr;
+ u16 x87_ftw, x87_fsw, x87_fcw, x87_fop, x87_ds, x87_cs;
+ u64 x87_rip;
+ u8 fpreg_x87[80];
+ u8 fpreg_xmm[256];
+ u8 fpreg_ymm[256];
+} __packed;
+
+static_assert(offsetof(struct test_vmsa, vmpl) == 0xca);
+static_assert(offsetof(struct test_vmsa, rip) == 0x178);
+static_assert(offsetof(struct test_vmsa, sev_features) == 0x3b0);
+static_assert(offsetof(struct test_vmsa, xcr0) == 0x3e8);
+
+static void guest_direct_entry(u64 *marker)
+{
+ *marker = DIRECT_MARKER;
+ wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
+ vmgexit();
+}
+
+static void guest_legacy_entry(u64 *marker)
+{
+ *marker = LEGACY_MARKER;
+ wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
+ vmgexit();
+}
+
+static void copy_segment(struct vmcb_seg *dst, const struct kvm_segment *src)
+{
+ dst->selector = src->selector;
+ dst->base = src->base;
+ dst->limit = src->limit;
+ dst->attrib = src->type |
+ (src->s << SVM_SELECTOR_S_SHIFT) |
+ (src->dpl << SVM_SELECTOR_DPL_SHIFT) |
+ ((src->present && !src->unusable) << SVM_SELECTOR_P_SHIFT) |
+ (src->avl << SVM_SELECTOR_AVL_SHIFT) |
+ (src->l << SVM_SELECTOR_L_SHIFT) |
+ (src->db << SVM_SELECTOR_DB_SHIFT) |
+ (src->g << SVM_SELECTOR_G_SHIFT);
+}
+
+static void copy_dtable(struct vmcb_seg *dst, const struct kvm_dtable *src)
+{
+ dst->base = src->base;
+ dst->limit = src->limit;
+}
+
+static void prepare_vmsa(struct kvm_vcpu *vcpu, struct test_vmsa *vmsa,
+ void *entry)
+{
+ struct kvm_sregs sregs;
+ struct kvm_regs regs;
+
+ memset(vmsa, 0, PAGE_SIZE);
+ vcpu_sregs_get(vcpu, &sregs);
+ vcpu_regs_get(vcpu, ®s);
+
+ copy_segment(&vmsa->es, &sregs.es);
+ copy_segment(&vmsa->cs, &sregs.cs);
+ copy_segment(&vmsa->ss, &sregs.ss);
+ copy_segment(&vmsa->ds, &sregs.ds);
+ copy_segment(&vmsa->fs, &sregs.fs);
+ copy_segment(&vmsa->gs, &sregs.gs);
+ copy_dtable(&vmsa->gdtr, &sregs.gdt);
+ copy_segment(&vmsa->ldtr, &sregs.ldt);
+ copy_dtable(&vmsa->idtr, &sregs.idt);
+ copy_segment(&vmsa->tr, &sregs.tr);
+
+ vmsa->cpl = sregs.cs.dpl;
+ /*
+ * KVM_GET_SREGS exposes the guest-visible EFER and therefore omits
+ * SVME, which KVM normally adds to the hardware VMSA itself.
+ */
+ vmsa->efer = sregs.efer | EFER_SVME;
+ vmsa->cr4 = sregs.cr4;
+ vmsa->cr3 = sregs.cr3;
+ vmsa->cr0 = sregs.cr0;
+ vmsa->dr7 = 0x400;
+ vmsa->dr6 = 0xffff0ff0;
+ vmsa->rflags = regs.rflags;
+ vmsa->rip = (u64)entry;
+ vmsa->rsp = regs.rsp;
+ vmsa->rax = regs.rax;
+ vmsa->rcx = regs.rcx;
+ vmsa->rdx = regs.rdx;
+ vmsa->rbx = regs.rbx;
+ vmsa->rbp = regs.rbp;
+ vmsa->rsi = regs.rsi;
+ vmsa->rdi = regs.rdi;
+ vmsa->r8 = regs.r8;
+ vmsa->r9 = regs.r9;
+ vmsa->r10 = regs.r10;
+ vmsa->r11 = regs.r11;
+ vmsa->r12 = regs.r12;
+ vmsa->r13 = regs.r13;
+ vmsa->r14 = regs.r14;
+ vmsa->r15 = regs.r15;
+ vmsa->g_pat = 0x0007040600070406ULL;
+ vmsa->sev_features = SNP_ACTIVE_SEV_FEATURE;
+ vmsa->xcr0 = 1;
+ vmsa->mxcsr = 0x1f80;
+ vmsa->x87_fcw = 0x37f;
+}
+
+static void expect_launch_update_vmsa_error(struct kvm_vm *vm, gpa_t gpa,
+ void *vmsa, u64 size)
+{
+ struct kvm_sev_snp_launch_update update = {
+ .gfn_start = gpa >> PAGE_SHIFT,
+ .uaddr = (u64)vmsa,
+ .len = size,
+ .type = KVM_SEV_SNP_PAGE_TYPE_VMSA,
+ };
+
+ errno = 0;
+ TEST_ASSERT_EQ(__vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_UPDATE, &update), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+}
+
+static void exclude_from_normal_launch(struct kvm_vm *vm, gpa_t gpa,
+ unsigned int npages)
+{
+ struct userspace_mem_region *region;
+
+ region = memslot2region(vm, vm->memslots[MEM_REGION_TEST_DATA]);
+ sparsebit_clear_num(region->protected_phy_pages, gpa >> PAGE_SHIFT, npages);
+}
+
+static void assert_vcpu_terminated(struct kvm_vcpu *vcpu)
+{
+ vcpu_run(vcpu);
+ TEST_ASSERT_EQ(vcpu->run->exit_reason, KVM_EXIT_SYSTEM_EVENT);
+ TEST_ASSERT_EQ(vcpu->run->system_event.type, KVM_SYSTEM_EVENT_SEV_TERM);
+}
+
+static struct kvm_vm *create_direct_vmsa_vm(unsigned int nr_vcpus,
+ void *guest_code,
+ struct kvm_vcpu **vcpus)
+{
+ struct vm_shape shape = {
+ .mode = VM_MODE_DEFAULT,
+ .type = KVM_X86_SNP_VM,
+ };
+ struct kvm_vm *vm;
+ unsigned int i;
+
+ vm = __vm_create(shape, nr_vcpus, 0);
+ vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0);
+ for (i = 0; i < nr_vcpus; i++)
+ vcpus[i] = vm_vcpu_add(vm, i, guest_code);
+ kvm_arch_vm_finalize_vcpus(vm);
+
+ return vm;
+}
+
+static void test_direct_vmsa(void)
+{
+ struct test_vmsa *vmsas, *selected_vmsa;
+ struct kvm_sev_snp_vcpu_state state = {};
+ struct kvm_mp_state mp_state = {
+ .mp_state = KVM_MP_STATE_UNINITIALIZED,
+ };
+ struct kvm_vcpu *vcpus[2];
+ struct kvm_vcpu *vcpu, *ap;
+ struct kvm_vm *vm;
+ gva_t marker_gva;
+ gpa_t vmsa_gpa;
+ u64 *marker;
+
+ vm = create_direct_vmsa_vm(ARRAY_SIZE(vcpus), guest_legacy_entry, vcpus);
+ vcpu = vcpus[0];
+ ap = vcpus[1];
+ /* Restore normal AP state after vm_vcpu_add() makes it runnable. */
+ vcpu_mp_state_set(ap, &mp_state);
+ marker_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ marker = addr_gva2hva(vm, marker_gva);
+ vcpu_args_set(vcpu, 1, marker_gva);
+
+ vmsa_gpa = vm_phy_pages_alloc(vm, 2, VMSA_MIN_GPA,
+ vm->memslots[MEM_REGION_TEST_DATA]);
+ TEST_ASSERT(vmsa_gpa & (VMSA_PMD_SIZE - 1), "unsafe VMSA GPA");
+ vmsas = aligned_alloc(PAGE_SIZE, 2 * PAGE_SIZE);
+ TEST_ASSERT(vmsas, "Failed to allocate VMSA source pages");
+ selected_vmsa = (void *)vmsas + PAGE_SIZE;
+ prepare_vmsa(vcpu, &vmsas[0], guest_legacy_entry);
+ prepare_vmsa(vcpu, selected_vmsa, guest_direct_entry);
+
+ snp_vm_launch_start(vm, snp_default_policy());
+ vm_mem_set_private(vm, vmsa_gpa, 2 * PAGE_SIZE);
+ expect_launch_update_vmsa_error(vm, vmsa_gpa, vmsas, 2 * PAGE_SIZE);
+
+ vmsas[0].vmpl = 1;
+ expect_launch_update_vmsa_error(vm, vmsa_gpa, vmsas, PAGE_SIZE);
+ vmsas[0].vmpl = 0;
+ vmsas[0].sev_features = 0;
+ expect_launch_update_vmsa_error(vm, vmsa_gpa, vmsas, PAGE_SIZE);
+ vmsas[0].sev_features = SNP_ACTIVE_SEV_FEATURE;
+
+ snp_launch_update_vmsa(vm, vmsa_gpa, vmsas);
+ snp_launch_update_vmsa(vm, vmsa_gpa + PAGE_SIZE, selected_vmsa);
+ exclude_from_normal_launch(vm, vmsa_gpa, 2);
+ snp_vm_launch_update(vm);
+
+ /* Rebinding is allowed; the second, selected VMSA must win. */
+ snp_set_vcpu_state(vcpu, vmsa_gpa);
+ snp_set_vcpu_state(vcpu, vmsa_gpa + PAGE_SIZE);
+ snp_get_vcpu_state(vcpu, &state);
+ TEST_ASSERT_EQ(state.valid_fields,
+ KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |
+ KVM_SEV_SNP_VCPU_STATE_GHCB_VALID);
+ TEST_ASSERT_EQ(state.vmsa_gpa, vmsa_gpa + PAGE_SIZE);
+ TEST_ASSERT_EQ(state.ghcb_gpa, 0);
+ snp_vm_launch_finish(vm);
+
+ vcpu_mp_state_get(ap, &mp_state);
+ TEST_ASSERT_EQ(mp_state.mp_state, KVM_MP_STATE_UNINITIALIZED);
+ *marker = 0;
+ assert_vcpu_terminated(vcpu);
+ TEST_ASSERT_EQ(*marker, DIRECT_MARKER);
+
+ state = (struct kvm_sev_snp_vcpu_state) {
+ .vmsa_gpa = vmsa_gpa,
+ .valid_fields = KVM_SEV_SNP_VCPU_STATE_VMSA_VALID,
+ };
+ errno = 0;
+ TEST_ASSERT_EQ(__vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE,
+ &state), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+
+ free(vmsas);
+ kvm_vm_free(vm);
+}
+
+static void expect_set_vcpu_state_error(struct kvm_vcpu *vcpu,
+ struct kvm_sev_snp_vcpu_state *state,
+ int expected_errno)
+{
+ errno = 0;
+ TEST_ASSERT_EQ(__vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, state), -1);
+ TEST_ASSERT_EQ(errno, expected_errno);
+}
+
+static void test_invalid_requests(void)
+{
+ struct kvm_sev_snp_vcpu_state state = {
+ .valid_fields = KVM_SEV_SNP_VCPU_STATE_VMSA_VALID,
+ };
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ gva_t shared_gva;
+ gpa_t private_gpa;
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_legacy_entry);
+ expect_set_vcpu_state_error(vcpu, &state, ENOTTY);
+ kvm_vm_free(vm);
+
+ vm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_legacy_entry, &vcpu);
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ snp_vm_launch_start(vm, snp_default_policy());
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ kvm_vm_free(vm);
+
+ vm = create_direct_vmsa_vm(1, guest_legacy_entry, &vcpu);
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ snp_vm_launch_start(vm, snp_default_policy());
+
+ state.pad[4] = 1;
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ state.pad[4] = 0;
+ state.valid_fields |= BIT_ULL(2);
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ state.valid_fields &= ~BIT_ULL(2);
+ state.vmsa_gpa = PAGE_SIZE + 1;
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ state.vmsa_gpa = VMSA_PMD_SIZE;
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ state.vmsa_gpa = BIT_ULL(40) + PAGE_SIZE;
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+
+ shared_gva = vm_alloc_shared(vm, 2 * PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ state.vmsa_gpa = addr_gva2gpa(vm, shared_gva);
+ if (!(state.vmsa_gpa & (VMSA_PMD_SIZE - 1)))
+ state.vmsa_gpa += PAGE_SIZE;
+ state.ghcb_gpa = BIT_ULL(40);
+ state.valid_fields |= KVM_SEV_SNP_VCPU_STATE_GHCB_VALID;
+ vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, &state);
+
+ private_gpa = vm_phy_page_alloc(vm, VMSA_MIN_GPA,
+ vm->memslots[MEM_REGION_TEST_DATA]);
+ vm_mem_set_private(vm, private_gpa, PAGE_SIZE);
+ state.vmsa_gpa = private_gpa;
+ vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, &state);
+
+ memset(&state, 0, sizeof(state));
+ snp_get_vcpu_state(vcpu, &state);
+ TEST_ASSERT_EQ(state.valid_fields,
+ KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |
+ KVM_SEV_SNP_VCPU_STATE_GHCB_VALID);
+ TEST_ASSERT_EQ(state.vmsa_gpa, private_gpa);
+ TEST_ASSERT_EQ(state.ghcb_gpa, BIT_ULL(40));
+
+ kvm_vm_free(vm);
+}
+
+static void test_direct_vmsa_capability(void)
+{
+ struct kvm_enable_cap cap = {
+ .cap = KVM_CAP_SNP_DIRECT_VMSA,
+ };
+ struct vm_shape shape = {
+ .mode = VM_MODE_DEFAULT,
+ .type = KVM_X86_SNP_VM,
+ };
+ struct kvm_vm *vm;
+
+ vm = vm_create_barebones();
+ TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ kvm_vm_free(vm);
+
+ vm = __vm_create(shape, 1, 0);
+ TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 1), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ cap.args[3] = 1;
+ TEST_ASSERT_EQ(__vm_ioctl(vm, KVM_ENABLE_CAP, &cap), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ cap.args[3] = 0;
+ cap.flags = 1;
+ TEST_ASSERT_EQ(__vm_ioctl(vm, KVM_ENABLE_CAP, &cap), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0);
+ vm_vcpu_add(vm, 0, guest_legacy_entry);
+ TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ kvm_vm_free(vm);
+}
+
+static void test_legacy_launch(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ gva_t marker_gva;
+ u64 *marker;
+
+ vm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_legacy_entry, &vcpu);
+ marker_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ marker = addr_gva2hva(vm, marker_gva);
+ vcpu_args_set(vcpu, 1, marker_gva);
+ vm_sev_launch(vm, snp_default_policy(), NULL);
+ *marker = 0;
+ assert_vcpu_terminated(vcpu);
+ TEST_ASSERT_EQ(*marker, LEGACY_MARKER);
+ kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_SNP_VCPU_STATE));
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_SNP_DIRECT_VMSA));
+ TEST_ASSERT(sizeof(struct test_vmsa) <= PAGE_SIZE, "VMSA structure is too large");
+
+ test_direct_vmsa_capability();
+ test_invalid_requests();
+ test_direct_vmsa();
+ test_legacy_launch();
+ return 0;
+}
|
| 3/2 | 2026/09/08 13:02 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 25050 output: 34 thoughts: 717 Thoughts: **Analyzing the KVM AMD SEV-SNP Patch Series for Fuzzing Potential** Okay, let's break this down. First, I need to understand what this patch series actually *does*. It's introducing direct VMSA and APIC ID list support for AMD SEV-SNP within KVM/x86/SVM. I see it's adding new capabilities (`KVM_CAP_SNP_DIRECT_VMSA`, `KVM_CAP_SNP_VCPU_STATE`), ioctls (`KVM_SEV_SNP_GET_VCPU_STATE`, `KVM_SEV_SNP_SET_VCPU_STATE`), and modifying several existing functions related to SEV-SNP launch, vCPU creation, and VMSA installation. It also includes new callbacks and documentation, which is always welcome. Now comes the crucial part: reachability. My priority is to figure out if this code is even *reachable* in a standard fuzzing environment. We're dealing with AMD64 and SEV-SNP, so the question immediately is: Can I trigger this in GCE or QEMU? Our rules are clear: if the code doesn't execute in a standard virtualized environment, it's a no-go for fuzzing. Specifically, I need to verify that SEV/SEV-SNP doesn't absolutely require physical AMD hardware. Can these KVM ioctls be invoked? Are parts of this code path accessible without specific AMD hardware enablement? I see `KVM_CAP_SNP_DIRECT_VMSA`, `kvm_vm_ioctl_enable_cap`, and `sev_vcpu_mem_enc_ioctl` mentioned. I know Syzkaller can fuzz KVM; there are existing descriptions for KVM setup and ioctls. But the critical question is: Does QEMU (or GCE) support *nested* AMD SEV-SNP virtualization? This is key. SEV-SNP needs specific SNP-enabled hardware (firmware, RMP table). Does `is_sev_snp_guest` or `kvm_caps.supported_vm_types & BIT(KVM_X86_SNP_VM)` work without it? I need to dive into how KVM SEV/SNP is *initialized* within the kernel. I'll probably start with `grepper` or just dig through the source code directly. This is the crucial path. If it's not reachable without special hardware, we don't need to go further. |
| 4/2 | 2026/09/08 13:02 | tool | grepper |
0mArgs:{
"Expression": "snp_init",
"PathPrefix": "arch/x86/kvm/svm/"
}Results: {
"Output": "arch/x86/kvm/svm/sev.c=3182=void __init sev_set_cpu_caps(void)\n--\narch/x86/kvm/svm/sev.c-3193-\narch/x86/kvm/svm/sev.c:3194:static bool is_sev_snp_initialized(void)\narch/x86/kvm/svm/sev.c-3195-{\n--\narch/x86/kvm/svm/sev.c=3226=void __init sev_hardware_setup(void)\n--\narch/x86/kvm/svm/sev.c-3343-\t\telse if (sev_snp_supported)\narch/x86/kvm/svm/sev.c:3344:\t\t\tsev_snp_supported = is_sev_snp_initialized();\narch/x86/kvm/svm/sev.c-3345-\n--\narch/x86/kvm/svm/sev.c=4222=static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)\n--\narch/x86/kvm/svm/sev.c-4295- */\narch/x86/kvm/svm/sev.c:4296:static void sev_snp_init_protected_guest_state(struct kvm_vcpu *vcpu)\narch/x86/kvm/svm/sev.c-4297-{\n--\narch/x86/kvm/svm/sev.c=5096=void sev_init_vmcb(struct vcpu_svm *svm, bool init_event)\n--\narch/x86/kvm/svm/sev.c-5109-\tif (init_event \u0026\u0026 is_sev_snp_guest(vcpu))\narch/x86/kvm/svm/sev.c:5110:\t\tsev_snp_init_protected_guest_state(vcpu);\narch/x86/kvm/svm/sev.c-5111-\n"
} |
| 5/2 | 2026/09/08 13:02 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 26869 output: 43 thoughts: 38 Thoughts: Here's my summary, as if I were thinking these thoughts: **Analyzing SEV-SNP Initialization and Hardware Setup in `kvm/svm/sev.c`** Okay, time to dig into the AMD Secure Encrypted Virtualization (SEV) Secure Nested Paging (SNP) code within KVM, specifically focusing on how it gets initialized. I'm going to start by examining `arch/x86/kvm/svm/sev.c`, and more precisely, the sections related to `is_sev_snp_initialized` and `sev_hardware_setup`. I need to understand the sequencing and critical points in the setup process. I need to figure out what triggers the initialization and what are the pre-conditions, particularly for SNP. The `is_sev_snp_initialized` check is likely crucial as it dictates whether we're even *in* an SEV-SNP context, or if we are dealing with a more legacy SEV configuration. I'll need to see how the system is probed for the relevant hardware features and which bits get set where. `sev_hardware_setup` will probably contain the guts of the hardware configuration: which MSRs are being tweaked, what memory regions are being allocated, etc. It's highly probable that this will involve some platform-specific details, so I'll need to look for any architecture-dependent macros or conditionals to understand the flow for different AMD CPUs. This will all be critical for understanding any potential boot-time or runtime issues related to secure virtualization. |
| 6/2 | 2026/09/08 13:02 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/svm/sev.c",
"FirstLine": 3190,
"LineCount": 70
}Results: {
"Contents": "3190:\t\tif (sev_snp_enabled)\n3191:\t\t\tkvm_cpu_cap_set(X86_FEATURE_SEV_SNP);\n3192:\t}\n3193:\t\n3194:\tstatic bool is_sev_snp_initialized(void)\n3195:\t{\n3196:\t\tstruct sev_user_data_snp_status *status;\n3197:\t\tstruct sev_data_snp_addr buf;\n3198:\t\tbool initialized = false;\n3199:\t\tint ret, error = 0;\n3200:\t\n3201:\t\tstatus = snp_alloc_firmware_page(GFP_KERNEL | __GFP_ZERO);\n3202:\t\tif (!status)\n3203:\t\t\treturn false;\n3204:\t\n3205:\t\tbuf.address = __psp_pa(status);\n3206:\t\tret = sev_do_cmd(SEV_CMD_SNP_PLATFORM_STATUS, \u0026buf, \u0026error);\n3207:\t\tif (ret) {\n3208:\t\t\tpr_err(\"SEV: SNP_PLATFORM_STATUS failed ret=%d, fw_error=%d (%#x)\\n\",\n3209:\t\t\t ret, error, error);\n3210:\t\t\tgoto out;\n3211:\t\t}\n3212:\t\n3213:\t\tinitialized = !!status-\u003estate;\n3214:\t\n3215:\tout:\n3216:\t\tsnp_free_firmware_page(status);\n3217:\t\n3218:\t\treturn initialized;\n3219:\t}\n3220:\t\n3221:\tstatic const char * __init sev_str_feature_state(bool is_supported, bool is_usable)\n3222:\t{\n3223:\t\treturn is_supported ? is_usable ? \"enabled\" : \"unusable\" : \"disabled\";\n3224:\t}\n3225:\t\n3226:\tvoid __init sev_hardware_setup(void)\n3227:\t{\n3228:\t\tunsigned int eax, ebx, ecx, edx, sev_asid_count, sev_es_asid_count;\n3229:\t\tstruct sev_platform_init_args init_args = {0};\n3230:\t\tbool sev_snp_supported = false;\n3231:\t\tbool sev_es_supported = false;\n3232:\t\tbool sev_supported = false;\n3233:\t\tu32 vm_types = 0;\n3234:\t\n3235:\t\tif (!sev_enabled || !npt_enabled || !nrips)\n3236:\t\t\tgoto out;\n3237:\t\n3238:\t\t/*\n3239:\t\t * SEV must obviously be supported in hardware. Sanity check that the\n3240:\t\t * CPU supports decode assists, which is mandatory for SEV guests to\n3241:\t\t * support instruction emulation. Ditto for flushing by ASID, as SEV\n3242:\t\t * guests are bound to a single ASID, i.e. KVM can't rotate to a new\n3243:\t\t * ASID to effect a TLB flush.\n3244:\t\t */\n3245:\t\tif (!boot_cpu_has(X86_FEATURE_SEV) ||\n3246:\t\t WARN_ON_ONCE(!boot_cpu_has(X86_FEATURE_DECODEASSISTS)) ||\n3247:\t\t WARN_ON_ONCE(!boot_cpu_has(X86_FEATURE_FLUSHBYASID)))\n3248:\t\t\tgoto out;\n3249:\t\n3250:\t\t/*\n3251:\t\t * The kernel's initcall infrastructure lacks the ability to express\n3252:\t\t * dependencies between initcalls, whereas the modules infrastructure\n3253:\t\t * automatically handles dependencies via symbol loading. Ensure the\n3254:\t\t * PSP SEV driver is initialized before proceeding if KVM is built-in,\n3255:\t\t * as the dependency isn't handled by the initcall infrastructure.\n3256:\t\t */\n3257:\t\tif (IS_BUILTIN(CONFIG_KVM_AMD) \u0026\u0026 sev_module_init())\n3258:\t\t\tgoto out;\n3259:\t\n"
} |
| 7/2 | 2026/09/08 13:02 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 28390 output: 43 thoughts: 14 Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deeply familiar with `sev_hardware_setup` and its context. **Diving Deeper into the Hardware Setup** Alright, so the initial direction is clear – we need to delve into `sev_hardware_setup`. The comment implies that this function or section holds the key to the next piece of this puzzle. My experience tells me that this is probably a crucial setup function, likely involving configuration of the Secure Encrypted Virtualization (SEV) hardware, or perhaps the initialisation steps of the AMD Secure Processor. I'm expecting to find details around register writes, memory mapping, or perhaps even firmware calls related to securing the system. I'm hoping I don't have to deal with low-level kernel code today, but I am ready. Let's see what’s in there! |
| 8/2 | 2026/09/08 13:02 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/svm/sev.c",
"FirstLine": 3260,
"LineCount": 90
}Results: {
"Contents": "3260:\t\t/* Retrieve SEV CPUID information */\n3261:\t\tcpuid(0x8000001f, \u0026eax, \u0026ebx, \u0026ecx, \u0026edx);\n3262:\t\n3263:\t\t/* Set encryption bit location for SEV-ES guests */\n3264:\t\tsev_enc_bit = ebx \u0026 0x3f;\n3265:\t\n3266:\t\t/* Maximum number of encrypted guests supported simultaneously */\n3267:\t\tmax_sev_asid = ecx;\n3268:\t\tif (!max_sev_asid)\n3269:\t\t\tgoto out;\n3270:\t\n3271:\t\t/* Minimum ASID value that should be used for SEV guest */\n3272:\t\tmin_sev_asid = edx;\n3273:\t\tsev_me_mask = 1UL \u003c\u003c (ebx \u0026 0x3f);\n3274:\t\n3275:\t\t/*\n3276:\t\t * Initialize SEV ASID bitmaps. Allocate space for ASID 0 in the bitmap,\n3277:\t\t * even though it's never used, so that the bitmap is indexed by the\n3278:\t\t * actual ASID.\n3279:\t\t */\n3280:\t\tnr_asids = max_sev_asid + 1;\n3281:\t\tsev_asid_bitmap = bitmap_zalloc(nr_asids, GFP_KERNEL);\n3282:\t\tif (!sev_asid_bitmap)\n3283:\t\t\tgoto out;\n3284:\t\n3285:\t\tsev_reclaim_asid_bitmap = bitmap_zalloc(nr_asids, GFP_KERNEL);\n3286:\t\tif (!sev_reclaim_asid_bitmap) {\n3287:\t\t\tbitmap_free(sev_asid_bitmap);\n3288:\t\t\tsev_asid_bitmap = NULL;\n3289:\t\t\tgoto out;\n3290:\t\t}\n3291:\t\n3292:\t\tif (min_sev_asid \u003c= max_sev_asid) {\n3293:\t\t\tsev_asid_count = max_sev_asid - min_sev_asid + 1;\n3294:\t\t\tWARN_ON_ONCE(misc_cg_set_capacity(MISC_CG_RES_SEV, sev_asid_count));\n3295:\t\t}\n3296:\t\tsev_supported = true;\n3297:\t\n3298:\t\t/* SEV-ES support requested? */\n3299:\t\tif (!sev_es_enabled)\n3300:\t\t\tgoto out;\n3301:\t\n3302:\t\t/*\n3303:\t\t * SEV-ES requires MMIO caching as KVM doesn't have access to the guest\n3304:\t\t * instruction stream, i.e. can't emulate in response to a #NPF and\n3305:\t\t * instead relies on #NPF(RSVD) being reflected into the guest as #VC\n3306:\t\t * (the guest can then do a #VMGEXIT to request MMIO emulation).\n3307:\t\t */\n3308:\t\tif (!enable_mmio_caching)\n3309:\t\t\tgoto out;\n3310:\t\n3311:\t\t/* Does the CPU support SEV-ES? */\n3312:\t\tif (!boot_cpu_has(X86_FEATURE_SEV_ES))\n3313:\t\t\tgoto out;\n3314:\t\n3315:\t\tif (!lbrv) {\n3316:\t\t\tWARN_ONCE(!boot_cpu_has(X86_FEATURE_LBRV),\n3317:\t\t\t\t \"LBRV must be present for SEV-ES support\");\n3318:\t\t\tgoto out;\n3319:\t\t}\n3320:\t\n3321:\t\t/* Has the system been allocated ASIDs for SEV-ES? */\n3322:\t\tif (min_sev_asid == 1)\n3323:\t\t\tgoto out;\n3324:\t\n3325:\t\tmin_sev_es_asid = min_snp_asid = 1;\n3326:\t\tmax_sev_es_asid = max_snp_asid = min_sev_asid - 1;\n3327:\t\n3328:\t\tsev_es_asid_count = min_sev_asid - 1;\n3329:\t\tWARN_ON_ONCE(misc_cg_set_capacity(MISC_CG_RES_SEV_ES, sev_es_asid_count));\n3330:\t\tsev_es_supported = true;\n3331:\t\tsev_snp_supported = sev_snp_enabled \u0026\u0026 cc_platform_has(CC_ATTR_HOST_SEV_SNP);\n3332:\t\n3333:\tout:\n3334:\t\tif (sev_enabled) {\n3335:\t\t\tinit_args.probe = true;\n3336:\t\n3337:\t\t\tif (sev_is_snp_ciphertext_hiding_supported())\n3338:\t\t\t\tinit_args.max_snp_asid = min(nr_ciphertext_hiding_asids,\n3339:\t\t\t\t\t\t\t min_sev_asid - 1);\n3340:\t\n3341:\t\t\tif (sev_platform_init(\u0026init_args))\n3342:\t\t\t\tsev_supported = sev_es_supported = sev_snp_supported = false;\n3343:\t\t\telse if (sev_snp_supported)\n3344:\t\t\t\tsev_snp_supported = is_sev_snp_initialized();\n3345:\t\n3346:\t\t\tif (sev_snp_supported) {\n3347:\t\t\t\tsnp_supported_policy_bits = sev_get_snp_policy_bits() \u0026\n3348:\t\t\t\t\t\t\t KVM_SNP_POLICY_MASK_VALID;\n3349:\t\t\t\tnr_ciphertext_hiding_asids = init_args.max_snp_asid;\n"
} |
| 9/2 | 2026/09/08 13:02 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 30184 output: 37 thoughts: 1326 Thoughts: **SEV-SNP and Fuzzing Feasibility: A Deep Dive** Okay, let's break this down. First, I need to establish the context: we're dealing with SEV-SNP (Secure Encrypted Virtualization - Secure Nested Paging), and the question is whether the changes related to it are worth fuzzing, especially within the confines of a standard fuzzing environment like syzkaller. My initial thoughts are centered around the hardware prerequisites. I know SEV-SNP relies on specific AMD hardware, specifically CPUs with SEV-SNP support, AMD PSP firmware, and RMP initialization. That's a huge hurdle. Standard QEMU or GCE VMs can't act as SEV-SNP hosts because they don't emulate the necessary AMD hardware components like the RMP and PSP. Nested virtualization, running SEV-SNP guests within a QEMU/GCE VM, is a non-starter for these reasons. The central question is this: is SEV-SNP considered "structurally unreachable hardware" in the fuzzing context, or is KVM itself a fuzzed target? Now, the instructions are clear: *reachability* is paramount. A change is only worth fuzzing if it can execute in a standard virtualized environment (QEMU/GCE). Structurally unreachable hardware isn't fuzzed. That's the gate. The instructions specifically outline negative criteria: changes are not worth fuzzing if they involve non-executable changes, or "Structurally unreachable hardware." Specifically, this includes vendor-specific devices, such as the AMD hardware required for SEV-SNP. Now, let's dig into the patch's modifications. I need to understand the scope. The patch touches several files, so let's list them: 1. `Documentation/virt/kvm/x86/amd-memory-encryption.rst` (docs) - Probably not directly executable, but worth a cursory glance to understand what's being enabled. 2. `arch/x86/include/asm/kvm-x86-ops.h` and associated includes: introduces `enable_vm_cap` and related function pointers. 3. `arch/x86/include/asm/sev-common.h`: likely just definitions. 4. `arch/x86/include/uapi/asm/kvm.h` and `arch/x86/include/uapi/asm/svm.h`: UAPI definitions. 5. `arch/x86/kvm/svm/sev.c`: Contains the core SEV-SNP logic. This is definitely a focal point. 6. `arch/x86/kvm/svm/svm.c`: Handles `svm_enable_vm_cap` and registers the function pointers, related to SEV-SNP enablement. 7. `arch/x86/kvm/svm/svm.h`: Declarations. 8. `arch/x86/kvm/x86.c`: modifies `kvm_vm_ioctl_check_extension` and `kvm_vm_ioctl_enable_cap`. `KVM_CAP_SNP_VCPU_STATE` and `KVM_CAP_SNP_DIRECT_VMSA` are key. 9. `include/uapi/linux/kvm.h` and testing files. Looking closely at `arch/x86/kvm/x86.c`, I see `kvm_vm_ioctl_enable_cap` and `kvm_vm_ioctl_check_extension`, plus `sev_vcpu_mem_enc_ioctl`. How is this function called? The presence of those ioctls, especially `sev_vcpu_mem_enc_ioctl`, is interesting. This means we are definitely hooking into the KVM interface. It's not just a completely hardware-specific module. However, I need to trace the call paths to determine if it is reachable or not. This is key. We need to ascertain if the execution path can be reached in the standard, QEMU-based fuzzing context, considering the SEV-SNP hardware dependencies. |
| 10/2 | 2026/09/08 13:02 | tool | grepper |
0mArgs:{
"Expression": "vcpu_mem_enc_ioctl",
"PathPrefix": "arch/x86/kvm/"
}Results: {
"Output": "arch/x86/kvm/svm/sev.c=2759=int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp)\n--\narch/x86/kvm/svm/sev.c-2868-\narch/x86/kvm/svm/sev.c:2869:int sev_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp)\narch/x86/kvm/svm/sev.c-2870-{\n--\narch/x86/kvm/svm/svm.c=5349=struct kvm_x86_ops svm_x86_ops __initdata = {\n--\narch/x86/kvm/svm/svm.c-5468-\t.mem_enc_ioctl = sev_mem_enc_ioctl,\narch/x86/kvm/svm/svm.c:5469:\t.vcpu_mem_enc_ioctl = sev_vcpu_mem_enc_ioctl,\narch/x86/kvm/svm/svm.c-5470-\t.mem_enc_register_region = sev_mem_enc_register_region,\n--\narch/x86/kvm/svm/svm.h=985=int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp);\narch/x86/kvm/svm/svm.h:986:int sev_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp);\narch/x86/kvm/svm/svm.h-987-int sev_mem_enc_register_region(struct kvm *kvm,\n--\narch/x86/kvm/vmx/main.c=839=static int vt_mem_enc_ioctl(struct kvm *kvm, void __user *argp)\n--\narch/x86/kvm/vmx/main.c-846-\narch/x86/kvm/vmx/main.c:847:static int vt_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp)\narch/x86/kvm/vmx/main.c-848-{\n--\narch/x86/kvm/vmx/main.c=888=struct kvm_x86_ops vt_x86_ops __initdata = {\n--\narch/x86/kvm/vmx/main.c-1035-\t.mem_enc_ioctl = vt_op_tdx_only(mem_enc_ioctl),\narch/x86/kvm/vmx/main.c:1036:\t.vcpu_mem_enc_ioctl = vt_op_tdx_only(vcpu_mem_enc_ioctl),\narch/x86/kvm/vmx/main.c-1037-\t.vcpu_mem_enc_unlocked_ioctl = vt_op_tdx_only(vcpu_mem_enc_unlocked_ioctl),\n--\narch/x86/kvm/x86.c=3420=long kvm_arch_vcpu_ioctl(struct file *filp,\n--\narch/x86/kvm/x86.c-3878-\t\tr = -ENOTTY;\narch/x86/kvm/x86.c:3879:\t\tif (!kvm_x86_ops.vcpu_mem_enc_ioctl)\narch/x86/kvm/x86.c-3880-\t\t\tgoto out;\narch/x86/kvm/x86.c:3881:\t\tr = kvm_x86_ops.vcpu_mem_enc_ioctl(vcpu, argp);\narch/x86/kvm/x86.c-3882-\t\tbreak;\n"
} |
| 11/2 | 2026/09/08 13:02 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 33444 output: 34 thoughts: 935 Thoughts: **Analyzing KVM on AMD SVM for Vulnerabilities** Okay, let's break this down. I'm looking at how KVM interacts with AMD's Secure Virtual Machine (SVM) extensions. Specifically, I see that within `kvm_arch_vcpu_ioctl`, which gets called during KVM testing on AMD SVM, we're calling `kvm_x86_ops.vcpu_mem_enc_ioctl` when the `KVM_MEMORY_ENCRYPT_OP` is triggered. Since `kvm_x86_ops` points to `svm_x86_ops` when SVM is active (like in nested SVM with QEMU or GCE AMD instances), this is key. Then, when we get to `kvm_vm_ioctl_enable_cap`, I see something interesting. For unknown caps (or those not explicitly handled by the generic switch), there's a fall-through that calls `kvm_x86_call(enable_vm_cap)`, which leads to `svm_enable_vm_cap`. Now let's examine `svm_enable_vm_cap`: This is where the magic happens, specifically regarding `KVM_CAP_SNP_DIRECT_VMSA`. If it's enabled, we're verifying the argument with `memchr_inv` to ensure the structure is properly populated and the VM type is `KVM_X86_SNP_VM`. We also guard with `kvm->lock` and check if there are any created vcpus. It looks like setting `snp_direct_vmsa` here. What about `sev_vcpu_mem_enc_ioctl`? It only gets called if `sev_enabled` is true, and it handles the `KVM_SEV_SNP_GET_VCPU_STATE` and `KVM_SEV_SNP_SET_VCPU_STATE` ioctls via `snp_get_vcpu_state` and `snp_set_vcpu_state`. And finally, looking at `kvm_vm_ioctl_check_extension`, the cases for `KVM_CAP_SNP_VCPU_STATE` and `KVM_CAP_SNP_DIRECT_VMSA` are checking if the `KVM_X86_SNP_VM` bit is supported. The real question is if we can actually reach these code paths. Considering that KVM x86 is a primary target of syzkaller, and that it's heavily fuzzed in QEMU and GCE environments on amd64, that answer is, *yes*. `CONFIG_KVM`, `CONFIG_KVM_AMD`, and `CONFIG_KVM_INTEL` are enabled. So now I'm going to verify if `sev_enabled` and any of the relevant `kvm` ioctls are reachable. Let's dig into the Kconfig options for `KVM_AMD_SEV` and `KVM_AMD`. |
| 12/2 | 2026/09/08 13:03 | tool | grepper |
0mArgs:{
"Expression": "config KVM_AMD",
"PathPrefix": "arch/x86/kvm/"
}Results: {
"Output": "arch/x86/kvm/Kconfig=134=config KVM_INTEL_TDX\n--\narch/x86/kvm/Kconfig-145-\narch/x86/kvm/Kconfig:146:config KVM_AMD\narch/x86/kvm/Kconfig-147-\ttristate \"KVM for AMD processors support\"\n--\narch/x86/kvm/Kconfig-155-\narch/x86/kvm/Kconfig:156:config KVM_AMD_SEV\narch/x86/kvm/Kconfig-157-\tbool \"AMD Secure Encrypted Virtualization (SEV) support\"\n"
} |
| 13/2 | 2026/09/08 13:03 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 35250 output: 39 thoughts: 28 Thoughts: Here's a summary of my thought process, as requested: **Navigating the Kconfig Labyrinth** Okay, time to dive into the nitty-gritty. I need to understand a specific configuration aspect within the KVM/x86 code. My initial thought is to zero in on `arch/x86/kvm/Kconfig`. That's the obvious place to start, given the context. Now, based on my past experience, the relevant options related to the subject are most likely located somewhere between lines 145 and 170. I'll start scanning that section of the file. That range should allow me to hone in on the specific configuration options I need to examine. Hopefully, the naming conventions and structure are relatively straightforward within that section, and I can quickly identify the key parameters. If not, I'll need to expand my search. |
| 14/2 | 2026/09/08 13:03 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/Kconfig",
"FirstLine": 145,
"LineCount": 40
}Results: {
"Contents": " 145:\t\n 146:\tconfig KVM_AMD\n 147:\t\ttristate \"KVM for AMD processors support\"\n 148:\t\tdepends on KVM \u0026\u0026 (CPU_SUP_AMD || CPU_SUP_HYGON)\n 149:\t\thelp\n 150:\t\t Provides support for KVM on AMD processors equipped with the AMD-V\n 151:\t\t (SVM) extensions.\n 152:\t\n 153:\t\t To compile this as a module, choose M here: the module\n 154:\t\t will be called kvm-amd.\n 155:\t\n 156:\tconfig KVM_AMD_SEV\n 157:\t\tbool \"AMD Secure Encrypted Virtualization (SEV) support\"\n 158:\t\tdefault y\n 159:\t\tdepends on KVM_AMD \u0026\u0026 X86_64\n 160:\t\tdepends on CRYPTO_DEV_SP_PSP \u0026\u0026 !(KVM_AMD=y \u0026\u0026 CRYPTO_DEV_CCP_DD=m)\n 161:\t\tselect ARCH_HAS_CC_PLATFORM\n 162:\t\tselect KVM_GENERIC_MEMORY_ATTRIBUTES\n 163:\t\tselect HAVE_KVM_ARCH_GMEM_CONVERT\n 164:\t\tselect HAVE_KVM_ARCH_GMEM_RECLAIM\n 165:\t\tselect HAVE_KVM_ARCH_GMEM_INVALIDATE\n 166:\t\tselect HAVE_KVM_ARCH_GMEM_POPULATE\n 167:\t\thelp\n 168:\t\t Provides support for launching encrypted VMs which use Secure\n 169:\t\t Encrypted Virtualization (SEV), Secure Encrypted Virtualization with\n 170:\t\t Encrypted State (SEV-ES), and Secure Encrypted Virtualization with\n 171:\t\t Secure Nested Paging (SEV-SNP) technologies on AMD processors.\n 172:\t\n 173:\tconfig KVM_IOAPIC\n 174:\t\tbool \"I/O APIC, PIC, and PIT emulation\"\n 175:\t\tdefault y\n 176:\t\tdepends on KVM_X86\n 177:\t\thelp\n 178:\t\t Provides support for KVM to emulate an I/O APIC, PIC, and PIT, i.e.\n 179:\t\t for full in-kernel APIC emulation.\n 180:\t\n 181:\t\t If unsure, say Y.\n 182:\t\n 183:\tconfig KVM_SMM\n 184:\t\tbool \"System Management Mode emulation\"\n"
} |
| 15/2 | 2026/09/08 13:03 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 36161 output: 33 thoughts: 24 Thoughts: Here's my thought process, as though I were considering this myself: **Kconfig Dependency Deep Dive** Okay, so I need to understand the dependencies of `CRYPTO_DEV_SP_PSP`. First, let's just dive right into the Kconfig files. No need to overthink it; I'll get the lay of the land quickly by going straight to the source. The Kconfig system is usually pretty good at laying out the dependencies in a logical manner, so it should be a straightforward task to identify what other features or options need to be enabled for this one to be available. This is the best first step. Let's see what Kconfig reveals. |
| 16/2 | 2026/09/08 13:03 | tool | grepper |
0mArgs:{
"Expression": "config CRYPTO_DEV_SP_PSP",
"PathPrefix": "drivers/crypto/"
}Results: {
"Output": "drivers/crypto/ccp/Kconfig=23=config CRYPTO_DEV_CCP_CRYPTO\n--\ndrivers/crypto/ccp/Kconfig-37-\ndrivers/crypto/ccp/Kconfig:38:config CRYPTO_DEV_SP_PSP\ndrivers/crypto/ccp/Kconfig-39-\tbool \"Platform Security Processor (PSP) device\"\n"
} |
| 17/2 | 2026/09/08 13:03 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 36460 output: 36 thoughts: 21 Thoughts: Here's my summary, as though I'm reading the provided text: **Examining the CCP Kconfig File** Okay, let's dive into the `drivers/crypto/ccp/Kconfig` file. I'm specifically interested in what's going on around line 38. Given the context, I'm expecting to find some configuration options or dependencies related to the CCP hardware. This Kconfig file likely defines the build-time configuration parameters for the CCP driver, so understanding these settings is crucial for proper integration. I'm assuming it's defining some features or the hardware details. I'll focus on the specific conditionals. |
| 18/2 | 2026/09/08 13:03 | tool | read-file |
0mArgs:{
"File": "drivers/crypto/ccp/Kconfig",
"FirstLine": 25,
"LineCount": 40
}Results: {
"Contents": " 25:\t\tdefault m\n 26:\t\tdepends on CRYPTO_DEV_CCP_DD\n 27:\t\tdepends on CRYPTO_DEV_SP_CCP\n 28:\t\tselect CRYPTO_HASH\n 29:\t\tselect CRYPTO_SKCIPHER\n 30:\t\tselect CRYPTO_AUTHENC\n 31:\t\tselect CRYPTO_RSA\n 32:\t\tselect CRYPTO_LIB_AES\n 33:\t\thelp\n 34:\t\t Support for using the cryptographic API with the AMD Cryptographic\n 35:\t\t Coprocessor. This module supports offload of SHA and AES algorithms.\n 36:\t\t If you choose 'M' here, this module will be called ccp_crypto.\n 37:\t\n 38:\tconfig CRYPTO_DEV_SP_PSP\n 39:\t\tbool \"Platform Security Processor (PSP) device\"\n 40:\t\tdefault y\n 41:\t\tdepends on CRYPTO_DEV_CCP_DD \u0026\u0026 X86_64 \u0026\u0026 AMD_IOMMU\n 42:\t\tselect PCI_TSM if PCI\n 43:\t\thelp\n 44:\t\t Provide support for the AMD Platform Security Processor (PSP).\n 45:\t\t The PSP is a dedicated processor that provides support for key\n 46:\t\t management commands in Secure Encrypted Virtualization (SEV) mode,\n 47:\t\t along with software-based Trusted Execution Environment (TEE) to\n 48:\t\t enable third-party trusted applications.\n 49:\t\n 50:\tconfig CRYPTO_DEV_CCP_DEBUGFS\n 51:\t\tbool \"Enable CCP Internals in DebugFS\"\n 52:\t\tdefault n\n 53:\t\tdepends on CRYPTO_DEV_SP_CCP\n 54:\t\thelp\n 55:\t\t Expose CCP device information such as operation statistics, feature\n 56:\t\t information, and descriptor queue contents.\n"
} |
| 19/2 | 2026/09/08 13:03 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 37119 output: 33 thoughts: 1363 Thoughts: **Fuzzing Strategy Assessment** Okay, let's break this down. My initial assessment is that this patch series *definitely* warrants fuzzing. We're looking at core KVM infrastructure on x86, specifically around AMD's SEV-SNP (Secure Encrypted Virtualization - Secure Nested Paging) support. That's a critical area, inherently complex, and potentially exposed to security-sensitive data. The code touches key areas: ioctl interfaces for VM management, locking primitives, memory management routines including gmem and MMU invalidation sequences, and even VMGEXIT handling which is how we interface with the hypervisor. Those are all good targets for fuzzing. So, we're definitely fuzzing this. Now, let's identify the most important bits – the functions to target. I've distilled a list of the unique, non-ubiquitous kernel functions added or heavily modified by this patch series: * `svm_enable_vm_cap` * `sev_vcpu_mem_enc_ioctl` * `snp_get_vcpu_state` * `snp_set_vcpu_state` * `sev_snp_install_guest_vmsa` * `__sev_snp_reload_vmsa` * `sev_snp_get_apic_ids` * `snp_launch_update` * `snp_launch_update_vmsa` * `sev_vcpu_create` * `sev_free_vcpu` I've verified that these symbols are indeed non-static and visible in the relevant source files: `svm.c` and `sev.c`. That's a critical first step. Next, we need to understand the configuration requirements. To enable this functionality we need the following configs: * `KVM` (depends on `KVM_X86` / `X86_64`) * `KVM_AMD` * `KVM_AMD_SEV` * `CRYPTO_DEV_SP_PSP` (since `KVM_AMD_SEV` depends on `KVM_AMD && X86_64 && CRYPTO_DEV_SP_PSP && !(KVM_AMD=y && CRYPTO_DEV_CCP_DD=m)`) * `CRYPTO_DEV_CCP_DD` (needed for `CRYPTO_DEV_SP_PSP`) * `AMD_IOMMU` (needed for `CRYPTO_DEV_SP_PSP`) The dependencies for `KVM_AMD_SEV` itself are clearly defined in the `Kconfig` files. That's a good sanity check. Also, ensuring that `CRYPTO_DEV_CCP_DD` and `CRYPTO_DEV_SP_PSP` are properly configured is important as well. With these configuration flags in mind, we can start constructing a fuzzing environment and begin testing. |
| 20/2 | 2026/09/08 13:04 | tool | grepper |
0mArgs:{
"Expression": "config CRYPTO_DEV_CCP_DD",
"PathPrefix": "drivers/crypto/"
}Results: {
"Output": "drivers/crypto/ccp/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/crypto/ccp/Kconfig:2:config CRYPTO_DEV_CCP_DD\ndrivers/crypto/ccp/Kconfig-3-\ttristate \"Secure Processor device driver\"\n"
} |
| 21/2 | 2026/09/08 13:04 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 39253 output: 35 thoughts: 22 Thoughts: Here's my summary, as if I were thinking these thoughts: **Examining the CCP Crypto Driver Configuration** Okay, time to dive into the configuration for the CCP crypto driver. Let's start by taking a look at `drivers/crypto/ccp/Kconfig`, specifically the first 25 lines. This Kconfig file is the linchpin for defining the build options and dependencies of the driver, so this is where the interesting bits will be. I need to understand what features are conditionally compiled, what dependencies they have, and how they relate to the underlying hardware and other parts of the kernel. This is probably going to give me a good lay of the land, detailing the basic building blocks and dependencies for the driver. I’ll be looking for defines, defaults, and the structure of the overall configuration hierarchy. Hopefully it doesn't get *too* deep, but I am prepared to dive into any conditional logic that is present to get a clear picture. |
| 22/2 | 2026/09/08 13:04 | tool | read-file |
0mArgs:{
"File": "drivers/crypto/ccp/Kconfig",
"FirstLine": 1,
"LineCount": 25
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0-only\n 2:\tconfig CRYPTO_DEV_CCP_DD\n 3:\t\ttristate \"Secure Processor device driver\"\n 4:\t\tdepends on CPU_SUP_AMD || ARM64\n 5:\t\tdefault m\n 6:\t\thelp\n 7:\t\t Provides AMD Secure Processor device driver.\n 8:\t\t If you choose 'M' here, this module will be called ccp.\n 9:\t\n 10:\tconfig CRYPTO_DEV_SP_CCP\n 11:\t\tbool \"Cryptographic Coprocessor device\"\n 12:\t\tdefault y\n 13:\t\tdepends on CRYPTO_DEV_CCP_DD \u0026\u0026 DMADEVICES\n 14:\t\tselect HW_RANDOM\n 15:\t\tselect DMA_ENGINE\n 16:\t\tselect CRYPTO_SHA1\n 17:\t\tselect CRYPTO_SHA256\n 18:\t\thelp\n 19:\t\t Provides the support for AMD Cryptographic Coprocessor (CCP) device\n 20:\t\t which can be used to offload encryption operations such as SHA, AES\n 21:\t\t and more.\n 22:\t\n 23:\tconfig CRYPTO_DEV_CCP_CRYPTO\n 24:\t\ttristate \"Encryption and hashing offload support\"\n 25:\t\tdefault m\n"
} |
| 23/2 | 2026/09/08 13:04 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 39850 output: 340 thoughts: 400 Thoughts: **My Assessment of the KVM Configuration for Fuzzing** Okay, let me quickly run through this. So, the configurations we're looking at are `KVM`, `KVM_AMD`, `KVM_AMD_SEV`, `CRYPTO_DEV_SP_PSP`, `CRYPTO_DEV_CCP_DD`, and `AMD_IOMMU`. Good. Let's make sure all the details are correct. `WorthFuzzing`: True, definitely. The `FocusSymbols` list confirms this—we've got `svm_enable_vm_cap`, `sev_vcpu_mem_enc_ioctl`, and all the `snp_*` and `sev_*` functions for SEV-SNP state management. That's a good set of targets for fuzzing. The list includes `sev_vcpu_create`, `sev_free_vcpu` which is critical and the others that deal with APIC ID discovery and other important aspects. `EnableConfigs` confirms what I expected: the base `KVM` configurations, the AMD-specific ones, and the SEV extensions. It's a nice, tight, and self-contained configuration. The `Reasoning` section summarizes it perfectly. It's concise and correct. Adds new KVM ioctl entrypoints on vcpu fds for SEV-SNP VCPU state getting/setting, direct VMSA management, APIC ID discovery VMGEXIT handling, and modifies VM capability enablement and guest memory management in KVM AMD SEV. Alright, a final quick scan to make sure I haven't missed anything... Yep, everything looks solid. Accurate, and well-justified. I'm ready to move forward. |
| 24/2 | 2026/09/08 13:04 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"KVM",
"KVM_AMD",
"KVM_AMD_SEV",
"CRYPTO_DEV_SP_PSP",
"CRYPTO_DEV_CCP_DD",
"AMD_IOMMU"
],
"FocusSymbols": [
"svm_enable_vm_cap",
"sev_vcpu_mem_enc_ioctl",
"snp_get_vcpu_state",
"snp_set_vcpu_state",
"sev_snp_install_guest_vmsa",
"__sev_snp_reload_vmsa",
"sev_snp_get_apic_ids",
"snp_launch_update",
"snp_launch_update_vmsa",
"sev_vcpu_create",
"sev_free_vcpu"
],
"Reasoning": "The patch series introduces direct-VMSA support and APIC ID enumeration for AMD SEV-SNP guests in KVM. It adds new vCPU ioctl commands (KVM_SEV_SNP_GET_VCPU_STATE and KVM_SEV_SNP_SET_VCPU_STATE), implements VM capability enablement (KVM_CAP_SNP_DIRECT_VMSA) in KVM SVM, adds the SVM_VMGEXIT_GET_APIC_IDS exit handler, and refactors VMSA guest memory installation and MMU invalidation sequencing. Fuzzing is warranted to test new state transitions, validation checks, and locking invariants.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"KVM",
"KVM_AMD",
"KVM_AMD_SEV",
"CRYPTO_DEV_SP_PSP",
"CRYPTO_DEV_CCP_DD",
"AMD_IOMMU"
],
"FocusSymbols": [
"svm_enable_vm_cap",
"sev_vcpu_mem_enc_ioctl",
"snp_get_vcpu_state",
"snp_set_vcpu_state",
"sev_snp_install_guest_vmsa",
"__sev_snp_reload_vmsa",
"sev_snp_get_apic_ids",
"snp_launch_update",
"snp_launch_update_vmsa",
"sev_vcpu_create",
"sev_free_vcpu"
],
"Reasoning": "The patch series introduces direct-VMSA support and APIC ID enumeration for AMD SEV-SNP guests in KVM. It adds new vCPU ioctl commands (KVM_SEV_SNP_GET_VCPU_STATE and KVM_SEV_SNP_SET_VCPU_STATE), implements VM capability enablement (KVM_CAP_SNP_DIRECT_VMSA) in KVM SVM, adds the SVM_VMGEXIT_GET_APIC_IDS exit handler, and refactors VMSA guest memory installation and MMU invalidation sequencing. Fuzzing is warranted to test new state transitions, validation checks, and locking invariants.",
"WorthFuzzing": true
} |
| 25/1 | 2026/09/08 13:04 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 26/2 | 2026/09/08 13:04 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch adds support for direct VMSAs and guest APIC ID list queries in AMD SEV-SNP (including new vCPU ioctls KVM_SEV_SNP_GET_VCPU_STATE / KVM_SEV_SNP_SET_VCPU_STATE and VMGEXIT handling for SVM_VMGEXIT_GET_APIC_IDS).\n\nAll kernel structures returned to userspace or written to guest memory are fully initialized:\n- `struct kvm_sev_snp_vcpu_state` in `snp_get_vcpu_state` is initialized with `= {}`, consists solely of `__u64` fields with no padding holes, and is fully zeroed before `copy_to_user()`.\n- `sev_cmd` in `sev_vcpu_mem_enc_ioctl` is copied from userspace via `copy_from_user()` before being copied back.\n- `struct sev_apic_id_desc` in `sev_snp_get_apic_ids` is allocated with `kvzalloc()` and completely populated before `kvm_write_guest()`.\n\nAny memory safety risks introduced by the patch (such as memory mapping, guest page validation, or concurrency) fall under standard detectors (KASAN, LOCKDEP). There are no uninitialized memory reads or kernel info-leak risks requiring a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 760726df0f542fb0f0b066de39ad501e3c2c3594
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Sep 8 13:02:00 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/virt/kvm/x86/amd-memory-encryption.rst b/Documentation/virt/kvm/x86/amd-memory-encryption.rst
index bd04a908a8dbd..427b673c3832f 100644
--- a/Documentation/virt/kvm/x86/amd-memory-encryption.rst
+++ b/Documentation/virt/kvm/x86/amd-memory-encryption.rst
@@ -536,6 +536,7 @@ Returns: 0 on success, < 0 on error, -EAGAIN if caller should retry
where the allowed values for page_type are #define'd as::
KVM_SEV_SNP_PAGE_TYPE_NORMAL
+ KVM_SEV_SNP_PAGE_TYPE_VMSA
KVM_SEV_SNP_PAGE_TYPE_ZERO
KVM_SEV_SNP_PAGE_TYPE_UNMEASURED
KVM_SEV_SNP_PAGE_TYPE_SECRETS
@@ -544,7 +545,68 @@ where the allowed values for page_type are #define'd as::
See the SEV-SNP spec [snp-fw-abi]_ for further details on how each page type is
used/measured.
-20. KVM_SEV_SNP_LAUNCH_FINISH
+``KVM_SEV_SNP_PAGE_TYPE_VMSA`` creates VMSA pages as part of the measured
+initial image. A request must contain exactly one 4 KiB VMSA page, but the
+command may be used multiple times. Creating a VMSA page does not associate it
+with a vCPU; use ``KVM_SEV_SNP_SET_VCPU_STATE`` on the intended vCPU file
+descriptor before launch finish to make that association. KVM treats the
+VMSA contents as guest-owned data, but requires VMPL 0 and a ``sev_features``
+value that matches the VM's configured VMSA features.
+
+20. KVM_SEV_SNP_GET_VCPU_STATE / KVM_SEV_SNP_SET_VCPU_STATE
+------------------------------------------------------------
+
+These commands get or set the VMSA and GHCB addresses for the vCPU on whose
+file descriptor the command is issued. Unlike the other SEV commands,
+userspace must issue KVM_MEMORY_ENCRYPT_OP on a vCPU file descriptor. The
+capability is reported as ``KVM_CAP_SNP_VCPU_STATE``.
+
+Parameters (in/out): struct kvm_sev_snp_vcpu_state
+
+Returns: 0 on success, -negative on error
+
+::
+
+ #define KVM_SEV_SNP_VCPU_STATE_VMSA_VALID _BITULL(0)
+ #define KVM_SEV_SNP_VCPU_STATE_GHCB_VALID _BITULL(1)
+
+ struct kvm_sev_snp_vcpu_state {
+ __u64 valid_fields;
+ __u64 vmsa_gpa;
+ __u64 ghcb_gpa;
+ __u64 pad[5]; /* Must be zero */
+ };
+
+``KVM_SEV_SNP_GET_VCPU_STATE`` returns the current addresses and sets the
+corresponding bit in ``valid_fields`` for each valid address.
+
+``KVM_SEV_SNP_SET_VCPU_STATE`` sets addresses whose validity bits are present
+and invalidates addresses whose bits are absent. The command must be issued
+after launch start and before KVM_SEV_SNP_LAUNCH_FINISH, and the VM must have
+enabled ``KVM_CAP_SNP_DIRECT_VMSA``. A valid VMSA GPA must be backed by
+guest_memfd and populated. The GPA must be 4-KiB aligned. A valid GHCB
+address is copied without inspecting its backing page. Nonzero reserved
+fields or unknown validity bits are rejected.
+
+``KVM_CAP_SNP_DIRECT_VMSA`` is a VM-scoped capability that selects direct-VMSA
+mode. Userspace enables it with ``KVM_ENABLE_CAP`` on an SNP VM before
+creating any vCPUs. ``flags`` and all elements of ``args`` must be zero.
+Enabling the capability on a non-SNP VM or after creating a vCPU is rejected.
+
+In direct-VMSA mode, KVM does not allocate a KVM-owned VMSA when a vCPU is
+created and does not generate or measure one at launch finish. All launch
+VMSAs are owned and supplied by userspace. Valid VMSAs selected with
+``KVM_SEV_SNP_SET_VCPU_STATE`` are preserved, while vCPUs without a valid VMSA
+have no runnable VMSA until the guest uses SNP AP creation to supply one. If
+the capability is not enabled, launch finish retains the legacy behavior of
+generating and measuring a KVM-owned VMSA for every vCPU. VMSAs that were
+measured but not selected remain ordinary valid pages in the initial image.
+
+Direct VMSAs make the launch measurement independent of KVM's selected VMSA
+GPA and of the configured vCPU count. This gives VMMs a stable launch
+measurement across hypervisors.
+
+21. KVM_SEV_SNP_LAUNCH_FINISH
-----------------------------
After completion of the SNP guest launch flow, the KVM_SEV_SNP_LAUNCH_FINISH
@@ -572,7 +634,7 @@ Returns: 0 on success, -negative on error
See SNP_LAUNCH_FINISH in the SEV-SNP specification [snp-fw-abi]_ for further
details on the input parameters in ``struct kvm_sev_snp_launch_finish``.
-21. KVM_SEV_SNP_ENABLE_REQ_CERTS
+22. KVM_SEV_SNP_ENABLE_REQ_CERTS
--------------------------------
The KVM_SEV_SNP_ENABLE_REQ_CERTS command will configure KVM to exit to
diff --git a/arch/x86/include/asm/kvm-x86-ops.h b/arch/x86/include/asm/kvm-x86-ops.h
index e213c9ae3e301..a48259696ca6e 100644
--- a/arch/x86/include/asm/kvm-x86-ops.h
+++ b/arch/x86/include/asm/kvm-x86-ops.h
@@ -23,6 +23,7 @@ KVM_X86_OP(vcpu_after_set_cpuid)
KVM_X86_OP(vm_init)
KVM_X86_OP_OPTIONAL(vm_destroy)
KVM_X86_OP_OPTIONAL(vm_pre_destroy)
+KVM_X86_OP_OPTIONAL(enable_vm_cap)
KVM_X86_OP_OPTIONAL_RET0(vcpu_precreate)
KVM_X86_OP(vcpu_create)
KVM_X86_OP(vcpu_free)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 683bb8bf43a94..3bfc02bb79984 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1519,6 +1519,7 @@ struct kvm_x86_ops {
int (*vm_init)(struct kvm *kvm);
void (*vm_destroy)(struct kvm *kvm);
void (*vm_pre_destroy)(struct kvm *kvm);
+ int (*enable_vm_cap)(struct kvm *kvm, struct kvm_enable_cap *cap);
/* Create, but do not attach this VCPU */
int (*vcpu_precreate)(struct kvm *kvm);
diff --git a/arch/x86/include/asm/sev-common.h b/arch/x86/include/asm/sev-common.h
index 01a6e4dbe4235..c041f85dfd5c8 100644
--- a/arch/x86/include/asm/sev-common.h
+++ b/arch/x86/include/asm/sev-common.h
@@ -136,6 +136,7 @@ enum psc_op {
#define GHCB_HV_FT_SNP BIT_ULL(0)
#define GHCB_HV_FT_SNP_AP_CREATION BIT_ULL(1)
+#define GHCB_HV_FT_APIC_ID_LIST BIT_ULL(4)
#define GHCB_HV_FT_SNP_MULTI_VMPL BIT_ULL(5)
/*
diff --git a/arch/x86/include/uapi/asm/kvm.h b/arch/x86/include/uapi/asm/kvm.h
index 1585ec8040666..5ae041e617b82 100644
--- a/arch/x86/include/uapi/asm/kvm.h
+++ b/arch/x86/include/uapi/asm/kvm.h
@@ -748,6 +748,8 @@ enum sev_cmd_id {
KVM_SEV_SNP_LAUNCH_UPDATE,
KVM_SEV_SNP_LAUNCH_FINISH,
KVM_SEV_SNP_ENABLE_REQ_CERTS,
+ KVM_SEV_SNP_GET_VCPU_STATE,
+ KVM_SEV_SNP_SET_VCPU_STATE,
KVM_SEV_NR_MAX,
};
@@ -887,6 +889,7 @@ struct kvm_sev_snp_launch_start {
/* Kept in sync with firmware values for simplicity. */
#define KVM_SEV_PAGE_TYPE_INVALID 0x0
#define KVM_SEV_SNP_PAGE_TYPE_NORMAL 0x1
+#define KVM_SEV_SNP_PAGE_TYPE_VMSA 0x2
#define KVM_SEV_SNP_PAGE_TYPE_ZERO 0x3
#define KVM_SEV_SNP_PAGE_TYPE_UNMEASURED 0x4
#define KVM_SEV_SNP_PAGE_TYPE_SECRETS 0x5
@@ -903,6 +906,16 @@ struct kvm_sev_snp_launch_update {
__u64 pad2[4];
};
+#define KVM_SEV_SNP_VCPU_STATE_VMSA_VALID _BITULL(0)
+#define KVM_SEV_SNP_VCPU_STATE_GHCB_VALID _BITULL(1)
+
+struct kvm_sev_snp_vcpu_state {
+ __u64 valid_fields;
+ __u64 vmsa_gpa;
+ __u64 ghcb_gpa;
+ __u64 pad[5];
+};
+
#define KVM_SEV_SNP_ID_BLOCK_SIZE 96
#define KVM_SEV_SNP_ID_AUTH_SIZE 4096
#define KVM_SEV_SNP_FINISH_DATA_SIZE 32
diff --git a/arch/x86/include/uapi/asm/svm.h b/arch/x86/include/uapi/asm/svm.h
index 010a45c9f6147..05e5e31e23e18 100644
--- a/arch/x86/include/uapi/asm/svm.h
+++ b/arch/x86/include/uapi/asm/svm.h
@@ -117,6 +117,7 @@
#define SVM_VMGEXIT_AP_CREATE_ON_INIT 0
#define SVM_VMGEXIT_AP_CREATE 1
#define SVM_VMGEXIT_AP_DESTROY 2
+#define SVM_VMGEXIT_GET_APIC_IDS 0x80000017ull
#define SVM_VMGEXIT_SNP_RUN_VMPL 0x80000018ull
#define SVM_VMGEXIT_SAVIC 0x8000001aull
#define SVM_VMGEXIT_SAVIC_REGISTER_GPA 0
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 5705723f1f412..3e0727e251a93 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -40,7 +40,9 @@
#define GHCB_VERSION_MAX 2ULL
#define GHCB_VERSION_MIN 1ULL
-#define GHCB_HV_FT_SUPPORTED (GHCB_HV_FT_SNP | GHCB_HV_FT_SNP_AP_CREATION)
+#define GHCB_HV_FT_SUPPORTED (GHCB_HV_FT_SNP | \
+ GHCB_HV_FT_SNP_AP_CREATION | \
+ GHCB_HV_FT_APIC_ID_LIST)
/*
* The GHCB spec essentially states that all non-zero error codes other than
@@ -2344,6 +2346,7 @@ struct sev_gmem_populate_args {
__u8 type;
int sev_fd;
int fw_error;
+ bool vmsa_invalid;
};
static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
@@ -2367,11 +2370,20 @@ static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
if (src_page) {
void *src_vaddr = kmap_local_page(src_page);
void *dst_vaddr = kmap_local_pfn(pfn);
+ struct sev_es_save_area *vmsa = dst_vaddr;
memcpy(dst_vaddr, src_vaddr, PAGE_SIZE);
+ if (sev_populate_args->type == KVM_SEV_SNP_PAGE_TYPE_VMSA &&
+ (vmsa->vmpl || vmsa->sev_features != sev->vmsa_features)) {
+ sev_populate_args->vmsa_invalid = true;
+ ret = -EINVAL;
+ }
kunmap_local(dst_vaddr);
kunmap_local(src_vaddr);
+
+ if (ret)
+ goto out;
}
ret = rmp_make_private(pfn, gfn << PAGE_SHIFT, PG_LEVEL_4K,
@@ -2437,7 +2449,10 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
params.gfn_start, params.len, params.type, params.flags);
if (!params.len || !PAGE_ALIGNED(params.len) || params.flags ||
+ (params.type == KVM_SEV_SNP_PAGE_TYPE_VMSA &&
+ (!sev->snp_direct_vmsa || params.len != PAGE_SIZE)) ||
(params.type != KVM_SEV_SNP_PAGE_TYPE_NORMAL &&
+ params.type != KVM_SEV_SNP_PAGE_TYPE_VMSA &&
params.type != KVM_SEV_SNP_PAGE_TYPE_ZERO &&
params.type != KVM_SEV_SNP_PAGE_TYPE_UNMEASURED &&
params.type != KVM_SEV_SNP_PAGE_TYPE_SECRETS &&
@@ -2485,6 +2500,9 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
params.type == KVM_SEV_SNP_PAGE_TYPE_CPUID,
sev_gmem_post_populate, &sev_populate_args);
if (count < 0) {
+ if (sev_populate_args.vmsa_invalid)
+ return -EINVAL;
+
argp->error = sev_populate_args.fw_error;
pr_debug("%s: kvm_gmem_populate failed, ret %ld (fw_error %d)\n",
__func__, count, argp->error);
@@ -2502,6 +2520,8 @@ static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)
return 0;
}
+static int sev_snp_install_guest_vmsa(struct vcpu_svm *svm, gpa_t gpa);
+
static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
{
struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
@@ -2522,8 +2542,15 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
kvm_for_each_vcpu(i, vcpu, kvm) {
struct vcpu_svm *svm = to_svm(vcpu);
- u64 pfn = __pa(svm->sev_es.vmsa) >> PAGE_SHIFT;
+ u64 pfn;
+
+ if (sev->snp_direct_vmsa) {
+ if (!svm->sev_es.snp_has_guest_vmsa)
+ svm->vmcb->control.vmsa_pa = INVALID_PAGE;
+ goto protect_vcpu;
+ }
+ pfn = __pa(svm->sev_es.vmsa) >> PAGE_SHIFT;
ret = sev_es_sync_vmsa(svm);
if (ret)
goto out;
@@ -2543,6 +2570,7 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
goto out;
}
+protect_vcpu:
svm->vcpu.arch.guest_state_protected = true;
/*
* SEV-ES (and thus SNP) guest mandates LBR Virtualization to
@@ -2559,6 +2587,89 @@ static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)
return ret;
}
+static int snp_get_vcpu_state(struct kvm_vcpu *vcpu,
+ struct kvm_sev_cmd *argp)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+ struct kvm *kvm = vcpu->kvm;
+ struct kvm_sev_snp_vcpu_state state = {};
+
+ if (!is_sev_snp_guest(vcpu))
+ return -ENOTTY;
+ if (!to_kvm_sev_info(kvm)->snp_context)
+ return -EINVAL;
+
+ guard(mutex)(&svm->sev_es.snp_vmsa_mutex);
+
+ if (VALID_PAGE(svm->sev_es.snp_guest_vmsa_gpa) &&
+ VALID_PAGE(svm->vmcb->control.vmsa_pa)) {
+ state.vmsa_gpa = svm->sev_es.snp_guest_vmsa_gpa;
+ state.valid_fields |= KVM_SEV_SNP_VCPU_STATE_VMSA_VALID;
+ }
+
+ if (VALID_PAGE(svm->vmcb->control.ghcb_gpa)) {
+ state.ghcb_gpa = svm->vmcb->control.ghcb_gpa;
+ state.valid_fields |= KVM_SEV_SNP_VCPU_STATE_GHCB_VALID;
+ }
+
+ if (copy_to_user(u64_to_user_ptr(argp->data), &state, sizeof(state)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int snp_set_vcpu_state(struct kvm_vcpu *vcpu,
+ struct kvm_sev_cmd *argp)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+ struct kvm *kvm = vcpu->kvm;
+ struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
+ struct kvm_sev_snp_vcpu_state state;
+ int ret;
+
+ if (!is_sev_snp_guest(vcpu))
+ return -ENOTTY;
+ if (!sev->snp_direct_vmsa)
+ return -EINVAL;
+ if (!sev->snp_context || kvm->arch.pre_fault_allowed)
+ return -EINVAL;
+
+ if (copy_from_user(&state, u64_to_user_ptr(argp->data), sizeof(state)))
+ return -EFAULT;
+
+ if (memchr_inv(state.pad, 0, sizeof(state.pad)) ||
+ state.valid_fields & ~(KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |
+ KVM_SEV_SNP_VCPU_STATE_GHCB_VALID))
+ return -EINVAL;
+
+ if (state.valid_fields & KVM_SEV_SNP_VCPU_STATE_VMSA_VALID) {
+ if (!PAGE_ALIGNED(state.vmsa_gpa) ||
+ !page_address_valid(vcpu, state.vmsa_gpa) ||
+ IS_ALIGNED(state.vmsa_gpa, PMD_SIZE))
+ return -EINVAL;
+ }
+
+ guard(mutex)(&svm->sev_es.snp_vmsa_mutex);
+
+ if (state.valid_fields & KVM_SEV_SNP_VCPU_STATE_VMSA_VALID) {
+ ret = sev_snp_install_guest_vmsa(svm, state.vmsa_gpa);
+ if (ret)
+ return ret;
+ } else {
+ svm->sev_es.snp_has_guest_vmsa = true;
+ svm->sev_es.snp_guest_vmsa_gpa = INVALID_PAGE;
+ svm->vmcb->control.vmsa_pa = INVALID_PAGE;
+ }
+
+ if (state.valid_fields & KVM_SEV_SNP_VCPU_STATE_GHCB_VALID)
+ svm->vmcb->control.ghcb_gpa = state.ghcb_gpa;
+ else
+ svm->vmcb->control.ghcb_gpa = INVALID_PAGE;
+
+ vmcb_mark_all_dirty(svm->vmcb);
+ return 0;
+}
+
static int snp_launch_finish(struct kvm *kvm, struct kvm_sev_cmd *argp)
{
struct kvm_sev_info *sev = to_kvm_sev_info(kvm);
@@ -2755,6 +2866,35 @@ int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp)
return r;
}
+int sev_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp)
+{
+ struct kvm_sev_cmd sev_cmd;
+ int ret;
+
+ if (!sev_enabled)
+ return -ENOTTY;
+ if (!argp)
+ return -EINVAL;
+ if (copy_from_user(&sev_cmd, argp, sizeof(sev_cmd)))
+ return -EFAULT;
+
+ switch (sev_cmd.id) {
+ case KVM_SEV_SNP_GET_VCPU_STATE:
+ ret = snp_get_vcpu_state(vcpu, &sev_cmd);
+ break;
+ case KVM_SEV_SNP_SET_VCPU_STATE:
+ ret = snp_set_vcpu_state(vcpu, &sev_cmd);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (copy_to_user(argp, &sev_cmd, sizeof(sev_cmd)))
+ return -EFAULT;
+
+ return ret;
+}
+
int sev_mem_enc_register_region(struct kvm *kvm,
struct kvm_enc_region *range)
{
@@ -3493,7 +3633,8 @@ static bool sev_es_are_required_ghcb_fields_valid(struct vcpu_svm *svm)
case SVM_VMGEXIT_AP_CREATION:
return kvm_ghcb_rax_is_valid(svm) ||
lower_32_bits(control->exit_info_1) == SVM_VMGEXIT_AP_DESTROY;
- break;
+ case SVM_VMGEXIT_GET_APIC_IDS:
+ return kvm_ghcb_rax_is_valid(svm);
case SVM_VMGEXIT_MMIO_READ:
case SVM_VMGEXIT_MMIO_WRITE:
case SVM_VMGEXIT_PSC:
@@ -3556,6 +3697,9 @@ void sev_free_vcpu(struct kvm_vcpu *vcpu)
* a guest-owned page. Transition the page to hypervisor state before
* releasing it back to the system.
*/
+ if (!svm->sev_es.vmsa)
+ goto skip_vmsa_free;
+
if (is_sev_snp_guest(vcpu)) {
u64 pfn = __pa(svm->sev_es.vmsa) >> PAGE_SHIFT;
@@ -4025,6 +4169,56 @@ static int snp_begin_psc(struct vcpu_svm *svm)
return snp_do_psc(svm);
}
+/*
+ * Install a guest-owned VMSA. The caller must serialize against AP creation
+ * and destruction with snp_vmsa_mutex.
+ */
+static int sev_snp_install_guest_vmsa(struct vcpu_svm *svm, gpa_t gpa)
+{
+ struct kvm *kvm = svm->vcpu.kvm;
+ struct kvm_memory_slot *slot;
+ unsigned long mmu_seq;
+ struct page *page;
+ kvm_pfn_t pfn;
+ gfn_t gfn;
+ int idx;
+ int ret;
+
+ lockdep_assert_held(&svm->sev_es.snp_vmsa_mutex);
+
+ gfn = gpa_to_gfn(gpa);
+ idx = srcu_read_lock(&kvm->srcu);
+ slot = gfn_to_memslot(kvm, gfn);
+ if (!slot) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ mmu_seq = kvm->mmu_invalidate_seq;
+ /* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */
+ smp_rmb();
+
+ /* Guest-owned VMSAs are backed by guest_memfd private memory. */
+ ret = kvm_gmem_get_pfn(kvm, slot, gfn, &pfn, &page, NULL);
+ if (ret)
+ goto out_unlock;
+
+ read_lock(&kvm->mmu_lock);
+ if (mmu_invalidate_retry_gfn(kvm, mmu_seq, gfn)) {
+ ret = -EAGAIN;
+ } else {
+ svm->sev_es.snp_has_guest_vmsa = true;
+ WRITE_ONCE(svm->sev_es.snp_guest_vmsa_gpa, gpa);
+ svm->vmcb->control.vmsa_pa = pfn_to_hpa(pfn);
+ }
+ read_unlock(&kvm->mmu_lock);
+
+ kvm_release_page_clean(page);
+out_unlock:
+ srcu_read_unlock(&kvm->srcu, idx);
+ return ret;
+}
+
static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
{
struct vcpu_svm *svm = to_svm(vcpu);
@@ -4034,6 +4228,7 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
unsigned long mmu_seq;
struct page *page;
kvm_pfn_t pfn;
+ int idx;
lockdep_assert_held(&svm->sev_es.snp_vmsa_mutex);
@@ -4052,22 +4247,19 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
vmcb_mark_all_dirty(svm->vmcb);
/*
- * From this point forward, the VMSA will always be a guest-mapped page
- * rather than the initial one allocated by KVM in svm->sev_es.vmsa. In
- * theory, svm->sev_es.vmsa could be free'd and cleaned up here, but
- * that involves cleanups like flushing caches, which would ideally be
- * handled during teardown rather than guest boot. Deferring that also
- * allows the existing logic for SEV-ES VMSAs to be re-used with
- * minimal SNP-specific changes.
+ * From this point forward, the VMSA will always be a guest-mapped page.
+ * If KVM allocated an initial VMSA, keep it until teardown to defer
+ * cache flushing and other cleanup out of the guest boot path.
*/
svm->sev_es.snp_has_guest_vmsa = true;
if (!VALID_PAGE(gpa))
return;
- slot = gfn_to_memslot(vcpu->kvm, gfn);
+ idx = srcu_read_lock(&kvm->srcu);
+ slot = gfn_to_memslot(kvm, gfn);
if (!slot)
- return;
+ goto out_unlock;
mmu_seq = kvm->mmu_invalidate_seq;
smp_rmb();
@@ -4076,8 +4268,8 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
* The new VMSA will be private memory guest memory, so retrieve the
* PFN from the gmem backend.
*/
- if (kvm_gmem_get_pfn(vcpu->kvm, slot, gfn, &pfn, &page, NULL))
- return;
+ if (kvm_gmem_get_pfn(kvm, slot, gfn, &pfn, &page, NULL))
+ goto out_unlock;
read_lock(&kvm->mmu_lock);
/*
@@ -4094,6 +4286,8 @@ static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa)
read_unlock(&kvm->mmu_lock);
kvm_release_page_clean(page);
+out_unlock:
+ srcu_read_unlock(&kvm->srcu, idx);
}
/*
@@ -4215,6 +4409,63 @@ static int sev_snp_ap_creation(struct vcpu_svm *svm)
return 0;
}
+struct sev_apic_id_desc {
+ u32 num_entries;
+ u32 apic_ids[];
+};
+
+static int sev_snp_get_apic_ids(struct vcpu_svm *svm)
+{
+ struct kvm_vcpu *vcpu = &svm->vcpu;
+ struct kvm *kvm = vcpu->kvm;
+ struct sev_apic_id_desc *desc;
+ unsigned int nr_vcpus, size;
+ unsigned int i;
+ gpa_t gpa, end_gpa;
+ u64 pages;
+
+ nr_vcpus = atomic_read(&kvm->online_vcpus);
+ size = sizeof(*desc) + (nr_vcpus * sizeof(desc->apic_ids[0]));
+
+ pages = vcpu->arch.regs[VCPU_REGS_RAX];
+ if (pages < PFN_UP(size)) {
+ vcpu->arch.regs[VCPU_REGS_RAX] = PFN_UP(size);
+ return 1;
+ }
+
+ gpa = svm->vmcb->control.exit_info_1;
+ if (!PAGE_ALIGNED(gpa) ||
+ check_add_overflow(gpa, size - 1, &end_gpa) ||
+ !page_address_valid(vcpu, gpa) ||
+ !page_address_valid(vcpu, end_gpa & PAGE_MASK))
+ goto invalid_buffer;
+
+ desc = kvzalloc(size, GFP_KERNEL_ACCOUNT);
+ if (!desc)
+ return -ENOMEM;
+
+ desc->num_entries = nr_vcpus;
+ for (i = 0; i < nr_vcpus; i++) {
+ struct kvm_vcpu *listed_vcpu = kvm_get_vcpu(kvm, i);
+
+ if (WARN_ON_ONCE(!listed_vcpu))
+ goto invalid_buffer_free;
+ desc->apic_ids[i] = listed_vcpu->vcpu_id;
+ }
+
+ if (kvm_write_guest(kvm, gpa, desc, size))
+ goto invalid_buffer_free;
+
+ kvfree(desc);
+ return 1;
+
+invalid_buffer_free:
+ kvfree(desc);
+invalid_buffer:
+ svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);
+ return 1;
+}
+
static int snp_handle_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t resp_gpa)
{
struct sev_data_snp_guest_request data = {0};
@@ -4494,6 +4745,7 @@ static bool is_snp_only_vmgexit(u64 exit_code)
{
switch (exit_code) {
case SVM_VMGEXIT_AP_CREATION:
+ case SVM_VMGEXIT_GET_APIC_IDS:
case SVM_VMGEXIT_GUEST_REQUEST:
case SVM_VMGEXIT_EXT_GUEST_REQUEST:
case SVM_VMGEXIT_PSC:
@@ -4665,6 +4917,8 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
if (sev_snp_ap_creation(svm))
svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT);
return 1;
+ case SVM_VMGEXIT_GET_APIC_IDS:
+ return sev_snp_get_apic_ids(svm);
case SVM_VMGEXIT_GUEST_REQUEST:
case SVM_VMGEXIT_EXT_GUEST_REQUEST:
if (!PAGE_ALIGNED(control->exit_info_1) ||
@@ -4862,12 +5116,15 @@ void sev_init_vmcb(struct vcpu_svm *svm, bool init_event)
int sev_vcpu_create(struct kvm_vcpu *vcpu)
{
struct vcpu_svm *svm = to_svm(vcpu);
+ struct kvm_sev_info *sev = to_kvm_sev_info(vcpu->kvm);
struct page *vmsa_page;
mutex_init(&svm->sev_es.snp_vmsa_mutex);
if (!is_sev_es_guest(vcpu))
return 0;
+ if (is_sev_snp_guest(vcpu) && sev->snp_direct_vmsa)
+ goto init_vmsa_state;
/*
* SEV-ES guests require a separate (from the VMCB) VMSA page used to
@@ -4878,6 +5135,8 @@ int sev_vcpu_create(struct kvm_vcpu *vcpu)
return -ENOMEM;
svm->sev_es.vmsa = page_address(vmsa_page);
+
+init_vmsa_state:
svm->sev_es.snp_pending_vmsa_gpa = INVALID_PAGE;
svm->sev_es.snp_guest_vmsa_gpa = INVALID_PAGE;
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 7d59d301e1e54..378d94409134f 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5325,6 +5325,27 @@ static void *svm_alloc_apic_backing_page(struct kvm_vcpu *vcpu)
return page_address(page);
}
+static int svm_enable_vm_cap(struct kvm *kvm, struct kvm_enable_cap *cap)
+{
+ switch (cap->cap) {
+#ifdef CONFIG_KVM_AMD_SEV
+ case KVM_CAP_SNP_DIRECT_VMSA:
+ if (memchr_inv(cap->args, 0, sizeof(cap->args)) ||
+ kvm->arch.vm_type != KVM_X86_SNP_VM)
+ return -EINVAL;
+
+ guard(mutex)(&kvm->lock);
+ if (kvm->created_vcpus)
+ return -EINVAL;
+
+ to_kvm_sev_info(kvm)->snp_direct_vmsa = true;
+ return 0;
+#endif
+ default:
+ return -EINVAL;
+ }
+}
+
struct kvm_x86_ops svm_x86_ops __initdata = {
.name = KBUILD_MODNAME,
@@ -5345,6 +5366,7 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
.vm_init = svm_vm_init,
.vm_pre_destroy = avic_vm_pre_destroy,
.vm_destroy = svm_vm_destroy,
+ .enable_vm_cap = svm_enable_vm_cap,
.prepare_switch_to_guest = svm_prepare_switch_to_guest,
.vcpu_load = svm_vcpu_load,
@@ -5444,6 +5466,7 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
.vcpu_needs_initialization = sev_vcpu_needs_initialization,
.dev_get_attr = sev_dev_get_attr,
.mem_enc_ioctl = sev_mem_enc_ioctl,
+ .vcpu_mem_enc_ioctl = sev_vcpu_mem_enc_ioctl,
.mem_enc_register_region = sev_mem_enc_register_region,
.mem_enc_unregister_region = sev_mem_enc_unregister_region,
.guest_memory_reclaimed = sev_guest_memory_reclaimed,
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index e958943b81627..c920c797b7ecf 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -121,6 +121,7 @@ struct kvm_sev_info {
struct mutex guest_req_mutex; /* Must acquire before using bounce buffers */
cpumask_var_t have_run_cpus; /* CPUs that have done VMRUN for this VM. */
bool snp_certs_enabled; /* SNP certificate-fetching support. */
+ bool snp_direct_vmsa; /* Userspace provides and measures VMSA pages. */
};
#endif
@@ -982,6 +983,7 @@ void sev_es_unmap_ghcb(struct vcpu_svm *svm);
#ifdef CONFIG_KVM_AMD_SEV
bool sev_vcpu_needs_initialization(struct kvm_vcpu *vcpu);
int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp);
+int sev_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp);
int sev_mem_enc_register_region(struct kvm *kvm,
struct kvm_enc_region *range);
int sev_mem_enc_unregister_region(struct kvm *kvm,
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 79468ddfe4736..232507ae504a6 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2404,6 +2404,10 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_VM_TYPES:
r = kvm_caps.supported_vm_types;
break;
+ case KVM_CAP_SNP_VCPU_STATE:
+ case KVM_CAP_SNP_DIRECT_VMSA:
+ r = !!(kvm_caps.supported_vm_types & BIT(KVM_X86_SNP_VM));
+ break;
case KVM_CAP_READONLY_MEM:
r = kvm ? kvm_arch_has_readonly_mem(kvm) : 1;
break;
@@ -4212,6 +4216,8 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
}
default:
r = -EINVAL;
+ if (kvm_x86_ops.enable_vm_cap)
+ r = kvm_x86_call(enable_vm_cap)(kvm, cap);
break;
}
return r;
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index ac2d77d149635..8c6765f78c1c1 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -999,6 +999,8 @@ struct kvm_enable_cap {
#define KVM_CAP_S390_HPAGE_2G 249
#define KVM_CAP_PPC_COMPAT_CAPS 250
#define KVM_CAP_ARM_PMU_V3_STRICT 251
+#define KVM_CAP_SNP_DIRECT_VMSA 252
+#define KVM_CAP_SNP_VCPU_STATE 253
struct kvm_irq_routing_irqchip {
__u32 irqchip;
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39e..4cf0a8297b041 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -149,6 +149,8 @@ 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_snp_apic_id_test
+TEST_GEN_PROGS_x86 += x86/sev_snp_direct_vmsa_test
TEST_GEN_PROGS_x86 += x86/sev_smoke_test
TEST_GEN_PROGS_x86 += x86/amx_test
TEST_GEN_PROGS_x86 += x86/max_vcpuid_cap_test
diff --git a/tools/testing/selftests/kvm/include/x86/sev.h b/tools/testing/selftests/kvm/include/x86/sev.h
index dec383e59a47e..0f6c92b802eea 100644
--- a/tools/testing/selftests/kvm/include/x86/sev.h
+++ b/tools/testing/selftests/kvm/include/x86/sev.h
@@ -100,6 +100,30 @@ static inline u64 snp_default_policy(void)
__TEST_ASSERT_VM_VCPU_IOCTL(!ret, #cmd, ret, vm); \
})
+static inline int __vcpu_sev_ioctl(struct kvm_vcpu *vcpu, u32 cmd, void *arg)
+{
+ union {
+ struct kvm_sev_cmd c;
+ unsigned long raw;
+ } sev_cmd = { .c = {
+ .id = cmd,
+ .data = (u64)arg,
+ .sev_fd = vcpu->vm->arch.sev_fd,
+ } };
+ int ret;
+
+ ret = __vcpu_ioctl(vcpu, KVM_MEMORY_ENCRYPT_OP, &sev_cmd.raw);
+ return ret ?: sev_cmd.c.error;
+}
+
+#define vcpu_sev_ioctl(vcpu, cmd, arg) \
+({ \
+ struct kvm_vcpu *__vcpu = (vcpu); \
+ int ret = __vcpu_sev_ioctl(__vcpu, cmd, arg); \
+ \
+ __TEST_ASSERT_VM_VCPU_IOCTL(!ret, #cmd, ret, __vcpu->vm); \
+})
+
void sev_vm_init(struct kvm_vm *vm);
void sev_es_vm_init(struct kvm_vm *vm);
void snp_vm_init(struct kvm_vm *vm);
@@ -144,6 +168,31 @@ 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 snp_launch_update_vmsa(struct kvm_vm *vm, gpa_t gpa,
+ void *vmsa)
+{
+ vm_mem_set_private(vm, gpa, PAGE_SIZE);
+ snp_launch_update_data(vm, gpa, (u64)vmsa, PAGE_SIZE,
+ KVM_SEV_SNP_PAGE_TYPE_VMSA);
+}
+
+static inline void snp_get_vcpu_state(struct kvm_vcpu *vcpu,
+ struct kvm_sev_snp_vcpu_state *state)
+{
+ vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_GET_VCPU_STATE, state);
+}
+
+static inline void snp_set_vcpu_state(struct kvm_vcpu *vcpu, gpa_t vmsa_gpa)
+{
+ struct kvm_sev_snp_vcpu_state state = {
+ .vmsa_gpa = vmsa_gpa,
+ .valid_fields = KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |
+ KVM_SEV_SNP_VCPU_STATE_GHCB_VALID,
+ };
+
+ vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, &state);
+}
+
static inline void sev_dbg_crypt_memory(struct kvm_vm *vm, unsigned int cmd,
void *dst, void *src, unsigned int len)
{
diff --git a/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c b/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c
new file mode 100644
index 0000000000000..8ad9c15091c2d
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/sev_snp_apic_id_test.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <stddef.h>
+#include <stdint.h>
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "sev.h"
+#include "svm_util.h"
+
+#define GHCB_SAVE_RAX_OFFSET 0x1f8
+#define GHCB_SAVE_SW_EXIT_CODE_OFFSET 0x390
+#define GHCB_SAVE_SW_EXIT_INFO_1_OFFSET 0x398
+#define GHCB_SAVE_SW_EXIT_INFO_2_OFFSET 0x3a0
+#define GHCB_SAVE_VALID_BITMAP_OFFSET 0x3f0
+
+#define GHCB_MSR_REG_GPA_REQ 0x012
+#define GHCB_MSR_REG_GPA_RESP 0x013
+#define GHCB_MSR_INFO_MASK GENMASK_ULL(11, 0)
+
+#define GHCB_HV_RESP_MALFORMED_INPUT 2
+#define GHCB_ERR_MISSING_INPUT 4
+#define GHCB_ERR_INVALID_INPUT 5
+
+struct apic_id_desc {
+ u32 nr_entries;
+ u32 apic_ids[];
+};
+
+struct apic_id_results {
+ u64 missing_info1;
+ u64 missing_info2;
+ u64 zero_info1;
+ u64 zero_info2;
+ u64 zero_rax;
+ u64 invalid_info1;
+ u64 invalid_info2;
+ u64 valid_info1;
+ u64 valid_info2;
+ u32 nr_entries;
+ u32 first_apic_id;
+ u32 last_apic_id;
+};
+
+static void ghcb_set_field(void *ghcb, size_t offset, u64 value, bool valid)
+{
+ u8 *valid_bitmap = ghcb + GHCB_SAVE_VALID_BITMAP_OFFSET;
+
+ *(u64 *)(ghcb + offset) = value;
+ if (valid)
+ valid_bitmap[(offset / sizeof(u64)) / 8] |=
+ BIT((offset / sizeof(u64)) % 8);
+}
+
+static u64 ghcb_get_field(void *ghcb, size_t offset)
+{
+ return *(u64 *)(ghcb + offset);
+}
+
+static void do_get_apic_ids(void *ghcb, gpa_t buffer_gpa, u64 pages,
+ bool rax_valid)
+{
+ memset(ghcb, 0, PAGE_SIZE);
+ ghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_CODE_OFFSET,
+ SVM_VMGEXIT_GET_APIC_IDS, true);
+ ghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET, buffer_gpa, true);
+ ghcb_set_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET, 0, true);
+ ghcb_set_field(ghcb, GHCB_SAVE_RAX_OFFSET, pages, rax_valid);
+ vmgexit();
+}
+
+static void guest_code(void *ghcb, gpa_t ghcb_gpa, void *list,
+ gpa_t list_gpa, struct apic_id_results *results,
+ u64 expected_vcpus)
+{
+ struct apic_id_desc *desc = list;
+ u64 msr;
+
+ wrmsr(MSR_AMD64_SEV_ES_GHCB,
+ (ghcb_gpa >> PAGE_SHIFT) << PAGE_SHIFT | GHCB_MSR_REG_GPA_REQ);
+ vmgexit();
+ msr = rdmsr(MSR_AMD64_SEV_ES_GHCB);
+ if ((msr & GHCB_MSR_INFO_MASK) != GHCB_MSR_REG_GPA_RESP)
+ goto terminate;
+
+ wrmsr(MSR_AMD64_SEV_ES_GHCB, ghcb_gpa);
+
+ do_get_apic_ids(ghcb, list_gpa, 1, false);
+ results->missing_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+ results->missing_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+
+ do_get_apic_ids(ghcb, list_gpa, 0, true);
+ results->zero_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+ results->zero_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+ results->zero_rax = ghcb_get_field(ghcb, GHCB_SAVE_RAX_OFFSET);
+
+ do_get_apic_ids(ghcb, BIT_ULL(52), 2, true);
+ results->invalid_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+ results->invalid_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+
+ do_get_apic_ids(ghcb, list_gpa, 2, true);
+ results->valid_info1 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_1_OFFSET);
+ results->valid_info2 = ghcb_get_field(ghcb, GHCB_SAVE_SW_EXIT_INFO_2_OFFSET);
+ results->nr_entries = desc->nr_entries;
+ results->first_apic_id = desc->apic_ids[0];
+ results->last_apic_id = desc->apic_ids[expected_vcpus - 1];
+
+terminate:
+ wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
+ vmgexit();
+}
+
+static void run_apic_id_test(unsigned int nr_vcpus)
+{
+ struct apic_id_results *results;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ gva_t ghcb_gva, list_gva, results_gva;
+ gpa_t ghcb_gpa, list_gpa;
+ unsigned int i;
+
+ kvm_set_files_rlimit(nr_vcpus);
+ vm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_code, &vcpu);
+ for (i = 1; i < nr_vcpus; i++)
+ __vm_vcpu_add(vm, i);
+
+ ghcb_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ list_gva = vm_alloc_shared(vm, 2 * PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ results_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ ghcb_gpa = addr_gva2gpa(vm, ghcb_gva);
+ list_gpa = addr_gva2gpa(vm, list_gva);
+ results = addr_gva2hva(vm, results_gva);
+
+ vcpu_args_set(vcpu, 6, ghcb_gva, ghcb_gpa, list_gva, list_gpa,
+ results_gva, nr_vcpus);
+ memset(addr_gva2hva(vm, ghcb_gva), 0, PAGE_SIZE);
+ memset(addr_gva2hva(vm, list_gva), 0, 2 * PAGE_SIZE);
+ memset(results, 0, PAGE_SIZE);
+ vm_sev_launch(vm, snp_default_policy(), NULL);
+
+ vcpu_run(vcpu);
+ TEST_ASSERT_EQ(vcpu->run->exit_reason, KVM_EXIT_SYSTEM_EVENT);
+ TEST_ASSERT_EQ(vcpu->run->system_event.type, KVM_SYSTEM_EVENT_SEV_TERM);
+
+ TEST_ASSERT_EQ(results->missing_info1, GHCB_HV_RESP_MALFORMED_INPUT);
+ TEST_ASSERT_EQ(results->missing_info2, GHCB_ERR_MISSING_INPUT);
+ TEST_ASSERT_EQ(results->zero_info1, 0);
+ TEST_ASSERT_EQ(results->zero_info2, 0);
+ TEST_ASSERT_EQ(results->zero_rax,
+ DIV_ROUND_UP(sizeof(struct apic_id_desc) + nr_vcpus * sizeof(u32),
+ PAGE_SIZE));
+ TEST_ASSERT_EQ(results->invalid_info1, GHCB_HV_RESP_MALFORMED_INPUT);
+ TEST_ASSERT_EQ(results->invalid_info2, GHCB_ERR_INVALID_INPUT);
+ TEST_ASSERT_EQ(results->valid_info1, 0);
+ TEST_ASSERT_EQ(results->valid_info2, 0);
+ TEST_ASSERT_EQ(results->nr_entries, nr_vcpus);
+ TEST_ASSERT_EQ(results->first_apic_id, 0);
+ TEST_ASSERT_EQ(results->last_apic_id, nr_vcpus - 1);
+
+ kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int max_vcpus;
+
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SNP_VM));
+ run_apic_id_test(2);
+
+ /* 1024 IDs cross the one-page descriptor boundary. */
+ max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);
+ if (max_vcpus >= 1024)
+ run_apic_id_test(1024);
+ else
+ pr_info("Skipping vCPU-count boundary test (max vCPUs: %u)\n", max_vcpus);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c b/tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c
new file mode 100644
index 0000000000000..72eecf3853a32
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/sev_snp_direct_vmsa_test.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <errno.h>
+#include <stdlib.h>
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "sev.h"
+#include "svm_util.h"
+
+#define DIRECT_MARKER 0x444952454354564dULL
+#define LEGACY_MARKER 0x4c4547414359564dULL
+#define SNP_ACTIVE_SEV_FEATURE BIT_ULL(0)
+#define VMSA_PMD_SIZE BIT_ULL(21)
+#define VMSA_MIN_GPA (VMSA_PMD_SIZE + PAGE_SIZE)
+
+struct test_vmsa {
+ struct vmcb_seg es, cs, ss, ds, fs, gs;
+ struct vmcb_seg gdtr, ldtr, idtr, tr;
+ u64 pl0_ssp, pl1_ssp, pl2_ssp, pl3_ssp;
+ u64 u_cet;
+ u8 reserved_0xc8[2];
+ u8 vmpl;
+ u8 cpl;
+ u8 reserved_0xcc[4];
+ u64 efer;
+ u8 reserved_0xd8[104];
+ u64 xss;
+ u64 cr4, cr3, cr0, dr7, dr6, rflags, rip;
+ u64 dr0, dr1, dr2, dr3;
+ u64 dr0_addr_mask, dr1_addr_mask, dr2_addr_mask, dr3_addr_mask;
+ u8 reserved_0x1c0[24];
+ u64 rsp, s_cet, ssp, isst_addr, rax;
+ u64 star, lstar, cstar, sfmask, kernel_gs_base;
+ u64 sysenter_cs, sysenter_esp, sysenter_eip, cr2;
+ u8 reserved_0x248[32];
+ u64 g_pat, dbgctl, br_from, br_to, last_excp_from, last_excp_to;
+ u8 reserved_0x298[80];
+ u32 pkru, tsc_aux;
+ u64 tsc_scale, tsc_offset;
+ u8 reserved_0x300[8];
+ u64 rcx, rdx, rbx, reserved_0x320, rbp, rsi, rdi;
+ u64 r8, r9, r10, r11, r12, r13, r14, r15;
+ u8 reserved_0x380[16];
+ u64 guest_exit_info_1, guest_exit_info_2, guest_exit_int_info, guest_nrip;
+ u64 sev_features, vintr_ctrl, guest_exit_code, virtual_tom, tlb_id, pcpu_id;
+ u64 event_inj, xcr0;
+ u8 reserved_0x3f0[16];
+ u64 x87_dp;
+ u32 mxcsr;
+ u16 x87_ftw, x87_fsw, x87_fcw, x87_fop, x87_ds, x87_cs;
+ u64 x87_rip;
+ u8 fpreg_x87[80];
+ u8 fpreg_xmm[256];
+ u8 fpreg_ymm[256];
+} __packed;
+
+static_assert(offsetof(struct test_vmsa, vmpl) == 0xca);
+static_assert(offsetof(struct test_vmsa, rip) == 0x178);
+static_assert(offsetof(struct test_vmsa, sev_features) == 0x3b0);
+static_assert(offsetof(struct test_vmsa, xcr0) == 0x3e8);
+
+static void guest_direct_entry(u64 *marker)
+{
+ *marker = DIRECT_MARKER;
+ wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
+ vmgexit();
+}
+
+static void guest_legacy_entry(u64 *marker)
+{
+ *marker = LEGACY_MARKER;
+ wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
+ vmgexit();
+}
+
+static void copy_segment(struct vmcb_seg *dst, const struct kvm_segment *src)
+{
+ dst->selector = src->selector;
+ dst->base = src->base;
+ dst->limit = src->limit;
+ dst->attrib = src->type |
+ (src->s << SVM_SELECTOR_S_SHIFT) |
+ (src->dpl << SVM_SELECTOR_DPL_SHIFT) |
+ ((src->present && !src->unusable) << SVM_SELECTOR_P_SHIFT) |
+ (src->avl << SVM_SELECTOR_AVL_SHIFT) |
+ (src->l << SVM_SELECTOR_L_SHIFT) |
+ (src->db << SVM_SELECTOR_DB_SHIFT) |
+ (src->g << SVM_SELECTOR_G_SHIFT);
+}
+
+static void copy_dtable(struct vmcb_seg *dst, const struct kvm_dtable *src)
+{
+ dst->base = src->base;
+ dst->limit = src->limit;
+}
+
+static void prepare_vmsa(struct kvm_vcpu *vcpu, struct test_vmsa *vmsa,
+ void *entry)
+{
+ struct kvm_sregs sregs;
+ struct kvm_regs regs;
+
+ memset(vmsa, 0, PAGE_SIZE);
+ vcpu_sregs_get(vcpu, &sregs);
+ vcpu_regs_get(vcpu, ®s);
+
+ copy_segment(&vmsa->es, &sregs.es);
+ copy_segment(&vmsa->cs, &sregs.cs);
+ copy_segment(&vmsa->ss, &sregs.ss);
+ copy_segment(&vmsa->ds, &sregs.ds);
+ copy_segment(&vmsa->fs, &sregs.fs);
+ copy_segment(&vmsa->gs, &sregs.gs);
+ copy_dtable(&vmsa->gdtr, &sregs.gdt);
+ copy_segment(&vmsa->ldtr, &sregs.ldt);
+ copy_dtable(&vmsa->idtr, &sregs.idt);
+ copy_segment(&vmsa->tr, &sregs.tr);
+
+ vmsa->cpl = sregs.cs.dpl;
+ /*
+ * KVM_GET_SREGS exposes the guest-visible EFER and therefore omits
+ * SVME, which KVM normally adds to the hardware VMSA itself.
+ */
+ vmsa->efer = sregs.efer | EFER_SVME;
+ vmsa->cr4 = sregs.cr4;
+ vmsa->cr3 = sregs.cr3;
+ vmsa->cr0 = sregs.cr0;
+ vmsa->dr7 = 0x400;
+ vmsa->dr6 = 0xffff0ff0;
+ vmsa->rflags = regs.rflags;
+ vmsa->rip = (u64)entry;
+ vmsa->rsp = regs.rsp;
+ vmsa->rax = regs.rax;
+ vmsa->rcx = regs.rcx;
+ vmsa->rdx = regs.rdx;
+ vmsa->rbx = regs.rbx;
+ vmsa->rbp = regs.rbp;
+ vmsa->rsi = regs.rsi;
+ vmsa->rdi = regs.rdi;
+ vmsa->r8 = regs.r8;
+ vmsa->r9 = regs.r9;
+ vmsa->r10 = regs.r10;
+ vmsa->r11 = regs.r11;
+ vmsa->r12 = regs.r12;
+ vmsa->r13 = regs.r13;
+ vmsa->r14 = regs.r14;
+ vmsa->r15 = regs.r15;
+ vmsa->g_pat = 0x0007040600070406ULL;
+ vmsa->sev_features = SNP_ACTIVE_SEV_FEATURE;
+ vmsa->xcr0 = 1;
+ vmsa->mxcsr = 0x1f80;
+ vmsa->x87_fcw = 0x37f;
+}
+
+static void expect_launch_update_vmsa_error(struct kvm_vm *vm, gpa_t gpa,
+ void *vmsa, u64 size)
+{
+ struct kvm_sev_snp_launch_update update = {
+ .gfn_start = gpa >> PAGE_SHIFT,
+ .uaddr = (u64)vmsa,
+ .len = size,
+ .type = KVM_SEV_SNP_PAGE_TYPE_VMSA,
+ };
+
+ errno = 0;
+ TEST_ASSERT_EQ(__vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_UPDATE, &update), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+}
+
+static void exclude_from_normal_launch(struct kvm_vm *vm, gpa_t gpa,
+ unsigned int npages)
+{
+ struct userspace_mem_region *region;
+
+ region = memslot2region(vm, vm->memslots[MEM_REGION_TEST_DATA]);
+ sparsebit_clear_num(region->protected_phy_pages, gpa >> PAGE_SHIFT, npages);
+}
+
+static void assert_vcpu_terminated(struct kvm_vcpu *vcpu)
+{
+ vcpu_run(vcpu);
+ TEST_ASSERT_EQ(vcpu->run->exit_reason, KVM_EXIT_SYSTEM_EVENT);
+ TEST_ASSERT_EQ(vcpu->run->system_event.type, KVM_SYSTEM_EVENT_SEV_TERM);
+}
+
+static struct kvm_vm *create_direct_vmsa_vm(unsigned int nr_vcpus,
+ void *guest_code,
+ struct kvm_vcpu **vcpus)
+{
+ struct vm_shape shape = {
+ .mode = VM_MODE_DEFAULT,
+ .type = KVM_X86_SNP_VM,
+ };
+ struct kvm_vm *vm;
+ unsigned int i;
+
+ vm = __vm_create(shape, nr_vcpus, 0);
+ vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0);
+ for (i = 0; i < nr_vcpus; i++)
+ vcpus[i] = vm_vcpu_add(vm, i, guest_code);
+ kvm_arch_vm_finalize_vcpus(vm);
+
+ return vm;
+}
+
+static void test_direct_vmsa(void)
+{
+ struct test_vmsa *vmsas, *selected_vmsa;
+ struct kvm_sev_snp_vcpu_state state = {};
+ struct kvm_mp_state mp_state = {
+ .mp_state = KVM_MP_STATE_UNINITIALIZED,
+ };
+ struct kvm_vcpu *vcpus[2];
+ struct kvm_vcpu *vcpu, *ap;
+ struct kvm_vm *vm;
+ gva_t marker_gva;
+ gpa_t vmsa_gpa;
+ u64 *marker;
+
+ vm = create_direct_vmsa_vm(ARRAY_SIZE(vcpus), guest_legacy_entry, vcpus);
+ vcpu = vcpus[0];
+ ap = vcpus[1];
+ /* Restore normal AP state after vm_vcpu_add() makes it runnable. */
+ vcpu_mp_state_set(ap, &mp_state);
+ marker_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ marker = addr_gva2hva(vm, marker_gva);
+ vcpu_args_set(vcpu, 1, marker_gva);
+
+ vmsa_gpa = vm_phy_pages_alloc(vm, 2, VMSA_MIN_GPA,
+ vm->memslots[MEM_REGION_TEST_DATA]);
+ TEST_ASSERT(vmsa_gpa & (VMSA_PMD_SIZE - 1), "unsafe VMSA GPA");
+ vmsas = aligned_alloc(PAGE_SIZE, 2 * PAGE_SIZE);
+ TEST_ASSERT(vmsas, "Failed to allocate VMSA source pages");
+ selected_vmsa = (void *)vmsas + PAGE_SIZE;
+ prepare_vmsa(vcpu, &vmsas[0], guest_legacy_entry);
+ prepare_vmsa(vcpu, selected_vmsa, guest_direct_entry);
+
+ snp_vm_launch_start(vm, snp_default_policy());
+ vm_mem_set_private(vm, vmsa_gpa, 2 * PAGE_SIZE);
+ expect_launch_update_vmsa_error(vm, vmsa_gpa, vmsas, 2 * PAGE_SIZE);
+
+ vmsas[0].vmpl = 1;
+ expect_launch_update_vmsa_error(vm, vmsa_gpa, vmsas, PAGE_SIZE);
+ vmsas[0].vmpl = 0;
+ vmsas[0].sev_features = 0;
+ expect_launch_update_vmsa_error(vm, vmsa_gpa, vmsas, PAGE_SIZE);
+ vmsas[0].sev_features = SNP_ACTIVE_SEV_FEATURE;
+
+ snp_launch_update_vmsa(vm, vmsa_gpa, vmsas);
+ snp_launch_update_vmsa(vm, vmsa_gpa + PAGE_SIZE, selected_vmsa);
+ exclude_from_normal_launch(vm, vmsa_gpa, 2);
+ snp_vm_launch_update(vm);
+
+ /* Rebinding is allowed; the second, selected VMSA must win. */
+ snp_set_vcpu_state(vcpu, vmsa_gpa);
+ snp_set_vcpu_state(vcpu, vmsa_gpa + PAGE_SIZE);
+ snp_get_vcpu_state(vcpu, &state);
+ TEST_ASSERT_EQ(state.valid_fields,
+ KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |
+ KVM_SEV_SNP_VCPU_STATE_GHCB_VALID);
+ TEST_ASSERT_EQ(state.vmsa_gpa, vmsa_gpa + PAGE_SIZE);
+ TEST_ASSERT_EQ(state.ghcb_gpa, 0);
+ snp_vm_launch_finish(vm);
+
+ vcpu_mp_state_get(ap, &mp_state);
+ TEST_ASSERT_EQ(mp_state.mp_state, KVM_MP_STATE_UNINITIALIZED);
+ *marker = 0;
+ assert_vcpu_terminated(vcpu);
+ TEST_ASSERT_EQ(*marker, DIRECT_MARKER);
+
+ state = (struct kvm_sev_snp_vcpu_state) {
+ .vmsa_gpa = vmsa_gpa,
+ .valid_fields = KVM_SEV_SNP_VCPU_STATE_VMSA_VALID,
+ };
+ errno = 0;
+ TEST_ASSERT_EQ(__vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE,
+ &state), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+
+ free(vmsas);
+ kvm_vm_free(vm);
+}
+
+static void expect_set_vcpu_state_error(struct kvm_vcpu *vcpu,
+ struct kvm_sev_snp_vcpu_state *state,
+ int expected_errno)
+{
+ errno = 0;
+ TEST_ASSERT_EQ(__vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, state), -1);
+ TEST_ASSERT_EQ(errno, expected_errno);
+}
+
+static void test_invalid_requests(void)
+{
+ struct kvm_sev_snp_vcpu_state state = {
+ .valid_fields = KVM_SEV_SNP_VCPU_STATE_VMSA_VALID,
+ };
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ gva_t shared_gva;
+ gpa_t private_gpa;
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_legacy_entry);
+ expect_set_vcpu_state_error(vcpu, &state, ENOTTY);
+ kvm_vm_free(vm);
+
+ vm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_legacy_entry, &vcpu);
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ snp_vm_launch_start(vm, snp_default_policy());
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ kvm_vm_free(vm);
+
+ vm = create_direct_vmsa_vm(1, guest_legacy_entry, &vcpu);
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ snp_vm_launch_start(vm, snp_default_policy());
+
+ state.pad[4] = 1;
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ state.pad[4] = 0;
+ state.valid_fields |= BIT_ULL(2);
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ state.valid_fields &= ~BIT_ULL(2);
+ state.vmsa_gpa = PAGE_SIZE + 1;
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ state.vmsa_gpa = VMSA_PMD_SIZE;
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+ state.vmsa_gpa = BIT_ULL(40) + PAGE_SIZE;
+ expect_set_vcpu_state_error(vcpu, &state, EINVAL);
+
+ shared_gva = vm_alloc_shared(vm, 2 * PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ state.vmsa_gpa = addr_gva2gpa(vm, shared_gva);
+ if (!(state.vmsa_gpa & (VMSA_PMD_SIZE - 1)))
+ state.vmsa_gpa += PAGE_SIZE;
+ state.ghcb_gpa = BIT_ULL(40);
+ state.valid_fields |= KVM_SEV_SNP_VCPU_STATE_GHCB_VALID;
+ vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, &state);
+
+ private_gpa = vm_phy_page_alloc(vm, VMSA_MIN_GPA,
+ vm->memslots[MEM_REGION_TEST_DATA]);
+ vm_mem_set_private(vm, private_gpa, PAGE_SIZE);
+ state.vmsa_gpa = private_gpa;
+ vcpu_sev_ioctl(vcpu, KVM_SEV_SNP_SET_VCPU_STATE, &state);
+
+ memset(&state, 0, sizeof(state));
+ snp_get_vcpu_state(vcpu, &state);
+ TEST_ASSERT_EQ(state.valid_fields,
+ KVM_SEV_SNP_VCPU_STATE_VMSA_VALID |
+ KVM_SEV_SNP_VCPU_STATE_GHCB_VALID);
+ TEST_ASSERT_EQ(state.vmsa_gpa, private_gpa);
+ TEST_ASSERT_EQ(state.ghcb_gpa, BIT_ULL(40));
+
+ kvm_vm_free(vm);
+}
+
+static void test_direct_vmsa_capability(void)
+{
+ struct kvm_enable_cap cap = {
+ .cap = KVM_CAP_SNP_DIRECT_VMSA,
+ };
+ struct vm_shape shape = {
+ .mode = VM_MODE_DEFAULT,
+ .type = KVM_X86_SNP_VM,
+ };
+ struct kvm_vm *vm;
+
+ vm = vm_create_barebones();
+ TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ kvm_vm_free(vm);
+
+ vm = __vm_create(shape, 1, 0);
+ TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 1), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ cap.args[3] = 1;
+ TEST_ASSERT_EQ(__vm_ioctl(vm, KVM_ENABLE_CAP, &cap), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ cap.args[3] = 0;
+ cap.flags = 1;
+ TEST_ASSERT_EQ(__vm_ioctl(vm, KVM_ENABLE_CAP, &cap), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0);
+ vm_vcpu_add(vm, 0, guest_legacy_entry);
+ TEST_ASSERT_EQ(__vm_enable_cap(vm, KVM_CAP_SNP_DIRECT_VMSA, 0), -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+ kvm_vm_free(vm);
+}
+
+static void test_legacy_launch(void)
+{
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ gva_t marker_gva;
+ u64 *marker;
+
+ vm = vm_sev_create_with_one_vcpu(KVM_X86_SNP_VM, guest_legacy_entry, &vcpu);
+ marker_gva = vm_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
+ MEM_REGION_TEST_DATA);
+ marker = addr_gva2hva(vm, marker_gva);
+ vcpu_args_set(vcpu, 1, marker_gva);
+ vm_sev_launch(vm, snp_default_policy(), NULL);
+ *marker = 0;
+ assert_vcpu_terminated(vcpu);
+ TEST_ASSERT_EQ(*marker, LEGACY_MARKER);
+ kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_SNP_VCPU_STATE));
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_SNP_DIRECT_VMSA));
+ TEST_ASSERT(sizeof(struct test_vmsa) <= PAGE_SIZE, "VMSA structure is too large");
+
+ test_direct_vmsa_capability();
+ test_invalid_requests();
+ test_direct_vmsa();
+ test_legacy_launch();
+ return 0;
+}
|
| 27/3 | 2026/09/08 13:04 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 24262 output: 21 thoughts: 1816 Thoughts: **Analyzing KVM SEV/SNP Patch** Okay, let's dive into this patch. It looks like we're adding support for KVM SEV/SNP, specifically focusing on the VMSA (Virtual Machine State Area) and VCPU state management. The changes are quite extensive, spanning documentation, UAPI headers, and the core SVM code. First, I see that they've added new documentation around `KVM_SEV_SNP_PAGE_TYPE_VMSA`, `KVM_SEV_SNP_GET_VCPU_STATE`, `KVM_SEV_SNP_SET_VCPU_STATE`, and `KVM_CAP_SNP_DIRECT_VMSA`. That's a good start; documentation is critical. Moving on to the header files, `kvm_host.h` and `kvm-x86-ops.h` now include an `enable_vm_cap` function in `kvm_x86_ops`. I'll need to see how this function is used and what capabilities it enables. In the UAPI headers (`kvm.h`), they've added the new ioctls `KVM_SEV_SNP_GET_VCPU_STATE`, `KVM_SEV_SNP_SET_VCPU_STATE` and the page type `KVM_SEV_SNP_PAGE_TYPE_VMSA` (0x2). The `struct kvm_sev_snp_vcpu_state` is also defined here, and it's important to note the structure members: `valid_fields`, `vmsa_gpa`, `ghcb_gpa`, and `pad[5]`. The `pad` element will require close scrutiny to make sure it is handled correctly, and is appropriately zeroed out. The `svm.h` header adds `SVM_VMGEXIT_GET_APIC_IDS`. Now, the core logic in `sev.c`. The code now incorporates checks for the `KVM_SEV_SNP_PAGE_TYPE_VMSA` in `sev_gmem_post_populate` and `snp_launch_update`. In `snp_launch_update_vmsa` it looks for `sev->snp_direct_vmsa`. This likely enables direct access to the VMSA, which is important for performance. The new functions, `snp_get_vcpu_state` and `snp_set_vcpu_state`, are the heart of the changes. `snp_get_vcpu_state` reads the VMSA and GHCB GPAs and returns the information to the user in the `kvm_sev_snp_vcpu_state` struct. A crucial detail is that the struct is zero-initialized and then potentially filled. This could be a security measure to prevent information leaks. The `copy_to_user` call then sends this state back to userspace. In `snp_set_vcpu_state` the code reads in this state struct from user space and validates the `pad` field using `memchr_inv` to ensure that it is all zero, preventing potential vulnerabilities. It also checks that valid fields are set correctly, and the snp_direct_vmsa is set. I will need to verify the user-space interactions here. Any misuse of these calls could have serious security implications, so careful review is essential. The checks to ensure that the guest is SEV/SNP enabled, and that the snp_context is initialized are also very important. The `sev_vcpu_mem_enc_ioctl` function is the entry point for these new ioctls. It dispatches to `snp_get_vcpu_state` or `snp_set_vcpu_state` based on the `sev_cmd.id`. I will need to trace the execution path and look at `struct kvm_sev_cmd` to fully understand what is being passed in. It looks like the `sev_cmd` structure is copied from user space, and then the return value is copied back. I'll use grepper to see how this struct is defined to look for the security implications. |
| 28/3 | 2026/09/08 13:04 | tool | grepper |
0mArgs:{
"Expression": "struct kvm_sev_cmd"
}Results: {
"Output": "Documentation/virt/kvm/x86/amd-memory-encryption.rst=57=of zero if SEV is enabled). If non-NULL, the argument to\nDocumentation/virt/kvm/x86/amd-memory-encryption.rst:58:KVM_MEMORY_ENCRYPT_OP must be a struct kvm_sev_cmd::\nDocumentation/virt/kvm/x86/amd-memory-encryption.rst-59-\nDocumentation/virt/kvm/x86/amd-memory-encryption.rst:60: struct kvm_sev_cmd {\nDocumentation/virt/kvm/x86/amd-memory-encryption.rst-61- __u32 id;\n--\narch/x86/include/uapi/asm/kvm.h=711=enum sev_cmd_id {\n--\narch/x86/include/uapi/asm/kvm.h-756-\narch/x86/include/uapi/asm/kvm.h:757:struct kvm_sev_cmd {\narch/x86/include/uapi/asm/kvm.h-758-\t__u32 id;\n--\narch/x86/kvm/svm/sev.c=479=static void snp_guest_req_cleanup(struct kvm *kvm)\n--\narch/x86/kvm/svm/sev.c-492-\narch/x86/kvm/svm/sev.c:493:static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp,\narch/x86/kvm/svm/sev.c-494-\t\t\t struct kvm_sev_init *data,\n--\narch/x86/kvm/svm/sev.c-582-\narch/x86/kvm/svm/sev.c:583:static int sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-584-{\n--\narch/x86/kvm/svm/sev.c-605-\narch/x86/kvm/svm/sev.c:606:static int sev_guest_init2(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-607-{\n--\narch/x86/kvm/svm/sev.c=648=static int sev_issue_cmd(struct kvm *kvm, int id, void *data, int *error)\n--\narch/x86/kvm/svm/sev.c-654-\narch/x86/kvm/svm/sev.c:655:static int sev_launch_start(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-656-{\n--\narch/x86/kvm/svm/sev.c=882=static unsigned long get_num_contig_pages(unsigned long idx,\n--\narch/x86/kvm/svm/sev.c-902-\narch/x86/kvm/svm/sev.c:903:static int sev_launch_update_data(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-904-{\n--\narch/x86/kvm/svm/sev.c=1070=static int __sev_launch_update_vmsa(struct kvm *kvm, struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/svm/sev.c-1120-\narch/x86/kvm/svm/sev.c:1121:static int sev_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1122-{\n--\narch/x86/kvm/svm/sev.c-1146-\narch/x86/kvm/svm/sev.c:1147:static int sev_launch_measure(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1148-{\n--\narch/x86/kvm/svm/sev.c-1207-\narch/x86/kvm/svm/sev.c:1208:static int sev_launch_finish(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1209-{\n--\narch/x86/kvm/svm/sev.c-1218-\narch/x86/kvm/svm/sev.c:1219:static int sev_guest_status(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1220-{\n--\narch/x86/kvm/svm/sev.c=1335=static int sev_dbg_encrypt_slow(struct kvm *kvm, unsigned long src,\n--\narch/x86/kvm/svm/sev.c-1367-\narch/x86/kvm/svm/sev.c:1368:static int sev_dbg_crypt(struct kvm *kvm, struct kvm_sev_cmd *argp,\narch/x86/kvm/svm/sev.c-1369-\t\t\t unsigned int cmd)\n--\narch/x86/kvm/svm/sev.c-1443-\narch/x86/kvm/svm/sev.c:1444:static int sev_launch_secret(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1445-{\n--\narch/x86/kvm/svm/sev.c-1517-\narch/x86/kvm/svm/sev.c:1518:static int sev_get_attestation_report(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1519-{\n--\narch/x86/kvm/svm/sev.c=1579=static int\narch/x86/kvm/svm/sev.c:1580:__sev_send_start_query_session_length(struct kvm *kvm, struct kvm_sev_cmd *argp,\narch/x86/kvm/svm/sev.c-1581-\t\t\t\t struct kvm_sev_send_start *params)\n--\narch/x86/kvm/svm/sev.c-1597-\narch/x86/kvm/svm/sev.c:1598:static int sev_send_start(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1599-{\n--\narch/x86/kvm/svm/sev.c=1688=static int\narch/x86/kvm/svm/sev.c:1689:__sev_send_update_data_query_lengths(struct kvm *kvm, struct kvm_sev_cmd *argp,\narch/x86/kvm/svm/sev.c-1690-\t\t\t\t struct kvm_sev_send_update_data *params)\n--\narch/x86/kvm/svm/sev.c-1708-\narch/x86/kvm/svm/sev.c:1709:static int sev_send_update_data(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1710-{\n--\narch/x86/kvm/svm/sev.c-1789-\narch/x86/kvm/svm/sev.c:1790:static int sev_send_finish(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1791-{\n--\narch/x86/kvm/svm/sev.c-1800-\narch/x86/kvm/svm/sev.c:1801:static int sev_send_cancel(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1802-{\n--\narch/x86/kvm/svm/sev.c-1811-\narch/x86/kvm/svm/sev.c:1812:static int sev_receive_start(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1813-{\n--\narch/x86/kvm/svm/sev.c-1885-\narch/x86/kvm/svm/sev.c:1886:static int sev_receive_update_data(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1887-{\n--\narch/x86/kvm/svm/sev.c-1959-\narch/x86/kvm/svm/sev.c:1960:static int sev_receive_finish(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-1961-{\n--\narch/x86/kvm/svm/sev.c=2218=int sev_dev_get_attr(u32 group, u64 attr, u64 *val)\n--\narch/x86/kvm/svm/sev.c-2245- */\narch/x86/kvm/svm/sev.c:2246:static void *snp_context_create(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-2247-{\n--\narch/x86/kvm/svm/sev.c=2269=static int snp_bind_asid(struct kvm *kvm, int *error)\n--\narch/x86/kvm/svm/sev.c-2278-\narch/x86/kvm/svm/sev.c:2279:static int snp_launch_start(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-2280-{\n--\narch/x86/kvm/svm/sev.c=2352=static int sev_gmem_post_populate(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,\n--\narch/x86/kvm/svm/sev.c-2432-\narch/x86/kvm/svm/sev.c:2433:static int snp_launch_update(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-2434-{\n--\narch/x86/kvm/svm/sev.c=2523=static int sev_snp_install_guest_vmsa(struct vcpu_svm *svm, gpa_t gpa);\narch/x86/kvm/svm/sev.c-2524-\narch/x86/kvm/svm/sev.c:2525:static int snp_launch_update_vmsa(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-2526-{\n--\narch/x86/kvm/svm/sev.c=2590=static int snp_get_vcpu_state(struct kvm_vcpu *vcpu,\narch/x86/kvm/svm/sev.c:2591:\t\t\t struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-2592-{\n--\narch/x86/kvm/svm/sev.c=2621=static int snp_set_vcpu_state(struct kvm_vcpu *vcpu,\narch/x86/kvm/svm/sev.c:2622:\t\t\t struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-2623-{\n--\narch/x86/kvm/svm/sev.c-2672-\narch/x86/kvm/svm/sev.c:2673:static int snp_launch_finish(struct kvm *kvm, struct kvm_sev_cmd *argp)\narch/x86/kvm/svm/sev.c-2674-{\n--\narch/x86/kvm/svm/sev.c=2759=int sev_mem_enc_ioctl(struct kvm *kvm, void __user *argp)\narch/x86/kvm/svm/sev.c-2760-{\narch/x86/kvm/svm/sev.c:2761:\tstruct kvm_sev_cmd sev_cmd;\narch/x86/kvm/svm/sev.c-2762-\tint r;\n--\narch/x86/kvm/svm/sev.c-2769-\narch/x86/kvm/svm/sev.c:2770:\tif (copy_from_user(\u0026sev_cmd, argp, sizeof(struct kvm_sev_cmd)))\narch/x86/kvm/svm/sev.c-2771-\t\treturn -EFAULT;\n--\narch/x86/kvm/svm/sev.c-2862-\narch/x86/kvm/svm/sev.c:2863:\tif (copy_to_user(argp, \u0026sev_cmd, sizeof(struct kvm_sev_cmd)))\narch/x86/kvm/svm/sev.c-2864-\t\tr = -EFAULT;\n--\narch/x86/kvm/svm/sev.c=2869=int sev_vcpu_mem_enc_ioctl(struct kvm_vcpu *vcpu, void __user *argp)\narch/x86/kvm/svm/sev.c-2870-{\narch/x86/kvm/svm/sev.c:2871:\tstruct kvm_sev_cmd sev_cmd;\narch/x86/kvm/svm/sev.c-2872-\tint ret;\n--\ntools/arch/x86/include/uapi/asm/kvm.h=711=enum sev_cmd_id {\n--\ntools/arch/x86/include/uapi/asm/kvm.h-754-\ntools/arch/x86/include/uapi/asm/kvm.h:755:struct kvm_sev_cmd {\ntools/arch/x86/include/uapi/asm/kvm.h-756-\t__u32 id;\n--\ntools/testing/selftests/kvm/include/x86/sev.h=67=static inline u64 snp_default_policy(void)\n--\ntools/testing/selftests/kvm/include/x86/sev.h-83-\tunion {\t\t\t\t\t\t\t\t\\\ntools/testing/selftests/kvm/include/x86/sev.h:84:\t\tstruct kvm_sev_cmd c;\t\t\t\t\t\\\ntools/testing/selftests/kvm/include/x86/sev.h-85-\t\tunsigned long raw;\t\t\t\t\t\\\n--\ntools/testing/selftests/kvm/include/x86/sev.h=103=static inline int __vcpu_sev_ioctl(struct kvm_vcpu *vcpu, u32 cmd, void *arg)\n--\ntools/testing/selftests/kvm/include/x86/sev.h-105-\tunion {\ntools/testing/selftests/kvm/include/x86/sev.h:106:\t\tstruct kvm_sev_cmd c;\ntools/testing/selftests/kvm/include/x86/sev.h-107-\t\tunsigned long raw;\n--\ntools/testing/selftests/kvm/x86/sev_init2_tests.c=33=static int __sev_ioctl(int vm_fd, int cmd_id, void *data)\ntools/testing/selftests/kvm/x86/sev_init2_tests.c-34-{\ntools/testing/selftests/kvm/x86/sev_init2_tests.c:35:\tstruct kvm_sev_cmd cmd = {\ntools/testing/selftests/kvm/x86/sev_init2_tests.c-36-\t\t.id = cmd_id,\n"
} |
| 29/3 | 2026/09/08 13:04 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 31380 output: 45 thoughts: 33 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into the KVM SEV Command Structure** Okay, time to get my hands dirty. I need to take a look at the `struct kvm_sev_cmd`. My immediate thought is, let's go straight to the source. I'm hitting up the header file – `arch/x86/include/uapi/asm/kvm.h`. That's where I expect to find the definitive structure definition for the SEV (Secure Encrypted Virtualization) command interface used by KVM on x86 systems. I want to understand the layout, the fields, and how the communication with the SEV-enabled firmware is structured. This is the bedrock for figuring out how the guest can interact with the host and the AMD Secure Processor. I'm expecting this structure to hold the keys for understanding the low-level interactions and any potential vulnerabilities or interesting features. Let's see what we've got. |
| 30/3 | 2026/09/08 13:04 | tool | read-file |
0mArgs:{
"File": "arch/x86/include/uapi/asm/kvm.h",
"FirstLine": 750,
"LineCount": 25
}Results: {
"Contents": " 750:\t\tKVM_SEV_SNP_ENABLE_REQ_CERTS,\n 751:\t\tKVM_SEV_SNP_GET_VCPU_STATE,\n 752:\t\tKVM_SEV_SNP_SET_VCPU_STATE,\n 753:\t\n 754:\t\tKVM_SEV_NR_MAX,\n 755:\t};\n 756:\t\n 757:\tstruct kvm_sev_cmd {\n 758:\t\t__u32 id;\n 759:\t\t__u32 pad0;\n 760:\t\t__u64 data;\n 761:\t\t__u32 error;\n 762:\t\t__u32 sev_fd;\n 763:\t};\n 764:\t\n 765:\tstruct kvm_sev_init {\n 766:\t\t__u64 vmsa_features;\n 767:\t\t__u32 flags;\n 768:\t\t__u16 ghcb_version;\n 769:\t\t__u16 pad1;\n 770:\t\t__u32 pad2[8];\n 771:\t};\n 772:\t\n 773:\tstruct kvm_sev_launch_start {\n 774:\t\t__u32 handle;\n"
} |
| 31/3 | 2026/09/08 13:04 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 32025 output: 44 thoughts: 3033 Thoughts: **Analysis of Secure Encrypted Virtualization (SEV) Code for Potential Uninitialized Memory Issues**
Alright, let's break down this code and ensure we aren't introducing any vulnerabilities related to uninitialized memory in the SEV-SNP implementation. I've meticulously examined the provided code snippets.
First, within `sev_vcpu_mem_enc_ioctl`, I see that the `sev_cmd` structure is *entirely* populated from userspace via `copy_from_user`. Subsequently, the same `sev_cmd` is copied back. This is generally safe because all fields are overwritten. The critical thing here is to make certain that *all* fields are used.
Then, I focused on `snp_get_vcpu_state`. The `state` structure is initialized with `{}` which zeroes its entire content before being copied to userspace. Also, `struct kvm_sev_snp_vcpu_state` is properly defined, containing only known types, without padding and so it is fine.
Next, I scrutinized `sev_snp_get_apic_ids`. The key here is the allocation and initialization of `desc`. I can see that `kvzalloc` is used, which ensures that all bytes of `desc` are zeroed out before use. That is great. I confirm that there is no padding in `struct sev_apic_id_desc`, and that its members are contiguous. The code correctly populates the `num_entries` and `apic_ids` fields. Finally, it uses `kvm_write_guest` which is good.
I then went over the other changes, `sev_gmem_post_populate`, `snp_launch_update_vmsa`, `sev_snp_install_guest_vmsa`, `__sev_snp_reload_vmsa`, and `sev_vcpu_create` to ensure that there are no uninitialized variables.
In summary, there is no apparent uninitialized memory being exposed to userspace. The code takes care to zero-initialize data structures before copying them to userspace, or populating them and copying from user. The use of `kvzalloc` in several critical sections is a good sign.
|
| 32/3 | 2026/09/08 13:04 | tool | grepper |
0mArgs:{
"Expression": "svm_create_vcpu|sev_vcpu_create",
"PathPrefix": "arch/x86/kvm/svm/"
}Results: {
"Output": "arch/x86/kvm/svm/sev.c=5096=void sev_init_vmcb(struct vcpu_svm *svm, bool init_event)\n--\narch/x86/kvm/svm/sev.c-5115-\narch/x86/kvm/svm/sev.c:5116:int sev_vcpu_create(struct kvm_vcpu *vcpu)\narch/x86/kvm/svm/sev.c-5117-{\n--\narch/x86/kvm/svm/svm.c=1315=static int svm_vcpu_create(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/svm.c-1328-\narch/x86/kvm/svm/svm.c:1329:\terr = sev_vcpu_create(vcpu);\narch/x86/kvm/svm/svm.c-1330-\tif (err)\n--\narch/x86/kvm/svm/svm.h=1003=void sev_snp_reload_vmsa(struct kvm_vcpu *vcpu);\narch/x86/kvm/svm/svm.h-1004-\narch/x86/kvm/svm/svm.h:1005:int sev_vcpu_create(struct kvm_vcpu *vcpu);\narch/x86/kvm/svm/svm.h-1006-void sev_free_vcpu(struct kvm_vcpu *vcpu);\n--\narch/x86/kvm/svm/svm.h=1028=static inline struct page *snp_safe_alloc_page(void)\n--\narch/x86/kvm/svm/svm.h-1032-\narch/x86/kvm/svm/svm.h:1033:static inline int sev_vcpu_create(struct kvm_vcpu *vcpu) { return 0; }\narch/x86/kvm/svm/svm.h-1034-static inline void sev_free_vcpu(struct kvm_vcpu *vcpu) {}\n"
} |
| 33/3 | 2026/09/08 13:04 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 36130 output: 43 thoughts: 58 Thoughts: Here's my thought process, as an expert in my field: **Diving into the SVM VCPU Creation** Okay, so I need to understand the lifecycle of an SVM Virtual CPU, and where its core data structure is allocated. My initial instinct is to go straight to the source. Let's start by examining the `svm_vcpu_create` function. I'll find that in the `arch/x86/kvm/svm/svm.c` file. This should give me the entry point. From there, I'll trace how `vcpu_svm` is actually allocated, which is likely inside `svm_create_vcpu`. It's possible the allocation is handled by a standard kernel allocator like `kmem_cache_zalloc`, or it might involve a custom allocation strategy specific to SVM. The key is finding out how and when the memory for the `vcpu_svm` structure is reserved and initialized. Understanding this allocation process is fundamental to grasping the low-level mechanics of AMD-V guest management, especially around topics such as CPUID, MSR access, and event handling within the SVM context. |
| 34/3 | 2026/09/08 13:04 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/svm/svm.c",
"FirstLine": 1310,
"LineCount": 40
}Results: {
"Contents": "1310:\t{\n1311:\t\tsvm-\u003ecurrent_vmcb = target_vmcb;\n1312:\t\tsvm-\u003evmcb = target_vmcb-\u003eptr;\n1313:\t}\n1314:\t\n1315:\tstatic int svm_vcpu_create(struct kvm_vcpu *vcpu)\n1316:\t{\n1317:\t\tstruct vcpu_svm *svm;\n1318:\t\tstruct page *vmcb01_page;\n1319:\t\tint err;\n1320:\t\n1321:\t\tBUILD_BUG_ON(offsetof(struct vcpu_svm, vcpu) != 0);\n1322:\t\tsvm = to_svm(vcpu);\n1323:\t\n1324:\t\terr = -ENOMEM;\n1325:\t\tvmcb01_page = snp_safe_alloc_page();\n1326:\t\tif (!vmcb01_page)\n1327:\t\t\tgoto out;\n1328:\t\n1329:\t\terr = sev_vcpu_create(vcpu);\n1330:\t\tif (err)\n1331:\t\t\tgoto error_free_vmcb_page;\n1332:\t\n1333:\t\terr = avic_init_vcpu(svm);\n1334:\t\tif (err)\n1335:\t\t\tgoto error_free_sev;\n1336:\t\n1337:\t\tsvm-\u003emsrpm = svm_vcpu_alloc_msrpm();\n1338:\t\tif (!svm-\u003emsrpm) {\n1339:\t\t\terr = -ENOMEM;\n1340:\t\t\tgoto error_free_sev;\n1341:\t\t}\n1342:\t\n1343:\t\tsvm-\u003ex2avic_msrs_intercepted = true;\n1344:\t\tsvm-\u003elbr_msrs_intercepted = true;\n1345:\t\n1346:\t\tsvm-\u003evmcb01.ptr = page_address(vmcb01_page);\n1347:\t\tsvm-\u003evmcb01.pa = __sme_set(page_to_pfn(vmcb01_page) \u003c\u003c PAGE_SHIFT);\n1348:\t\tsvm_switch_vmcb(svm, \u0026svm-\u003evmcb01);\n1349:\t\n"
} |
| 35/3 | 2026/09/08 13:04 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 37082 output: 324 thoughts: 851 Thoughts: **KMSAN/KASAN Analysis for This Patch Series**
Okay, let's break this down. The core of this patch series deals with changes related to KVM SEV-SNP support, specifically handling VMSAs, providing ioctls for getting/setting vCPU state, and managing APIC ID list requests. My primary concern here is memory safety and whether this warrants dedicated KMSAN fuzzing.
First, I need to evaluate the KMSAN criteria. Based on the rules, I need to look for cases where the patch introduces or modifies kernel structures sent to userspace, conditional logic on uninitialized variables, allocation of complex uninitialized data, or changes to bounds checks that could lead to uninitialized byte access.
Starting with the allocation, the `vcpu` allocation uses `kmem_cache_zalloc`, so it's zero-initialized by default. We have a corresponding `sev_free_vcpu` added which explicitly handles the potential `NULL` `svm->sev_es.vmsa` case, which is good.
Now, scrutinizing each potential KMSAN risk. First, `snp_get_vcpu_state` utilizes `struct kvm_sev_snp_vcpu_state state = {};` for the `state` variable. This structure is explicitly zero-initialized, and contains only aligned `__u64` fields, which means no padding concerns. In `sev_vcpu_mem_enc_ioctl`, data (`sev_cmd`) is copied to and from userspace, but the code does copies and there's no evidence of incomplete initialization. In `sev_snp_get_apic_ids`, a `desc` is allocated via `kvzalloc` and completely filled with `vcpu_id`s before writing to guest memory. So, these all seem safe.
Next, I need to confirm the KMSAN criteria are met to skip KMSAN testing. I'm confirming that all structures are fully zeroed or initialized, and that there's no risk of uninitialized memory usage or information leaks.
Therefore, after a thorough review, I'm confident that this patch series **does not** warrant a dedicated KMSAN fuzzing session. The `NeedsKMSAN` flag should be set to `false`. The data structures passed to userspace and the guest are either explicitly zero-initialized or completely filled before use.
As for KASAN, I know this patch will modify KVM SEV-SNP support, the risk of out-of-bounds accesses during guest memory access (reading/writing), locking issues, and use-after-free conditions are covered by KASAN and standard kernel debuggers like LOCKDEP. Dedicated KMSAN fuzzing isn't justified here.
So, the new data structures like `struct kvm_sev_snp_vcpu_state` and `struct sev_apic_id_desc` are initialized. No uninitialized memory is read or leaked. Thus, no dedicated KMSAN fuzzing is necessary.
I think it's time to call the `set-results` tool now.
|
| 36/3 | 2026/09/08 13:04 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch adds support for direct VMSAs and guest APIC ID list queries in AMD SEV-SNP (including new vCPU ioctls KVM_SEV_SNP_GET_VCPU_STATE / KVM_SEV_SNP_SET_VCPU_STATE and VMGEXIT handling for SVM_VMGEXIT_GET_APIC_IDS).\n\nAll kernel structures returned to userspace or written to guest memory are fully initialized:\n- `struct kvm_sev_snp_vcpu_state` in `snp_get_vcpu_state` is initialized with `= {}`, consists solely of `__u64` fields with no padding holes, and is fully zeroed before `copy_to_user()`.\n- `sev_cmd` in `sev_vcpu_mem_enc_ioctl` is copied from userspace via `copy_from_user()` before being copied back.\n- `struct sev_apic_id_desc` in `sev_snp_get_apic_ids` is allocated with `kvzalloc()` and completely populated before `kvm_write_guest()`.\n\nAny memory safety risks introduced by the patch (such as memory mapping, guest page validation, or concurrency) fall under standard detectors (KASAN, LOCKDEP). There are no uninitialized memory reads or kernel info-leak risks requiring a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch adds support for direct VMSAs and guest APIC ID list queries in AMD SEV-SNP (including new vCPU ioctls KVM_SEV_SNP_GET_VCPU_STATE / KVM_SEV_SNP_SET_VCPU_STATE and VMGEXIT handling for SVM_VMGEXIT_GET_APIC_IDS).\n\nAll kernel structures returned to userspace or written to guest memory are fully initialized:\n- `struct kvm_sev_snp_vcpu_state` in `snp_get_vcpu_state` is initialized with `= {}`, consists solely of `__u64` fields with no padding holes, and is fully zeroed before `copy_to_user()`.\n- `sev_cmd` in `sev_vcpu_mem_enc_ioctl` is copied from userspace via `copy_from_user()` before being copied back.\n- `struct sev_apic_id_desc` in `sev_snp_get_apic_ids` is allocated with `kvzalloc()` and completely populated before `kvm_write_guest()`.\n\nAny memory safety risks introduced by the patch (such as memory mapping, guest page validation, or concurrency) fall under standard detectors (KASAN, LOCKDEP). There are no uninitialized memory reads or kernel info-leak risks requiring a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|