| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/13 07:01 | flow | patch-triage |
4mArgs:null Results: {
"EnableConfigs": [
"KVM",
"KVM_AMD"
],
"FocusSymbols": [
"nested_svm_update_vmcb12_insn_bytes",
"nested_svm_fetch_insn_bytes",
"nested_svm_prepare_synthesized_insn_bytes",
"nested_svm_copy_insn_bytes",
"nested_svm_exit_handled",
"svm_check_intercept",
"x86_emulator_copy_insn_bytes",
"kvm_fetch_guest_virt"
],
"KMSANReasoning": "The patch implements nested SVM DecodeAssists support in KVM on AMD x86. This entails advertising the CPUID capability, populating EXITINFO1 during emulated intercepts, synthesizing/propagating instruction bytes into VMCB02 and VMCB12 on nested VM-exits, and adding helper functions for instruction byte extraction from the emulator cache or guest virtual memory.\n\nKMSAN vs KASAN applicability:\n- No kernel-to-user information leaks: The data copied is written to guest memory (VMCB12) or hardware-allocated VMCB02 (allocated with `__GFP_ZERO`). There are no new or modified `copy_to_user`, `put_user`, socket, netlink, or ioctl user-facing buffers.\n- All new and modified structures and local variables are properly initialized before being read: `x86_instruction_info` uses designated initializers (zeroing omitted fields), `linear` in `get_invlpg_linear_addr` is explicitly initialized to 0, `e` in `nested_svm_fetch_insn_bytes` is cleared via `memset` in `kvm_fetch_guest_virt`, and `vmcb02_insn_bytes_valid` is zero-initialized with the vCPU allocation and properly invalidated across transitions.\n- Bounds and length checks: The instruction length calculations in `nested_svm_get_insn_bytes_len`, `nested_svm_fetch_insn_bytes`, and `x86_emulator_copy_insn_bytes` ensure memory is copied strictly within valid bounds and only up to the count of successfully fetched bytes.\n- Memory corruption or out-of-bounds risks (e.g. in `insn_bytes` copying or limit checks) are spatial access violations that are detected by standard KASAN, not uninitialized memory reads.\n\nTherefore, this patch series does not introduce uninitialized memory risks or info-leaks, and a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false,
"Reasoning": "The patch series implements AMD SVM nested virtualization support for DecodeAssists (X86_FEATURE_DECODEASSISTS). It modifies reachable code in KVM and the nested SVM subsystem to supply instruction bytes and decode assist information (e.g. exit_info_1 for intercepted instructions, guest instruction bytes for nested page faults). It includes new guest memory fetching and emulation cache extraction logic (kvm_fetch_guest_virt, x86_emulator_copy_insn_bytes, nested_svm_fetch_insn_bytes) and adds runtime checks (WARN_ON_ONCE in nested_svm_copy_insn_bytes). This reachable virtualization code is exposed via /dev/kvm and warrants fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/13 07:01 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 84b93f2b93403c96ba08905c3de300acf68146ab\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sun Sep 13 07:01:03 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c\nindex ddb022cb203a2..b644e45eac710 100644\n--- a/arch/x86/kvm/cpuid.c\n+++ b/arch/x86/kvm/cpuid.c\n@@ -1208,6 +1208,7 @@ void kvm_initialize_cpu_caps(void)\n \t\tVENDOR_F(NPT),\n \t\tVENDOR_F(VMCBCLEAN),\n \t\tVENDOR_F(FLUSHBYASID),\n+\t\tVENDOR_F(DECODEASSISTS),\n \t\tVENDOR_F(NRIPS),\n \t\tVENDOR_F(TSCRATEMSR),\n \t\tVENDOR_F(V_VMSAVE_VMLOAD),\ndiff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c\nindex 8071b372d233e..5017a9aa1d4bb 100644\n--- a/arch/x86/kvm/emulate.c\n+++ b/arch/x86/kvm/emulate.c\n@@ -410,6 +410,9 @@ static int em_salc(struct x86_emulate_ctxt *ctxt)\n \t_fault ? X86EMUL_UNHANDLEABLE : X86EMUL_CONTINUE; \\\n })\n \n+static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,\n+\t\t\t\t enum x86_intercept intercept);\n+\n static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,\n \t\t\t\t enum x86_intercept intercept,\n \t\t\t\t enum x86_intercept_stage stage)\n@@ -427,6 +430,7 @@ static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,\n \t\t.src_type = ctxt-\u003esrc.type,\n \t\t.dst_type = ctxt-\u003edst.type,\n \t\t.ad_bytes = ctxt-\u003ead_bytes,\n+\t\t.invlpg_linear_addr = get_invlpg_linear_addr(ctxt, intercept),\n \t\t.rip\t = ctxt-\u003eeip,\n \t\t.next_rip = ctxt-\u003e_eip,\n \t};\n@@ -702,6 +706,20 @@ static __always_inline int __linearize(struct x86_emulate_ctxt *ctxt,\n \t\treturn emulate_gp(ctxt, 0);\n }\n \n+static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,\n+\t\t\t\t enum x86_intercept intercept)\n+{\n+\tunsigned int max_size;\n+\tunsigned long linear = 0;\n+\n+\tif (intercept != x86_intercept_invlpg)\n+\t\treturn 0;\n+\n+\t__linearize(ctxt, ctxt-\u003esrc.addr.mem, \u0026max_size, 1, ctxt-\u003emode,\n+\t\t \u0026linear, X86EMUL_F_INVLPG);\n+\treturn linear;\n+}\n+\n static int linearize(struct x86_emulate_ctxt *ctxt,\n \t\t struct segmented_address addr,\n \t\t unsigned size, bool write,\n@@ -895,6 +913,28 @@ static __always_inline int do_insn_fetch_bytes(struct x86_emulate_ctxt *ctxt,\n \t\treturn X86EMUL_CONTINUE;\n }\n \n+/*\n+ * The caller is responsible for ensuring that the fetch cache belongs to\n+ * the current instruction. Matching EIP alone does not guarantee this.\n+ */\n+unsigned int x86_emulator_copy_insn_bytes(struct x86_emulate_ctxt *ctxt,\n+\t\t\t\t\t unsigned long eip, u8 *bytes,\n+\t\t\t\t\t unsigned int max_bytes)\n+{\n+\tunsigned int nr_bytes;\n+\n+\tif (!ctxt || ctxt-\u003eeip != eip ||\n+\t ctxt-\u003efetch.end \u003c ctxt-\u003efetch.data ||\n+\t ctxt-\u003efetch.end \u003e ctxt-\u003efetch.data + sizeof(ctxt-\u003efetch.data))\n+\t\treturn 0;\n+\n+\tnr_bytes = min_t(unsigned int, ctxt-\u003efetch.end - ctxt-\u003efetch.data,\n+\t\t\t max_bytes);\n+\tmemcpy(bytes, ctxt-\u003efetch.data, nr_bytes);\n+\treturn nr_bytes;\n+}\n+EXPORT_SYMBOL_FOR_KVM_INTERNAL(x86_emulator_copy_insn_bytes);\n+\n /* Fetch next part of the instruction being emulated. */\n #define insn_fetch(_type, _ctxt)\t\t\t\t\t\\\n ({\t_type _x;\t\t\t\t\t\t\t\\\ndiff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h\nindex 3e375af15c035..5361d42e6e057 100644\n--- a/arch/x86/kvm/kvm_emulate.h\n+++ b/arch/x86/kvm/kvm_emulate.h\n@@ -51,6 +51,7 @@ struct x86_instruction_info {\n \tu8 src_type;\t\t/* type of source operand\t\t*/\n \tu8 dst_type;\t\t/* type of destination operand\t\t*/\n \tu8 ad_bytes; /* size of src/dst address */\n+\tu64 invlpg_linear_addr;\t/* linear address, if INVLPG */\n \tu64 rip;\t\t/* rip of the instruction\t\t*/\n \tu64 next_rip; /* rip following the instruction */\n };\n@@ -527,6 +528,9 @@ enum x86_intercept {\n };\n \n int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int emulation_type);\n+unsigned int x86_emulator_copy_insn_bytes(struct x86_emulate_ctxt *ctxt,\n+\t\t\t\t\t unsigned long eip, u8 *bytes,\n+\t\t\t\t\t unsigned int max_bytes);\n bool x86_page_table_writing_insn(struct x86_emulate_ctxt *ctxt);\n #define EMULATION_FAILED -1\n #define EMULATION_OK 0\ndiff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c\nindex 73f37b050d0a0..2dc515d73eb62 100644\n--- a/arch/x86/kvm/svm/nested.c\n+++ b/arch/x86/kvm/svm/nested.c\n@@ -35,12 +35,162 @@\n \n #define CC KVM_NESTED_VMENTER_CONSISTENCY_CHECK\n \n+static void nested_svm_invalidate_insn_bytes(struct vmcb *vmcb)\n+{\n+\tvmcb-\u003econtrol.insn_len = 0;\n+}\n+\n+static void nested_svm_copy_insn_bytes(struct vmcb *to,\n+\t\t\t\t const struct vmcb *from)\n+{\n+\tu8 insn_len = from-\u003econtrol.insn_len;\n+\n+\tif (WARN_ON_ONCE(insn_len \u003e sizeof(from-\u003econtrol.insn_bytes))) {\n+\t\tnested_svm_invalidate_insn_bytes(to);\n+\t\treturn;\n+\t}\n+\n+\tmemcpy(to-\u003econtrol.insn_bytes, from-\u003econtrol.insn_bytes, insn_len);\n+\tto-\u003econtrol.insn_len = insn_len;\n+}\n+\n+static bool nested_svm_vmexit_supports_insn_bytes(const struct vmcb *vmcb)\n+{\n+\tu64 exit_code = vmcb-\u003econtrol.exit_code;\n+\n+\tif (exit_code != SVM_EXIT_NPF \u0026\u0026\n+\t exit_code != SVM_EXIT_EXCP_BASE + PF_VECTOR)\n+\t\treturn false;\n+\n+\treturn !(vmcb-\u003econtrol.exit_info_1 \u0026 PFERR_FETCH_MASK);\n+}\n+\n+static void nested_svm_set_vmcb02_insn_bytes_valid(struct vcpu_svm *svm)\n+{\n+\tsvm-\u003enested.vmcb02_insn_bytes_valid =\n+\t\tstatic_cpu_has(X86_FEATURE_DECODEASSISTS) \u0026\u0026\n+\t\tnested_svm_vmexit_supports_insn_bytes(svm-\u003enested.vmcb02.ptr);\n+}\n+\n+static void nested_svm_invalidate_vmcb02_insn_bytes(struct vcpu_svm *svm)\n+{\n+\tnested_svm_invalidate_insn_bytes(svm-\u003enested.vmcb02.ptr);\n+\tsvm-\u003enested.vmcb02_insn_bytes_valid = false;\n+}\n+\n+static u8 nested_svm_get_insn_bytes_len(struct kvm_vcpu *vcpu, u8 max_bytes)\n+{\n+\tif (!is_64_bit_mode(vcpu)) {\n+\t\tu32 eip = kvm_rip_read(vcpu);\n+\t\tu32 limit = to_svm(vcpu)-\u003evmcb-\u003esave.cs.limit;\n+\n+\t\tif (eip \u003e limit)\n+\t\t\treturn 0;\n+\t\tmax_bytes = min_t(u64, max_bytes, (u64)limit - eip + 1);\n+\t}\n+\n+\treturn max_bytes;\n+}\n+\n+static u8 nested_svm_fetch_insn_bytes(struct kvm_vcpu *vcpu, u8 *bytes,\n+\t\t\t\t u8 count, u8 max_bytes)\n+{\n+\tstruct x86_exception e;\n+\tgva_t rip = kvm_get_linear_rip(vcpu);\n+\n+\tmax_bytes = nested_svm_get_insn_bytes_len(vcpu, max_bytes);\n+\tcount = min(count, max_bytes);\n+\n+\twhile (count \u003c max_bytes) {\n+\t\tgva_t addr = rip + count;\n+\t\tu8 chunk;\n+\n+\t\tif (!is_64_bit_mode(vcpu))\n+\t\t\taddr = (u32)addr;\n+\t\telse if (is_noncanonical_address(addr, vcpu, 0))\n+\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * Read one page at a time to recheck the linear address at each\n+\t\t * boundary.\n+\t\t */\n+\t\tchunk = min_t(unsigned int, max_bytes - count,\n+\t\t\t PAGE_SIZE - offset_in_page(addr));\n+\t\tif (kvm_fetch_guest_virt(vcpu, addr, bytes + count,\n+\t\t\t\t\t chunk, \u0026e) != X86EMUL_CONTINUE)\n+\t\t\tbreak;\n+\t\tcount += chunk;\n+\t}\n+\n+\treturn count;\n+}\n+\n+static void nested_svm_prepare_synthesized_insn_bytes(struct kvm_vcpu *vcpu)\n+{\n+\tstruct vcpu_svm *svm = to_svm(vcpu);\n+\tstruct vmcb *vmcb02 = svm-\u003enested.vmcb02.ptr;\n+\tstruct x86_emulate_ctxt *ctxt = vcpu-\u003earch.emulate_ctxt;\n+\tu8 max_bytes = sizeof(vmcb02-\u003econtrol.insn_bytes);\n+\tu8 count;\n+\n+\tnested_svm_invalidate_vmcb02_insn_bytes(svm);\n+\n+\tif (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))\n+\t\treturn;\n+\n+\tmax_bytes = nested_svm_get_insn_bytes_len(vcpu, max_bytes);\n+\tcount = x86_emulator_copy_insn_bytes(ctxt, kvm_rip_read(vcpu),\n+\t\t\t\t\t vmcb02-\u003econtrol.insn_bytes, max_bytes);\n+\tif (!is_sev_guest(vcpu))\n+\t\tcount = nested_svm_fetch_insn_bytes(vcpu,\n+\t\t\t\t\t\t vmcb02-\u003econtrol.insn_bytes,\n+\t\t\t\t\t\t count, max_bytes);\n+\tvmcb02-\u003econtrol.insn_len = count;\n+\n+\tsvm-\u003enested.vmcb02_insn_bytes_valid = true;\n+}\n+\n+static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,\n+\t\t\t\t\t\tstruct vmcb *vmcb12,\n+\t\t\t\t\t\tconst struct vmcb *vmcb02)\n+{\n+\tstruct vcpu_svm *svm = to_svm(vcpu);\n+\tconst u8 max_bytes = sizeof(vmcb12-\u003econtrol.insn_bytes);\n+\n+\tif (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))\n+\t\tgoto out;\n+\n+\tif (!nested_svm_vmexit_supports_insn_bytes(vmcb02)) {\n+\t\tnested_svm_invalidate_insn_bytes(vmcb12);\n+\t\tgoto out;\n+\t}\n+\n+\tif (svm-\u003enested.vmcb02_insn_bytes_valid) {\n+\t\tnested_svm_copy_insn_bytes(vmcb12, vmcb02);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!is_sev_guest(vcpu))\n+\t\tvmcb12-\u003econtrol.insn_len =\n+\t\t\tnested_svm_fetch_insn_bytes(vcpu,\n+\t\t\t\t\t\t vmcb12-\u003econtrol.insn_bytes,\n+\t\t\t\t\t\t 0,\n+\t\t\t\t\t\t max_bytes);\n+\telse\n+\t\tnested_svm_invalidate_insn_bytes(vmcb12);\n+\n+out:\n+\tsvm-\u003enested.vmcb02_insn_bytes_valid = false;\n+}\n+\n static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,\n \t\t\t\t struct x86_exception *fault,\n \t\t\t\t bool from_hardware)\n {\n \tstruct vcpu_svm *svm = to_svm(vcpu);\n \tstruct vmcb *vmcb = svm-\u003evmcb;\n+\tstruct x86_emulate_ctxt *ctxt = vcpu-\u003earch.emulate_ctxt;\n+\tbool from_emulation = ctxt \u0026\u0026 fault == \u0026ctxt-\u003eexception;\n \tu64 fault_stage;\n \n \t/*\n@@ -68,6 +218,12 @@ static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,\n \t\t\t\t (fault-\u003eerror_code \u0026 ~PFERR_GUEST_FAULT_STAGE_MASK);\n \tvmcb-\u003econtrol.exit_info_2 = fault-\u003eaddress;\n \n+\tif (from_hardware)\n+\t\tnested_svm_set_vmcb02_insn_bytes_valid(svm);\n+\telse if (from_emulation \u0026\u0026 !(fault-\u003eerror_code \u0026 PFERR_FETCH_MASK))\n+\t\tnested_svm_prepare_synthesized_insn_bytes(vcpu);\n+\telse\n+\t\tsvm-\u003enested.vmcb02_insn_bytes_valid = false;\n \tnested_svm_vmexit(svm);\n }\n \n@@ -869,6 +1025,7 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)\n \t * Filled at exit: exit_code, exit_info_1, exit_info_2, exit_int_info,\n \t * exit_int_info_err, next_rip, insn_len, insn_bytes.\n \t */\n+\tnested_svm_invalidate_vmcb02_insn_bytes(svm);\n \n \tif (guest_cpu_cap_has(vcpu, X86_FEATURE_VGIF) \u0026\u0026\n \t (vmcb12_ctrl-\u003eint_ctl \u0026 V_GIF_ENABLE_MASK))\n@@ -1297,6 +1454,8 @@ static int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu)\n \tif (guest_cpu_cap_has(vcpu, X86_FEATURE_NRIPS))\n \t\tvmcb12-\u003econtrol.next_rip = vmcb02-\u003econtrol.next_rip;\n \n+\tnested_svm_update_vmcb12_insn_bytes(vcpu, vmcb12, vmcb02);\n+\n \tif (nested_vmcb12_has_lbrv(vcpu))\n \t\tsvm_copy_lbrs(\u0026vmcb12-\u003esave, \u0026vmcb02-\u003esave);\n \n@@ -1649,8 +1808,10 @@ int nested_svm_exit_handled(struct vcpu_svm *svm)\n \n \tvmexit = nested_svm_intercept(svm);\n \n-\tif (vmexit == NESTED_EXIT_DONE)\n+\tif (vmexit == NESTED_EXIT_DONE) {\n+\t\tnested_svm_set_vmcb02_insn_bytes_valid(svm);\n \t\tnested_svm_vmexit(svm);\n+\t}\n \n \treturn vmexit;\n }\ndiff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c\nindex ea647938a2a65..c91561a9a49f7 100644\n--- a/arch/x86/kvm/svm/svm.c\n+++ b/arch/x86/kvm/svm/svm.c\n@@ -4837,6 +4837,7 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,\n \tint vmexit, ret = X86EMUL_CONTINUE;\n \tstruct __x86_intercept icpt_info;\n \tstruct vmcb *vmcb = svm-\u003evmcb;\n+\tbool decode_assists;\n \n \tif (info-\u003eintercept \u003e= ARRAY_SIZE(x86_intercept_map))\n \t\tgoto out;\n@@ -4846,14 +4847,26 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,\n \tif (stage != icpt_info.stage)\n \t\tgoto out;\n \n+\tdecode_assists = guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS);\n+\n \tswitch (icpt_info.exit_code) {\n \tcase SVM_EXIT_READ_CR0:\n \t\tif (info-\u003eintercept == x86_intercept_cr_read)\n \t\t\ticpt_info.exit_code += info-\u003emodrm_reg;\n+\n+\t\tif (decode_assists)\n+\t\t\tvmcb-\u003econtrol.exit_info_1 =\n+\t\t\t\tinfo-\u003eintercept == x86_intercept_cr_read ?\n+\t\t\t\tBIT_ULL(63) | (info-\u003emodrm_rm \u0026 0xf) : 0;\n \t\tbreak;\n \tcase SVM_EXIT_WRITE_CR0: {\n \t\tunsigned long cr0, val;\n \n+\t\tif (decode_assists)\n+\t\t\tvmcb-\u003econtrol.exit_info_1 =\n+\t\t\t\tinfo-\u003eintercept == x86_intercept_cr_write ?\n+\t\t\t\tBIT_ULL(63) | (info-\u003emodrm_rm \u0026 0xf) : 0;\n+\n \t\t/*\n \t\t * Adjust the exit code accordingly if a CR other than CR0 is\n \t\t * being written, and skip straight to the common handling as\n@@ -4891,13 +4904,30 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,\n \t\t */\n \t\tcr0 = vcpu-\u003earch.cr0 \u0026 ~SVM_CR0_SELECTIVE_MASK;\n \t\tval = info-\u003esrc_val \u0026 ~SVM_CR0_SELECTIVE_MASK;\n-\t\tif (cr0 ^ val)\n+\t\tif (cr0 ^ val) {\n \t\t\ticpt_info.exit_code = SVM_EXIT_CR0_SEL_WRITE;\n+\t\t\tif (decode_assists)\n+\t\t\t\tvmcb-\u003econtrol.exit_info_1 = 0;\n+\t\t}\n \t\tbreak;\n \t}\n \tcase SVM_EXIT_READ_DR0:\n \tcase SVM_EXIT_WRITE_DR0:\n \t\ticpt_info.exit_code += info-\u003emodrm_reg;\n+\t\tif (decode_assists)\n+\t\t\tvmcb-\u003econtrol.exit_info_1 = info-\u003emodrm_rm \u0026 0xf;\n+\t\tbreak;\n+\tcase SVM_EXIT_SWINT:\n+\t\tif (decode_assists)\n+\t\t\tvmcb-\u003econtrol.exit_info_1 = info-\u003esrc_val \u0026 0xff;\n+\t\tbreak;\n+\tcase SVM_EXIT_INVLPG:\n+\t\tif (decode_assists)\n+\t\t\tvmcb-\u003econtrol.exit_info_1 = info-\u003einvlpg_linear_addr;\n+\t\tbreak;\n+\tcase SVM_EXIT_INVLPGA:\n+\t\tif (decode_assists)\n+\t\t\tvmcb-\u003econtrol.exit_info_1 = 0;\n \t\tbreak;\n \tcase SVM_EXIT_MSR:\n \t\tif (info-\u003eintercept == x86_intercept_wrmsr)\n@@ -5530,6 +5560,8 @@ static __init void svm_set_cpu_caps(void)\n \t\t */\n \t\tkvm_cpu_cap_set(X86_FEATURE_FLUSHBYASID);\n \n+\t\tkvm_cpu_cap_check_and_set(X86_FEATURE_DECODEASSISTS);\n+\n \t\tif (nrips)\n \t\t\tkvm_cpu_cap_set(X86_FEATURE_NRIPS);\n \ndiff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h\nindex e958943b81627..1e12ef1105c01 100644\n--- a/arch/x86/kvm/svm/svm.h\n+++ b/arch/x86/kvm/svm/svm.h\n@@ -243,6 +243,9 @@ struct svm_nested_state {\n \t * on its side.\n \t */\n \tbool force_msr_bitmap_recalc;\n+\n+\t/* True if VMCB02 has instruction bytes for the current nested exit. */\n+\tbool vmcb02_insn_bytes_valid;\n };\n \n struct vcpu_sev_es_state {\ndiff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c\nindex 4b3681796c752..735b67781d554 100644\n--- a/arch/x86/kvm/x86.c\n+++ b/arch/x86/kvm/x86.c\n@@ -4848,9 +4848,24 @@ static int kvm_read_guest_virt_helper(gva_t addr, void *val, unsigned int bytes,\n }\n \n /* used for instruction fetching */\n-static int kvm_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,\n-\t\t\t\tgva_t addr, void *val, unsigned int bytes,\n-\t\t\t\tstruct x86_exception *exception)\n+int kvm_fetch_guest_virt(struct kvm_vcpu *vcpu, gva_t addr, void *val,\n+\t\t\t unsigned int bytes, struct x86_exception *exception)\n+{\n+\tu64 access = PFERR_FETCH_MASK;\n+\n+\tif (kvm_x86_call(get_cpl)(vcpu) == 3)\n+\t\taccess |= PFERR_USER_MASK;\n+\n+\tmemset(exception, 0, sizeof(*exception));\n+\treturn kvm_read_guest_virt_helper(addr, val, bytes, vcpu, access,\n+\t\t\t\t\t exception);\n+}\n+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_fetch_guest_virt);\n+\n+static int emulator_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,\n+\t\t\t\t gva_t addr, void *val,\n+\t\t\t\t unsigned int bytes,\n+\t\t\t\t struct x86_exception *exception)\n {\n \tstruct kvm_vcpu *vcpu = emul_to_vcpu(ctxt);\n \tstruct kvm_pagewalk *gva_walk = \u0026vcpu-\u003earch.gva_walk;\n@@ -5817,7 +5832,7 @@ static const struct x86_emulate_ops emulate_ops = {\n \t.write_gpr = emulator_write_gpr,\n \t.read_std = emulator_read_std,\n \t.write_std = emulator_write_std,\n-\t.fetch = kvm_fetch_guest_virt,\n+\t.fetch = emulator_fetch_guest_virt,\n \t.read_emulated = emulator_read_emulated,\n \t.write_emulated = emulator_write_emulated,\n \t.cmpxchg_emulated = emulator_cmpxchg_emulated,\ndiff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h\nindex 0f5919b092e47..f03419a79a118 100644\n--- a/arch/x86/kvm/x86.h\n+++ b/arch/x86/kvm/x86.h\n@@ -356,6 +356,9 @@ static inline void adjust_tsc_offset_host(struct kvm_vcpu *vcpu, s64 adjustment)\n int kvm_read_guest_virt(struct kvm_vcpu *vcpu,\n \tgva_t addr, void *val, unsigned int bytes,\n \tstruct x86_exception *exception);\n+int kvm_fetch_guest_virt(struct kvm_vcpu *vcpu, gva_t addr, void *val,\n+\t\t\t unsigned int bytes,\n+\t\t\t struct x86_exception *exception);\n \n int kvm_write_guest_virt_system(struct kvm_vcpu *vcpu,\n \tgva_t addr, void *val, unsigned int bytes,\ndiff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm\nindex 96bab7002d39e..bb55a8c32c420 100644\n--- a/tools/testing/selftests/kvm/Makefile.kvm\n+++ b/tools/testing/selftests/kvm/Makefile.kvm\n@@ -118,6 +118,7 @@ TEST_GEN_PROGS_x86 += x86/vmx_preemption_timer_test\n TEST_GEN_PROGS_x86 += x86/svm_vmcall_test\n TEST_GEN_PROGS_x86 += x86/svm_int_ctl_test\n TEST_GEN_PROGS_x86 += x86/svm_nested_clear_efer_svme\n+TEST_GEN_PROGS_x86 += x86/svm_nested_decode_assists_test\n TEST_GEN_PROGS_x86 += x86/svm_nested_shutdown_test\n TEST_GEN_PROGS_x86 += x86/svm_nested_soft_inject_test\n TEST_GEN_PROGS_x86 += x86/svm_nested_vmcb12_gpa\ndiff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h\nindex 6e6f70035508a..e9d745a2c1313 100644\n--- a/tools/testing/selftests/kvm/include/x86/processor.h\n+++ b/tools/testing/selftests/kvm/include/x86/processor.h\n@@ -220,6 +220,7 @@ struct kvm_x86_cpu_feature {\n #define\tX86_FEATURE_LBRV\t\tKVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 1)\n #define\tX86_FEATURE_NRIPS\t\tKVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 3)\n #define X86_FEATURE_TSCRATEMSR KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 4)\n+#define X86_FEATURE_DECODEASSISTS KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 7)\n #define X86_FEATURE_PAUSEFILTER KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 10)\n #define X86_FEATURE_PFTHRESHOLD KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 12)\n #define\tX86_FEATURE_V_VMSAVE_VMLOAD\tKVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 15)\ndiff --git a/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c b/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c\nnew file mode 100644\nindex 0000000000000..9f729e8542f2c\n--- /dev/null\n+++ b/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c\n@@ -0,0 +1,547 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * Test KVM's virtualization of SVM DecodeAssists for nested guests.\n+ */\n+\n+#include \"test_util.h\"\n+#include \"kvm_util.h\"\n+#include \"processor.h\"\n+#include \"svm_util.h\"\n+\n+#define TEST_INT_VECTOR 0x81\n+\n+/* Any canonical virtual address that is never mapped by the selftest VM. */\n+#define PF_TEST_GVA BIT_ULL(40)\n+#define PF_FETCH_TEST_GVA BIT_ULL(41)\n+\n+#define OUTSB_OPCODE 0x6e\n+#define MOVSB_OPCODE 0xa4\n+#define NOP_OPCODE 0x90\n+#define BOUNDARY_OUTSB_CODE_SIZE 15\n+#define TEST_IOPM_SIZE (3 * PAGE_SIZE)\n+\n+static u8 npf_target[PAGE_SIZE] __aligned(PAGE_SIZE);\n+static u8 mmio_source[PAGE_SIZE] __aligned(PAGE_SIZE);\n+static u8 boundary_outsb_code[2 * PAGE_SIZE] __aligned(PAGE_SIZE);\n+\n+static void l2_read_code(void)\n+{\n+\tasm volatile(\"mov (%0), %%rax\" : : \"r\"(\u0026npf_target) : \"rax\", \"memory\");\n+\tGUEST_FAIL(\"L2 read did not cause a nested page fault\");\n+}\n+\n+static void l2_outsb_code(void)\n+{\n+\tasm volatile(\"mov %0, %%rsi\\n\\t\"\n+\t\t \"mov $0x80, %%dx\\n\\t\"\n+\t\t \"outsb\"\n+\t\t : : \"r\"(\u0026npf_target) : \"rsi\", \"rdx\", \"memory\");\n+\tGUEST_FAIL(\"L2 OUTSB did not cause a nested page fault\");\n+}\n+\n+extern u8 l2_movsb_insn[];\n+\n+static void l2_movsb_code(void)\n+{\n+\tasm volatile(\"mov %0, %%rsi\\n\\t\"\n+\t\t \"mov %1, %%rdi\\n\\t\"\n+\t\t \"l2_movsb_insn:\\n\\t\"\n+\t\t \"movsb\"\n+\t\t : : \"r\"(\u0026mmio_source), \"r\"(\u0026npf_target)\n+\t\t : \"rsi\", \"rdi\", \"memory\");\n+\tGUEST_FAIL(\"L2 MOVSB did not cause a nested page fault\");\n+}\n+\n+extern u8 l2_userspace_pf_insn[];\n+\n+static void l2_userspace_pf_code(void)\n+{\n+\tasm volatile(\"movzbl (%0), %%eax\\n\\t\"\n+\t\t \"l2_userspace_pf_insn:\\n\\t\"\n+\t\t \"nop\"\n+\t\t : : \"r\"(\u0026mmio_source) : \"rax\", \"memory\");\n+\tGUEST_FAIL(\"Userspace-injected #PF was not intercepted by L1\");\n+}\n+\n+static void l2_pf_code(void)\n+{\n+\tasm volatile(\"mov (%0), %%rax\"\n+\t\t : : \"r\"(PF_TEST_GVA) : \"rax\", \"memory\");\n+\tGUEST_FAIL(\"L2 access to an unmapped VA did not #PF\");\n+}\n+\n+static void l2_fep_pf_code(void)\n+{\n+\tasm volatile(KVM_FEP \"mov (%0), %%rax\"\n+\t\t : : \"r\"(PF_TEST_GVA) : \"rax\", \"memory\");\n+\tGUEST_FAIL(\"L2 forced-emulated access to an unmapped VA did not #PF\");\n+}\n+\n+static void l2_fep_mov_from_cr4_code(void)\n+{\n+\tasm volatile(KVM_FEP \"mov %%cr4, %%r10\" : : : \"r10\");\n+\tGUEST_FAIL(\"L2 forced-emulated MOV-from-CR4 was not intercepted\");\n+}\n+\n+static void l2_fep_mov_to_dr7_code(void)\n+{\n+\tasm volatile(\"mov %%dr7, %%rax\\n\\t\"\n+\t\t \"mov %%rax, %%rbx\\n\\t\"\n+\t\t KVM_FEP \"mov %%rbx, %%dr7\" : : : \"rax\", \"rbx\");\n+\tGUEST_FAIL(\"L2 forced-emulated MOV-to-DR7 was not intercepted\");\n+}\n+\n+static void l2_fep_clts_code(void)\n+{\n+\tasm volatile(KVM_FEP \"clts\" : : : \"memory\");\n+\tGUEST_FAIL(\"L2 forced-emulated CLTS was not intercepted\");\n+}\n+\n+static void l2_fep_int_code(void)\n+{\n+\tasm volatile(KVM_FEP \"int %0\" : : \"i\"(TEST_INT_VECTOR));\n+\tGUEST_FAIL(\"L2 forced-emulated INTn was not intercepted\");\n+}\n+\n+static void l2_fep_invlpg_code(void)\n+{\n+\tasm volatile(KVM_FEP \"invlpg (%0)\" : : \"r\"(\u0026npf_target) : \"memory\");\n+\tGUEST_FAIL(\"L2 forced-emulated INVLPG was not intercepted\");\n+}\n+\n+static void l2_fep_invlpga_code(void)\n+{\n+\tasm volatile(KVM_FEP \"invlpga\"\n+\t\t : : \"a\"(\u0026npf_target), \"c\"(0) : \"memory\");\n+\tGUEST_FAIL(\"L2 forced-emulated INVLPGA was not intercepted\");\n+}\n+\n+struct instruction_intercept_test {\n+\tconst char *name;\n+\tvoid (*code)(void);\n+\tu64 intercept;\n+\tu32 intercept_cr;\n+\tu32 intercept_dr;\n+\tu64 exit_code;\n+\tu64 exit_info_1;\n+\tu64 exit_info_1_mask;\n+\tbool check_rax;\n+\tu64 rax;\n+};\n+\n+static const struct instruction_intercept_test instruction_intercept_tests[] = {\n+\t{\n+\t\t.name = \"MOV-from-CR4\",\n+\t\t.code = l2_fep_mov_from_cr4_code,\n+\t\t.intercept_cr = BIT(INTERCEPT_CR4_READ),\n+\t\t.exit_code = SVM_EXIT_READ_CR4,\n+\t\t.exit_info_1 = BIT_ULL(63) | 10,\n+\t\t.exit_info_1_mask = ~0ULL,\n+\t}, {\n+\t\t.name = \"MOV-to-DR7\",\n+\t\t.code = l2_fep_mov_to_dr7_code,\n+\t\t.intercept_dr = BIT(INTERCEPT_DR7_WRITE),\n+\t\t.exit_code = SVM_EXIT_WRITE_DR7,\n+\t\t.exit_info_1 = 3,\n+\t\t.exit_info_1_mask = ~0ULL,\n+\t}, {\n+\t\t.name = \"CLTS\",\n+\t\t.code = l2_fep_clts_code,\n+\t\t.intercept_cr = BIT(INTERCEPT_CR0_WRITE),\n+\t\t.exit_code = SVM_EXIT_WRITE_CR0,\n+\t\t.exit_info_1_mask = BIT_ULL(63),\n+\t}, {\n+\t\t.name = \"INTn\",\n+\t\t.code = l2_fep_int_code,\n+\t\t.intercept = BIT_ULL(INTERCEPT_INTn),\n+\t\t.exit_code = SVM_EXIT_SWINT,\n+\t\t.exit_info_1 = TEST_INT_VECTOR,\n+\t\t.exit_info_1_mask = ~0ULL,\n+\t}, {\n+\t\t.name = \"INVLPG\",\n+\t\t.code = l2_fep_invlpg_code,\n+\t\t.intercept = BIT_ULL(INTERCEPT_INVLPG),\n+\t\t.exit_code = SVM_EXIT_INVLPG,\n+\t\t.exit_info_1 = (u64)\u0026npf_target,\n+\t\t.exit_info_1_mask = ~0ULL,\n+\t}, {\n+\t\t.name = \"INVLPGA\",\n+\t\t.code = l2_fep_invlpga_code,\n+\t\t.intercept = BIT_ULL(INTERCEPT_INVLPGA),\n+\t\t.exit_code = SVM_EXIT_INVLPGA,\n+\t\t.exit_info_1_mask = ~0ULL,\n+\t\t.check_rax = true,\n+\t\t.rax = (u64)\u0026npf_target,\n+\t},\n+};\n+\n+static void assert_decode_assist_insn_bytes(struct vmcb *vmcb)\n+{\n+\tGUEST_ASSERT(vmcb-\u003econtrol.insn_len);\n+\tGUEST_ASSERT(vmcb-\u003econtrol.insn_len \u003c=\n+\t\t sizeof(vmcb-\u003econtrol.insn_bytes));\n+\tGUEST_ASSERT(!memcmp(vmcb-\u003econtrol.insn_bytes,\n+\t\t\t (void *)vmcb-\u003esave.rip,\n+\t\t\t vmcb-\u003econtrol.insn_len));\n+}\n+\n+static void assert_full_decode_assist_insn_bytes(struct vmcb *vmcb)\n+{\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.insn_len,\n+\t\t\tsizeof(vmcb-\u003econtrol.insn_bytes));\n+\tassert_decode_assist_insn_bytes(vmcb);\n+}\n+\n+static void prepare_l2_for_vmrun(struct svm_test_data *svm, gva_t rip)\n+{\n+\tstruct vmcb *vmcb = svm-\u003evmcb;\n+\n+\tvmcb-\u003esave.rip = rip;\n+\tvmcb-\u003esave.rsp = (u64)svm-\u003estack;\n+}\n+\n+static void run_intercept_test(struct svm_test_data *svm,\n+\t\t\t const struct instruction_intercept_test *test)\n+{\n+\tstruct vmcb *vmcb = svm-\u003evmcb;\n+\tstruct vmcb_control_area *control = \u0026vmcb-\u003econtrol;\n+\tu64 expected_exit_info_1 = test-\u003eexit_info_1 \u0026 test-\u003eexit_info_1_mask;\n+\n+\tcontrol-\u003eintercept |= test-\u003eintercept;\n+\tcontrol-\u003eintercept_cr |= test-\u003eintercept_cr;\n+\tcontrol-\u003eintercept_dr |= test-\u003eintercept_dr;\n+\n+\tcontrol-\u003eexit_info_1 = ~0ULL;\n+\tprepare_l2_for_vmrun(svm, (u64)test-\u003ecode);\n+\n+\trun_guest(vmcb, svm-\u003evmcb_gpa);\n+\n+\t__GUEST_ASSERT(control-\u003eexit_code == test-\u003eexit_code,\n+\t\t \"%s: expected exit code %#lx, got %#lx\",\n+\t\t test-\u003ename, (unsigned long)test-\u003eexit_code,\n+\t\t (unsigned long)control-\u003eexit_code);\n+\t__GUEST_ASSERT((control-\u003eexit_info_1 \u0026 test-\u003eexit_info_1_mask) ==\n+\t\t expected_exit_info_1,\n+\t\t \"%s: expected EXITINFO1 %#lx with mask %#lx, got %#lx\",\n+\t\t test-\u003ename, (unsigned long)expected_exit_info_1,\n+\t\t (unsigned long)test-\u003eexit_info_1_mask,\n+\t\t (unsigned long)control-\u003eexit_info_1);\n+\t__GUEST_ASSERT(!control-\u003einsn_len,\n+\t\t \"%s: expected no instruction bytes, got %u\",\n+\t\t test-\u003ename, control-\u003einsn_len);\n+\n+\tif (test-\u003echeck_rax)\n+\t\t__GUEST_ASSERT(vmcb-\u003esave.rax == test-\u003erax,\n+\t\t\t \"%s: expected rAX %#lx, got %#lx\",\n+\t\t\t test-\u003ename, (unsigned long)test-\u003erax,\n+\t\t\t (unsigned long)vmcb-\u003esave.rax);\n+\n+\tcontrol-\u003eintercept \u0026= ~test-\u003eintercept;\n+\tcontrol-\u003eintercept_cr \u0026= ~test-\u003eintercept_cr;\n+\tcontrol-\u003eintercept_dr \u0026= ~test-\u003eintercept_dr;\n+}\n+\n+static void test_instruction_intercepts(struct svm_test_data *svm)\n+{\n+\tint i;\n+\n+\tif (!is_forced_emulation_enabled)\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(instruction_intercept_tests); i++)\n+\t\trun_intercept_test(svm, \u0026instruction_intercept_tests[i]);\n+}\n+\n+static void test_hardware_npf(struct svm_test_data *svm, gpa_t npf_gpa)\n+{\n+\tstruct vmcb *vmcb = svm-\u003evmcb;\n+\n+\tprepare_l2_for_vmrun(svm, (u64)l2_read_code);\n+\trun_guest(vmcb, svm-\u003evmcb_gpa);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_code, SVM_EXIT_NPF);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_info_2, npf_gpa);\n+\tassert_decode_assist_insn_bytes(vmcb);\n+}\n+\n+/*\n+ * The IOIO intercept causes L0 to emulate OUTSB before accessing its source\n+ * operand. The emulated read then faults on L1's NPT, resulting in a\n+ * KVM-synthesized #NPF.\n+ */\n+static void test_synthesized_npf(struct svm_test_data *svm, gpa_t npf_gpa)\n+{\n+\tstruct vmcb *vmcb = svm-\u003evmcb;\n+\n+\tprepare_l2_for_vmrun(svm, (u64)l2_outsb_code);\n+\trun_guest(vmcb, svm-\u003evmcb_gpa);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_code, SVM_EXIT_NPF);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_info_2, npf_gpa);\n+\tassert_full_decode_assist_insn_bytes(vmcb);\n+}\n+\n+/*\n+ * MOVSB first reads from MMIO, causing a hardware #NPF that L0 emulates.\n+ * After userspace completes the read, the emulated destination write faults\n+ * on L1's NPT. The new #NPF must not reuse the original hardware exit's GPA.\n+ * Userspace replaces MOVSB with NOP while the read is pending. DecodeAssist\n+ * must retain the cached MOVSB bytes instead of re-reading the opcode.\n+ */\n+static void test_synthesized_npf_after_hardware_npf(struct svm_test_data *svm,\n+\t\t\t\t\t\t gpa_t npf_gpa)\n+{\n+\tstruct vmcb *vmcb = svm-\u003evmcb;\n+\tu8 insn_bytes[sizeof(vmcb-\u003econtrol.insn_bytes)];\n+\n+\tmemcpy(insn_bytes, l2_movsb_insn, sizeof(insn_bytes));\n+\tGUEST_ASSERT_EQ(insn_bytes[0], MOVSB_OPCODE);\n+\tprepare_l2_for_vmrun(svm, (u64)l2_movsb_code);\n+\trun_guest(vmcb, svm-\u003evmcb_gpa);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_code, SVM_EXIT_NPF);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_info_2, npf_gpa);\n+\tGUEST_ASSERT_EQ(vmcb-\u003esave.rip, (u64)l2_movsb_insn);\n+\tGUEST_ASSERT_EQ(l2_movsb_insn[0], NOP_OPCODE);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.insn_len, sizeof(insn_bytes));\n+\tGUEST_ASSERT(!memcmp(vmcb-\u003econtrol.insn_bytes, insn_bytes,\n+\t\t\t sizeof(insn_bytes)));\n+}\n+\n+/*\n+ * OUTSB is the final byte of a mapped code page, and the following page is\n+ * not present in L2's page tables. DecodeAssist byte fetching must stop at\n+ * the page boundary and report only the OUTSB opcode.\n+ */\n+static void test_synthesized_npf_truncated(struct svm_test_data *svm,\n+\t\t\t\t\t gpa_t npf_gpa)\n+{\n+\tstruct vmcb *vmcb = svm-\u003evmcb;\n+\n+\tprepare_l2_for_vmrun(svm,\n+\t\t\t (u64)\u0026boundary_outsb_code[PAGE_SIZE -\n+\t\t\t\t\t\t BOUNDARY_OUTSB_CODE_SIZE]);\n+\trun_guest(vmcb, svm-\u003evmcb_gpa);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_code, SVM_EXIT_NPF);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_info_2, npf_gpa);\n+\tGUEST_ASSERT_EQ(vmcb-\u003esave.rip,\n+\t\t\t(u64)\u0026boundary_outsb_code[PAGE_SIZE - 1]);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.insn_len, 1);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.insn_bytes[0], OUTSB_OPCODE);\n+}\n+\n+static void test_hardware_intercepted_pf(struct svm_test_data *svm)\n+{\n+\tstruct vmcb *vmcb = svm-\u003evmcb;\n+\n+\tprepare_l2_for_vmrun(svm, (u64)l2_pf_code);\n+\trun_guest(vmcb, svm-\u003evmcb_gpa);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_info_2, PF_TEST_GVA);\n+\tGUEST_ASSERT(!(vmcb-\u003econtrol.exit_info_1 \u0026 PFERR_PRESENT_MASK));\n+\tGUEST_ASSERT(!(vmcb-\u003econtrol.exit_info_1 \u0026 PFERR_FETCH_MASK));\n+\tassert_decode_assist_insn_bytes(vmcb);\n+}\n+\n+static void test_hardware_intercepted_fetch_pf(struct svm_test_data *svm)\n+{\n+\tstruct vmcb *vmcb = svm-\u003evmcb;\n+\n+\tprepare_l2_for_vmrun(svm, PF_FETCH_TEST_GVA);\n+\trun_guest(vmcb, svm-\u003evmcb_gpa);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_info_2, PF_FETCH_TEST_GVA);\n+\tGUEST_ASSERT(!(vmcb-\u003econtrol.exit_info_1 \u0026 PFERR_PRESENT_MASK));\n+\tGUEST_ASSERT(vmcb-\u003econtrol.exit_info_1 \u0026 PFERR_FETCH_MASK);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.insn_len, 0);\n+}\n+\n+static void test_synthesized_pf(struct svm_test_data *svm)\n+{\n+\tstruct vmcb *vmcb = svm-\u003evmcb;\n+\n+\tif (!is_forced_emulation_enabled)\n+\t\treturn;\n+\n+\tprepare_l2_for_vmrun(svm, (u64)l2_fep_pf_code);\n+\trun_guest(vmcb, svm-\u003evmcb_gpa);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_info_2, PF_TEST_GVA);\n+\tGUEST_ASSERT(!(vmcb-\u003econtrol.exit_info_1 \u0026 PFERR_PRESENT_MASK));\n+\tGUEST_ASSERT(!(vmcb-\u003econtrol.exit_info_1 \u0026 PFERR_FETCH_MASK));\n+\tassert_full_decode_assist_insn_bytes(vmcb);\n+}\n+\n+/* The pending MMIO instruction must complete before reflecting userspace #PF. */\n+static void test_userspace_injected_pf_during_emulation(struct svm_test_data *svm)\n+{\n+\tstruct vmcb *vmcb = svm-\u003evmcb;\n+\n+\tprepare_l2_for_vmrun(svm, (u64)l2_userspace_pf_code);\n+\trun_guest(vmcb, svm-\u003evmcb_gpa);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_info_1, 0);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.exit_info_2, PF_TEST_GVA);\n+\tGUEST_ASSERT_EQ(vmcb-\u003esave.rip, (u64)l2_userspace_pf_insn);\n+\tGUEST_ASSERT_EQ(vmcb-\u003esave.rax, 0x5a);\n+\tassert_full_decode_assist_insn_bytes(vmcb);\n+\tGUEST_ASSERT_EQ(vmcb-\u003econtrol.insn_bytes[0], NOP_OPCODE);\n+}\n+\n+static void l1_guest_code(struct svm_test_data *svm, gpa_t npf_gpa,\n+\t\t\t gpa_t iopm_gpa)\n+{\n+\tstruct vmcb *vmcb = svm-\u003evmcb;\n+\n+\tGUEST_ASSERT(this_cpu_has(X86_FEATURE_DECODEASSISTS));\n+\n+\tgeneric_svm_setup(svm, l2_read_code);\n+\tvmcb-\u003econtrol.iopm_base_pa = iopm_gpa;\n+\n+\tvmcb-\u003econtrol.intercept |= BIT_ULL(INTERCEPT_IOIO_PROT);\n+\tvmcb-\u003econtrol.intercept_exceptions |= 1U \u003c\u003c PF_VECTOR;\n+\n+\ttest_hardware_npf(svm, npf_gpa);\n+\ttest_synthesized_npf(svm, npf_gpa);\n+\ttest_synthesized_npf_after_hardware_npf(svm, npf_gpa);\n+\ttest_synthesized_npf_truncated(svm, npf_gpa);\n+\ttest_hardware_intercepted_pf(svm);\n+\ttest_hardware_intercepted_fetch_pf(svm);\n+\ttest_synthesized_pf(svm);\n+\ttest_userspace_injected_pf_during_emulation(svm);\n+\ttest_instruction_intercepts(svm);\n+\n+\tGUEST_DONE();\n+}\n+\n+static void build_boundary_outsb_code(u8 *code)\n+{\n+\tu64 source = (u64)\u0026npf_target;\n+\n+\t/* movabs $npf_target, %rsi */\n+\tcode[0] = 0x48;\n+\tcode[1] = 0xbe;\n+\tmemcpy(\u0026code[2], \u0026source, sizeof(source));\n+\n+\t/* mov $0x80, %dx; outsb */\n+\tcode[10] = 0x66;\n+\tcode[11] = 0xba;\n+\tcode[12] = 0x80;\n+\tcode[13] = 0x00;\n+\tcode[14] = OUTSB_OPCODE;\n+}\n+\n+static void prepare_boundary_outsb_code(struct kvm_vm *vm)\n+{\n+\tgva_t code_gva = (gva_t)\u0026boundary_outsb_code[PAGE_SIZE -\n+\t\t\t\t\t\t BOUNDARY_OUTSB_CODE_SIZE];\n+\n+\tbuild_boundary_outsb_code(addr_gva2hva(vm, code_gva));\n+}\n+\n+static void queue_userspace_pf(struct kvm_vcpu *vcpu)\n+{\n+\tstruct kvm_vcpu_events events;\n+\n+\tvcpu_events_get(vcpu, \u0026events);\n+\tTEST_ASSERT(!events.exception.pending \u0026\u0026 !events.exception.injected,\n+\t\t \"Unexpected exception before userspace #PF injection\");\n+\tTEST_ASSERT(events.flags \u0026 KVM_VCPUEVENT_VALID_PAYLOAD,\n+\t\t \"KVM_CAP_EXCEPTION_PAYLOAD was not enabled\");\n+\n+\tevents.exception.pending = true;\n+\tevents.exception.nr = PF_VECTOR;\n+\tevents.exception.has_error_code = true;\n+\tevents.exception.error_code = 0;\n+\tevents.exception_has_payload = true;\n+\tevents.exception_payload = PF_TEST_GVA;\n+\tvcpu_events_set(vcpu, \u0026events);\n+}\n+\n+static void complete_mmio_read(struct kvm_vcpu *vcpu, gpa_t expected_gpa,\n+\t\t\t u8 value)\n+{\n+\tif (vcpu-\u003erun-\u003eexit_reason == KVM_EXIT_IO) {\n+\t\tstruct ucall uc;\n+\n+\t\tif (get_ucall(vcpu, \u0026uc) == UCALL_ABORT)\n+\t\t\tREPORT_GUEST_ASSERT(uc);\n+\t}\n+\n+\tTEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);\n+\tTEST_ASSERT(!vcpu-\u003erun-\u003emmio.is_write,\n+\t\t \"Expected an MMIO read, got a write\");\n+\tTEST_ASSERT_EQ(vcpu-\u003erun-\u003emmio.phys_addr, expected_gpa);\n+\tTEST_ASSERT_EQ(vcpu-\u003erun-\u003emmio.len, 1);\n+\tvcpu-\u003erun-\u003emmio.data[0] = value;\n+}\n+\n+static void assert_ucall_done(struct kvm_vcpu *vcpu)\n+{\n+\tstruct ucall uc;\n+\tu64 actual;\n+\n+\tTEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);\n+\tactual = get_ucall(vcpu, \u0026uc);\n+\tif (actual == UCALL_ABORT)\n+\t\tREPORT_GUEST_ASSERT(uc);\n+\n+\tTEST_ASSERT_EQ(actual, UCALL_DONE);\n+}\n+\n+int main(int argc, char *argv[])\n+{\n+\tgva_t svm_gva, npf_gva, boundary_page_gva, iopm_gva;\n+\tgpa_t npf_gpa, mmio_source_gpa, mmio_gpa, iopm_gpa;\n+\tstruct userspace_mem_region *region;\n+\tstruct kvm_vcpu *vcpu;\n+\tstruct kvm_vm *vm;\n+\tu8 *movsb_insn;\n+\tu64 *pte;\n+\n+\tTEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));\n+\tTEST_REQUIRE(kvm_cpu_has(X86_FEATURE_NPT));\n+\tTEST_REQUIRE(this_cpu_has(X86_FEATURE_DECODEASSISTS));\n+\tTEST_ASSERT(kvm_cpu_has(X86_FEATURE_DECODEASSISTS),\n+\t\t \"KVM failed to expose DecodeAssists\");\n+\tTEST_REQUIRE(kvm_has_cap(KVM_CAP_EXCEPTION_PAYLOAD));\n+\n+\tvm = vm_create_with_one_vcpu(\u0026vcpu, l1_guest_code);\n+\tvm_enable_cap(vm, KVM_CAP_EXCEPTION_PAYLOAD, 1);\n+\tprepare_boundary_outsb_code(vm);\n+\tvm_enable_npt(vm);\n+\tvcpu_alloc_svm(vm, \u0026svm_gva);\n+\tiopm_gva = vm_alloc_pages(vm, TEST_IOPM_SIZE / PAGE_SIZE);\n+\tiopm_gpa = addr_gva2gpa(vm, iopm_gva);\n+\tmemset(addr_gva2hva(vm, iopm_gva), 0, TEST_IOPM_SIZE);\n+\tnpf_gva = (gva_t)\u0026npf_target;\n+\tnpf_gpa = addr_gva2gpa(vm, npf_gva);\n+\n+\ttdp_identity_map_default_memslots(vm);\n+\tpte = tdp_get_pte(vm, npf_gpa);\n+\t*pte \u0026= ~PTE_PRESENT_MASK(\u0026vm-\u003estage2_mmu);\n+\tregion = memslot2region(vm, 0);\n+\tmmio_gpa = region-\u003eregion.guest_phys_addr +\n+\t\t region-\u003eregion.memory_size + PAGE_SIZE;\n+\tmmio_source_gpa = addr_gva2gpa(vm, (gva_t)\u0026mmio_source);\n+\tpte = tdp_get_pte(vm, mmio_source_gpa);\n+\t*pte = (*pte \u0026 ~PHYSICAL_PAGE_MASK) | mmio_gpa;\n+\n+\tboundary_page_gva = (gva_t)\u0026boundary_outsb_code[PAGE_SIZE];\n+\tpte = vm_get_pte(vm, boundary_page_gva);\n+\t*pte \u0026= ~PTE_PRESENT_MASK(\u0026vm-\u003emmu);\n+\n+\tvcpu_args_set(vcpu, 3, svm_gva, npf_gpa, iopm_gpa);\n+\n+\t/* Complete the MOVSB source read. */\n+\tvcpu_run(vcpu);\n+\tcomplete_mmio_read(vcpu, mmio_gpa, 0xa5);\n+\tmovsb_insn = addr_gva2hva(vm, (gva_t)l2_movsb_insn);\n+\tTEST_ASSERT_EQ(movsb_insn[0], MOVSB_OPCODE);\n+\tmovsb_insn[0] = NOP_OPCODE;\n+\tvcpu_run(vcpu);\n+\tcomplete_mmio_read(vcpu, mmio_gpa, 0x5a);\n+\tqueue_userspace_pf(vcpu);\n+\tvcpu_run(vcpu);\n+\tassert_ucall_done(vcpu);\n+\n+\tkvm_vm_free(vm);\n+\treturn 0;\n+}\n"
} |
| 2/1 | 2026/09/13 07:01 | agent | patch-evaluator |
1mModel:core Args: null Results: {
"EnableConfigs": [
"KVM",
"KVM_AMD"
],
"FocusSymbols": [
"nested_svm_update_vmcb12_insn_bytes",
"nested_svm_fetch_insn_bytes",
"nested_svm_prepare_synthesized_insn_bytes",
"nested_svm_copy_insn_bytes",
"nested_svm_exit_handled",
"svm_check_intercept",
"x86_emulator_copy_insn_bytes",
"kvm_fetch_guest_virt"
],
"Reasoning": "The patch series implements AMD SVM nested virtualization support for DecodeAssists (X86_FEATURE_DECODEASSISTS). It modifies reachable code in KVM and the nested SVM subsystem to supply instruction bytes and decode assist information (e.g. exit_info_1 for intercepted instructions, guest instruction bytes for nested page faults). It includes new guest memory fetching and emulation cache extraction logic (kvm_fetch_guest_virt, x86_emulator_copy_insn_bytes, nested_svm_fetch_insn_bytes) and adds runtime checks (WARN_ON_ONCE in nested_svm_copy_insn_bytes). This reachable virtualization code is exposed via /dev/kvm and warrants fuzzing.",
"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 84b93f2b93403c96ba08905c3de300acf68146ab
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Sep 13 07:01:03 2026 +0000
syz-cluster: applied patch under review
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index ddb022cb203a2..b644e45eac710 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -1208,6 +1208,7 @@ void kvm_initialize_cpu_caps(void)
VENDOR_F(NPT),
VENDOR_F(VMCBCLEAN),
VENDOR_F(FLUSHBYASID),
+ VENDOR_F(DECODEASSISTS),
VENDOR_F(NRIPS),
VENDOR_F(TSCRATEMSR),
VENDOR_F(V_VMSAVE_VMLOAD),
diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
index 8071b372d233e..5017a9aa1d4bb 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -410,6 +410,9 @@ static int em_salc(struct x86_emulate_ctxt *ctxt)
_fault ? X86EMUL_UNHANDLEABLE : X86EMUL_CONTINUE; \
})
+static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,
+ enum x86_intercept intercept);
+
static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
enum x86_intercept intercept,
enum x86_intercept_stage stage)
@@ -427,6 +430,7 @@ static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
.src_type = ctxt->src.type,
.dst_type = ctxt->dst.type,
.ad_bytes = ctxt->ad_bytes,
+ .invlpg_linear_addr = get_invlpg_linear_addr(ctxt, intercept),
.rip = ctxt->eip,
.next_rip = ctxt->_eip,
};
@@ -702,6 +706,20 @@ static __always_inline int __linearize(struct x86_emulate_ctxt *ctxt,
return emulate_gp(ctxt, 0);
}
+static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,
+ enum x86_intercept intercept)
+{
+ unsigned int max_size;
+ unsigned long linear = 0;
+
+ if (intercept != x86_intercept_invlpg)
+ return 0;
+
+ __linearize(ctxt, ctxt->src.addr.mem, &max_size, 1, ctxt->mode,
+ &linear, X86EMUL_F_INVLPG);
+ return linear;
+}
+
static int linearize(struct x86_emulate_ctxt *ctxt,
struct segmented_address addr,
unsigned size, bool write,
@@ -895,6 +913,28 @@ static __always_inline int do_insn_fetch_bytes(struct x86_emulate_ctxt *ctxt,
return X86EMUL_CONTINUE;
}
+/*
+ * The caller is responsible for ensuring that the fetch cache belongs to
+ * the current instruction. Matching EIP alone does not guarantee this.
+ */
+unsigned int x86_emulator_copy_insn_bytes(struct x86_emulate_ctxt *ctxt,
+ unsigned long eip, u8 *bytes,
+ unsigned int max_bytes)
+{
+ unsigned int nr_bytes;
+
+ if (!ctxt || ctxt->eip != eip ||
+ ctxt->fetch.end < ctxt->fetch.data ||
+ ctxt->fetch.end > ctxt->fetch.data + sizeof(ctxt->fetch.data))
+ return 0;
+
+ nr_bytes = min_t(unsigned int, ctxt->fetch.end - ctxt->fetch.data,
+ max_bytes);
+ memcpy(bytes, ctxt->fetch.data, nr_bytes);
+ return nr_bytes;
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(x86_emulator_copy_insn_bytes);
+
/* Fetch next part of the instruction being emulated. */
#define insn_fetch(_type, _ctxt) \
({ _type _x; \
diff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h
index 3e375af15c035..5361d42e6e057 100644
--- a/arch/x86/kvm/kvm_emulate.h
+++ b/arch/x86/kvm/kvm_emulate.h
@@ -51,6 +51,7 @@ struct x86_instruction_info {
u8 src_type; /* type of source operand */
u8 dst_type; /* type of destination operand */
u8 ad_bytes; /* size of src/dst address */
+ u64 invlpg_linear_addr; /* linear address, if INVLPG */
u64 rip; /* rip of the instruction */
u64 next_rip; /* rip following the instruction */
};
@@ -527,6 +528,9 @@ enum x86_intercept {
};
int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int emulation_type);
+unsigned int x86_emulator_copy_insn_bytes(struct x86_emulate_ctxt *ctxt,
+ unsigned long eip, u8 *bytes,
+ unsigned int max_bytes);
bool x86_page_table_writing_insn(struct x86_emulate_ctxt *ctxt);
#define EMULATION_FAILED -1
#define EMULATION_OK 0
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 73f37b050d0a0..2dc515d73eb62 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -35,12 +35,162 @@
#define CC KVM_NESTED_VMENTER_CONSISTENCY_CHECK
+static void nested_svm_invalidate_insn_bytes(struct vmcb *vmcb)
+{
+ vmcb->control.insn_len = 0;
+}
+
+static void nested_svm_copy_insn_bytes(struct vmcb *to,
+ const struct vmcb *from)
+{
+ u8 insn_len = from->control.insn_len;
+
+ if (WARN_ON_ONCE(insn_len > sizeof(from->control.insn_bytes))) {
+ nested_svm_invalidate_insn_bytes(to);
+ return;
+ }
+
+ memcpy(to->control.insn_bytes, from->control.insn_bytes, insn_len);
+ to->control.insn_len = insn_len;
+}
+
+static bool nested_svm_vmexit_supports_insn_bytes(const struct vmcb *vmcb)
+{
+ u64 exit_code = vmcb->control.exit_code;
+
+ if (exit_code != SVM_EXIT_NPF &&
+ exit_code != SVM_EXIT_EXCP_BASE + PF_VECTOR)
+ return false;
+
+ return !(vmcb->control.exit_info_1 & PFERR_FETCH_MASK);
+}
+
+static void nested_svm_set_vmcb02_insn_bytes_valid(struct vcpu_svm *svm)
+{
+ svm->nested.vmcb02_insn_bytes_valid =
+ static_cpu_has(X86_FEATURE_DECODEASSISTS) &&
+ nested_svm_vmexit_supports_insn_bytes(svm->nested.vmcb02.ptr);
+}
+
+static void nested_svm_invalidate_vmcb02_insn_bytes(struct vcpu_svm *svm)
+{
+ nested_svm_invalidate_insn_bytes(svm->nested.vmcb02.ptr);
+ svm->nested.vmcb02_insn_bytes_valid = false;
+}
+
+static u8 nested_svm_get_insn_bytes_len(struct kvm_vcpu *vcpu, u8 max_bytes)
+{
+ if (!is_64_bit_mode(vcpu)) {
+ u32 eip = kvm_rip_read(vcpu);
+ u32 limit = to_svm(vcpu)->vmcb->save.cs.limit;
+
+ if (eip > limit)
+ return 0;
+ max_bytes = min_t(u64, max_bytes, (u64)limit - eip + 1);
+ }
+
+ return max_bytes;
+}
+
+static u8 nested_svm_fetch_insn_bytes(struct kvm_vcpu *vcpu, u8 *bytes,
+ u8 count, u8 max_bytes)
+{
+ struct x86_exception e;
+ gva_t rip = kvm_get_linear_rip(vcpu);
+
+ max_bytes = nested_svm_get_insn_bytes_len(vcpu, max_bytes);
+ count = min(count, max_bytes);
+
+ while (count < max_bytes) {
+ gva_t addr = rip + count;
+ u8 chunk;
+
+ if (!is_64_bit_mode(vcpu))
+ addr = (u32)addr;
+ else if (is_noncanonical_address(addr, vcpu, 0))
+ break;
+
+ /*
+ * Read one page at a time to recheck the linear address at each
+ * boundary.
+ */
+ chunk = min_t(unsigned int, max_bytes - count,
+ PAGE_SIZE - offset_in_page(addr));
+ if (kvm_fetch_guest_virt(vcpu, addr, bytes + count,
+ chunk, &e) != X86EMUL_CONTINUE)
+ break;
+ count += chunk;
+ }
+
+ return count;
+}
+
+static void nested_svm_prepare_synthesized_insn_bytes(struct kvm_vcpu *vcpu)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+ struct vmcb *vmcb02 = svm->nested.vmcb02.ptr;
+ struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+ u8 max_bytes = sizeof(vmcb02->control.insn_bytes);
+ u8 count;
+
+ nested_svm_invalidate_vmcb02_insn_bytes(svm);
+
+ if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
+ return;
+
+ max_bytes = nested_svm_get_insn_bytes_len(vcpu, max_bytes);
+ count = x86_emulator_copy_insn_bytes(ctxt, kvm_rip_read(vcpu),
+ vmcb02->control.insn_bytes, max_bytes);
+ if (!is_sev_guest(vcpu))
+ count = nested_svm_fetch_insn_bytes(vcpu,
+ vmcb02->control.insn_bytes,
+ count, max_bytes);
+ vmcb02->control.insn_len = count;
+
+ svm->nested.vmcb02_insn_bytes_valid = true;
+}
+
+static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
+ struct vmcb *vmcb12,
+ const struct vmcb *vmcb02)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+ const u8 max_bytes = sizeof(vmcb12->control.insn_bytes);
+
+ if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
+ goto out;
+
+ if (!nested_svm_vmexit_supports_insn_bytes(vmcb02)) {
+ nested_svm_invalidate_insn_bytes(vmcb12);
+ goto out;
+ }
+
+ if (svm->nested.vmcb02_insn_bytes_valid) {
+ nested_svm_copy_insn_bytes(vmcb12, vmcb02);
+ goto out;
+ }
+
+ if (!is_sev_guest(vcpu))
+ vmcb12->control.insn_len =
+ nested_svm_fetch_insn_bytes(vcpu,
+ vmcb12->control.insn_bytes,
+ 0,
+ max_bytes);
+ else
+ nested_svm_invalidate_insn_bytes(vmcb12);
+
+out:
+ svm->nested.vmcb02_insn_bytes_valid = false;
+}
+
static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
struct x86_exception *fault,
bool from_hardware)
{
struct vcpu_svm *svm = to_svm(vcpu);
struct vmcb *vmcb = svm->vmcb;
+ struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+ bool from_emulation = ctxt && fault == &ctxt->exception;
u64 fault_stage;
/*
@@ -68,6 +218,12 @@ static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
(fault->error_code & ~PFERR_GUEST_FAULT_STAGE_MASK);
vmcb->control.exit_info_2 = fault->address;
+ if (from_hardware)
+ nested_svm_set_vmcb02_insn_bytes_valid(svm);
+ else if (from_emulation && !(fault->error_code & PFERR_FETCH_MASK))
+ nested_svm_prepare_synthesized_insn_bytes(vcpu);
+ else
+ svm->nested.vmcb02_insn_bytes_valid = false;
nested_svm_vmexit(svm);
}
@@ -869,6 +1025,7 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)
* Filled at exit: exit_code, exit_info_1, exit_info_2, exit_int_info,
* exit_int_info_err, next_rip, insn_len, insn_bytes.
*/
+ nested_svm_invalidate_vmcb02_insn_bytes(svm);
if (guest_cpu_cap_has(vcpu, X86_FEATURE_VGIF) &&
(vmcb12_ctrl->int_ctl & V_GIF_ENABLE_MASK))
@@ -1297,6 +1454,8 @@ static int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu)
if (guest_cpu_cap_has(vcpu, X86_FEATURE_NRIPS))
vmcb12->control.next_rip = vmcb02->control.next_rip;
+ nested_svm_update_vmcb12_insn_bytes(vcpu, vmcb12, vmcb02);
+
if (nested_vmcb12_has_lbrv(vcpu))
svm_copy_lbrs(&vmcb12->save, &vmcb02->save);
@@ -1649,8 +1808,10 @@ int nested_svm_exit_handled(struct vcpu_svm *svm)
vmexit = nested_svm_intercept(svm);
- if (vmexit == NESTED_EXIT_DONE)
+ if (vmexit == NESTED_EXIT_DONE) {
+ nested_svm_set_vmcb02_insn_bytes_valid(svm);
nested_svm_vmexit(svm);
+ }
return vmexit;
}
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index ea647938a2a65..c91561a9a49f7 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -4837,6 +4837,7 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
int vmexit, ret = X86EMUL_CONTINUE;
struct __x86_intercept icpt_info;
struct vmcb *vmcb = svm->vmcb;
+ bool decode_assists;
if (info->intercept >= ARRAY_SIZE(x86_intercept_map))
goto out;
@@ -4846,14 +4847,26 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
if (stage != icpt_info.stage)
goto out;
+ decode_assists = guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS);
+
switch (icpt_info.exit_code) {
case SVM_EXIT_READ_CR0:
if (info->intercept == x86_intercept_cr_read)
icpt_info.exit_code += info->modrm_reg;
+
+ if (decode_assists)
+ vmcb->control.exit_info_1 =
+ info->intercept == x86_intercept_cr_read ?
+ BIT_ULL(63) | (info->modrm_rm & 0xf) : 0;
break;
case SVM_EXIT_WRITE_CR0: {
unsigned long cr0, val;
+ if (decode_assists)
+ vmcb->control.exit_info_1 =
+ info->intercept == x86_intercept_cr_write ?
+ BIT_ULL(63) | (info->modrm_rm & 0xf) : 0;
+
/*
* Adjust the exit code accordingly if a CR other than CR0 is
* being written, and skip straight to the common handling as
@@ -4891,13 +4904,30 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
*/
cr0 = vcpu->arch.cr0 & ~SVM_CR0_SELECTIVE_MASK;
val = info->src_val & ~SVM_CR0_SELECTIVE_MASK;
- if (cr0 ^ val)
+ if (cr0 ^ val) {
icpt_info.exit_code = SVM_EXIT_CR0_SEL_WRITE;
+ if (decode_assists)
+ vmcb->control.exit_info_1 = 0;
+ }
break;
}
case SVM_EXIT_READ_DR0:
case SVM_EXIT_WRITE_DR0:
icpt_info.exit_code += info->modrm_reg;
+ if (decode_assists)
+ vmcb->control.exit_info_1 = info->modrm_rm & 0xf;
+ break;
+ case SVM_EXIT_SWINT:
+ if (decode_assists)
+ vmcb->control.exit_info_1 = info->src_val & 0xff;
+ break;
+ case SVM_EXIT_INVLPG:
+ if (decode_assists)
+ vmcb->control.exit_info_1 = info->invlpg_linear_addr;
+ break;
+ case SVM_EXIT_INVLPGA:
+ if (decode_assists)
+ vmcb->control.exit_info_1 = 0;
break;
case SVM_EXIT_MSR:
if (info->intercept == x86_intercept_wrmsr)
@@ -5530,6 +5560,8 @@ static __init void svm_set_cpu_caps(void)
*/
kvm_cpu_cap_set(X86_FEATURE_FLUSHBYASID);
+ kvm_cpu_cap_check_and_set(X86_FEATURE_DECODEASSISTS);
+
if (nrips)
kvm_cpu_cap_set(X86_FEATURE_NRIPS);
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index e958943b81627..1e12ef1105c01 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -243,6 +243,9 @@ struct svm_nested_state {
* on its side.
*/
bool force_msr_bitmap_recalc;
+
+ /* True if VMCB02 has instruction bytes for the current nested exit. */
+ bool vmcb02_insn_bytes_valid;
};
struct vcpu_sev_es_state {
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 4b3681796c752..735b67781d554 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -4848,9 +4848,24 @@ static int kvm_read_guest_virt_helper(gva_t addr, void *val, unsigned int bytes,
}
/* used for instruction fetching */
-static int kvm_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,
- gva_t addr, void *val, unsigned int bytes,
- struct x86_exception *exception)
+int kvm_fetch_guest_virt(struct kvm_vcpu *vcpu, gva_t addr, void *val,
+ unsigned int bytes, struct x86_exception *exception)
+{
+ u64 access = PFERR_FETCH_MASK;
+
+ if (kvm_x86_call(get_cpl)(vcpu) == 3)
+ access |= PFERR_USER_MASK;
+
+ memset(exception, 0, sizeof(*exception));
+ return kvm_read_guest_virt_helper(addr, val, bytes, vcpu, access,
+ exception);
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_fetch_guest_virt);
+
+static int emulator_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,
+ gva_t addr, void *val,
+ unsigned int bytes,
+ struct x86_exception *exception)
{
struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt);
struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
@@ -5817,7 +5832,7 @@ static const struct x86_emulate_ops emulate_ops = {
.write_gpr = emulator_write_gpr,
.read_std = emulator_read_std,
.write_std = emulator_write_std,
- .fetch = kvm_fetch_guest_virt,
+ .fetch = emulator_fetch_guest_virt,
.read_emulated = emulator_read_emulated,
.write_emulated = emulator_write_emulated,
.cmpxchg_emulated = emulator_cmpxchg_emulated,
diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
index 0f5919b092e47..f03419a79a118 100644
--- a/arch/x86/kvm/x86.h
+++ b/arch/x86/kvm/x86.h
@@ -356,6 +356,9 @@ static inline void adjust_tsc_offset_host(struct kvm_vcpu *vcpu, s64 adjustment)
int kvm_read_guest_virt(struct kvm_vcpu *vcpu,
gva_t addr, void *val, unsigned int bytes,
struct x86_exception *exception);
+int kvm_fetch_guest_virt(struct kvm_vcpu *vcpu, gva_t addr, void *val,
+ unsigned int bytes,
+ struct x86_exception *exception);
int kvm_write_guest_virt_system(struct kvm_vcpu *vcpu,
gva_t addr, void *val, unsigned int bytes,
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39e..bb55a8c32c420 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -118,6 +118,7 @@ TEST_GEN_PROGS_x86 += x86/vmx_preemption_timer_test
TEST_GEN_PROGS_x86 += x86/svm_vmcall_test
TEST_GEN_PROGS_x86 += x86/svm_int_ctl_test
TEST_GEN_PROGS_x86 += x86/svm_nested_clear_efer_svme
+TEST_GEN_PROGS_x86 += x86/svm_nested_decode_assists_test
TEST_GEN_PROGS_x86 += x86/svm_nested_shutdown_test
TEST_GEN_PROGS_x86 += x86/svm_nested_soft_inject_test
TEST_GEN_PROGS_x86 += x86/svm_nested_vmcb12_gpa
diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h
index 6e6f70035508a..e9d745a2c1313 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -220,6 +220,7 @@ struct kvm_x86_cpu_feature {
#define X86_FEATURE_LBRV KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 1)
#define X86_FEATURE_NRIPS KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 3)
#define X86_FEATURE_TSCRATEMSR KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 4)
+#define X86_FEATURE_DECODEASSISTS KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 7)
#define X86_FEATURE_PAUSEFILTER KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 10)
#define X86_FEATURE_PFTHRESHOLD KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 12)
#define X86_FEATURE_V_VMSAVE_VMLOAD KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 15)
diff --git a/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c b/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c
new file mode 100644
index 0000000000000..9f729e8542f2c
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c
@@ -0,0 +1,547 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test KVM's virtualization of SVM DecodeAssists for nested guests.
+ */
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "svm_util.h"
+
+#define TEST_INT_VECTOR 0x81
+
+/* Any canonical virtual address that is never mapped by the selftest VM. */
+#define PF_TEST_GVA BIT_ULL(40)
+#define PF_FETCH_TEST_GVA BIT_ULL(41)
+
+#define OUTSB_OPCODE 0x6e
+#define MOVSB_OPCODE 0xa4
+#define NOP_OPCODE 0x90
+#define BOUNDARY_OUTSB_CODE_SIZE 15
+#define TEST_IOPM_SIZE (3 * PAGE_SIZE)
+
+static u8 npf_target[PAGE_SIZE] __aligned(PAGE_SIZE);
+static u8 mmio_source[PAGE_SIZE] __aligned(PAGE_SIZE);
+static u8 boundary_outsb_code[2 * PAGE_SIZE] __aligned(PAGE_SIZE);
+
+static void l2_read_code(void)
+{
+ asm volatile("mov (%0), %%rax" : : "r"(&npf_target) : "rax", "memory");
+ GUEST_FAIL("L2 read did not cause a nested page fault");
+}
+
+static void l2_outsb_code(void)
+{
+ asm volatile("mov %0, %%rsi\n\t"
+ "mov $0x80, %%dx\n\t"
+ "outsb"
+ : : "r"(&npf_target) : "rsi", "rdx", "memory");
+ GUEST_FAIL("L2 OUTSB did not cause a nested page fault");
+}
+
+extern u8 l2_movsb_insn[];
+
+static void l2_movsb_code(void)
+{
+ asm volatile("mov %0, %%rsi\n\t"
+ "mov %1, %%rdi\n\t"
+ "l2_movsb_insn:\n\t"
+ "movsb"
+ : : "r"(&mmio_source), "r"(&npf_target)
+ : "rsi", "rdi", "memory");
+ GUEST_FAIL("L2 MOVSB did not cause a nested page fault");
+}
+
+extern u8 l2_userspace_pf_insn[];
+
+static void l2_userspace_pf_code(void)
+{
+ asm volatile("movzbl (%0), %%eax\n\t"
+ "l2_userspace_pf_insn:\n\t"
+ "nop"
+ : : "r"(&mmio_source) : "rax", "memory");
+ GUEST_FAIL("Userspace-injected #PF was not intercepted by L1");
+}
+
+static void l2_pf_code(void)
+{
+ asm volatile("mov (%0), %%rax"
+ : : "r"(PF_TEST_GVA) : "rax", "memory");
+ GUEST_FAIL("L2 access to an unmapped VA did not #PF");
+}
+
+static void l2_fep_pf_code(void)
+{
+ asm volatile(KVM_FEP "mov (%0), %%rax"
+ : : "r"(PF_TEST_GVA) : "rax", "memory");
+ GUEST_FAIL("L2 forced-emulated access to an unmapped VA did not #PF");
+}
+
+static void l2_fep_mov_from_cr4_code(void)
+{
+ asm volatile(KVM_FEP "mov %%cr4, %%r10" : : : "r10");
+ GUEST_FAIL("L2 forced-emulated MOV-from-CR4 was not intercepted");
+}
+
+static void l2_fep_mov_to_dr7_code(void)
+{
+ asm volatile("mov %%dr7, %%rax\n\t"
+ "mov %%rax, %%rbx\n\t"
+ KVM_FEP "mov %%rbx, %%dr7" : : : "rax", "rbx");
+ GUEST_FAIL("L2 forced-emulated MOV-to-DR7 was not intercepted");
+}
+
+static void l2_fep_clts_code(void)
+{
+ asm volatile(KVM_FEP "clts" : : : "memory");
+ GUEST_FAIL("L2 forced-emulated CLTS was not intercepted");
+}
+
+static void l2_fep_int_code(void)
+{
+ asm volatile(KVM_FEP "int %0" : : "i"(TEST_INT_VECTOR));
+ GUEST_FAIL("L2 forced-emulated INTn was not intercepted");
+}
+
+static void l2_fep_invlpg_code(void)
+{
+ asm volatile(KVM_FEP "invlpg (%0)" : : "r"(&npf_target) : "memory");
+ GUEST_FAIL("L2 forced-emulated INVLPG was not intercepted");
+}
+
+static void l2_fep_invlpga_code(void)
+{
+ asm volatile(KVM_FEP "invlpga"
+ : : "a"(&npf_target), "c"(0) : "memory");
+ GUEST_FAIL("L2 forced-emulated INVLPGA was not intercepted");
+}
+
+struct instruction_intercept_test {
+ const char *name;
+ void (*code)(void);
+ u64 intercept;
+ u32 intercept_cr;
+ u32 intercept_dr;
+ u64 exit_code;
+ u64 exit_info_1;
+ u64 exit_info_1_mask;
+ bool check_rax;
+ u64 rax;
+};
+
+static const struct instruction_intercept_test instruction_intercept_tests[] = {
+ {
+ .name = "MOV-from-CR4",
+ .code = l2_fep_mov_from_cr4_code,
+ .intercept_cr = BIT(INTERCEPT_CR4_READ),
+ .exit_code = SVM_EXIT_READ_CR4,
+ .exit_info_1 = BIT_ULL(63) | 10,
+ .exit_info_1_mask = ~0ULL,
+ }, {
+ .name = "MOV-to-DR7",
+ .code = l2_fep_mov_to_dr7_code,
+ .intercept_dr = BIT(INTERCEPT_DR7_WRITE),
+ .exit_code = SVM_EXIT_WRITE_DR7,
+ .exit_info_1 = 3,
+ .exit_info_1_mask = ~0ULL,
+ }, {
+ .name = "CLTS",
+ .code = l2_fep_clts_code,
+ .intercept_cr = BIT(INTERCEPT_CR0_WRITE),
+ .exit_code = SVM_EXIT_WRITE_CR0,
+ .exit_info_1_mask = BIT_ULL(63),
+ }, {
+ .name = "INTn",
+ .code = l2_fep_int_code,
+ .intercept = BIT_ULL(INTERCEPT_INTn),
+ .exit_code = SVM_EXIT_SWINT,
+ .exit_info_1 = TEST_INT_VECTOR,
+ .exit_info_1_mask = ~0ULL,
+ }, {
+ .name = "INVLPG",
+ .code = l2_fep_invlpg_code,
+ .intercept = BIT_ULL(INTERCEPT_INVLPG),
+ .exit_code = SVM_EXIT_INVLPG,
+ .exit_info_1 = (u64)&npf_target,
+ .exit_info_1_mask = ~0ULL,
+ }, {
+ .name = "INVLPGA",
+ .code = l2_fep_invlpga_code,
+ .intercept = BIT_ULL(INTERCEPT_INVLPGA),
+ .exit_code = SVM_EXIT_INVLPGA,
+ .exit_info_1_mask = ~0ULL,
+ .check_rax = true,
+ .rax = (u64)&npf_target,
+ },
+};
+
+static void assert_decode_assist_insn_bytes(struct vmcb *vmcb)
+{
+ GUEST_ASSERT(vmcb->control.insn_len);
+ GUEST_ASSERT(vmcb->control.insn_len <=
+ sizeof(vmcb->control.insn_bytes));
+ GUEST_ASSERT(!memcmp(vmcb->control.insn_bytes,
+ (void *)vmcb->save.rip,
+ vmcb->control.insn_len));
+}
+
+static void assert_full_decode_assist_insn_bytes(struct vmcb *vmcb)
+{
+ GUEST_ASSERT_EQ(vmcb->control.insn_len,
+ sizeof(vmcb->control.insn_bytes));
+ assert_decode_assist_insn_bytes(vmcb);
+}
+
+static void prepare_l2_for_vmrun(struct svm_test_data *svm, gva_t rip)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ vmcb->save.rip = rip;
+ vmcb->save.rsp = (u64)svm->stack;
+}
+
+static void run_intercept_test(struct svm_test_data *svm,
+ const struct instruction_intercept_test *test)
+{
+ struct vmcb *vmcb = svm->vmcb;
+ struct vmcb_control_area *control = &vmcb->control;
+ u64 expected_exit_info_1 = test->exit_info_1 & test->exit_info_1_mask;
+
+ control->intercept |= test->intercept;
+ control->intercept_cr |= test->intercept_cr;
+ control->intercept_dr |= test->intercept_dr;
+
+ control->exit_info_1 = ~0ULL;
+ prepare_l2_for_vmrun(svm, (u64)test->code);
+
+ run_guest(vmcb, svm->vmcb_gpa);
+
+ __GUEST_ASSERT(control->exit_code == test->exit_code,
+ "%s: expected exit code %#lx, got %#lx",
+ test->name, (unsigned long)test->exit_code,
+ (unsigned long)control->exit_code);
+ __GUEST_ASSERT((control->exit_info_1 & test->exit_info_1_mask) ==
+ expected_exit_info_1,
+ "%s: expected EXITINFO1 %#lx with mask %#lx, got %#lx",
+ test->name, (unsigned long)expected_exit_info_1,
+ (unsigned long)test->exit_info_1_mask,
+ (unsigned long)control->exit_info_1);
+ __GUEST_ASSERT(!control->insn_len,
+ "%s: expected no instruction bytes, got %u",
+ test->name, control->insn_len);
+
+ if (test->check_rax)
+ __GUEST_ASSERT(vmcb->save.rax == test->rax,
+ "%s: expected rAX %#lx, got %#lx",
+ test->name, (unsigned long)test->rax,
+ (unsigned long)vmcb->save.rax);
+
+ control->intercept &= ~test->intercept;
+ control->intercept_cr &= ~test->intercept_cr;
+ control->intercept_dr &= ~test->intercept_dr;
+}
+
+static void test_instruction_intercepts(struct svm_test_data *svm)
+{
+ int i;
+
+ if (!is_forced_emulation_enabled)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(instruction_intercept_tests); i++)
+ run_intercept_test(svm, &instruction_intercept_tests[i]);
+}
+
+static void test_hardware_npf(struct svm_test_data *svm, gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_read_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+ assert_decode_assist_insn_bytes(vmcb);
+}
+
+/*
+ * The IOIO intercept causes L0 to emulate OUTSB before accessing its source
+ * operand. The emulated read then faults on L1's NPT, resulting in a
+ * KVM-synthesized #NPF.
+ */
+static void test_synthesized_npf(struct svm_test_data *svm, gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_outsb_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+ assert_full_decode_assist_insn_bytes(vmcb);
+}
+
+/*
+ * MOVSB first reads from MMIO, causing a hardware #NPF that L0 emulates.
+ * After userspace completes the read, the emulated destination write faults
+ * on L1's NPT. The new #NPF must not reuse the original hardware exit's GPA.
+ * Userspace replaces MOVSB with NOP while the read is pending. DecodeAssist
+ * must retain the cached MOVSB bytes instead of re-reading the opcode.
+ */
+static void test_synthesized_npf_after_hardware_npf(struct svm_test_data *svm,
+ gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+ u8 insn_bytes[sizeof(vmcb->control.insn_bytes)];
+
+ memcpy(insn_bytes, l2_movsb_insn, sizeof(insn_bytes));
+ GUEST_ASSERT_EQ(insn_bytes[0], MOVSB_OPCODE);
+ prepare_l2_for_vmrun(svm, (u64)l2_movsb_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+ GUEST_ASSERT_EQ(vmcb->save.rip, (u64)l2_movsb_insn);
+ GUEST_ASSERT_EQ(l2_movsb_insn[0], NOP_OPCODE);
+ GUEST_ASSERT_EQ(vmcb->control.insn_len, sizeof(insn_bytes));
+ GUEST_ASSERT(!memcmp(vmcb->control.insn_bytes, insn_bytes,
+ sizeof(insn_bytes)));
+}
+
+/*
+ * OUTSB is the final byte of a mapped code page, and the following page is
+ * not present in L2's page tables. DecodeAssist byte fetching must stop at
+ * the page boundary and report only the OUTSB opcode.
+ */
+static void test_synthesized_npf_truncated(struct svm_test_data *svm,
+ gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm,
+ (u64)&boundary_outsb_code[PAGE_SIZE -
+ BOUNDARY_OUTSB_CODE_SIZE]);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+ GUEST_ASSERT_EQ(vmcb->save.rip,
+ (u64)&boundary_outsb_code[PAGE_SIZE - 1]);
+ GUEST_ASSERT_EQ(vmcb->control.insn_len, 1);
+ GUEST_ASSERT_EQ(vmcb->control.insn_bytes[0], OUTSB_OPCODE);
+}
+
+static void test_hardware_intercepted_pf(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_pf_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_TEST_GVA);
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_PRESENT_MASK));
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_FETCH_MASK));
+ assert_decode_assist_insn_bytes(vmcb);
+}
+
+static void test_hardware_intercepted_fetch_pf(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, PF_FETCH_TEST_GVA);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_FETCH_TEST_GVA);
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_PRESENT_MASK));
+ GUEST_ASSERT(vmcb->control.exit_info_1 & PFERR_FETCH_MASK);
+ GUEST_ASSERT_EQ(vmcb->control.insn_len, 0);
+}
+
+static void test_synthesized_pf(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ if (!is_forced_emulation_enabled)
+ return;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_fep_pf_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_TEST_GVA);
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_PRESENT_MASK));
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_FETCH_MASK));
+ assert_full_decode_assist_insn_bytes(vmcb);
+}
+
+/* The pending MMIO instruction must complete before reflecting userspace #PF. */
+static void test_userspace_injected_pf_during_emulation(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_userspace_pf_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_1, 0);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_TEST_GVA);
+ GUEST_ASSERT_EQ(vmcb->save.rip, (u64)l2_userspace_pf_insn);
+ GUEST_ASSERT_EQ(vmcb->save.rax, 0x5a);
+ assert_full_decode_assist_insn_bytes(vmcb);
+ GUEST_ASSERT_EQ(vmcb->control.insn_bytes[0], NOP_OPCODE);
+}
+
+static void l1_guest_code(struct svm_test_data *svm, gpa_t npf_gpa,
+ gpa_t iopm_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ GUEST_ASSERT(this_cpu_has(X86_FEATURE_DECODEASSISTS));
+
+ generic_svm_setup(svm, l2_read_code);
+ vmcb->control.iopm_base_pa = iopm_gpa;
+
+ vmcb->control.intercept |= BIT_ULL(INTERCEPT_IOIO_PROT);
+ vmcb->control.intercept_exceptions |= 1U << PF_VECTOR;
+
+ test_hardware_npf(svm, npf_gpa);
+ test_synthesized_npf(svm, npf_gpa);
+ test_synthesized_npf_after_hardware_npf(svm, npf_gpa);
+ test_synthesized_npf_truncated(svm, npf_gpa);
+ test_hardware_intercepted_pf(svm);
+ test_hardware_intercepted_fetch_pf(svm);
+ test_synthesized_pf(svm);
+ test_userspace_injected_pf_during_emulation(svm);
+ test_instruction_intercepts(svm);
+
+ GUEST_DONE();
+}
+
+static void build_boundary_outsb_code(u8 *code)
+{
+ u64 source = (u64)&npf_target;
+
+ /* movabs $npf_target, %rsi */
+ code[0] = 0x48;
+ code[1] = 0xbe;
+ memcpy(&code[2], &source, sizeof(source));
+
+ /* mov $0x80, %dx; outsb */
+ code[10] = 0x66;
+ code[11] = 0xba;
+ code[12] = 0x80;
+ code[13] = 0x00;
+ code[14] = OUTSB_OPCODE;
+}
+
+static void prepare_boundary_outsb_code(struct kvm_vm *vm)
+{
+ gva_t code_gva = (gva_t)&boundary_outsb_code[PAGE_SIZE -
+ BOUNDARY_OUTSB_CODE_SIZE];
+
+ build_boundary_outsb_code(addr_gva2hva(vm, code_gva));
+}
+
+static void queue_userspace_pf(struct kvm_vcpu *vcpu)
+{
+ struct kvm_vcpu_events events;
+
+ vcpu_events_get(vcpu, &events);
+ TEST_ASSERT(!events.exception.pending && !events.exception.injected,
+ "Unexpected exception before userspace #PF injection");
+ TEST_ASSERT(events.flags & KVM_VCPUEVENT_VALID_PAYLOAD,
+ "KVM_CAP_EXCEPTION_PAYLOAD was not enabled");
+
+ events.exception.pending = true;
+ events.exception.nr = PF_VECTOR;
+ events.exception.has_error_code = true;
+ events.exception.error_code = 0;
+ events.exception_has_payload = true;
+ events.exception_payload = PF_TEST_GVA;
+ vcpu_events_set(vcpu, &events);
+}
+
+static void complete_mmio_read(struct kvm_vcpu *vcpu, gpa_t expected_gpa,
+ u8 value)
+{
+ if (vcpu->run->exit_reason == KVM_EXIT_IO) {
+ struct ucall uc;
+
+ if (get_ucall(vcpu, &uc) == UCALL_ABORT)
+ REPORT_GUEST_ASSERT(uc);
+ }
+
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
+ TEST_ASSERT(!vcpu->run->mmio.is_write,
+ "Expected an MMIO read, got a write");
+ TEST_ASSERT_EQ(vcpu->run->mmio.phys_addr, expected_gpa);
+ TEST_ASSERT_EQ(vcpu->run->mmio.len, 1);
+ vcpu->run->mmio.data[0] = value;
+}
+
+static void assert_ucall_done(struct kvm_vcpu *vcpu)
+{
+ struct ucall uc;
+ u64 actual;
+
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+ actual = get_ucall(vcpu, &uc);
+ if (actual == UCALL_ABORT)
+ REPORT_GUEST_ASSERT(uc);
+
+ TEST_ASSERT_EQ(actual, UCALL_DONE);
+}
+
+int main(int argc, char *argv[])
+{
+ gva_t svm_gva, npf_gva, boundary_page_gva, iopm_gva;
+ gpa_t npf_gpa, mmio_source_gpa, mmio_gpa, iopm_gpa;
+ struct userspace_mem_region *region;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ u8 *movsb_insn;
+ u64 *pte;
+
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_NPT));
+ TEST_REQUIRE(this_cpu_has(X86_FEATURE_DECODEASSISTS));
+ TEST_ASSERT(kvm_cpu_has(X86_FEATURE_DECODEASSISTS),
+ "KVM failed to expose DecodeAssists");
+ TEST_REQUIRE(kvm_has_cap(KVM_CAP_EXCEPTION_PAYLOAD));
+
+ vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
+ vm_enable_cap(vm, KVM_CAP_EXCEPTION_PAYLOAD, 1);
+ prepare_boundary_outsb_code(vm);
+ vm_enable_npt(vm);
+ vcpu_alloc_svm(vm, &svm_gva);
+ iopm_gva = vm_alloc_pages(vm, TEST_IOPM_SIZE / PAGE_SIZE);
+ iopm_gpa = addr_gva2gpa(vm, iopm_gva);
+ memset(addr_gva2hva(vm, iopm_gva), 0, TEST_IOPM_SIZE);
+ npf_gva = (gva_t)&npf_target;
+ npf_gpa = addr_gva2gpa(vm, npf_gva);
+
+ tdp_identity_map_default_memslots(vm);
+ pte = tdp_get_pte(vm, npf_gpa);
+ *pte &= ~PTE_PRESENT_MASK(&vm->stage2_mmu);
+ region = memslot2region(vm, 0);
+ mmio_gpa = region->region.guest_phys_addr +
+ region->region.memory_size + PAGE_SIZE;
+ mmio_source_gpa = addr_gva2gpa(vm, (gva_t)&mmio_source);
+ pte = tdp_get_pte(vm, mmio_source_gpa);
+ *pte = (*pte & ~PHYSICAL_PAGE_MASK) | mmio_gpa;
+
+ boundary_page_gva = (gva_t)&boundary_outsb_code[PAGE_SIZE];
+ pte = vm_get_pte(vm, boundary_page_gva);
+ *pte &= ~PTE_PRESENT_MASK(&vm->mmu);
+
+ vcpu_args_set(vcpu, 3, svm_gva, npf_gpa, iopm_gpa);
+
+ /* Complete the MOVSB source read. */
+ vcpu_run(vcpu);
+ complete_mmio_read(vcpu, mmio_gpa, 0xa5);
+ movsb_insn = addr_gva2hva(vm, (gva_t)l2_movsb_insn);
+ TEST_ASSERT_EQ(movsb_insn[0], MOVSB_OPCODE);
+ movsb_insn[0] = NOP_OPCODE;
+ vcpu_run(vcpu);
+ complete_mmio_read(vcpu, mmio_gpa, 0x5a);
+ queue_userspace_pf(vcpu);
+ vcpu_run(vcpu);
+ assert_ucall_done(vcpu);
+
+ kvm_vm_free(vm);
+ return 0;
+}
|
| 3/2 | 2026/09/13 07:01 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 17913 output: 33 thoughts: 923 Thoughts: **Analyzing the KVM Patch Series: A Deep Dive** Okay, let's break down this patch series. My initial impression is that this is focused on enhancing KVM's emulation capabilities, specifically for AMD's SVM (Secure Virtual Machine) nested virtualization. Looks like they're adding support for instruction decoding assists, likely to optimize instruction emulation. First, let's pinpoint the changes. It seems they've modified several core KVM files: `cpuid.c` is updated to advertise `VENDOR_F(DECODEASSISTS)`. `emulate.c` sees additions for INVLPG address linearization and instruction byte caching/copying. I note the use of `EXPORT_SYMBOL_FOR_KVM_INTERNAL` which is interesting; makes those routines internal to KVM. The header files in both the KVM root and SVM specific directories have also had changes to support the new data and function signatures. The SVM specific files, especially `nested.c`, are *very* busy, with a slew of functions now handling instruction bytes within nested SVM contexts. And a warning in one of them – `WARN_ON_ONCE` – that needs further investigation. `svm.c` now sets up the decoding assists support in the `svm_check_intercept` function and `svm_set_cpu_caps` is added. The changes to the X86 architecture (`x86.c` and `x86.h`) look like refactoring, making `kvm_fetch_guest_virt()` available externally. Finally, some selftests have been added which is excellent, means there's a good way to determine if this works in practice. Now for reachability. This target architecture is AMD64, which is expected. The subsystem is clearly KVM and, specifically, AMD SVM, which is where it's at its most interesting. The critical question: how readily accessible is this for fuzzing? And the answer is – very! `/dev/kvm` is a standard syzkaller target, so that opens a huge surface for potential testing. Even more interesting, with AMD SVM, can we fuzz this in a standard QEMU or GCE environment? Yes! KVM, including nested SVM, is already thoroughly fuzzed by syzkaller. The critical question now is, what configurations are needed to enable this? I'll need to check the Kconfig files, especially for KVM and KVM_AMD. I need to make sure this is reachable and fuzzable. |
| 4/2 | 2026/09/13 07:01 | 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"
} |
| 5/2 | 2026/09/13 07:01 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 19583 output: 39 thoughts: 24 Thoughts: Here's a summary of my thought process, as requested: **Navigating the Kernel's Kconfig for x86 KVM Configuration** Okay, time to dig into the kernel configuration. I need to understand how KVM is configured for x86 systems, specifically from a feature or integration perspective. Let's see... I'm thinking the `arch/x86/kvm/Kconfig` file is a likely starting point. Line 146... hmm, I'll need to jump there and get my bearings. I'm assuming that line will touch on the relevant options for KVM's behavior. Time to delve into the dependencies and associated variables to refresh my memory and better understand how to configure the KVM settings in that context. |
| 6/2 | 2026/09/13 07:01 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/Kconfig",
"FirstLine": 140,
"LineCount": 30
}Results: {
"Contents": " 140:\t\thelp\n 141:\t\t Provides support for launching Intel Trust Domain Extensions (TDX)\n 142:\t\t confidential VMs on Intel processors.\n 143:\t\n 144:\t\t If unsure, say N.\n 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"
} |
| 7/2 | 2026/09/13 07:01 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 20306 output: 32 thoughts: 12 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Checking Kconfig for KVM** Alright, before we proceed further, I need to make sure we haven't overlooked something fundamental in the kernel's configuration. Given the context of what we're working on, specifically if the problem relates to virtualization or anything that could be impacted by the Kernel-based Virtual Machine, or KVM, that should be double checked. The `Kconfig` files are the heart of the configuration process. We need to explicitly verify the presence or absence of any relevant KVM options to confirm they're enabled or disabled as expected. This will help confirm that there isn't a simple configuration issue blocking the desired functionality or causing an unexpected conflict. This quick check in the configuration will help rule out any basic configuration errors, and it'll ensure we're accounting for virtualization in our overall troubleshooting and planning. It's a fundamental step I always take when interacting with anything that might touch virtualization. |
| 8/2 | 2026/09/13 07:01 | tool | grepper |
0mArgs:{
"Expression": "config KVM$",
"PathPrefix": "arch/x86/kvm"
}Results: {
"Output": "arch/x86/kvm/Kconfig=20=config KVM_X86\n--\narch/x86/kvm/Kconfig-51-\narch/x86/kvm/Kconfig:52:config KVM\narch/x86/kvm/Kconfig-53-\ttristate \"Kernel-based Virtual Machine (KVM) support\"\n"
} |
| 9/2 | 2026/09/13 07:01 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 20667 output: 43 thoughts: 1428 Thoughts: **Fuzzing Target Identification: KVM AMD DecodeAssists**
Okay, let's break this down. My initial assessment is that this patch introduces significant changes within KVM, specifically regarding AMD virtualization, and is absolutely worth fuzzing. I'm seeing a deep dive into how KVM interacts with AMD's nested virtualization capabilities. The fact that it concerns itself with `X86_FEATURE_DECODEASSISTS` and the fetching of guest instruction bytes during exits (NPF, PF, etc.) is a huge flag. This isn't just a minor tweak; it's touching core virtualization logic within KVM, nested SVM, and the x86 emulator.
I verified that this code is reachable in standard virtualized environments, like QEMU with an AMD CPU or nested KVM configurations, which aligns with fuzzing guidelines. QEMU and KVM support the relevant features, so this checks the reachability box.
I have observed modifications that include: new functions for instruction byte handling, memory access handling that involves guest virtual memory, pointer arithmetic, segmented addresses, and emulation buffer interactions. It's a complex dance. I also noticed the `WARN_ON_ONCE` assertion, which automatically triggers a red flag, as this is a runtime assertion in reachable code. That needs to be tested vigorously.
I am paying attention to the fuzzing guidelines. Specifically, the "REACHABILITY" requirement is met. There are no negative criteria that apply here: it's kernel code, it's executable, and it's not some structurally unreachable hardware feature. On the positive side, this directly impacts core KVM subsystems and introduces defensive checks.
I've carefully examined the code modifications, particularly in relation to the fuzzing guidelines. I'm focusing on "TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS", so I'm trying to filter out hot-paths and lifecycle functions.
Here's the function breakdown that I am focusing on:
* In `arch/x86/kvm/emulate.c`:
* `x86_emulator_copy_insn_bytes` (this is non-static and exported, meaning it has an entrypoint).
* In `arch/x86/kvm/svm/nested.c`:
* `nested_svm_exit_handled` (non-static entrypoint)
* *Also, I need to check the callers for `nested_svm_vmexit_update_vmcb12` and `nested_svm_inject_npf_exit` because those are called from other methods*
In essence, I'm honing in on the areas where the new `DECODEASSISTS` logic is directly implemented, where the guest instruction bytes are handled, and where exits are reflected. I am treating the code around memory access and emulation buffer copying as a target, due to the complexity and assertions. These seem to be the most relevant entry points. I'm now going to specifically check the callers of `nested_svm_vmexit_update_vmcb12` and `nested_svm_inject_npf_exit`, because those methods might be key entrypoints, and I want to find them. This will narrow my focus to the most relevant areas to fuzz.
|
| 10/2 | 2026/09/13 07:01 | tool | grepper |
0mArgs:{
"Expression": "nested_svm_vmexit_update_vmcb12",
"PathPrefix": "arch/x86/kvm/svm"
}Results: {
"Output": "arch/x86/kvm/svm/nested.c=1390=void svm_copy_vmloadsave_state(struct vmcb *to_vmcb, struct vmcb *from_vmcb)\n--\narch/x86/kvm/svm/nested.c-1405-\narch/x86/kvm/svm/nested.c:1406:static int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu)\narch/x86/kvm/svm/nested.c-1407-{\n--\narch/x86/kvm/svm/nested.c=1476=void nested_svm_vmexit(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/nested.c-1481-\narch/x86/kvm/svm/nested.c:1482:\tif (nested_svm_vmexit_update_vmcb12(vcpu))\narch/x86/kvm/svm/nested.c-1483-\t\tkvm_make_request(KVM_REQ_TRIPLE_FAULT, vcpu);\n"
} |
| 11/2 | 2026/09/13 07:01 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 23130 output: 40 thoughts: 17 Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert: **Investigating a Potential Vulnerability** Alright, so we're looking at things, and I need to circle back and double-check something. Specifically, I should go back and re-examine the `nested_svm_inject_npf_exit` function. It's crucial, and I want to make sure we've covered all the angles there. No stone left unturned, especially with something like this. Let's make sure it's as solid as possible. |
| 12/2 | 2026/09/13 07:01 | tool | grepper |
0mArgs:{
"Expression": "nested_svm_inject_npf_exit",
"PathPrefix": "arch/x86/kvm/svm"
}Results: {
"Output": "arch/x86/kvm/svm/nested.c=153=static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/svm/nested.c-185-\narch/x86/kvm/svm/nested.c:186:static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,\narch/x86/kvm/svm/nested.c-187-\t\t\t\t struct x86_exception *fault,\n--\narch/x86/kvm/svm/nested.c=255=static void nested_svm_init_mmu_context(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/nested.c-274-\tvcpu-\u003earch.ngpa_walk.get_pdptr = nested_svm_get_tdp_pdptr;\narch/x86/kvm/svm/nested.c:275:\tvcpu-\u003earch.ngpa_walk.inject_page_fault = nested_svm_inject_npf_exit;\narch/x86/kvm/svm/nested.c-276-}\n"
} |
| 13/2 | 2026/09/13 07:01 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 23634 output: 37 thoughts: 11 Thoughts: Here's a summary of my thought process, as if I were considering the question "What calls `nested_svm_vmexit`?": **Digging into the Nested SVM VM Exit Path** Okay, so I'm trying to figure out the callers of `nested_svm_vmexit`. That means I'm looking for where this specific VM exit handler is invoked within the virtualization code. This isn't just a generic VM exit; it's *nested* SVM, which immediately tells me this is related to running a guest OS *inside* another guest OS (likely using AMD's Secure Virtual Machine technology). My initial thought is, the entry point for this is probably within the hypervisor that's managing the nested guest. So, I need to start tracing back from the execution of a nested guest. I would have to look at the hypervisor code, specifically its SVM-related sections. I'm imagining calls within the hypervisor's trap handling logic related to nested virtualization. I would expect `nested_svm_vmexit` to be the target of a jump when a nested guest triggers some kind of event that requires the hypervisor to step in. A VMCALL from the nested guest, a page fault, a timer interrupt, or even access to privileged registers would all be the kind of things that would require a VM exit. I'd need to examine those entry points, searching for logic or function calls that ultimately lead to the `nested_svm_vmexit` handler. I'd also consider if there are any specific AMD documentation or processor manuals that discuss nested virtualization and the flow of control within nested SVM environments. Those should outline the expected VM exit scenarios and indicate which events trigger `nested_svm_vmexit`. I'd definitely want to cross-reference the code with those documents. Finally, I'd want to check any debugging tools I'm using. If the hypervisor has support for tracing or breakpointing on VM exits, I could watch for the exact moment `nested_svm_vmexit` is called and then follow the call stack to see the full path. That's usually the fastest way to get to the true answer. |
| 14/2 | 2026/09/13 07:01 | tool | grepper |
0mArgs:{
"Expression": "nested_svm_vmexit\\(",
"PathPrefix": "arch/x86/kvm/svm"
}Results: {
"Output": "arch/x86/kvm/svm/hyperv.c=9=void svm_hv_inject_synthetic_vmexit_post_tlb_flush(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/hyperv.c-21-\tsvm-\u003evmcb-\u003econtrol.exit_info_2 = 0;\narch/x86/kvm/svm/hyperv.c:22:\tnested_svm_vmexit(svm);\narch/x86/kvm/svm/hyperv.c-23-}\n--\narch/x86/kvm/svm/nested.c=186=static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/svm/nested.c-226-\t\tsvm-\u003enested.vmcb02_insn_bytes_valid = false;\narch/x86/kvm/svm/nested.c:227:\tnested_svm_vmexit(svm);\narch/x86/kvm/svm/nested.c-228-}\n--\narch/x86/kvm/svm/nested.c=1270=int nested_svm_vmrun(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/nested.c-1345-\narch/x86/kvm/svm/nested.c:1346:\t\tnested_svm_vmexit(svm);\narch/x86/kvm/svm/nested.c-1347-\t}\n--\narch/x86/kvm/svm/nested.c=1406=static int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/nested.c-1475-\narch/x86/kvm/svm/nested.c:1476:void nested_svm_vmexit(struct vcpu_svm *svm)\narch/x86/kvm/svm/nested.c-1477-{\n--\narch/x86/kvm/svm/nested.c=1805=int nested_svm_exit_handled(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/nested.c-1812-\t\tnested_svm_set_vmcb02_insn_bytes_valid(svm);\narch/x86/kvm/svm/nested.c:1813:\t\tnested_svm_vmexit(svm);\narch/x86/kvm/svm/nested.c-1814-\t}\n--\narch/x86/kvm/svm/nested.c=1842=static void nested_svm_inject_exception_vmexit(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/nested.c-1873-\narch/x86/kvm/svm/nested.c:1874:\tnested_svm_vmexit(svm);\narch/x86/kvm/svm/nested.c-1875-}\n--\narch/x86/kvm/svm/svm.h=883=void svm_copy_vmloadsave_state(struct vmcb *to_vmcb, struct vmcb *from_vmcb);\narch/x86/kvm/svm/svm.h:884:void nested_svm_vmexit(struct vcpu_svm *svm);\narch/x86/kvm/svm/svm.h-885-\narch/x86/kvm/svm/svm.h=886=static inline void nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code)\n--\narch/x86/kvm/svm/svm.h-890-\tsvm-\u003evmcb-\u003econtrol.exit_info_2\t= 0;\narch/x86/kvm/svm/svm.h:891:\tnested_svm_vmexit(svm);\narch/x86/kvm/svm/svm.h-892-}\n"
} |
| 15/2 | 2026/09/13 07:01 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 25174 output: 33 thoughts: 243 Thoughts: **My Analysis of the Nested SVM VM Exit Handling** Okay, so I'm looking at this code and immediately keying in on `nested_svm_vmexit`. I see that it's exported within the SVM module, and it's a `void nested_svm_vmexit(struct vcpu_svm *svm)` function. That's our primary entry point for dealing with VM exits. My focus then shifts to the modification of `nested_svm_exit_handled`. I'm seeing a code diff here, and it's straightforward: they've added `nested_svm_set_vmcb02_insn_bytes_valid(svm)` within an `if` block. This block appears to execute when `vmexit` is `NESTED_EXIT_DONE`. So, after the nested exit has completed, it's marking some instruction bytes as valid within the VMCB02 structure – interesting. I need to understand why this validation is necessary now. What triggered this change? I then see the patch also touches `svm_check_intercept`. It looks like it is also being modified. I need to refresh my memory on what `svm_check_intercept` does. It's time to dig into the `svm.c` file and understand how these pieces fit together. I want to see how these intercepted exits are being checked and how this new validation logic will integrate. This is where the core logic of the hypervisor is modified, so this is worth a closer look. |
| 16/2 | 2026/09/13 07:01 | tool | grepper |
0mArgs:{
"Expression": "svm_check_intercept",
"PathPrefix": "arch/x86/kvm"
}Results: {
"Output": "arch/x86/kvm/svm/svm.c=4774=static const struct __x86_intercept {\n--\narch/x86/kvm/svm/svm.c-4830-\narch/x86/kvm/svm/svm.c:4831:static int svm_check_intercept(struct kvm_vcpu *vcpu,\narch/x86/kvm/svm/svm.c-4832-\t\t\t struct x86_instruction_info *info,\n--\narch/x86/kvm/svm/svm.c=5358=struct kvm_x86_ops svm_x86_ops __initdata = {\n--\narch/x86/kvm/svm/svm.c-5458-\narch/x86/kvm/svm/svm.c:5459:\t.check_intercept = svm_check_intercept,\narch/x86/kvm/svm/svm.c-5460-\t.handle_exit_irqoff = svm_handle_exit_irqoff,\n"
} |
| 17/2 | 2026/09/13 07:01 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 26048 output: 31 thoughts: 49 Thoughts: Here's my summary, as though I'm thinking through this process: **Investigating the `svm_check_intercept` Function** Okay, so `svm_check_intercept` is a static function, and it's registered within `svm_x86_ops.check_intercept`. That's the setup, got it. My next question is: *what's calling `kvm_x86_call(check_intercept)`*? I need to trace the execution path to understand when and why this specific intercept is being triggered. I'll have to delve into the code to figure out which other parts of KVM are using that function. Time to start digging and see where `kvm_x86_call(check_intercept)` actually gets called. |
| 18/2 | 2026/09/13 07:01 | tool | grepper |
0mArgs:{
"Expression": "check_intercept",
"PathPrefix": "arch/x86/kvm"
}Results: {
"Output": "arch/x86/kvm/emulate.c=413=static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,\n--\narch/x86/kvm/emulate.c-415-\narch/x86/kvm/emulate.c:416:static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,\narch/x86/kvm/emulate.c-417-\t\t\t\t enum x86_intercept intercept,\n--\narch/x86/kvm/emulate.c=5314=void init_decode_cache(struct x86_emulate_ctxt *ctxt)\n--\narch/x86/kvm/emulate.c-5330-\narch/x86/kvm/emulate.c:5331:int x86_emulate_insn(struct x86_emulate_ctxt *ctxt, bool check_intercepts)\narch/x86/kvm/emulate.c-5332-{\n--\narch/x86/kvm/emulate.c-5396-\narch/x86/kvm/emulate.c:5397:\t\tif (unlikely(check_intercepts) \u0026\u0026 ctxt-\u003eintercept) {\narch/x86/kvm/emulate.c:5398:\t\t\trc = emulator_check_intercept(ctxt, ctxt-\u003eintercept,\narch/x86/kvm/emulate.c-5399-\t\t\t\t\t\t X86_ICPT_PRE_EXCEPT);\n--\narch/x86/kvm/emulate.c-5425-\narch/x86/kvm/emulate.c:5426:\t\tif (unlikely(check_intercepts) \u0026\u0026 (ctxt-\u003ed \u0026 Intercept)) {\narch/x86/kvm/emulate.c:5427:\t\t\trc = emulator_check_intercept(ctxt, ctxt-\u003eintercept,\narch/x86/kvm/emulate.c-5428-\t\t\t\t\t\t X86_ICPT_POST_EXCEPT);\n--\narch/x86/kvm/emulate.c-5479-\narch/x86/kvm/emulate.c:5480:\tif (unlikely(check_intercepts) \u0026\u0026 (ctxt-\u003ed \u0026 Intercept)) {\narch/x86/kvm/emulate.c:5481:\t\trc = emulator_check_intercept(ctxt, ctxt-\u003eintercept,\narch/x86/kvm/emulate.c-5482-\t\t\t\t\t X86_ICPT_POST_MEMACCESS);\n--\narch/x86/kvm/kvm_emulate.h=539=void init_decode_cache(struct x86_emulate_ctxt *ctxt);\narch/x86/kvm/kvm_emulate.h:540:int x86_emulate_insn(struct x86_emulate_ctxt *ctxt, bool check_intercepts);\narch/x86/kvm/kvm_emulate.h-541-int emulator_task_switch(struct x86_emulate_ctxt *ctxt,\n--\narch/x86/kvm/svm/svm.c=4774=static const struct __x86_intercept {\n--\narch/x86/kvm/svm/svm.c-4830-\narch/x86/kvm/svm/svm.c:4831:static int svm_check_intercept(struct kvm_vcpu *vcpu,\narch/x86/kvm/svm/svm.c-4832-\t\t\t struct x86_instruction_info *info,\n--\narch/x86/kvm/svm/svm.c=5358=struct kvm_x86_ops svm_x86_ops __initdata = {\n--\narch/x86/kvm/svm/svm.c-5458-\narch/x86/kvm/svm/svm.c:5459:\t.check_intercept = svm_check_intercept,\narch/x86/kvm/svm/svm.c-5460-\t.handle_exit_irqoff = svm_handle_exit_irqoff,\n--\narch/x86/kvm/vmx/main.c=888=struct kvm_x86_ops vt_x86_ops __initdata = {\n--\narch/x86/kvm/vmx/main.c-1001-\narch/x86/kvm/vmx/main.c:1002:\t.check_intercept = vmx_check_intercept,\narch/x86/kvm/vmx/main.c-1003-\t.handle_exit_irqoff = vmx_handle_exit_irqoff,\n--\narch/x86/kvm/vmx/vmx.c=8142=static bool vmx_is_io_intercepted(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/vmx/vmx.c-8187-\narch/x86/kvm/vmx/vmx.c:8188:int vmx_check_intercept(struct kvm_vcpu *vcpu,\narch/x86/kvm/vmx/vmx.c-8189-\t\t\tstruct x86_instruction_info *info,\n--\narch/x86/kvm/vmx/x86_ops.h=41=int vmx_check_emulate_instruction(struct kvm_vcpu *vcpu, int emul_type,\narch/x86/kvm/vmx/x86_ops.h-42-\t\t\t\t void *insn, int insn_len);\narch/x86/kvm/vmx/x86_ops.h:43:int vmx_check_intercept(struct kvm_vcpu *vcpu,\narch/x86/kvm/vmx/x86_ops.h-44-\t\t\tstruct x86_instruction_info *info,\n--\narch/x86/kvm/x86.c=5714=static int emulator_intercept(struct x86_emulate_ctxt *ctxt,\n--\narch/x86/kvm/x86.c-5717-{\narch/x86/kvm/x86.c:5718:\treturn kvm_x86_call(check_intercept)(emul_to_vcpu(ctxt), info, stage,\narch/x86/kvm/x86.c-5719-\t\t\t\t\t \u0026ctxt-\u003eexception);\n"
} |
| 19/2 | 2026/09/13 07:01 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 27979 output: 37 thoughts: 1249 Thoughts: **Analyzing KVM Instruction Emulation for Potential Focus Symbols** Okay, let's break this down systematically. I'm looking at KVM's instruction emulation path. The initial investigation points to `emulator_intercept` calling `kvm_x86_call(check_intercept)`, and `x86_emulate_insn` which is key, calling `emulator_check_intercept`, and ultimately the `ctxt->ops->intercept` function, which is again, `emulator_intercept`. I have to keep these call paths in mind. Now, I see `kvm_fetch_guest_virt` in `arch/x86/kvm/x86.c`. This function is interesting because it's *exported internally* for KVM use. That's a good sign. It fetches guest virtual addresses, and is used by other parts of the KVM. And similarly `x86_emulator_copy_insn_bytes` is also exported internally. It seems like these could be points of interest. Next, I'm checking the symbols in `nested.c`. I note that `nested_svm_exit_handled`, `nested_svm_vmexit`, and `nested_svm_vmrun` are all *non-static*. Those are probably important, and can be used directly from other modules, but also that static functions like `nested_svm_update_vmcb12_insn_bytes`, `nested_svm_fetch_insn_bytes`, and `nested_svm_prepare_synthesized_insn_bytes` might or might not be inlined depending on the compiler. And if I am focusing on the changes, maybe the static functions shouldn't be my primary focus. However, `nested_svm_exit_handled`, `nested_svm_vmexit`, and the functions, `x86_emulator_copy_insn_bytes` and `kvm_fetch_guest_virt` are all non-static! This means they're exposed as symbols, which makes them easier to target. Let's not forget about `svm_check_intercept`, which is static, so I will have to look at it's caller `x86_emulate_insn`, just to double check, but my focus is shifting towards the non-static functions. Wait a second… `x86_emulate_insn`! That's the KVM x86 *instruction emulator* itself! And I can focus on that. That's a specific, key part of the KVM architecture. I need to check how ubiquitous it is... and it's not. I am reminded that `x86_emulate_insn` is ONLY called when KVM needs to emulate an instruction, which is not ubiquitous in the OS. Okay, let's step back and list all the functions that were *modified*. This gives me a clearer picture: * `arch/x86/kvm/emulate.c`: `x86_emulator_copy_insn_bytes` (new, non-static, important!), `emulator_check_intercept` (static), and `get_invlpg_linear_addr` (new static) * `arch/x86/kvm/svm/nested.c`: Several static functions like `nested_svm_copy_insn_bytes`, `nested_svm_fetch_insn_bytes`, `nested_svm_prepare_synthesized_insn_bytes`, `nested_svm_update_vmcb12_insn_bytes`, `nested_svm_inject_npf_exit`, `nested_svm_vmexit_update_vmcb12`, and `nested_vmcb02_prepare_control`, but also the non-static `nested_svm_exit_handled`. * `arch/x86/kvm/svm/svm.c`: `svm_check_intercept` (static). * `arch/x86/kvm/x86.c`: `kvm_fetch_guest_virt` (new, non-static!), and `emulator_fetch_guest_virt` (static). So, the key non-static ones appear to be `x86_emulator_copy_insn_bytes`, `kvm_fetch_guest_virt` and the most interesting would be to focus on `nested_svm_exit_handled`, which are easier to target. Let's figure out which functions call `nested_svm_exit_handled` now. |
| 20/2 | 2026/09/13 07:02 | tool | grepper |
0mArgs:{
"Expression": "nested_svm_exit_handled",
"PathPrefix": "arch/x86/kvm/svm"
}Results: {
"Output": "arch/x86/kvm/svm/nested.c=1690=void svm_leave_nested(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/nested.c-1722-\narch/x86/kvm/svm/nested.c:1723:static int nested_svm_exit_handled_msr(struct vcpu_svm *svm)\narch/x86/kvm/svm/nested.c-1724-{\n--\narch/x86/kvm/svm/nested.c=1773=static int nested_svm_intercept(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/nested.c-1782-\tcase SVM_EXIT_MSR:\narch/x86/kvm/svm/nested.c:1783:\t\tvmexit = nested_svm_exit_handled_msr(svm);\narch/x86/kvm/svm/nested.c-1784-\t\tbreak;\n--\narch/x86/kvm/svm/nested.c-1804-\narch/x86/kvm/svm/nested.c:1805:int nested_svm_exit_handled(struct vcpu_svm *svm)\narch/x86/kvm/svm/nested.c-1806-{\n--\narch/x86/kvm/svm/svm.c=2563=static bool check_selective_cr0_intercepted(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/svm/svm.c-2578-\t\tsvm-\u003evmcb-\u003econtrol.exit_code = SVM_EXIT_CR0_SEL_WRITE;\narch/x86/kvm/svm/svm.c:2579:\t\tret = (nested_svm_exit_handled(svm) == NESTED_EXIT_DONE);\narch/x86/kvm/svm/svm.c-2580-\t}\n--\narch/x86/kvm/svm/svm.c=3715=static int svm_handle_exit(struct kvm_vcpu *vcpu, fastpath_t exit_fastpath)\n--\narch/x86/kvm/svm/svm.c-3730-\t\tif (vmexit == NESTED_EXIT_CONTINUE)\narch/x86/kvm/svm/svm.c:3731:\t\t\tvmexit = nested_svm_exit_handled(svm);\narch/x86/kvm/svm/svm.c-3732-\n--\narch/x86/kvm/svm/svm.c=4831=static int svm_check_intercept(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/svm/svm.c-4985-\tvmcb-\u003econtrol.exit_code = icpt_info.exit_code;\narch/x86/kvm/svm/svm.c:4986:\tvmexit = nested_svm_exit_handled(svm);\narch/x86/kvm/svm/svm.c-4987-\n--\narch/x86/kvm/svm/svm.h=886=static inline void nested_svm_simple_vmexit(struct vcpu_svm *svm, u32 exit_code)\n--\narch/x86/kvm/svm/svm.h-893-\narch/x86/kvm/svm/svm.h:894:int nested_svm_exit_handled(struct vcpu_svm *svm);\narch/x86/kvm/svm/svm.h-895-int nested_svm_check_permissions(struct kvm_vcpu *vcpu);\n"
} |
| 21/2 | 2026/09/13 07:02 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 31195 output: 36 thoughts: 315 Thoughts: **My Deep Dive into the SVM Exit Handling Code** Okay, so I'm tracing the execution path of SVM exit handling, and it's quite intricate, as usual. It all starts with the `svm_handle_exit` and `svm_check_intercept` functions. They both funnel into `nested_svm_exit_handled(svm)`. From there, it dives into `nested_svm_vmexit(svm)`, which then calls `nested_svm_vmexit_update_vmcb12(vcpu)`. This leads to `nested_svm_update_vmcb12_insn_bytes(vcpu, vmcb12, vmcb02)`. Finally, this function seems to be the one that actually fetches the instruction bytes, either through `nested_svm_copy_insn_bytes()` or `nested_svm_fetch_insn_bytes()`. I'd need to dig deeper to understand the specific situations each is used in. Now, an interesting side path emerges when a nested page fault (NPF) is injected from the emulation side. `nested_svm_inject_npf_exit()` then calls `nested_svm_prepare_synthesized_insn_bytes()`. This function seems to use both `x86_emulator_copy_insn_bytes()` and `nested_svm_fetch_insn_bytes()`, which seems to imply it's constructing an emulated instruction. And looking at `nested_svm_fetch_insn_bytes()` this is where we see a direct call to `kvm_fetch_guest_virt()`, which is what I would expect to actually retrieve guest memory. Okay, now let's make sure that all the necessary kernel configurations are in place for all this code to be compiled and included. I need to make sure the relevant SVM features are enabled, and anything related to nested virtualization. Let's start with `EnableConfigs`, and then check `arch/x86/kvm/Makefile` to see how things are compiled. |
| 22/2 | 2026/09/13 07:02 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/Makefile",
"FirstLine": 1,
"LineCount": 40
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0\n 2:\t\n 3:\tccflags-y += -I $(srctree)/arch/x86/kvm\n 4:\tccflags-$(CONFIG_KVM_WERROR) += -Werror\n 5:\t\n 6:\tinclude $(srctree)/virt/kvm/Makefile.kvm\n 7:\t\n 8:\tkvm-y\t\t\t+= x86.o emulate.o irq.o lapic.o cpuid.o msrs.o pmu.o regs.o \\\n 9:\t\t\t\t mtrr.o debugfs.o mmu/mmu.o mmu/page_track.o mmu/spte.o\n 10:\t\n 11:\tkvm-$(CONFIG_X86_64) += mmu/tdp_iter.o mmu/tdp_mmu.o\n 12:\tkvm-$(CONFIG_KVM_IOAPIC) += i8259.o i8254.o ioapic.o\n 13:\tkvm-$(CONFIG_KVM_HYPERV) += hyperv.o\n 14:\tkvm-$(CONFIG_KVM_XEN)\t+= xen.o\n 15:\tkvm-$(CONFIG_KVM_SMM)\t+= smm.o\n 16:\t\n 17:\tkvm-intel-y\t\t+= vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o vmx/vmcs12.o \\\n 18:\t\t\t\t vmx/nested.o vmx/posted_intr.o vmx/main.o\n 19:\t\n 20:\tkvm-intel-$(CONFIG_X86_SGX_KVM)\t+= vmx/sgx.o\n 21:\tkvm-intel-$(CONFIG_KVM_HYPERV)\t+= vmx/hyperv.o vmx/hyperv_evmcs.o\n 22:\tkvm-intel-$(CONFIG_KVM_INTEL_TDX)\t+= vmx/tdx.o\n 23:\t\n 24:\tkvm-amd-y\t\t+= svm/svm.o svm/vmenter.o svm/pmu.o svm/nested.o svm/avic.o\n 25:\t\n 26:\tkvm-amd-$(CONFIG_KVM_AMD_SEV)\t+= svm/sev.o\n 27:\tkvm-amd-$(CONFIG_KVM_HYPERV)\t+= svm/hyperv.o\n 28:\t\n 29:\tifdef CONFIG_HYPERV\n 30:\tkvm-y\t\t\t+= kvm_onhyperv.o\n 31:\tkvm-intel-y\t\t+= vmx/vmx_onhyperv.o vmx/hyperv_evmcs.o\n 32:\tkvm-amd-y\t\t+= svm/svm_onhyperv.o\n 33:\tendif\n 34:\t\n 35:\tobj-$(CONFIG_KVM_X86)\t+= kvm.o\n 36:\tobj-$(CONFIG_KVM_INTEL)\t+= kvm-intel.o\n 37:\tobj-$(CONFIG_KVM_AMD)\t+= kvm-amd.o\n 38:\t\n 39:\tAFLAGS_svm/vmenter.o := -iquote $(obj)\n 40:\t$(obj)/svm/vmenter.o: $(obj)/kvm-asm-offsets.h\n"
} |
| 23/2 | 2026/09/13 07:02 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 32866 output: 38 thoughts: 194 Thoughts: **KVM AMD Module Compilation and Dependencies** Okay, let's dive into this KVM AMD module build process. First, I need to confirm the configuration settings. I see we have `CONFIG_KVM_AMD` and also `CONFIG_KVM`. Right, so `obj-$(CONFIG_KVM_AMD) += kvm-amd.o` tells me that the `kvm-amd.o` object file gets included if and only if `CONFIG_KVM_AMD` is set. Then, I see the constituent parts of that module, `kvm-amd-y += svm/svm.o svm/vmenter.o svm/pmu.o svm/nested.o svm/avic.o`. This seems sensible; the AMD-specific KVM components are nicely encapsulated. Now, let's see what `obj-$(CONFIG_KVM_X86) += kvm.o` means for the generic x86 KVM module. That includes `kvm.o`, and then `kvm-y += x86.o emulate.o ...` specifies the build for the generic KVM module. Next, I need to check the dependencies. Looking in `arch/x86/kvm/Kconfig`, I see the definition for `config KVM_AMD`. Ah, good, it depends on `KVM && (CPU_SUP_AMD || CPU_SUP_HYGON)`. So `CONFIG_KVM_AMD` needs `KVM` to be configured, and either AMD or Hygon CPU support. Makes perfect sense. But what about `config KVM` itself? I need to check its dependencies. I'll need to look at lines 50-80 of `arch/x86/kvm/Kconfig` to figure that out. |
| 24/2 | 2026/09/13 07:02 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/Kconfig",
"FirstLine": 50,
"LineCount": 40
}Results: {
"Contents": " 50:\t\tselect KVM_GUEST_MEMFD if X86_64\n 51:\t\n 52:\tconfig KVM\n 53:\t\ttristate \"Kernel-based Virtual Machine (KVM) support\"\n 54:\t\tdepends on X86_LOCAL_APIC\n 55:\t\thelp\n 56:\t\t Support hosting fully virtualized guest machines using hardware\n 57:\t\t virtualization extensions. You will need a fairly recent\n 58:\t\t processor equipped with virtualization extensions. You will also\n 59:\t\t need to select one or more of the processor modules below.\n 60:\t\n 61:\t\t This module provides access to the hardware capabilities through\n 62:\t\t a character device node named /dev/kvm.\n 63:\t\n 64:\t\t To compile this as a module, choose M here: the module\n 65:\t\t will be called kvm.\n 66:\t\n 67:\t\t If unsure, say N.\n 68:\t\n 69:\tconfig KVM_WERROR\n 70:\t\tbool \"Compile KVM with -Werror\"\n 71:\t\t# Disallow KVM's -Werror if KASAN is enabled, e.g. to guard against\n 72:\t\t# randomized configs from selecting KVM_WERROR=y, which doesn't play\n 73:\t\t# nice with KASAN. KASAN builds generates warnings for the default\n 74:\t\t# FRAME_WARN, i.e. KVM_WERROR=y with KASAN=y requires special tuning.\n 75:\t\t# Building KVM with -Werror and KASAN is still doable via enabling\n 76:\t\t# the kernel-wide WERROR=y.\n 77:\t\tdepends on KVM_X86 \u0026\u0026 ((EXPERT \u0026\u0026 !KASAN) || WERROR)\n 78:\t\thelp\n 79:\t\t Add -Werror to the build flags for KVM.\n 80:\t\n 81:\t\t If in doubt, say \"N\".\n 82:\t\n 83:\tconfig KVM_SW_PROTECTED_VM\n 84:\t\tbool \"Enable support for KVM software-protected VMs\"\n 85:\t\tdepends on EXPERT\n 86:\t\tdepends on KVM_X86 \u0026\u0026 X86_64\n 87:\t\tselect KVM_GENERIC_MEMORY_ATTRIBUTES\n 88:\t\thelp\n 89:\t\t Enable support for KVM software-protected VMs. Currently, software-\n"
} |
| 25/2 | 2026/09/13 07:02 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 34124 output: 37 thoughts: 25 Thoughts: Here's my take on those lines from `arch/x86/kvm/Kconfig`: **Reviewing KVM Configuration Options** Alright, let's dive into lines 1 through 49 of `arch/x86/kvm/Kconfig`. I'm expecting this section to cover the fundamental, core KVM options for x86. I'll be particularly interested in: * **Dependencies and Defaults**: What architectural dependencies are enforced here? Are there any obvious defaults that need further scrutiny, especially if they are likely to change system behaviour. * **Feature Flags**: I want to identify the features that are controllable at compile-time. Are they orthogonal? Do they conflict? * **Documentation**: How well are these options documented within the config itself? A well-commented Kconfig file is a joy to work with, allowing for rapid understanding of the subsystem. * **Interaction with Hardware**: I'll be looking for clues on how the Kconfig options reflect and interact with specific hardware features, e.g. virtualization extensions, nested virtualization, etc. I'll need to remember the specific architecture features I'm working with. * **Potential for Performance Tuning**: Are there any hints here about options that could be tweaked to improve performance in certain virtualisation scenarios? This would be critical. Once I have a solid understanding of these initial lines, I can then assess the impact of these options on the kernel's overall configuration and the behaviour of a KVM-enabled system. |
| 26/2 | 2026/09/13 07:02 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/Kconfig",
"FirstLine": 1,
"LineCount": 49
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0\n 2:\t#\n 3:\t# KVM configuration\n 4:\t#\n 5:\t\n 6:\tsource \"virt/kvm/Kconfig\"\n 7:\t\n 8:\tmenuconfig VIRTUALIZATION\n 9:\t\tbool \"Virtualization\"\n 10:\t\tdefault y\n 11:\t\thelp\n 12:\t\t Say Y here to get to see options for using your Linux host to run other\n 13:\t\t operating systems inside virtual machines (guests).\n 14:\t\t This option alone does not add any kernel code.\n 15:\t\n 16:\t\t If you say N, all options in this submenu will be skipped and disabled.\n 17:\t\n 18:\tif VIRTUALIZATION\n 19:\t\n 20:\tconfig KVM_X86\n 21:\t\tdef_tristate KVM if (KVM_INTEL != n || KVM_AMD != n)\n 22:\t\tselect KVM_COMMON\n 23:\t\tselect KVM_ELIDE_TLB_FLUSH_IF_YOUNG\n 24:\t\tselect KVM_MMU_LOCKLESS_AGING\n 25:\t\tselect HAVE_KVM_IRQCHIP\n 26:\t\tselect HAVE_KVM_PFNCACHE\n 27:\t\tselect HAVE_KVM_DIRTY_RING_TSO\n 28:\t\tselect HAVE_KVM_DIRTY_RING_ACQ_REL\n 29:\t\tselect HAVE_KVM_IRQ_BYPASS\n 30:\t\tselect HAVE_KVM_IRQ_ROUTING\n 31:\t\tselect HAVE_KVM_READONLY_MEM\n 32:\t\tselect VHOST_TASK\n 33:\t\tselect KVM_ASYNC_PF\n 34:\t\tselect USER_RETURN_NOTIFIER\n 35:\t\tselect KVM_MMIO\n 36:\t\tselect SCHED_INFO\n 37:\t\tselect PERF_EVENTS\n 38:\t\tselect GUEST_PERF_EVENTS\n 39:\t\tselect PERF_GUEST_MEDIATED_PMU\n 40:\t\tselect HAVE_KVM_MSI\n 41:\t\tselect HAVE_KVM_CPU_RELAX_INTERCEPT\n 42:\t\tselect HAVE_KVM_NO_POLL\n 43:\t\tselect VIRT_XFER_TO_GUEST_WORK\n 44:\t\tselect KVM_GENERIC_DIRTYLOG_READ_PROTECT\n 45:\t\tselect KVM_VFIO\n 46:\t\tselect HAVE_KVM_PM_NOTIFIER if PM\n 47:\t\tselect KVM_GENERIC_HARDWARE_ENABLING\n 48:\t\tselect KVM_GENERIC_PRE_FAULT_MEMORY\n 49:\t\tselect KVM_WERROR if WERROR\n"
} |
| 27/2 | 2026/09/13 07:02 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 35217 output: 297 thoughts: 1315 Thoughts: **Fuzzing Target Selection: KVM AMD SVM DecodeAssist Implementation**
Alright, let's break this down. My goal here is to identify the most effective symbols to target for fuzzing, given this patch's introduction of AMD SVM DecodeAssists in nested virtualization. First, let me get the lay of the land, the dependencies: `KVM_AMD` is key, and it sits within `VIRTUALIZATION` and depends on `KVM` and either `CPU_SUP_AMD` or `CPU_SUP_HYGON`. `KVM` itself relies on `X86_LOCAL_APIC`, which is standard. Okay, good. That sets the stage.
Now, let's consult the fuzzing guidelines. I need to *avoid* ubiquitous functions that are called everywhere – allocators, page fault handlers, etc. Those will dilute the fuzzing effort. Instead, I need to focus on functions that specifically implement the *new* logic or are entry points for this particular subsystem. Considering inline functions, I should identify the non-static callers that exercise these inline helpers if the changes are mainly within the helpers.
Next, I need to list the symbols that look relevant, let's see which functions are changed:
* In `arch/x86/kvm/svm/nested.c`:
* `nested_svm_update_vmcb12_insn_bytes`
* `nested_svm_prepare_synthesized_insn_bytes`
* `nested_svm_fetch_insn_bytes`
* `nested_svm_copy_insn_bytes`
* `nested_svm_inject_npf_exit`
* `nested_svm_vmexit_update_vmcb12`
* `nested_svm_exit_handled`
* `nested_svm_vmexit`
* In `arch/x86/kvm/svm/svm.c`:
* `svm_check_intercept`
* In `arch/x86/kvm/emulate.c`:
* `x86_emulator_copy_insn_bytes`
* `get_invlpg_linear_addr`
* `emulator_check_intercept`
* In `arch/x86/kvm/x86.c`:
* `kvm_fetch_guest_virt`
Now, let's consider static vs. non-static functions. Syzkaller can, in fact, target static functions as long as they are present in kallsyms (unless inlined), but the non-static ones are guaranteed to be in the symbol table. I think I'll include both, and specifically focus on what is core to the new implementation:
So, to be clear, I'm including:
* `nested_svm_update_vmcb12_insn_bytes`
* `nested_svm_fetch_insn_bytes`
* `nested_svm_prepare_synthesized_insn_bytes`
* `svm_check_intercept`
* `x86_emulator_copy_insn_bytes`
* `kvm_fetch_guest_virt`
* `nested_svm_exit_handled`
Are any of these ubiquitous? No! Absolutely not. These are *all* specific to KVM AMD SVM nested virtualization and instruction emulation. They aren't called during normal OS operation outside of KVM SVM execution. This is *perfect*.
Let's review my reasoning: This patch is adding support for AMD SVM DecodeAssists in nested virtualization via the `cpuid` feature `X86_FEATURE_DECODEASSISTS`. It allows KVM to provide instruction bytes and decode assistance in VMCB12 on intercepted nested exits (such as nested page faults). It introduces new logic to fetch instruction bytes from guest memory (`kvm_fetch_guest_virt`), copy them from the emulator cache (`x86_emulator_copy_insn_bytes`), and sets exit information in `svm_check_intercept`. It also has defensive checks in `nested_svm_copy_insn_bytes`. These changes modify executable kernel code in the KVM core subsystem and nested SVM driver, reachable via `/dev/kvm` ioctls and nested VM execution on AMD hardware / QEMU virtualization. It is, therefore, worth fuzzing.
Required configs: `KVM` and `KVM_AMD`. No, it does *not* require `KVM_AMD_SEV`, even though there are conditional checks related to SEV,
|
| 28/2 | 2026/09/13 07:02 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"KVM",
"KVM_AMD"
],
"FocusSymbols": [
"nested_svm_update_vmcb12_insn_bytes",
"nested_svm_fetch_insn_bytes",
"nested_svm_prepare_synthesized_insn_bytes",
"nested_svm_copy_insn_bytes",
"nested_svm_exit_handled",
"svm_check_intercept",
"x86_emulator_copy_insn_bytes",
"kvm_fetch_guest_virt"
],
"Reasoning": "The patch series implements AMD SVM nested virtualization support for DecodeAssists (X86_FEATURE_DECODEASSISTS). It modifies reachable code in KVM and the nested SVM subsystem to supply instruction bytes and decode assist information (e.g. exit_info_1 for intercepted instructions, guest instruction bytes for nested page faults). It includes new guest memory fetching and emulation cache extraction logic (kvm_fetch_guest_virt, x86_emulator_copy_insn_bytes, nested_svm_fetch_insn_bytes) and adds runtime checks (WARN_ON_ONCE in nested_svm_copy_insn_bytes). This reachable virtualization code is exposed via /dev/kvm and warrants fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"KVM",
"KVM_AMD"
],
"FocusSymbols": [
"nested_svm_update_vmcb12_insn_bytes",
"nested_svm_fetch_insn_bytes",
"nested_svm_prepare_synthesized_insn_bytes",
"nested_svm_copy_insn_bytes",
"nested_svm_exit_handled",
"svm_check_intercept",
"x86_emulator_copy_insn_bytes",
"kvm_fetch_guest_virt"
],
"Reasoning": "The patch series implements AMD SVM nested virtualization support for DecodeAssists (X86_FEATURE_DECODEASSISTS). It modifies reachable code in KVM and the nested SVM subsystem to supply instruction bytes and decode assist information (e.g. exit_info_1 for intercepted instructions, guest instruction bytes for nested page faults). It includes new guest memory fetching and emulation cache extraction logic (kvm_fetch_guest_virt, x86_emulator_copy_insn_bytes, nested_svm_fetch_insn_bytes) and adds runtime checks (WARN_ON_ONCE in nested_svm_copy_insn_bytes). This reachable virtualization code is exposed via /dev/kvm and warrants fuzzing.",
"WorthFuzzing": true
} |
| 29/1 | 2026/09/13 07:02 | action | If |
2mArgs:{
"WorthFuzzing": true
}Results: null |
| 30/2 | 2026/09/13 07:02 | agent | kmsan-evaluator |
2mModel:core Args: null Results: {
"KMSANReasoning": "The patch implements nested SVM DecodeAssists support in KVM on AMD x86. This entails advertising the CPUID capability, populating EXITINFO1 during emulated intercepts, synthesizing/propagating instruction bytes into VMCB02 and VMCB12 on nested VM-exits, and adding helper functions for instruction byte extraction from the emulator cache or guest virtual memory.\n\nKMSAN vs KASAN applicability:\n- No kernel-to-user information leaks: The data copied is written to guest memory (VMCB12) or hardware-allocated VMCB02 (allocated with `__GFP_ZERO`). There are no new or modified `copy_to_user`, `put_user`, socket, netlink, or ioctl user-facing buffers.\n- All new and modified structures and local variables are properly initialized before being read: `x86_instruction_info` uses designated initializers (zeroing omitted fields), `linear` in `get_invlpg_linear_addr` is explicitly initialized to 0, `e` in `nested_svm_fetch_insn_bytes` is cleared via `memset` in `kvm_fetch_guest_virt`, and `vmcb02_insn_bytes_valid` is zero-initialized with the vCPU allocation and properly invalidated across transitions.\n- Bounds and length checks: The instruction length calculations in `nested_svm_get_insn_bytes_len`, `nested_svm_fetch_insn_bytes`, and `x86_emulator_copy_insn_bytes` ensure memory is copied strictly within valid bounds and only up to the count of successfully fetched bytes.\n- Memory corruption or out-of-bounds risks (e.g. in `insn_bytes` copying or limit checks) are spatial access violations that are detected by standard KASAN, not uninitialized memory reads.\n\nTherefore, this patch series does not introduce uninitialized memory risks or info-leaks, and a dedicated KMSAN fuzzing session is not warranted.",
"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 84b93f2b93403c96ba08905c3de300acf68146ab
Author: syz-cluster <triage@syzkaller.com>
Date: Sun Sep 13 07:01:03 2026 +0000
syz-cluster: applied patch under review
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index ddb022cb203a2..b644e45eac710 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -1208,6 +1208,7 @@ void kvm_initialize_cpu_caps(void)
VENDOR_F(NPT),
VENDOR_F(VMCBCLEAN),
VENDOR_F(FLUSHBYASID),
+ VENDOR_F(DECODEASSISTS),
VENDOR_F(NRIPS),
VENDOR_F(TSCRATEMSR),
VENDOR_F(V_VMSAVE_VMLOAD),
diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
index 8071b372d233e..5017a9aa1d4bb 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -410,6 +410,9 @@ static int em_salc(struct x86_emulate_ctxt *ctxt)
_fault ? X86EMUL_UNHANDLEABLE : X86EMUL_CONTINUE; \
})
+static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,
+ enum x86_intercept intercept);
+
static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
enum x86_intercept intercept,
enum x86_intercept_stage stage)
@@ -427,6 +430,7 @@ static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,
.src_type = ctxt->src.type,
.dst_type = ctxt->dst.type,
.ad_bytes = ctxt->ad_bytes,
+ .invlpg_linear_addr = get_invlpg_linear_addr(ctxt, intercept),
.rip = ctxt->eip,
.next_rip = ctxt->_eip,
};
@@ -702,6 +706,20 @@ static __always_inline int __linearize(struct x86_emulate_ctxt *ctxt,
return emulate_gp(ctxt, 0);
}
+static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,
+ enum x86_intercept intercept)
+{
+ unsigned int max_size;
+ unsigned long linear = 0;
+
+ if (intercept != x86_intercept_invlpg)
+ return 0;
+
+ __linearize(ctxt, ctxt->src.addr.mem, &max_size, 1, ctxt->mode,
+ &linear, X86EMUL_F_INVLPG);
+ return linear;
+}
+
static int linearize(struct x86_emulate_ctxt *ctxt,
struct segmented_address addr,
unsigned size, bool write,
@@ -895,6 +913,28 @@ static __always_inline int do_insn_fetch_bytes(struct x86_emulate_ctxt *ctxt,
return X86EMUL_CONTINUE;
}
+/*
+ * The caller is responsible for ensuring that the fetch cache belongs to
+ * the current instruction. Matching EIP alone does not guarantee this.
+ */
+unsigned int x86_emulator_copy_insn_bytes(struct x86_emulate_ctxt *ctxt,
+ unsigned long eip, u8 *bytes,
+ unsigned int max_bytes)
+{
+ unsigned int nr_bytes;
+
+ if (!ctxt || ctxt->eip != eip ||
+ ctxt->fetch.end < ctxt->fetch.data ||
+ ctxt->fetch.end > ctxt->fetch.data + sizeof(ctxt->fetch.data))
+ return 0;
+
+ nr_bytes = min_t(unsigned int, ctxt->fetch.end - ctxt->fetch.data,
+ max_bytes);
+ memcpy(bytes, ctxt->fetch.data, nr_bytes);
+ return nr_bytes;
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(x86_emulator_copy_insn_bytes);
+
/* Fetch next part of the instruction being emulated. */
#define insn_fetch(_type, _ctxt) \
({ _type _x; \
diff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h
index 3e375af15c035..5361d42e6e057 100644
--- a/arch/x86/kvm/kvm_emulate.h
+++ b/arch/x86/kvm/kvm_emulate.h
@@ -51,6 +51,7 @@ struct x86_instruction_info {
u8 src_type; /* type of source operand */
u8 dst_type; /* type of destination operand */
u8 ad_bytes; /* size of src/dst address */
+ u64 invlpg_linear_addr; /* linear address, if INVLPG */
u64 rip; /* rip of the instruction */
u64 next_rip; /* rip following the instruction */
};
@@ -527,6 +528,9 @@ enum x86_intercept {
};
int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int emulation_type);
+unsigned int x86_emulator_copy_insn_bytes(struct x86_emulate_ctxt *ctxt,
+ unsigned long eip, u8 *bytes,
+ unsigned int max_bytes);
bool x86_page_table_writing_insn(struct x86_emulate_ctxt *ctxt);
#define EMULATION_FAILED -1
#define EMULATION_OK 0
diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c
index 73f37b050d0a0..2dc515d73eb62 100644
--- a/arch/x86/kvm/svm/nested.c
+++ b/arch/x86/kvm/svm/nested.c
@@ -35,12 +35,162 @@
#define CC KVM_NESTED_VMENTER_CONSISTENCY_CHECK
+static void nested_svm_invalidate_insn_bytes(struct vmcb *vmcb)
+{
+ vmcb->control.insn_len = 0;
+}
+
+static void nested_svm_copy_insn_bytes(struct vmcb *to,
+ const struct vmcb *from)
+{
+ u8 insn_len = from->control.insn_len;
+
+ if (WARN_ON_ONCE(insn_len > sizeof(from->control.insn_bytes))) {
+ nested_svm_invalidate_insn_bytes(to);
+ return;
+ }
+
+ memcpy(to->control.insn_bytes, from->control.insn_bytes, insn_len);
+ to->control.insn_len = insn_len;
+}
+
+static bool nested_svm_vmexit_supports_insn_bytes(const struct vmcb *vmcb)
+{
+ u64 exit_code = vmcb->control.exit_code;
+
+ if (exit_code != SVM_EXIT_NPF &&
+ exit_code != SVM_EXIT_EXCP_BASE + PF_VECTOR)
+ return false;
+
+ return !(vmcb->control.exit_info_1 & PFERR_FETCH_MASK);
+}
+
+static void nested_svm_set_vmcb02_insn_bytes_valid(struct vcpu_svm *svm)
+{
+ svm->nested.vmcb02_insn_bytes_valid =
+ static_cpu_has(X86_FEATURE_DECODEASSISTS) &&
+ nested_svm_vmexit_supports_insn_bytes(svm->nested.vmcb02.ptr);
+}
+
+static void nested_svm_invalidate_vmcb02_insn_bytes(struct vcpu_svm *svm)
+{
+ nested_svm_invalidate_insn_bytes(svm->nested.vmcb02.ptr);
+ svm->nested.vmcb02_insn_bytes_valid = false;
+}
+
+static u8 nested_svm_get_insn_bytes_len(struct kvm_vcpu *vcpu, u8 max_bytes)
+{
+ if (!is_64_bit_mode(vcpu)) {
+ u32 eip = kvm_rip_read(vcpu);
+ u32 limit = to_svm(vcpu)->vmcb->save.cs.limit;
+
+ if (eip > limit)
+ return 0;
+ max_bytes = min_t(u64, max_bytes, (u64)limit - eip + 1);
+ }
+
+ return max_bytes;
+}
+
+static u8 nested_svm_fetch_insn_bytes(struct kvm_vcpu *vcpu, u8 *bytes,
+ u8 count, u8 max_bytes)
+{
+ struct x86_exception e;
+ gva_t rip = kvm_get_linear_rip(vcpu);
+
+ max_bytes = nested_svm_get_insn_bytes_len(vcpu, max_bytes);
+ count = min(count, max_bytes);
+
+ while (count < max_bytes) {
+ gva_t addr = rip + count;
+ u8 chunk;
+
+ if (!is_64_bit_mode(vcpu))
+ addr = (u32)addr;
+ else if (is_noncanonical_address(addr, vcpu, 0))
+ break;
+
+ /*
+ * Read one page at a time to recheck the linear address at each
+ * boundary.
+ */
+ chunk = min_t(unsigned int, max_bytes - count,
+ PAGE_SIZE - offset_in_page(addr));
+ if (kvm_fetch_guest_virt(vcpu, addr, bytes + count,
+ chunk, &e) != X86EMUL_CONTINUE)
+ break;
+ count += chunk;
+ }
+
+ return count;
+}
+
+static void nested_svm_prepare_synthesized_insn_bytes(struct kvm_vcpu *vcpu)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+ struct vmcb *vmcb02 = svm->nested.vmcb02.ptr;
+ struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+ u8 max_bytes = sizeof(vmcb02->control.insn_bytes);
+ u8 count;
+
+ nested_svm_invalidate_vmcb02_insn_bytes(svm);
+
+ if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
+ return;
+
+ max_bytes = nested_svm_get_insn_bytes_len(vcpu, max_bytes);
+ count = x86_emulator_copy_insn_bytes(ctxt, kvm_rip_read(vcpu),
+ vmcb02->control.insn_bytes, max_bytes);
+ if (!is_sev_guest(vcpu))
+ count = nested_svm_fetch_insn_bytes(vcpu,
+ vmcb02->control.insn_bytes,
+ count, max_bytes);
+ vmcb02->control.insn_len = count;
+
+ svm->nested.vmcb02_insn_bytes_valid = true;
+}
+
+static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,
+ struct vmcb *vmcb12,
+ const struct vmcb *vmcb02)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+ const u8 max_bytes = sizeof(vmcb12->control.insn_bytes);
+
+ if (!guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS))
+ goto out;
+
+ if (!nested_svm_vmexit_supports_insn_bytes(vmcb02)) {
+ nested_svm_invalidate_insn_bytes(vmcb12);
+ goto out;
+ }
+
+ if (svm->nested.vmcb02_insn_bytes_valid) {
+ nested_svm_copy_insn_bytes(vmcb12, vmcb02);
+ goto out;
+ }
+
+ if (!is_sev_guest(vcpu))
+ vmcb12->control.insn_len =
+ nested_svm_fetch_insn_bytes(vcpu,
+ vmcb12->control.insn_bytes,
+ 0,
+ max_bytes);
+ else
+ nested_svm_invalidate_insn_bytes(vmcb12);
+
+out:
+ svm->nested.vmcb02_insn_bytes_valid = false;
+}
+
static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
struct x86_exception *fault,
bool from_hardware)
{
struct vcpu_svm *svm = to_svm(vcpu);
struct vmcb *vmcb = svm->vmcb;
+ struct x86_emulate_ctxt *ctxt = vcpu->arch.emulate_ctxt;
+ bool from_emulation = ctxt && fault == &ctxt->exception;
u64 fault_stage;
/*
@@ -68,6 +218,12 @@ static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,
(fault->error_code & ~PFERR_GUEST_FAULT_STAGE_MASK);
vmcb->control.exit_info_2 = fault->address;
+ if (from_hardware)
+ nested_svm_set_vmcb02_insn_bytes_valid(svm);
+ else if (from_emulation && !(fault->error_code & PFERR_FETCH_MASK))
+ nested_svm_prepare_synthesized_insn_bytes(vcpu);
+ else
+ svm->nested.vmcb02_insn_bytes_valid = false;
nested_svm_vmexit(svm);
}
@@ -869,6 +1025,7 @@ static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)
* Filled at exit: exit_code, exit_info_1, exit_info_2, exit_int_info,
* exit_int_info_err, next_rip, insn_len, insn_bytes.
*/
+ nested_svm_invalidate_vmcb02_insn_bytes(svm);
if (guest_cpu_cap_has(vcpu, X86_FEATURE_VGIF) &&
(vmcb12_ctrl->int_ctl & V_GIF_ENABLE_MASK))
@@ -1297,6 +1454,8 @@ static int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu)
if (guest_cpu_cap_has(vcpu, X86_FEATURE_NRIPS))
vmcb12->control.next_rip = vmcb02->control.next_rip;
+ nested_svm_update_vmcb12_insn_bytes(vcpu, vmcb12, vmcb02);
+
if (nested_vmcb12_has_lbrv(vcpu))
svm_copy_lbrs(&vmcb12->save, &vmcb02->save);
@@ -1649,8 +1808,10 @@ int nested_svm_exit_handled(struct vcpu_svm *svm)
vmexit = nested_svm_intercept(svm);
- if (vmexit == NESTED_EXIT_DONE)
+ if (vmexit == NESTED_EXIT_DONE) {
+ nested_svm_set_vmcb02_insn_bytes_valid(svm);
nested_svm_vmexit(svm);
+ }
return vmexit;
}
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index ea647938a2a65..c91561a9a49f7 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -4837,6 +4837,7 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
int vmexit, ret = X86EMUL_CONTINUE;
struct __x86_intercept icpt_info;
struct vmcb *vmcb = svm->vmcb;
+ bool decode_assists;
if (info->intercept >= ARRAY_SIZE(x86_intercept_map))
goto out;
@@ -4846,14 +4847,26 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
if (stage != icpt_info.stage)
goto out;
+ decode_assists = guest_cpu_cap_has(vcpu, X86_FEATURE_DECODEASSISTS);
+
switch (icpt_info.exit_code) {
case SVM_EXIT_READ_CR0:
if (info->intercept == x86_intercept_cr_read)
icpt_info.exit_code += info->modrm_reg;
+
+ if (decode_assists)
+ vmcb->control.exit_info_1 =
+ info->intercept == x86_intercept_cr_read ?
+ BIT_ULL(63) | (info->modrm_rm & 0xf) : 0;
break;
case SVM_EXIT_WRITE_CR0: {
unsigned long cr0, val;
+ if (decode_assists)
+ vmcb->control.exit_info_1 =
+ info->intercept == x86_intercept_cr_write ?
+ BIT_ULL(63) | (info->modrm_rm & 0xf) : 0;
+
/*
* Adjust the exit code accordingly if a CR other than CR0 is
* being written, and skip straight to the common handling as
@@ -4891,13 +4904,30 @@ static int svm_check_intercept(struct kvm_vcpu *vcpu,
*/
cr0 = vcpu->arch.cr0 & ~SVM_CR0_SELECTIVE_MASK;
val = info->src_val & ~SVM_CR0_SELECTIVE_MASK;
- if (cr0 ^ val)
+ if (cr0 ^ val) {
icpt_info.exit_code = SVM_EXIT_CR0_SEL_WRITE;
+ if (decode_assists)
+ vmcb->control.exit_info_1 = 0;
+ }
break;
}
case SVM_EXIT_READ_DR0:
case SVM_EXIT_WRITE_DR0:
icpt_info.exit_code += info->modrm_reg;
+ if (decode_assists)
+ vmcb->control.exit_info_1 = info->modrm_rm & 0xf;
+ break;
+ case SVM_EXIT_SWINT:
+ if (decode_assists)
+ vmcb->control.exit_info_1 = info->src_val & 0xff;
+ break;
+ case SVM_EXIT_INVLPG:
+ if (decode_assists)
+ vmcb->control.exit_info_1 = info->invlpg_linear_addr;
+ break;
+ case SVM_EXIT_INVLPGA:
+ if (decode_assists)
+ vmcb->control.exit_info_1 = 0;
break;
case SVM_EXIT_MSR:
if (info->intercept == x86_intercept_wrmsr)
@@ -5530,6 +5560,8 @@ static __init void svm_set_cpu_caps(void)
*/
kvm_cpu_cap_set(X86_FEATURE_FLUSHBYASID);
+ kvm_cpu_cap_check_and_set(X86_FEATURE_DECODEASSISTS);
+
if (nrips)
kvm_cpu_cap_set(X86_FEATURE_NRIPS);
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index e958943b81627..1e12ef1105c01 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -243,6 +243,9 @@ struct svm_nested_state {
* on its side.
*/
bool force_msr_bitmap_recalc;
+
+ /* True if VMCB02 has instruction bytes for the current nested exit. */
+ bool vmcb02_insn_bytes_valid;
};
struct vcpu_sev_es_state {
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 4b3681796c752..735b67781d554 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -4848,9 +4848,24 @@ static int kvm_read_guest_virt_helper(gva_t addr, void *val, unsigned int bytes,
}
/* used for instruction fetching */
-static int kvm_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,
- gva_t addr, void *val, unsigned int bytes,
- struct x86_exception *exception)
+int kvm_fetch_guest_virt(struct kvm_vcpu *vcpu, gva_t addr, void *val,
+ unsigned int bytes, struct x86_exception *exception)
+{
+ u64 access = PFERR_FETCH_MASK;
+
+ if (kvm_x86_call(get_cpl)(vcpu) == 3)
+ access |= PFERR_USER_MASK;
+
+ memset(exception, 0, sizeof(*exception));
+ return kvm_read_guest_virt_helper(addr, val, bytes, vcpu, access,
+ exception);
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_fetch_guest_virt);
+
+static int emulator_fetch_guest_virt(struct x86_emulate_ctxt *ctxt,
+ gva_t addr, void *val,
+ unsigned int bytes,
+ struct x86_exception *exception)
{
struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt);
struct kvm_pagewalk *gva_walk = &vcpu->arch.gva_walk;
@@ -5817,7 +5832,7 @@ static const struct x86_emulate_ops emulate_ops = {
.write_gpr = emulator_write_gpr,
.read_std = emulator_read_std,
.write_std = emulator_write_std,
- .fetch = kvm_fetch_guest_virt,
+ .fetch = emulator_fetch_guest_virt,
.read_emulated = emulator_read_emulated,
.write_emulated = emulator_write_emulated,
.cmpxchg_emulated = emulator_cmpxchg_emulated,
diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
index 0f5919b092e47..f03419a79a118 100644
--- a/arch/x86/kvm/x86.h
+++ b/arch/x86/kvm/x86.h
@@ -356,6 +356,9 @@ static inline void adjust_tsc_offset_host(struct kvm_vcpu *vcpu, s64 adjustment)
int kvm_read_guest_virt(struct kvm_vcpu *vcpu,
gva_t addr, void *val, unsigned int bytes,
struct x86_exception *exception);
+int kvm_fetch_guest_virt(struct kvm_vcpu *vcpu, gva_t addr, void *val,
+ unsigned int bytes,
+ struct x86_exception *exception);
int kvm_write_guest_virt_system(struct kvm_vcpu *vcpu,
gva_t addr, void *val, unsigned int bytes,
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39e..bb55a8c32c420 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -118,6 +118,7 @@ TEST_GEN_PROGS_x86 += x86/vmx_preemption_timer_test
TEST_GEN_PROGS_x86 += x86/svm_vmcall_test
TEST_GEN_PROGS_x86 += x86/svm_int_ctl_test
TEST_GEN_PROGS_x86 += x86/svm_nested_clear_efer_svme
+TEST_GEN_PROGS_x86 += x86/svm_nested_decode_assists_test
TEST_GEN_PROGS_x86 += x86/svm_nested_shutdown_test
TEST_GEN_PROGS_x86 += x86/svm_nested_soft_inject_test
TEST_GEN_PROGS_x86 += x86/svm_nested_vmcb12_gpa
diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h
index 6e6f70035508a..e9d745a2c1313 100644
--- a/tools/testing/selftests/kvm/include/x86/processor.h
+++ b/tools/testing/selftests/kvm/include/x86/processor.h
@@ -220,6 +220,7 @@ struct kvm_x86_cpu_feature {
#define X86_FEATURE_LBRV KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 1)
#define X86_FEATURE_NRIPS KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 3)
#define X86_FEATURE_TSCRATEMSR KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 4)
+#define X86_FEATURE_DECODEASSISTS KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 7)
#define X86_FEATURE_PAUSEFILTER KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 10)
#define X86_FEATURE_PFTHRESHOLD KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 12)
#define X86_FEATURE_V_VMSAVE_VMLOAD KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 15)
diff --git a/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c b/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c
new file mode 100644
index 0000000000000..9f729e8542f2c
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/svm_nested_decode_assists_test.c
@@ -0,0 +1,547 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test KVM's virtualization of SVM DecodeAssists for nested guests.
+ */
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "svm_util.h"
+
+#define TEST_INT_VECTOR 0x81
+
+/* Any canonical virtual address that is never mapped by the selftest VM. */
+#define PF_TEST_GVA BIT_ULL(40)
+#define PF_FETCH_TEST_GVA BIT_ULL(41)
+
+#define OUTSB_OPCODE 0x6e
+#define MOVSB_OPCODE 0xa4
+#define NOP_OPCODE 0x90
+#define BOUNDARY_OUTSB_CODE_SIZE 15
+#define TEST_IOPM_SIZE (3 * PAGE_SIZE)
+
+static u8 npf_target[PAGE_SIZE] __aligned(PAGE_SIZE);
+static u8 mmio_source[PAGE_SIZE] __aligned(PAGE_SIZE);
+static u8 boundary_outsb_code[2 * PAGE_SIZE] __aligned(PAGE_SIZE);
+
+static void l2_read_code(void)
+{
+ asm volatile("mov (%0), %%rax" : : "r"(&npf_target) : "rax", "memory");
+ GUEST_FAIL("L2 read did not cause a nested page fault");
+}
+
+static void l2_outsb_code(void)
+{
+ asm volatile("mov %0, %%rsi\n\t"
+ "mov $0x80, %%dx\n\t"
+ "outsb"
+ : : "r"(&npf_target) : "rsi", "rdx", "memory");
+ GUEST_FAIL("L2 OUTSB did not cause a nested page fault");
+}
+
+extern u8 l2_movsb_insn[];
+
+static void l2_movsb_code(void)
+{
+ asm volatile("mov %0, %%rsi\n\t"
+ "mov %1, %%rdi\n\t"
+ "l2_movsb_insn:\n\t"
+ "movsb"
+ : : "r"(&mmio_source), "r"(&npf_target)
+ : "rsi", "rdi", "memory");
+ GUEST_FAIL("L2 MOVSB did not cause a nested page fault");
+}
+
+extern u8 l2_userspace_pf_insn[];
+
+static void l2_userspace_pf_code(void)
+{
+ asm volatile("movzbl (%0), %%eax\n\t"
+ "l2_userspace_pf_insn:\n\t"
+ "nop"
+ : : "r"(&mmio_source) : "rax", "memory");
+ GUEST_FAIL("Userspace-injected #PF was not intercepted by L1");
+}
+
+static void l2_pf_code(void)
+{
+ asm volatile("mov (%0), %%rax"
+ : : "r"(PF_TEST_GVA) : "rax", "memory");
+ GUEST_FAIL("L2 access to an unmapped VA did not #PF");
+}
+
+static void l2_fep_pf_code(void)
+{
+ asm volatile(KVM_FEP "mov (%0), %%rax"
+ : : "r"(PF_TEST_GVA) : "rax", "memory");
+ GUEST_FAIL("L2 forced-emulated access to an unmapped VA did not #PF");
+}
+
+static void l2_fep_mov_from_cr4_code(void)
+{
+ asm volatile(KVM_FEP "mov %%cr4, %%r10" : : : "r10");
+ GUEST_FAIL("L2 forced-emulated MOV-from-CR4 was not intercepted");
+}
+
+static void l2_fep_mov_to_dr7_code(void)
+{
+ asm volatile("mov %%dr7, %%rax\n\t"
+ "mov %%rax, %%rbx\n\t"
+ KVM_FEP "mov %%rbx, %%dr7" : : : "rax", "rbx");
+ GUEST_FAIL("L2 forced-emulated MOV-to-DR7 was not intercepted");
+}
+
+static void l2_fep_clts_code(void)
+{
+ asm volatile(KVM_FEP "clts" : : : "memory");
+ GUEST_FAIL("L2 forced-emulated CLTS was not intercepted");
+}
+
+static void l2_fep_int_code(void)
+{
+ asm volatile(KVM_FEP "int %0" : : "i"(TEST_INT_VECTOR));
+ GUEST_FAIL("L2 forced-emulated INTn was not intercepted");
+}
+
+static void l2_fep_invlpg_code(void)
+{
+ asm volatile(KVM_FEP "invlpg (%0)" : : "r"(&npf_target) : "memory");
+ GUEST_FAIL("L2 forced-emulated INVLPG was not intercepted");
+}
+
+static void l2_fep_invlpga_code(void)
+{
+ asm volatile(KVM_FEP "invlpga"
+ : : "a"(&npf_target), "c"(0) : "memory");
+ GUEST_FAIL("L2 forced-emulated INVLPGA was not intercepted");
+}
+
+struct instruction_intercept_test {
+ const char *name;
+ void (*code)(void);
+ u64 intercept;
+ u32 intercept_cr;
+ u32 intercept_dr;
+ u64 exit_code;
+ u64 exit_info_1;
+ u64 exit_info_1_mask;
+ bool check_rax;
+ u64 rax;
+};
+
+static const struct instruction_intercept_test instruction_intercept_tests[] = {
+ {
+ .name = "MOV-from-CR4",
+ .code = l2_fep_mov_from_cr4_code,
+ .intercept_cr = BIT(INTERCEPT_CR4_READ),
+ .exit_code = SVM_EXIT_READ_CR4,
+ .exit_info_1 = BIT_ULL(63) | 10,
+ .exit_info_1_mask = ~0ULL,
+ }, {
+ .name = "MOV-to-DR7",
+ .code = l2_fep_mov_to_dr7_code,
+ .intercept_dr = BIT(INTERCEPT_DR7_WRITE),
+ .exit_code = SVM_EXIT_WRITE_DR7,
+ .exit_info_1 = 3,
+ .exit_info_1_mask = ~0ULL,
+ }, {
+ .name = "CLTS",
+ .code = l2_fep_clts_code,
+ .intercept_cr = BIT(INTERCEPT_CR0_WRITE),
+ .exit_code = SVM_EXIT_WRITE_CR0,
+ .exit_info_1_mask = BIT_ULL(63),
+ }, {
+ .name = "INTn",
+ .code = l2_fep_int_code,
+ .intercept = BIT_ULL(INTERCEPT_INTn),
+ .exit_code = SVM_EXIT_SWINT,
+ .exit_info_1 = TEST_INT_VECTOR,
+ .exit_info_1_mask = ~0ULL,
+ }, {
+ .name = "INVLPG",
+ .code = l2_fep_invlpg_code,
+ .intercept = BIT_ULL(INTERCEPT_INVLPG),
+ .exit_code = SVM_EXIT_INVLPG,
+ .exit_info_1 = (u64)&npf_target,
+ .exit_info_1_mask = ~0ULL,
+ }, {
+ .name = "INVLPGA",
+ .code = l2_fep_invlpga_code,
+ .intercept = BIT_ULL(INTERCEPT_INVLPGA),
+ .exit_code = SVM_EXIT_INVLPGA,
+ .exit_info_1_mask = ~0ULL,
+ .check_rax = true,
+ .rax = (u64)&npf_target,
+ },
+};
+
+static void assert_decode_assist_insn_bytes(struct vmcb *vmcb)
+{
+ GUEST_ASSERT(vmcb->control.insn_len);
+ GUEST_ASSERT(vmcb->control.insn_len <=
+ sizeof(vmcb->control.insn_bytes));
+ GUEST_ASSERT(!memcmp(vmcb->control.insn_bytes,
+ (void *)vmcb->save.rip,
+ vmcb->control.insn_len));
+}
+
+static void assert_full_decode_assist_insn_bytes(struct vmcb *vmcb)
+{
+ GUEST_ASSERT_EQ(vmcb->control.insn_len,
+ sizeof(vmcb->control.insn_bytes));
+ assert_decode_assist_insn_bytes(vmcb);
+}
+
+static void prepare_l2_for_vmrun(struct svm_test_data *svm, gva_t rip)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ vmcb->save.rip = rip;
+ vmcb->save.rsp = (u64)svm->stack;
+}
+
+static void run_intercept_test(struct svm_test_data *svm,
+ const struct instruction_intercept_test *test)
+{
+ struct vmcb *vmcb = svm->vmcb;
+ struct vmcb_control_area *control = &vmcb->control;
+ u64 expected_exit_info_1 = test->exit_info_1 & test->exit_info_1_mask;
+
+ control->intercept |= test->intercept;
+ control->intercept_cr |= test->intercept_cr;
+ control->intercept_dr |= test->intercept_dr;
+
+ control->exit_info_1 = ~0ULL;
+ prepare_l2_for_vmrun(svm, (u64)test->code);
+
+ run_guest(vmcb, svm->vmcb_gpa);
+
+ __GUEST_ASSERT(control->exit_code == test->exit_code,
+ "%s: expected exit code %#lx, got %#lx",
+ test->name, (unsigned long)test->exit_code,
+ (unsigned long)control->exit_code);
+ __GUEST_ASSERT((control->exit_info_1 & test->exit_info_1_mask) ==
+ expected_exit_info_1,
+ "%s: expected EXITINFO1 %#lx with mask %#lx, got %#lx",
+ test->name, (unsigned long)expected_exit_info_1,
+ (unsigned long)test->exit_info_1_mask,
+ (unsigned long)control->exit_info_1);
+ __GUEST_ASSERT(!control->insn_len,
+ "%s: expected no instruction bytes, got %u",
+ test->name, control->insn_len);
+
+ if (test->check_rax)
+ __GUEST_ASSERT(vmcb->save.rax == test->rax,
+ "%s: expected rAX %#lx, got %#lx",
+ test->name, (unsigned long)test->rax,
+ (unsigned long)vmcb->save.rax);
+
+ control->intercept &= ~test->intercept;
+ control->intercept_cr &= ~test->intercept_cr;
+ control->intercept_dr &= ~test->intercept_dr;
+}
+
+static void test_instruction_intercepts(struct svm_test_data *svm)
+{
+ int i;
+
+ if (!is_forced_emulation_enabled)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(instruction_intercept_tests); i++)
+ run_intercept_test(svm, &instruction_intercept_tests[i]);
+}
+
+static void test_hardware_npf(struct svm_test_data *svm, gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_read_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+ assert_decode_assist_insn_bytes(vmcb);
+}
+
+/*
+ * The IOIO intercept causes L0 to emulate OUTSB before accessing its source
+ * operand. The emulated read then faults on L1's NPT, resulting in a
+ * KVM-synthesized #NPF.
+ */
+static void test_synthesized_npf(struct svm_test_data *svm, gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_outsb_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+ assert_full_decode_assist_insn_bytes(vmcb);
+}
+
+/*
+ * MOVSB first reads from MMIO, causing a hardware #NPF that L0 emulates.
+ * After userspace completes the read, the emulated destination write faults
+ * on L1's NPT. The new #NPF must not reuse the original hardware exit's GPA.
+ * Userspace replaces MOVSB with NOP while the read is pending. DecodeAssist
+ * must retain the cached MOVSB bytes instead of re-reading the opcode.
+ */
+static void test_synthesized_npf_after_hardware_npf(struct svm_test_data *svm,
+ gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+ u8 insn_bytes[sizeof(vmcb->control.insn_bytes)];
+
+ memcpy(insn_bytes, l2_movsb_insn, sizeof(insn_bytes));
+ GUEST_ASSERT_EQ(insn_bytes[0], MOVSB_OPCODE);
+ prepare_l2_for_vmrun(svm, (u64)l2_movsb_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+ GUEST_ASSERT_EQ(vmcb->save.rip, (u64)l2_movsb_insn);
+ GUEST_ASSERT_EQ(l2_movsb_insn[0], NOP_OPCODE);
+ GUEST_ASSERT_EQ(vmcb->control.insn_len, sizeof(insn_bytes));
+ GUEST_ASSERT(!memcmp(vmcb->control.insn_bytes, insn_bytes,
+ sizeof(insn_bytes)));
+}
+
+/*
+ * OUTSB is the final byte of a mapped code page, and the following page is
+ * not present in L2's page tables. DecodeAssist byte fetching must stop at
+ * the page boundary and report only the OUTSB opcode.
+ */
+static void test_synthesized_npf_truncated(struct svm_test_data *svm,
+ gpa_t npf_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm,
+ (u64)&boundary_outsb_code[PAGE_SIZE -
+ BOUNDARY_OUTSB_CODE_SIZE]);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_NPF);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, npf_gpa);
+ GUEST_ASSERT_EQ(vmcb->save.rip,
+ (u64)&boundary_outsb_code[PAGE_SIZE - 1]);
+ GUEST_ASSERT_EQ(vmcb->control.insn_len, 1);
+ GUEST_ASSERT_EQ(vmcb->control.insn_bytes[0], OUTSB_OPCODE);
+}
+
+static void test_hardware_intercepted_pf(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_pf_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_TEST_GVA);
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_PRESENT_MASK));
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_FETCH_MASK));
+ assert_decode_assist_insn_bytes(vmcb);
+}
+
+static void test_hardware_intercepted_fetch_pf(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, PF_FETCH_TEST_GVA);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_FETCH_TEST_GVA);
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_PRESENT_MASK));
+ GUEST_ASSERT(vmcb->control.exit_info_1 & PFERR_FETCH_MASK);
+ GUEST_ASSERT_EQ(vmcb->control.insn_len, 0);
+}
+
+static void test_synthesized_pf(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ if (!is_forced_emulation_enabled)
+ return;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_fep_pf_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_TEST_GVA);
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_PRESENT_MASK));
+ GUEST_ASSERT(!(vmcb->control.exit_info_1 & PFERR_FETCH_MASK));
+ assert_full_decode_assist_insn_bytes(vmcb);
+}
+
+/* The pending MMIO instruction must complete before reflecting userspace #PF. */
+static void test_userspace_injected_pf_during_emulation(struct svm_test_data *svm)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ prepare_l2_for_vmrun(svm, (u64)l2_userspace_pf_code);
+ run_guest(vmcb, svm->vmcb_gpa);
+ GUEST_ASSERT_EQ(vmcb->control.exit_code, SVM_EXIT_EXCP_BASE + PF_VECTOR);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_1, 0);
+ GUEST_ASSERT_EQ(vmcb->control.exit_info_2, PF_TEST_GVA);
+ GUEST_ASSERT_EQ(vmcb->save.rip, (u64)l2_userspace_pf_insn);
+ GUEST_ASSERT_EQ(vmcb->save.rax, 0x5a);
+ assert_full_decode_assist_insn_bytes(vmcb);
+ GUEST_ASSERT_EQ(vmcb->control.insn_bytes[0], NOP_OPCODE);
+}
+
+static void l1_guest_code(struct svm_test_data *svm, gpa_t npf_gpa,
+ gpa_t iopm_gpa)
+{
+ struct vmcb *vmcb = svm->vmcb;
+
+ GUEST_ASSERT(this_cpu_has(X86_FEATURE_DECODEASSISTS));
+
+ generic_svm_setup(svm, l2_read_code);
+ vmcb->control.iopm_base_pa = iopm_gpa;
+
+ vmcb->control.intercept |= BIT_ULL(INTERCEPT_IOIO_PROT);
+ vmcb->control.intercept_exceptions |= 1U << PF_VECTOR;
+
+ test_hardware_npf(svm, npf_gpa);
+ test_synthesized_npf(svm, npf_gpa);
+ test_synthesized_npf_after_hardware_npf(svm, npf_gpa);
+ test_synthesized_npf_truncated(svm, npf_gpa);
+ test_hardware_intercepted_pf(svm);
+ test_hardware_intercepted_fetch_pf(svm);
+ test_synthesized_pf(svm);
+ test_userspace_injected_pf_during_emulation(svm);
+ test_instruction_intercepts(svm);
+
+ GUEST_DONE();
+}
+
+static void build_boundary_outsb_code(u8 *code)
+{
+ u64 source = (u64)&npf_target;
+
+ /* movabs $npf_target, %rsi */
+ code[0] = 0x48;
+ code[1] = 0xbe;
+ memcpy(&code[2], &source, sizeof(source));
+
+ /* mov $0x80, %dx; outsb */
+ code[10] = 0x66;
+ code[11] = 0xba;
+ code[12] = 0x80;
+ code[13] = 0x00;
+ code[14] = OUTSB_OPCODE;
+}
+
+static void prepare_boundary_outsb_code(struct kvm_vm *vm)
+{
+ gva_t code_gva = (gva_t)&boundary_outsb_code[PAGE_SIZE -
+ BOUNDARY_OUTSB_CODE_SIZE];
+
+ build_boundary_outsb_code(addr_gva2hva(vm, code_gva));
+}
+
+static void queue_userspace_pf(struct kvm_vcpu *vcpu)
+{
+ struct kvm_vcpu_events events;
+
+ vcpu_events_get(vcpu, &events);
+ TEST_ASSERT(!events.exception.pending && !events.exception.injected,
+ "Unexpected exception before userspace #PF injection");
+ TEST_ASSERT(events.flags & KVM_VCPUEVENT_VALID_PAYLOAD,
+ "KVM_CAP_EXCEPTION_PAYLOAD was not enabled");
+
+ events.exception.pending = true;
+ events.exception.nr = PF_VECTOR;
+ events.exception.has_error_code = true;
+ events.exception.error_code = 0;
+ events.exception_has_payload = true;
+ events.exception_payload = PF_TEST_GVA;
+ vcpu_events_set(vcpu, &events);
+}
+
+static void complete_mmio_read(struct kvm_vcpu *vcpu, gpa_t expected_gpa,
+ u8 value)
+{
+ if (vcpu->run->exit_reason == KVM_EXIT_IO) {
+ struct ucall uc;
+
+ if (get_ucall(vcpu, &uc) == UCALL_ABORT)
+ REPORT_GUEST_ASSERT(uc);
+ }
+
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
+ TEST_ASSERT(!vcpu->run->mmio.is_write,
+ "Expected an MMIO read, got a write");
+ TEST_ASSERT_EQ(vcpu->run->mmio.phys_addr, expected_gpa);
+ TEST_ASSERT_EQ(vcpu->run->mmio.len, 1);
+ vcpu->run->mmio.data[0] = value;
+}
+
+static void assert_ucall_done(struct kvm_vcpu *vcpu)
+{
+ struct ucall uc;
+ u64 actual;
+
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+ actual = get_ucall(vcpu, &uc);
+ if (actual == UCALL_ABORT)
+ REPORT_GUEST_ASSERT(uc);
+
+ TEST_ASSERT_EQ(actual, UCALL_DONE);
+}
+
+int main(int argc, char *argv[])
+{
+ gva_t svm_gva, npf_gva, boundary_page_gva, iopm_gva;
+ gpa_t npf_gpa, mmio_source_gpa, mmio_gpa, iopm_gpa;
+ struct userspace_mem_region *region;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ u8 *movsb_insn;
+ u64 *pte;
+
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
+ TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_NPT));
+ TEST_REQUIRE(this_cpu_has(X86_FEATURE_DECODEASSISTS));
+ TEST_ASSERT(kvm_cpu_has(X86_FEATURE_DECODEASSISTS),
+ "KVM failed to expose DecodeAssists");
+ TEST_REQUIRE(kvm_has_cap(KVM_CAP_EXCEPTION_PAYLOAD));
+
+ vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
+ vm_enable_cap(vm, KVM_CAP_EXCEPTION_PAYLOAD, 1);
+ prepare_boundary_outsb_code(vm);
+ vm_enable_npt(vm);
+ vcpu_alloc_svm(vm, &svm_gva);
+ iopm_gva = vm_alloc_pages(vm, TEST_IOPM_SIZE / PAGE_SIZE);
+ iopm_gpa = addr_gva2gpa(vm, iopm_gva);
+ memset(addr_gva2hva(vm, iopm_gva), 0, TEST_IOPM_SIZE);
+ npf_gva = (gva_t)&npf_target;
+ npf_gpa = addr_gva2gpa(vm, npf_gva);
+
+ tdp_identity_map_default_memslots(vm);
+ pte = tdp_get_pte(vm, npf_gpa);
+ *pte &= ~PTE_PRESENT_MASK(&vm->stage2_mmu);
+ region = memslot2region(vm, 0);
+ mmio_gpa = region->region.guest_phys_addr +
+ region->region.memory_size + PAGE_SIZE;
+ mmio_source_gpa = addr_gva2gpa(vm, (gva_t)&mmio_source);
+ pte = tdp_get_pte(vm, mmio_source_gpa);
+ *pte = (*pte & ~PHYSICAL_PAGE_MASK) | mmio_gpa;
+
+ boundary_page_gva = (gva_t)&boundary_outsb_code[PAGE_SIZE];
+ pte = vm_get_pte(vm, boundary_page_gva);
+ *pte &= ~PTE_PRESENT_MASK(&vm->mmu);
+
+ vcpu_args_set(vcpu, 3, svm_gva, npf_gpa, iopm_gpa);
+
+ /* Complete the MOVSB source read. */
+ vcpu_run(vcpu);
+ complete_mmio_read(vcpu, mmio_gpa, 0xa5);
+ movsb_insn = addr_gva2hva(vm, (gva_t)l2_movsb_insn);
+ TEST_ASSERT_EQ(movsb_insn[0], MOVSB_OPCODE);
+ movsb_insn[0] = NOP_OPCODE;
+ vcpu_run(vcpu);
+ complete_mmio_read(vcpu, mmio_gpa, 0x5a);
+ queue_userspace_pf(vcpu);
+ vcpu_run(vcpu);
+ assert_ucall_done(vcpu);
+
+ kvm_vm_free(vm);
+ return 0;
+}
|
| 31/3 | 2026/09/13 07:02 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 17125 output: 35 thoughts: 461 Thoughts: **Analyzing the KVM Patch for Nested SVM DecodeAssists** Okay, let's dive into this patch. It looks like we're adding support for nested SVM DecodeAssists within KVM. My initial focus is on the implementation details. First, I see that `arch/x86/kvm/cpuid.c` is being modified. It's good to see `VENDOR_F(DECODEASSISTS)` being added to the CPUID capability initialization. This is the starting point, enabling the feature at the CPUID level. Now, let's get into the core of the emulation changes in `arch/x86/kvm/emulate.c`. I'm immediately interested in the introduction of `get_invlpg_linear_addr()`. This function specifically handles `invlpg` intercepts, which is important to understand. The call to `__linearize()` and the initialization of `linear = 0` are standard, but worth noting. The addition of `x86_emulator_copy_insn_bytes` is particularly interesting. I need to be sure the `ctxt->fetch.data` is always correctly initialized up to the `ctxt->fetch.end`. The code itself seems straightforward: It copies instruction bytes from the emulator context. However, I want to double-check the context handling and data validity. I'll need to verify how `ctxt->fetch` is populated and maintained within `arch/x86/kvm/emulate.c`. Time for some grepping or a thorough reading of the related code to confirm my assumptions about `ctxt->fetch`. |
| 32/3 | 2026/09/13 07:03 | tool | grepper |
0mArgs:{
"Expression": "fetch\\.",
"PathPrefix": "arch/x86/kvm/emulate.c"
}Results: {
"Output": "arch/x86/kvm/emulate.c=861=static int __do_insn_fetch_bytes(struct x86_emulate_ctxt *ctxt, int op_size)\n--\narch/x86/kvm/emulate.c-865-\tunsigned long linear;\narch/x86/kvm/emulate.c:866:\tint cur_size = ctxt-\u003efetch.end - ctxt-\u003efetch.data;\narch/x86/kvm/emulate.c-867-\tstruct segmented_address addr = { .seg = VCPU_SREG_CS,\n--\narch/x86/kvm/emulate.c-896-\narch/x86/kvm/emulate.c:897:\trc = ctxt-\u003eops-\u003efetch(ctxt, linear, ctxt-\u003efetch.end,\narch/x86/kvm/emulate.c-898-\t\t\t size, \u0026ctxt-\u003eexception);\n--\narch/x86/kvm/emulate.c-900-\t\treturn rc;\narch/x86/kvm/emulate.c:901:\tctxt-\u003efetch.end += size;\narch/x86/kvm/emulate.c-902-\treturn X86EMUL_CONTINUE;\n--\narch/x86/kvm/emulate.c=905=static __always_inline int do_insn_fetch_bytes(struct x86_emulate_ctxt *ctxt,\n--\narch/x86/kvm/emulate.c-907-{\narch/x86/kvm/emulate.c:908:\tunsigned done_size = ctxt-\u003efetch.end - ctxt-\u003efetch.ptr;\narch/x86/kvm/emulate.c-909-\n--\narch/x86/kvm/emulate.c=920=unsigned int x86_emulator_copy_insn_bytes(struct x86_emulate_ctxt *ctxt,\n--\narch/x86/kvm/emulate.c-926-\tif (!ctxt || ctxt-\u003eeip != eip ||\narch/x86/kvm/emulate.c:927:\t ctxt-\u003efetch.end \u003c ctxt-\u003efetch.data ||\narch/x86/kvm/emulate.c:928:\t ctxt-\u003efetch.end \u003e ctxt-\u003efetch.data + sizeof(ctxt-\u003efetch.data))\narch/x86/kvm/emulate.c-929-\t\treturn 0;\narch/x86/kvm/emulate.c-930-\narch/x86/kvm/emulate.c:931:\tnr_bytes = min_t(unsigned int, ctxt-\u003efetch.end - ctxt-\u003efetch.data,\narch/x86/kvm/emulate.c-932-\t\t\t max_bytes);\narch/x86/kvm/emulate.c:933:\tmemcpy(bytes, ctxt-\u003efetch.data, nr_bytes);\narch/x86/kvm/emulate.c-934-\treturn nr_bytes;\n--\narch/x86/kvm/emulate.c=936=EXPORT_SYMBOL_FOR_KVM_INTERNAL(x86_emulator_copy_insn_bytes);\n--\narch/x86/kvm/emulate.c-945-\tctxt-\u003e_eip += sizeof(_type);\t\t\t\t\t\\\narch/x86/kvm/emulate.c:946:\tmemcpy(\u0026_x, ctxt-\u003efetch.ptr, sizeof(_type));\t\t\t\\\narch/x86/kvm/emulate.c:947:\tctxt-\u003efetch.ptr += sizeof(_type);\t\t\t\t\\\narch/x86/kvm/emulate.c-948-\t_x;\t\t\t\t\t\t\t\t\\\n--\narch/x86/kvm/emulate.c-956-\tctxt-\u003e_eip += (_size);\t\t\t\t\t\t\\\narch/x86/kvm/emulate.c:957:\tmemcpy(_arr, ctxt-\u003efetch.ptr, _size);\t\t\t\t\\\narch/x86/kvm/emulate.c:958:\tctxt-\u003efetch.ptr += (_size);\t\t\t\t\t\\\narch/x86/kvm/emulate.c-959-})\n--\narch/x86/kvm/emulate.c=4909=int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int emulation_type)\n--\narch/x86/kvm/emulate.c-4922-\tctxt-\u003e_eip = ctxt-\u003eeip;\narch/x86/kvm/emulate.c:4923:\tctxt-\u003efetch.ptr = ctxt-\u003efetch.data;\narch/x86/kvm/emulate.c:4924:\tctxt-\u003efetch.end = ctxt-\u003efetch.data + insn_len;\narch/x86/kvm/emulate.c-4925-\tctxt-\u003eopcode_len = 1;\n--\narch/x86/kvm/emulate.c-4927-\tif (insn_len \u003e 0)\narch/x86/kvm/emulate.c:4928:\t\tmemcpy(ctxt-\u003efetch.data, insn, insn_len);\narch/x86/kvm/emulate.c-4929-\telse {\n"
} |
| 33/3 | 2026/09/13 07:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 19335 output: 47 thoughts: 557 Thoughts: **Analyzing the Instruction Fetch Mechanism in Nested SVM** Okay, let's break down how this instruction fetching works. Looking at `x86_decode_insn`, it's clear that the `ctxt->fetch` structure is the key. The pointers `ctxt->fetch.ptr` and `ctxt->fetch.end` define the fetch window within `ctxt->fetch.data`. Essentially, the instruction bytes are either copied directly via `memcpy` if `insn_len` is positive, or fetched byte-by-byte using `__do_insn_fetch_bytes` if `insn_len` is zero. That fetch data, from `ctxt->fetch.data` to `ctxt->fetch.end`, is where the decoded instruction resides. Now, let's pivot to the `nested.c` code and examine the context. I'm seeing functions like `nested_svm_invalidate_insn_bytes` and `nested_svm_copy_insn_bytes` operating on a `vmcb`. Aha, a Virtual Machine Control Block, or VMCB! Crucially, we're talking about `vmcb12`, the L1 guest's VMCB, which I understand is in guest memory or possibly mapped guest memory. And `vmcb02` is KVM's VMCB. A critical question arises: is this `vmcb12` data, specifically `to->control.insn_bytes`, readable by the L1 guest? Does this information leak to the guest? My immediate concern is the data movement in the `nested_svm_copy_insn_bytes` function. There's a `memcpy` from `from->control.insn_bytes` to `to->control.insn_bytes`, which raises a flag. We need to ascertain if the L1 guest *directly* accesses `vmcb12` or does it interact via `nested_svm_vmexit_update_vmcb12(vcpu)`? If the data gets copied into guest memory *and* the guest can read it, we have a possible information leak. Time to dig deeper into how `vmcb12` is mapped and written, specifically the code in `arch/x86/kvm/svm/nested.c`. |
| 34/3 | 2026/09/13 07:03 | tool | grepper |
0mArgs:{
"Expression": "nested_svm_vmexit_update_vmcb12",
"PathPrefix": "arch/x86/kvm/svm/nested.c"
}Results: {
"Output": "arch/x86/kvm/svm/nested.c=1390=void svm_copy_vmloadsave_state(struct vmcb *to_vmcb, struct vmcb *from_vmcb)\n--\narch/x86/kvm/svm/nested.c-1405-\narch/x86/kvm/svm/nested.c:1406:static int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu)\narch/x86/kvm/svm/nested.c-1407-{\n--\narch/x86/kvm/svm/nested.c=1476=void nested_svm_vmexit(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/nested.c-1481-\narch/x86/kvm/svm/nested.c:1482:\tif (nested_svm_vmexit_update_vmcb12(vcpu))\narch/x86/kvm/svm/nested.c-1483-\t\tkvm_make_request(KVM_REQ_TRIPLE_FAULT, vcpu);\n"
} |
| 35/3 | 2026/09/13 07:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 20730 output: 43 thoughts: 27 Thoughts: Here's a summary of my thought process, as requested: **Navigating the Codebase** Okay, so I'm diving into this `nested_svm_vmexit_update_vmcb12` function, specifically around line 1406. I know this code pretty well, so I'm probably looking for a specific behavior or interaction within that function. I'm focusing on that function and that line number because I have a hunch about what's going on or I need to track down a bug related to this area. I'm expecting something specific regarding nested SVM and VMCB updates, but the line number is my initial anchor. Let's see what that code block is all about. This likely deals with how the virtual machine control block (VMCB) is updated during a VM exit when nested virtualization with Secure Virtual Machines (SVM) is involved. I'm expecting to find details on control registers, guest state, and any potential issues with synchronization or access during a nested exit. |
| 36/3 | 2026/09/13 07:03 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/svm/nested.c",
"FirstLine": 1406,
"LineCount": 65
}Results: {
"Contents": "1406:\tstatic int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu)\n1407:\t{\n1408:\t\tstruct vcpu_svm *svm = to_svm(vcpu);\n1409:\t\tstruct vmcb *vmcb02 = svm-\u003enested.vmcb02.ptr;\n1410:\t\tstruct vmcb *vmcb12;\n1411:\t\n1412:\t\tCLASS(kvm_vcpu_map_local, m)(vcpu, gpa_to_gfn(svm-\u003enested.vmcb12_gpa));\n1413:\t\tif (m.ret)\n1414:\t\t\treturn m.ret;\n1415:\t\n1416:\t\tvmcb12 = m.map.hva;\n1417:\t\n1418:\t\tvmcb12-\u003esave.es = vmcb02-\u003esave.es;\n1419:\t\tvmcb12-\u003esave.cs = vmcb02-\u003esave.cs;\n1420:\t\tvmcb12-\u003esave.ss = vmcb02-\u003esave.ss;\n1421:\t\tvmcb12-\u003esave.ds = vmcb02-\u003esave.ds;\n1422:\t\tvmcb12-\u003esave.gdtr = vmcb02-\u003esave.gdtr;\n1423:\t\tvmcb12-\u003esave.idtr = vmcb02-\u003esave.idtr;\n1424:\t\tvmcb12-\u003esave.efer = svm-\u003evcpu.arch.efer;\n1425:\t\tvmcb12-\u003esave.cr0 = kvm_read_cr0(vcpu);\n1426:\t\tvmcb12-\u003esave.cr3 = kvm_read_cr3(vcpu);\n1427:\t\tvmcb12-\u003esave.cr2 = vcpu-\u003earch.cr2;\n1428:\t\tvmcb12-\u003esave.cr4 = svm-\u003evcpu.arch.cr4;\n1429:\t\tvmcb12-\u003esave.rflags = kvm_get_rflags(vcpu);\n1430:\t\tvmcb12-\u003esave.rip = kvm_rip_read(vcpu);\n1431:\t\tvmcb12-\u003esave.rsp = kvm_rsp_read(vcpu);\n1432:\t\tvmcb12-\u003esave.rax = kvm_rax_read_raw(vcpu);\n1433:\t\tvmcb12-\u003esave.dr7 = vmcb02-\u003esave.dr7;\n1434:\t\tvmcb12-\u003esave.dr6 = svm-\u003evcpu.arch.dr6;\n1435:\t\tvmcb12-\u003esave.cpl = vmcb02-\u003esave.cpl;\n1436:\t\n1437:\t\tif (l2_has_separate_pat(vcpu))\n1438:\t\t\tvmcb12-\u003esave.g_pat = vmcb02-\u003esave.g_pat;\n1439:\t\n1440:\t\tif (guest_cpu_cap_has(vcpu, X86_FEATURE_SHSTK)) {\n1441:\t\t\tvmcb12-\u003esave.s_cet\t= vmcb02-\u003esave.s_cet;\n1442:\t\t\tvmcb12-\u003esave.isst_addr\t= vmcb02-\u003esave.isst_addr;\n1443:\t\t\tvmcb12-\u003esave.ssp\t= vmcb02-\u003esave.ssp;\n1444:\t\t}\n1445:\t\n1446:\t\tvmcb12-\u003econtrol.int_state = vmcb02-\u003econtrol.int_state;\n1447:\t\tvmcb12-\u003econtrol.exit_code = vmcb02-\u003econtrol.exit_code;\n1448:\t\tvmcb12-\u003econtrol.exit_info_1 = vmcb02-\u003econtrol.exit_info_1;\n1449:\t\tvmcb12-\u003econtrol.exit_info_2 = vmcb02-\u003econtrol.exit_info_2;\n1450:\t\n1451:\t\tif (!svm_is_vmrun_failure(vmcb12-\u003econtrol.exit_code))\n1452:\t\t\tnested_save_pending_event_to_vmcb12(svm, vmcb12);\n1453:\t\n1454:\t\tif (guest_cpu_cap_has(vcpu, X86_FEATURE_NRIPS))\n1455:\t\t\tvmcb12-\u003econtrol.next_rip = vmcb02-\u003econtrol.next_rip;\n1456:\t\n1457:\t\tnested_svm_update_vmcb12_insn_bytes(vcpu, vmcb12, vmcb02);\n1458:\t\n1459:\t\tif (nested_vmcb12_has_lbrv(vcpu))\n1460:\t\t\tsvm_copy_lbrs(\u0026vmcb12-\u003esave, \u0026vmcb02-\u003esave);\n1461:\t\n1462:\t\tvmcb12-\u003econtrol.event_inj\t = 0;\n1463:\t\tvmcb12-\u003econtrol.event_inj_err\t = 0;\n1464:\t\tvmcb12-\u003econtrol.int_ctl = svm-\u003enested.ctl.int_ctl;\n1465:\t\n1466:\t\ttrace_kvm_nested_vmexit_inject(vmcb12-\u003econtrol.exit_code,\n1467:\t\t\t\t\t vmcb12-\u003econtrol.exit_info_1,\n1468:\t\t\t\t\t vmcb12-\u003econtrol.exit_info_2,\n1469:\t\t\t\t\t vmcb12-\u003econtrol.exit_int_info,\n1470:\t\t\t\t\t vmcb12-\u003econtrol.exit_int_info_err,\n"
} |
| 37/3 | 2026/09/13 07:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 22505 output: 35 thoughts: 618 Thoughts: **Analyzing Instruction Byte Handling in Nested SVM** Okay, let's break down this function, `nested_svm_update_vmcb12_insn_bytes`. My initial goal is to understand how it handles instruction bytes within the nested SVM environment. I see it's taking a `kvm_vcpu` pointer and two `vmcb` structures, `vmcb12` and `vmcb02`. The core seems to be about synchronizing instruction bytes between these two VMCBs, which is critical for instruction decoding in a nested virtualization scenario. First, there's a check for `X86_FEATURE_DECODEASSISTS`, which makes sense; this probably gates some optimization or functionality related to instruction decoding. If this feature isn't available, we bail out early. Then, there's a check for `nested_svm_vmexit_supports_insn_bytes(vmcb02)`. If the underlying VM exit doesn't support the instruction bytes, we invalidate the instruction bytes. The primary logic hinges on `svm->nested.vmcb02_insn_bytes_valid`. If it's valid, we copy the bytes from `vmcb02` to `vmcb12` using `nested_svm_copy_insn_bytes`. But if it is *not* valid, then we go down a different code path. If the current guest isn't SEV, then `nested_svm_fetch_insn_bytes` is called to populate the `vmcb12` instruction bytes and length. Otherwise, if SEV, the instructions are invalidated. I'm curious about the semantics of `nested_svm_fetch_insn_bytes` now... I'll have to investigate that separately. But now, I am specifically interested in `vmcb12->control.insn_bytes`. The more I look at this code, the more important it becomes to understand how these instruction bytes are managed. Looking deeper at `nested_svm_copy_insn_bytes`, the function copies the instruction bytes from `vmcb02` to `vmcb12` using `memcpy`. Crucially, it uses `from->control.insn_len` to determine the number of bytes to copy. There's a `WARN_ON_ONCE` that seems important. It checks if `insn_len` is greater than `sizeof(from->control.insn_bytes)`. If it is, the code invalidates the instruction bytes and returns. This could signal a potential overflow or data integrity issue. But... where does `vmcb02->control.insn_bytes` *come from*? I need to understand its provenance. I need to trace back where `vmcb02` is allocated and initialized. That's the key to understanding how these instruction bytes are populated in the first place. I must understand how `svm->nested.vmcb02` is being handled. This is likely where the real magic happens. |
| 38/3 | 2026/09/13 07:03 | tool | grepper |
0mArgs:{
"Expression": "vmcb02",
"PathPrefix": "arch/x86/kvm/svm/"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 501 lines.\nUse more precise expression if possible.\n\narch/x86/kvm/svm/nested.c=57=static bool nested_svm_vmexit_supports_insn_bytes(const struct vmcb *vmcb)\n--\narch/x86/kvm/svm/nested.c-67-\narch/x86/kvm/svm/nested.c:68:static void nested_svm_set_vmcb02_insn_bytes_valid(struct vcpu_svm *svm)\narch/x86/kvm/svm/nested.c-69-{\narch/x86/kvm/svm/nested.c:70:\tsvm-\u003enested.vmcb02_insn_bytes_valid =\narch/x86/kvm/svm/nested.c-71-\t\tstatic_cpu_has(X86_FEATURE_DECODEASSISTS) \u0026\u0026\narch/x86/kvm/svm/nested.c:72:\t\tnested_svm_vmexit_supports_insn_bytes(svm-\u003enested.vmcb02.ptr);\narch/x86/kvm/svm/nested.c-73-}\narch/x86/kvm/svm/nested.c-74-\narch/x86/kvm/svm/nested.c:75:static void nested_svm_invalidate_vmcb02_insn_bytes(struct vcpu_svm *svm)\narch/x86/kvm/svm/nested.c-76-{\narch/x86/kvm/svm/nested.c:77:\tnested_svm_invalidate_insn_bytes(svm-\u003enested.vmcb02.ptr);\narch/x86/kvm/svm/nested.c:78:\tsvm-\u003enested.vmcb02_insn_bytes_valid = false;\narch/x86/kvm/svm/nested.c-79-}\n--\narch/x86/kvm/svm/nested.c=128=static void nested_svm_prepare_synthesized_insn_bytes(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/nested.c-130-\tstruct vcpu_svm *svm = to_svm(vcpu);\narch/x86/kvm/svm/nested.c:131:\tstruct vmcb *vmcb02 = svm-\u003enested.vmcb02.ptr;\narch/x86/kvm/svm/nested.c-132-\tstruct x86_emulate_ctxt *ctxt = vcpu-\u003earch.emulate_ctxt;\narch/x86/kvm/svm/nested.c:133:\tu8 max_bytes = sizeof(vmcb02-\u003econtrol.insn_bytes);\narch/x86/kvm/svm/nested.c-134-\tu8 count;\narch/x86/kvm/svm/nested.c-135-\narch/x86/kvm/svm/nested.c:136:\tnested_svm_invalidate_vmcb02_insn_bytes(svm);\narch/x86/kvm/svm/nested.c-137-\n--\narch/x86/kvm/svm/nested.c-142-\tcount = x86_emulator_copy_insn_bytes(ctxt, kvm_rip_read(vcpu),\narch/x86/kvm/svm/nested.c:143:\t\t\t\t\t vmcb02-\u003econtrol.insn_bytes, max_bytes);\narch/x86/kvm/svm/nested.c-144-\tif (!is_sev_guest(vcpu))\narch/x86/kvm/svm/nested.c-145-\t\tcount = nested_svm_fetch_insn_bytes(vcpu,\narch/x86/kvm/svm/nested.c:146:\t\t\t\t\t\t vmcb02-\u003econtrol.insn_bytes,\narch/x86/kvm/svm/nested.c-147-\t\t\t\t\t\t count, max_bytes);\narch/x86/kvm/svm/nested.c:148:\tvmcb02-\u003econtrol.insn_len = count;\narch/x86/kvm/svm/nested.c-149-\narch/x86/kvm/svm/nested.c:150:\tsvm-\u003enested.vmcb02_insn_bytes_valid = true;\narch/x86/kvm/svm/nested.c-151-}\n--\narch/x86/kvm/svm/nested.c=153=static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,\narch/x86/kvm/svm/nested.c-154-\t\t\t\t\t\tstruct vmcb *vmcb12,\narch/x86/kvm/svm/nested.c:155:\t\t\t\t\t\tconst struct vmcb *vmcb02)\narch/x86/kvm/svm/nested.c-156-{\n--\narch/x86/kvm/svm/nested.c-162-\narch/x86/kvm/svm/nested.c:163:\tif (!nested_svm_vmexit_supports_insn_bytes(vmcb02)) {\narch/x86/kvm/svm/nested.c-164-\t\tnested_svm_invalidate_insn_bytes(vmcb12);\n--\narch/x86/kvm/svm/nested.c-167-\narch/x86/kvm/svm/nested.c:168:\tif (svm-\u003enested.vmcb02_insn_bytes_valid) {\narch/x86/kvm/svm/nested.c:169:\t\tnested_svm_copy_insn_bytes(vmcb12, vmcb02);\narch/x86/kvm/svm/nested.c-170-\t\tgoto out;\n--\narch/x86/kvm/svm/nested.c-182-out:\narch/x86/kvm/svm/nested.c:183:\tsvm-\u003enested.vmcb02_insn_bytes_valid = false;\narch/x86/kvm/svm/nested.c-184-}\n--\narch/x86/kvm/svm/nested.c=186=static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/svm/nested.c-221-\tif (from_hardware)\narch/x86/kvm/svm/nested.c:222:\t\tnested_svm_set_vmcb02_insn_bytes_valid(svm);\narch/x86/kvm/svm/nested.c-223-\telse if (from_emulation \u0026\u0026 !(fault-\u003eerror_code \u0026 PFERR_FETCH_MASK))\n--\narch/x86/kvm/svm/nested.c-225-\telse\narch/x86/kvm/svm/nested.c:226:\t\tsvm-\u003enested.vmcb02_insn_bytes_valid = false;\narch/x86/kvm/svm/nested.c-227-\tnested_svm_vmexit(svm);\n--\narch/x86/kvm/svm/nested.c=283=static bool nested_vmcb_needs_vls_intercept(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/nested.c-296-\narch/x86/kvm/svm/nested.c:297:void nested_vmcb02_recalc_intercepts(struct vcpu_svm *svm)\narch/x86/kvm/svm/nested.c-298-{\narch/x86/kvm/svm/nested.c-299-\tstruct vmcb_ctrl_area_cached *vmcb12_ctrl = \u0026svm-\u003enested.ctl;\narch/x86/kvm/svm/nested.c:300:\tstruct vmcb *vmcb02 = svm-\u003enested.vmcb02.ptr;\narch/x86/kvm/svm/nested.c-301-\tstruct vmcb *vmcb01 = svm-\u003evmcb01.ptr;\n--\narch/x86/kvm/svm/nested.c-303-\narch/x86/kvm/svm/nested.c:304:\tif (WARN_ON_ONCE(svm-\u003evmcb != vmcb02))\narch/x86/kvm/svm/nested.c-305-\t\treturn;\narch/x86/kvm/svm/nested.c-306-\narch/x86/kvm/svm/nested.c:307:\tvmcb_mark_dirty(vmcb02, VMCB_INTERCEPTS);\narch/x86/kvm/svm/nested.c-308-\narch/x86/kvm/svm/nested.c-309-\tfor (i = 0; i \u003c MAX_INTERCEPT; i++)\narch/x86/kvm/svm/nested.c:310:\t\tvmcb02-\u003econtrol.intercepts[i] = vmcb01-\u003econtrol.intercepts[i];\narch/x86/kvm/svm/nested.c-311-\n--\narch/x86/kvm/svm/nested.c-322-\t\t */\narch/x86/kvm/svm/nested.c:323:\t\tvmcb_clr_intercept(\u0026vmcb02-\u003econtrol, INTERCEPT_CR8_WRITE);\narch/x86/kvm/svm/nested.c-324-\t\tif (!(vmcb01-\u003esave.rflags \u0026 X86_EFLAGS_IF))\narch/x86/kvm/svm/nested.c:325:\t\t\tvmcb_clr_intercept(\u0026vmcb02-\u003econtrol, INTERCEPT_VINTR);\narch/x86/kvm/svm/nested.c-326-\t}\n--\narch/x86/kvm/svm/nested.c-328-\tfor (i = 0; i \u003c MAX_INTERCEPT; i++)\narch/x86/kvm/svm/nested.c:329:\t\tvmcb02-\u003econtrol.intercepts[i] |= vmcb12_ctrl-\u003eintercepts[i];\narch/x86/kvm/svm/nested.c-330-\n--\narch/x86/kvm/svm/nested.c-332-\tif (!intercept_smi)\narch/x86/kvm/svm/nested.c:333:\t\tvmcb_clr_intercept(\u0026vmcb02-\u003econtrol, INTERCEPT_SMI);\narch/x86/kvm/svm/nested.c-334-\n--\narch/x86/kvm/svm/nested.c-342-\tif (!vmcb12_is_intercept(\u0026svm-\u003enested.ctl, INTERCEPT_PAUSE))\narch/x86/kvm/svm/nested.c:343:\t\tvmcb_clr_intercept(\u0026vmcb02-\u003econtrol, INTERCEPT_PAUSE);\narch/x86/kvm/svm/nested.c-344-\n--\narch/x86/kvm/svm/nested.c-350-\t\t */\narch/x86/kvm/svm/nested.c:351:\t\tvmcb_set_intercept(\u0026vmcb02-\u003econtrol, INTERCEPT_VMLOAD);\narch/x86/kvm/svm/nested.c:352:\t\tvmcb_set_intercept(\u0026vmcb02-\u003econtrol, INTERCEPT_VMSAVE);\narch/x86/kvm/svm/nested.c-353-\t} else {\narch/x86/kvm/svm/nested.c:354:\t\tWARN_ON_ONCE(!(vmcb02-\u003econtrol.misc_ctl2 \u0026 SVM_MISC2_ENABLE_V_VMLOAD_VMSAVE));\narch/x86/kvm/svm/nested.c-355-\t}\n--\narch/x86/kvm/svm/nested.c=594=static bool nested_vmcb_check_save(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/svm/nested.c-631-\t/*\narch/x86/kvm/svm/nested.c:632:\t * If userspace contrives to get an invalid g_pat into vmcb02 by\narch/x86/kvm/svm/nested.c-633-\t * disabling KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT in a race with\n--\narch/x86/kvm/svm/nested.c=766=void nested_copy_vmcb_save_to_cache(struct vcpu_svm *svm,\n--\narch/x86/kvm/svm/nested.c-775- */\narch/x86/kvm/svm/nested.c:776:void nested_sync_control_from_vmcb02(struct vcpu_svm *svm)\narch/x86/kvm/svm/nested.c-777-{\n--\narch/x86/kvm/svm/nested.c-785-\t/*\narch/x86/kvm/svm/nested.c:786:\t * Don't sync vmcb02 V_IRQ back to vmcb12 if KVM (L0) is intercepting\narch/x86/kvm/svm/nested.c-787-\t * virtual interrupts in order to request an interrupt window, as KVM\narch/x86/kvm/svm/nested.c:788:\t * has usurped vmcb02's int_ctl. If an interrupt window opens before\narch/x86/kvm/svm/nested.c-789-\t * the next VM-Exit, svm_clear_vintr() will restore vmcb12's int_ctl.\n--\narch/x86/kvm/svm/nested.c=844=static void nested_svm_transition_tlb_flush(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/nested.c-855-\t * - Sync nested NPT MMU on VMRUN that flushes L2's ASID[*]\narch/x86/kvm/svm/nested.c:856:\t * - Don't crush a pending TLB flush in vmcb02 on nested VMRUN\narch/x86/kvm/svm/nested.c-857-\t * - Flush L1's ASID on KVM_REQ_TLB_FLUSH_GUEST\n--\narch/x86/kvm/svm/nested.c=894=static bool nested_vmcb12_has_lbrv(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/nested.c-899-\narch/x86/kvm/svm/nested.c:900:static void nested_vmcb02_prepare_save(struct vcpu_svm *svm)\narch/x86/kvm/svm/nested.c-901-{\n--\narch/x86/kvm/svm/nested.c-905-\tstruct vmcb *vmcb01 = svm-\u003evmcb01.ptr;\narch/x86/kvm/svm/nested.c:906:\tstruct vmcb *vmcb02 = svm-\u003enested.vmcb02.ptr;\narch/x86/kvm/svm/nested.c-907-\tstruct kvm_vcpu *vcpu = \u0026svm-\u003evcpu;\n--\narch/x86/kvm/svm/nested.c-916-\tif (unlikely(new_vmcb12 || vmcb12_is_dirty(control, VMCB_SEG))) {\narch/x86/kvm/svm/nested.c:917:\t\tvmcb02-\u003esave.es = save-\u003ees;\narch/x86/kvm/svm/nested.c:918:\t\tvmcb02-\u003esave.cs = save-\u003ecs;\narch/x86/kvm/svm/nested.c:919:\t\tvmcb02-\u003esave.ss = save-\u003ess;\narch/x86/kvm/svm/nested.c:920:\t\tvmcb02-\u003esave.ds = save-\u003eds;\narch/x86/kvm/svm/nested.c:921:\t\tvmcb02-\u003esave.cpl = save-\u003ecpl;\narch/x86/kvm/svm/nested.c:922:\t\tvmcb_mark_dirty(vmcb02, VMCB_SEG);\narch/x86/kvm/svm/nested.c-923-\t}\n--\narch/x86/kvm/svm/nested.c-925-\tif (unlikely(new_vmcb12 || vmcb12_is_dirty(control, VMCB_DT))) {\narch/x86/kvm/svm/nested.c:926:\t\tvmcb02-\u003esave.gdtr = save-\u003egdtr;\narch/x86/kvm/svm/nested.c:927:\t\tvmcb02-\u003esave.idtr = save-\u003eidtr;\narch/x86/kvm/svm/nested.c:928:\t\tvmcb_mark_dirty(vmcb02, VMCB_DT);\narch/x86/kvm/svm/nested.c-929-\t}\n--\narch/x86/kvm/svm/nested.c-932-\t (unlikely(new_vmcb12 || vmcb12_is_dirty(control, VMCB_CET)))) {\narch/x86/kvm/svm/nested.c:933:\t\tvmcb02-\u003esave.s_cet = save-\u003es_cet;\narch/x86/kvm/svm/nested.c:934:\t\tvmcb02-\u003esave.isst_addr = save-\u003eisst_addr;\narch/x86/kvm/svm/nested.c:935:\t\tvmcb02-\u003esave.ssp = save-\u003essp;\narch/x86/kvm/svm/nested.c:936:\t\tvmcb_mark_dirty(vmcb02, VMCB_CET);\narch/x86/kvm/svm/nested.c-937-\t}\n--\narch/x86/kvm/svm/nested.c-940-\t\tif (unlikely(new_vmcb12 || vmcb12_is_dirty(control, VMCB_NPT)))\narch/x86/kvm/svm/nested.c:941:\t\t\tvmcb_set_gpat(vmcb02, svm-\u003enested.save.g_pat);\narch/x86/kvm/svm/nested.c-942-\t} else if (npt_enabled) {\narch/x86/kvm/svm/nested.c:943:\t\tvmcb_set_gpat(vmcb02, vcpu-\u003earch.pat);\narch/x86/kvm/svm/nested.c-944-\t}\n--\narch/x86/kvm/svm/nested.c-959-\t/* In case we don't even reach vcpu_run, the fields are not updated */\narch/x86/kvm/svm/nested.c:960:\tvmcb02-\u003esave.rax = save-\u003erax;\narch/x86/kvm/svm/nested.c:961:\tvmcb02-\u003esave.rsp = save-\u003ersp;\narch/x86/kvm/svm/nested.c:962:\tvmcb02-\u003esave.rip = save-\u003erip;\narch/x86/kvm/svm/nested.c-963-\narch/x86/kvm/svm/nested.c-964-\tif (unlikely(new_vmcb12 || vmcb12_is_dirty(control, VMCB_DR))) {\narch/x86/kvm/svm/nested.c:965:\t\tvmcb02-\u003esave.dr7 = svm-\u003enested.save.dr7 | DR7_FIXED_1;\narch/x86/kvm/svm/nested.c-966-\t\tsvm-\u003evcpu.arch.dr6 = svm-\u003enested.save.dr6 | DR6_ACTIVE_LOW;\narch/x86/kvm/svm/nested.c:967:\t\tvmcb_mark_dirty(vmcb02, VMCB_DR);\narch/x86/kvm/svm/nested.c-968-\t}\n--\narch/x86/kvm/svm/nested.c-974-\t\t */\narch/x86/kvm/svm/nested.c:975:\t\tsvm_copy_lbrs(\u0026vmcb02-\u003esave, save);\narch/x86/kvm/svm/nested.c:976:\t\tvmcb02-\u003esave.dbgctl \u0026= ~DEBUGCTL_RESERVED_BITS;\narch/x86/kvm/svm/nested.c-977-\t} else {\narch/x86/kvm/svm/nested.c:978:\t\tsvm_copy_lbrs(\u0026vmcb02-\u003esave, \u0026vmcb01-\u003esave);\narch/x86/kvm/svm/nested.c-979-\t}\narch/x86/kvm/svm/nested.c:980:\tvmcb_mark_dirty(vmcb02, VMCB_LBR);\narch/x86/kvm/svm/nested.c-981-\tsvm_update_lbrv(\u0026svm-\u003evcpu);\n--\narch/x86/kvm/svm/nested.c=998=static bool is_evtinj_nmi(u32 evtinj)\n--\narch/x86/kvm/svm/nested.c-1007-\narch/x86/kvm/svm/nested.c:1008:static void nested_vmcb02_prepare_control(struct vcpu_svm *svm)\narch/x86/kvm/svm/nested.c-1009-{\n--\narch/x86/kvm/svm/nested.c-1013-\tstruct vmcb_ctrl_area_cached *vmcb12_ctrl = \u0026svm-\u003enested.ctl;\narch/x86/kvm/svm/nested.c:1014:\tstruct vmcb *vmcb02 = svm-\u003enested.vmcb02.ptr;\narch/x86/kvm/svm/nested.c-1015-\tstruct vmcb *vmcb01 = svm-\u003evmcb01.ptr;\n--\narch/x86/kvm/svm/nested.c-1027-\t */\narch/x86/kvm/svm/nested.c:1028:\tnested_svm_invalidate_vmcb02_insn_bytes(svm);\narch/x86/kvm/svm/nested.c-1029-\n--\narch/x86/kvm/svm/nested.c-1055-\t */\narch/x86/kvm/svm/nested.c:1056:\tvmcb02-\u003econtrol.misc_ctl = vmcb01-\u003econtrol.misc_ctl \u0026 (SVM_MISC_ENABLE_NP | SVM_MISC_ENABLE_GMET);\narch/x86/kvm/svm/nested.c:1057:\tvmcb02-\u003econtrol.iopm_base_pa = vmcb01-\u003econtrol.iopm_base_pa;\narch/x86/kvm/svm/nested.c:1058:\tvmcb02-\u003econtrol.msrpm_base_pa = vmcb01-\u003econtrol.msrpm_base_pa;\narch/x86/kvm/svm/nested.c:1059:\tvmcb_mark_dirty(vmcb02, VMCB_PERM_MAP);\narch/x86/kvm/svm/nested.c-1060-\narch/x86/kvm/svm/nested.c-1061-\t/*\narch/x86/kvm/svm/nested.c:1062:\t * Stash vmcb02's counter if the guest hasn't moved past the guilty\narch/x86/kvm/svm/nested.c-1063-\t * instruction; otherwise, reset the counter to '0'.\n--\narch/x86/kvm/svm/nested.c-1072-\t * the vCPU has clearly made forward progress and so KVM should reset\narch/x86/kvm/svm/nested.c:1073:\t * vmcb02's counter to '0'.\narch/x86/kvm/svm/nested.c-1074-\t *\n--\narch/x86/kvm/svm/nested.c-1082-\t */\narch/x86/kvm/svm/nested.c:1083:\tif (vmcb02-\u003esave.rip \u0026\u0026 (svm-\u003enested.last_bus_lock_rip == vmcb02-\u003esave.rip))\narch/x86/kvm/svm/nested.c:1084:\t\tvmcb02-\u003econtrol.bus_lock_counter = 1;\narch/x86/kvm/svm/nested.c-1085-\telse\narch/x86/kvm/svm/nested.c:1086:\t\tvmcb02-\u003econtrol.bus_lock_counter = 0;\narch/x86/kvm/svm/nested.c-1087-\n--\narch/x86/kvm/svm/nested.c-1090-\t/* Also overwritten later if necessary. */\narch/x86/kvm/svm/nested.c:1091:\tvmcb02-\u003econtrol.tlb_ctl = TLB_CONTROL_DO_NOTHING;\narch/x86/kvm/svm/nested.c-1092-\n--\narch/x86/kvm/svm/nested.c-1094-\tif (nested_npt_enabled(svm)) {\narch/x86/kvm/svm/nested.c:1095:\t\tvmcb02-\u003econtrol.misc_ctl \u0026= ~SVM_MISC_ENABLE_GMET;\narch/x86/kvm/svm/nested.c:1096:\t\tvmcb02-\u003econtrol.misc_ctl |= (svm-\u003enested.ctl.misc_ctl \u0026 SVM_MISC_ENABLE_GMET);\narch/x86/kvm/svm/nested.c-1097-\n--\narch/x86/kvm/svm/nested.c-1104-\narch/x86/kvm/svm/nested.c:1105:\tvmcb02-\u003econtrol.tsc_offset = vcpu-\u003earch.tsc_offset;\narch/x86/kvm/svm/nested.c-1106-\n--\narch/x86/kvm/svm/nested.c-1110-\narch/x86/kvm/svm/nested.c:1111:\tvmcb02-\u003econtrol.int_ctl =\narch/x86/kvm/svm/nested.c-1112-\t\t(vmcb12_ctrl-\u003eint_ctl \u0026 int_ctl_vmcb12_bits) |\n--\narch/x86/kvm/svm/nested.c-1114-\narch/x86/kvm/svm/nested.c:1115:\tvmcb02-\u003econtrol.int_vector = vmcb12_ctrl-\u003eint_vector;\narch/x86/kvm/svm/nested.c:1116:\tvmcb02-\u003econtrol.int_state = vmcb12_ctrl-\u003eint_state;\narch/x86/kvm/svm/nested.c:1117:\tvmcb02-\u003econtrol.event_inj = vmcb12_ctrl-\u003eevent_inj;\narch/x86/kvm/svm/nested.c:1118:\tvmcb02-\u003econtrol.event_inj_err = vmcb12_ctrl-\u003eevent_inj_err;\narch/x86/kvm/svm/nested.c-1119-\n--\narch/x86/kvm/svm/nested.c-1122-\t * advances L2's RIP before VMRUN instead of using NextRIP. KVM will\narch/x86/kvm/svm/nested.c:1123:\t * stuff the current RIP as vmcb02's NextRIP before L2 is run. After\narch/x86/kvm/svm/nested.c-1124-\t * the first run of L2 (e.g. after save+restore), NextRIP is updated by\n--\narch/x86/kvm/svm/nested.c-1128-\t !vcpu-\u003earch.nested_run_pending)\narch/x86/kvm/svm/nested.c:1129:\t\tvmcb02-\u003econtrol.next_rip = vmcb12_ctrl-\u003enext_rip;\narch/x86/kvm/svm/nested.c-1130-\narch/x86/kvm/svm/nested.c:1131:\tsvm-\u003enmi_l1_to_l2 = is_evtinj_nmi(vmcb02-\u003econtrol.event_inj);\narch/x86/kvm/svm/nested.c-1132-\n--\narch/x86/kvm/svm/nested.c-1136-\t */\narch/x86/kvm/svm/nested.c:1137:\tif (is_evtinj_soft(vmcb02-\u003econtrol.event_inj)) {\narch/x86/kvm/svm/nested.c-1138-\t\tsvm-\u003esoft_int_injected = true;\n--\narch/x86/kvm/svm/nested.c-1146-\tif (!nested_vmcb_needs_vls_intercept(svm))\narch/x86/kvm/svm/nested.c:1147:\t\tvmcb02-\u003econtrol.misc_ctl2 |= SVM_MISC2_ENABLE_V_VMLOAD_VMSAVE;\narch/x86/kvm/svm/nested.c-1148-\narch/x86/kvm/svm/nested.c-1149-\tif (guest_cpu_cap_has(vcpu, X86_FEATURE_PAUSEFILTER))\narch/x86/kvm/svm/nested.c:1150:\t\tvmcb02-\u003econtrol.pause_filter_count = vmcb12_ctrl-\u003epause_filter_count;\narch/x86/kvm/svm/nested.c-1151-\telse\narch/x86/kvm/svm/nested.c:1152:\t\tvmcb02-\u003econtrol.pause_filter_count = 0;\narch/x86/kvm/svm/nested.c-1153-\tif (guest_cpu_cap_has(vcpu, X86_FEATURE_PFTHRESHOLD))\narch/x86/kvm/svm/nested.c:1154:\t\tvmcb02-\u003econtrol.pause_filter_thresh = vmcb12_ctrl-\u003epause_filter_thresh;\narch/x86/kvm/svm/nested.c-1155-\telse\narch/x86/kvm/svm/nested.c:1156:\t\tvmcb02-\u003econtrol.pause_filter_thresh = 0;\narch/x86/kvm/svm/nested.c-1157-\n--\narch/x86/kvm/svm/nested.c-1166-\tif (guest_cpu_cap_has(vcpu, X86_FEATURE_ERAPS))\narch/x86/kvm/svm/nested.c:1167:\t\tvmcb02-\u003econtrol.erap_ctl = (vmcb12_ctrl-\u003eerap_ctl \u0026\narch/x86/kvm/svm/nested.c-1168-\t\t\t\t\t ERAP_CONTROL_ALLOW_LARGER_RAP) |\n--\narch/x86/kvm/svm/nested.c-1174-\t */\narch/x86/kvm/svm/nested.c:1175:\tnested_vmcb02_recalc_intercepts(svm);\narch/x86/kvm/svm/nested.c-1176-}\n--\narch/x86/kvm/svm/nested.c=1190=int enter_svm_guest_mode(struct kvm_vcpu *vcpu, u64 vmcb12_gpa, bool from_vmrun)\n--\narch/x86/kvm/svm/nested.c-1216-\narch/x86/kvm/svm/nested.c:1217:\tWARN_ON(svm-\u003evmcb == svm-\u003enested.vmcb02.ptr);\narch/x86/kvm/svm/nested.c-1218-\narch/x86/kvm/svm/nested.c:1219:\tnested_svm_copy_common_state(svm-\u003evmcb01.ptr, svm-\u003enested.vmcb02.ptr);\narch/x86/kvm/svm/nested.c-1220-\narch/x86/kvm/svm/nested.c:1221:\tsvm_switch_vmcb(svm, \u0026svm-\u003enested.vmcb02);\narch/x86/kvm/svm/nested.c:1222:\tnested_vmcb02_prepare_control(svm);\narch/x86/kvm/svm/nested.c:1223:\tnested_vmcb02_prepare_save(svm);\narch/x86/kvm/svm/nested.c-1224-\n--\narch/x86/kvm/svm/nested.c=1406=static int nested_svm_vmexit_update_vmcb12(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/nested.c-1408-\tstruct vcpu_svm *svm = to_svm(vcpu);\narch/x86/kvm/svm/nested.c:1409:\tstruct vmcb *vmcb02 = svm-\u003enested.vmcb02.ptr;\narch/x86/kvm/svm/nested.c-1410-\tstruct vmcb *vmcb12;\n--\narch/x86/kvm/svm/nested.c-1417-\narch/x86/kvm/svm/nested.c:1418:\tvmcb12-\u003esave.es = vmcb02-\u003esave.es;\narch/x86/kvm/svm/nested.c:1419:\tvmcb12-\u003esave.cs = vmcb02-\u003esave.cs;\narch/x86/kvm/svm/nested.c:1420:\tvmcb12-\u003esave.ss = vmcb02-\u003esave.ss;\narch/x86/kvm/svm/nested.c:1421:\tvmcb12-\u003esave.ds = vmcb02-\u003esave.ds;\narch/x86/kvm/svm/nested.c:1422:\tvmcb12-\u003esave.gdtr = vmcb02-\u003esave.gdtr;\narch/x86/kvm/svm/nested.c:1423:\tvmcb12-\u003esave.idtr = vmcb02-\u003esave.idtr;\narch/x86/kvm/svm/nested.c-1424-\tvmcb12-\u003esave.efer = svm-\u003evcpu.arch.efer;\n--\narch/x86/kvm/svm/nested.c-1432-\tvmcb12-\u003esave.rax = kvm_rax_read_raw(vcpu);\narch/x86/kvm/svm/nested.c:1433:\tvmcb12-\u003esave.dr7 = vmcb02-\u003esave.dr7;\narch/x86/kvm/svm/nested.c-1434-\tvmcb12-\u003esave.dr6 = svm-\u003evcpu.arch.dr6;\narch/x86/kvm/svm/nested.c:1435:\tvmcb12-\u003esave.cpl = vmcb02-\u003esave.cpl;\narch/x86/kvm/svm/nested.c-1436-\narch/x86/kvm/svm/nested.c-1437-\tif (l2_has_separate_pat(vcpu))\narch/x86/kvm/svm/nested.c:1438:\t\tvmcb12-\u003esave.g_pat = vmcb02-\u003esave.g_pat;\narch/x86/kvm/svm/nested.c-1439-\narch/x86/kvm/svm/nested.c-1440-\tif (guest_cpu_cap_has(vcpu, X86_FEATURE_SHSTK)) {\narch/x86/kvm/svm/nested.c:1441:\t\tvmcb12-\u003esave.s_cet\t= vmcb02-\u003esave.s_cet;\narch/x86/kvm/svm/nested.c:1442:\t\tvmcb12-\u003esave.isst_addr\t= vmcb02-\u003esave.isst_addr;\narch/x86/kvm/svm/nested.c:1443:\t\tvmcb12-\u003esave.ssp\t= vmcb02-\u003esave.ssp;\narch/x86/kvm/svm/nested.c-1444-\t}\narch/x86/kvm/svm/nested.c-1445-\narch/x86/kvm/svm/nested.c:1446:\tvmcb12-\u003econtrol.int_state = vmcb02-\u003econtrol.int_state;\narch/x86/kvm/svm/nested.c:1447:\tvmcb12-\u003econtrol.exit_code = vmcb02-\u003econtrol.exit_code;\narch/x86/kvm/svm/nested.c:1448:\tvmcb12-\u003econtrol.exit_info_1 = vmcb02-\u003econtrol.exit_info_1;\narch/x86/kvm/svm/nested.c:1449:\tvmcb12-\u003econtrol.exit_info_2 = vmcb02-\u003econtrol.exit_info_2;\narch/x86/kvm/svm/nested.c-1450-\n--\narch/x86/kvm/svm/nested.c-1454-\tif (guest_cpu_cap_has(vcpu, X86_FEATURE_NRIPS))\narch/x86/kvm/svm/nested.c:1455:\t\tvmcb12-\u003econtrol.next_rip = vmcb02-\u003econtrol.next_rip;\narch/x86/kvm/svm/nested.c-1456-\narch/x86/kvm/svm/nested.c:1457:\tnested_svm_update_vmcb12_insn_bytes(vcpu, vmcb12, vmcb02);\narch/x86/kvm/svm/nested.c-1458-\narch/x86/kvm/svm/nested.c-1459-\tif (nested_vmcb12_has_lbrv(vcpu))\narch/x86/kvm/svm/nested.c:1460:\t\tsvm_copy_lbrs(\u0026vmcb12-\u003esave, \u0026vmcb02-\u003esave);\narch/x86/kvm/svm/nested.c-1461-\n--\narch/x86/kvm/svm/nested.c=1476=void nested_svm_vmexit(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/nested.c-1479-\tstruct vmcb *vmcb01 = svm-\u003evmcb01.ptr;\narch/x86/kvm/svm/nested.c:1480:\tstruct vmcb *vmcb02 = svm-\u003enested.vmcb02.ptr;\narch/x86/kvm/svm/nested.c-1481-\n--\narch/x86/kvm/svm/nested.c-1501-\t */\narch/x86/kvm/svm/nested.c:1502:\tif (!vmcb02-\u003econtrol.bus_lock_counter)\narch/x86/kvm/svm/nested.c-1503-\t\tsvm-\u003enested.last_bus_lock_rip = INVALID_GPA;\narch/x86/kvm/svm/nested.c-1504-\narch/x86/kvm/svm/nested.c:1505:\tnested_svm_copy_common_state(svm-\u003enested.vmcb02.ptr, svm-\u003evmcb01.ptr);\narch/x86/kvm/svm/nested.c-1506-\n--\narch/x86/kvm/svm/nested.c-1514-\t/*\narch/x86/kvm/svm/nested.c:1515:\t * Rules for synchronizing int_ctl bits from vmcb02 to vmcb01:\narch/x86/kvm/svm/nested.c-1516-\t *\narch/x86/kvm/svm/nested.c-1517-\t * V_IRQ, V_IRQ_VECTOR, V_INTR_PRIO_MASK, V_IGN_TPR: If L1 doesn't\narch/x86/kvm/svm/nested.c:1518:\t * intercept interrupts, then KVM will use vmcb02's V_IRQ (and related\narch/x86/kvm/svm/nested.c-1519-\t * flags) to detect interrupt windows for L1 IRQs (even if L1 uses\n--\narch/x86/kvm/svm/nested.c-1521-\t * KVM re-requests an interrupt window if necessary, which implicitly\narch/x86/kvm/svm/nested.c:1522:\t * copies this bits from vmcb02 to vmcb01.\narch/x86/kvm/svm/nested.c-1523-\t *\narch/x86/kvm/svm/nested.c-1524-\t * V_TPR: If L1 doesn't use virtual interrupt masking, then L1's vTPR\narch/x86/kvm/svm/nested.c:1525:\t * is stored in vmcb02, but its value doesn't need to be copied from/to\narch/x86/kvm/svm/nested.c-1526-\t * vmcb01 because it is copied from/to the virtual APIC's TPR register\n--\narch/x86/kvm/svm/nested.c-1528-\t *\narch/x86/kvm/svm/nested.c:1529:\t * V_GIF: If nested vGIF is not used, KVM uses vmcb02's V_GIF for L1's\narch/x86/kvm/svm/nested.c-1530-\t * V_GIF. However, GIF is architecturally clear on each VM exit, thus\narch/x86/kvm/svm/nested.c:1531:\t * there is no need to copy V_GIF from vmcb02 to vmcb01.\narch/x86/kvm/svm/nested.c-1532-\t */\n--\narch/x86/kvm/svm/nested.c-1536-\tif (!nested_vmcb12_has_lbrv(vcpu)) {\narch/x86/kvm/svm/nested.c:1537:\t\tsvm_copy_lbrs(\u0026vmcb01-\u003esave, \u0026vmcb02-\u003esave);\narch/x86/kvm/svm/nested.c-1538-\t\tvmcb_mark_dirty(vmcb01, VMCB_LBR);\n--\narch/x86/kvm/svm/nested.c-1543-\tif (vnmi) {\narch/x86/kvm/svm/nested.c:1544:\t\tif (vmcb02-\u003econtrol.int_ctl \u0026 V_NMI_BLOCKING_MASK)\narch/x86/kvm/svm/nested.c-1545-\t\t\tvmcb01-\u003econtrol.int_ctl |= V_NMI_BLOCKING_MASK;\n--\narch/x86/kvm/svm/nested.c=1639=int svm_allocate_nested(struct vcpu_svm *svm)\narch/x86/kvm/svm/nested.c-1640-{\narch/x86/kvm/svm/nested.c:1641:\tstruct page *vmcb02_page;\narch/x86/kvm/svm/nested.c-1642-\n--\narch/x86/kvm/svm/nested.c-1645-\narch/x86/kvm/svm/nested.c:1646:\tvmcb02_page = snp_safe_alloc_page();\narch/x86/kvm/svm/nested.c:1647:\tif (!vmcb02_page)\narch/x86/kvm/svm/nested.c-1648-\t\treturn -ENOMEM;\narch/x86/kvm/svm/nested.c:1649:\tsvm-\u003enested.vmcb02.ptr = page_address(vmcb02_page);\narch/x86/kvm/svm/nested.c:1650:\tsvm-\u003enested.vmcb02.pa = __sme_set(page_to_pfn(vmcb02_page) \u003c\u003c PAGE_SHIFT);\narch/x86/kvm/svm/nested.c-1651-\n--\narch/x86/kvm/svm/nested.c-1653-\tif (!svm-\u003enested.msrpm)\narch/x86/kvm/svm/nested.c:1654:\t\tgoto err_free_vmcb02;\narch/x86/kvm/svm/nested.c-1655-\n--\narch/x86/kvm/svm/nested.c-1658-\narch/x86/kvm/svm/nested.c:1659:err_free_vmcb02:\narch/x86/kvm/svm/nested.c:1660:\t__free_page(vmcb02_page);\narch/x86/kvm/svm/nested.c-1661-\treturn -ENOMEM;\n--\narch/x86/kvm/svm/nested.c=1664=void svm_free_nested(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/nested.c-1674-\narch/x86/kvm/svm/nested.c:1675:\t__free_page(virt_to_page(svm-\u003enested.vmcb02.ptr));\narch/x86/kvm/svm/nested.c:1676:\tsvm-\u003enested.vmcb02.ptr = NULL;\narch/x86/kvm/svm/nested.c-1677-\n--\narch/x86/kvm/svm/nested.c-1682-\t *\narch/x86/kvm/svm/nested.c:1683:\t * When the vmcb02 is freed, this optimization becomes invalid.\narch/x86/kvm/svm/nested.c-1684-\t */\n--\narch/x86/kvm/svm/nested.c=1805=int nested_svm_exit_handled(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/nested.c-1811-\tif (vmexit == NESTED_EXIT_DONE) {\narch/x86/kvm/svm/nested.c:1812:\t\tnested_svm_set_vmcb02_insn_bytes_valid(svm);\narch/x86/kvm/svm/nested.c-1813-\t\tnested_svm_vmexit(svm);\n--\narch/x86/kvm/svm/nested.c=2107=static int svm_set_nested_state(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/svm/nested.c-2209-\t * vmcb12.control, which will be combined with L1 and stored into\narch/x86/kvm/svm/nested.c:2210:\t * vmcb02, and the L1 save state which we store in vmcb01.\narch/x86/kvm/svm/nested.c-2211-\t * L2 registers if needed are moved from the current VMCB to VMCB02.\n--\narch/x86/kvm/svm/nested.c-2216-\telse\narch/x86/kvm/svm/nested.c:2217:\t\tsvm-\u003enested.vmcb02.ptr-\u003esave = svm-\u003evmcb01.ptr-\u003esave;\narch/x86/kvm/svm/nested.c-2218-\n--\narch/x86/kvm/svm/nested.c-2230-\narch/x86/kvm/svm/nested.c:2231:\tsvm_switch_vmcb(svm, \u0026svm-\u003enested.vmcb02);\narch/x86/kvm/svm/nested.c-2232-\n--\narch/x86/kvm/svm/nested.c-2235-\narch/x86/kvm/svm/nested.c:2236:\tnested_vmcb02_prepare_control(svm);\narch/x86/kvm/svm/nested.c-2237-\n--\narch/x86/kvm/svm/nested.c-2239-\t * Any previously restored state (e.g. KVM_SET_SREGS) would mark fields\narch/x86/kvm/svm/nested.c:2240:\t * dirty in vmcb01 instead of vmcb02, so mark all of vmcb02 dirty here.\narch/x86/kvm/svm/nested.c-2241-\t */\n--\narch/x86/kvm/svm/svm.c=3880=static void svm_fixup_nested_rips(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/svm.c-3893-\t *\narch/x86/kvm/svm/svm.c:3894:\t * This is done here (as opposed to when preparing vmcb02) to use the\narch/x86/kvm/svm/svm.c-3895-\t * most up-to-date value of RIP regardless of the order of restoring\n--\narch/x86/kvm/svm/svm.c=4484=static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags)\n--\narch/x86/kvm/svm/svm.c-4610-\tif (is_guest_mode(vcpu)) {\narch/x86/kvm/svm/svm.c:4611:\t\tnested_sync_control_from_vmcb02(svm);\narch/x86/kvm/svm/svm.c-4612-\n--\narch/x86/kvm/svm/svm.h=213=struct svm_nested_state {\narch/x86/kvm/svm/svm.h:214:\tstruct kvm_vmcb_info vmcb02;\narch/x86/kvm/svm/svm.h-215-\tu64 hsave_msr;\n--\narch/x86/kvm/svm/svm.h-221-\t/*\narch/x86/kvm/svm/svm.h:222:\t * The MSR permissions map used for vmcb02, which is the merge result\narch/x86/kvm/svm/svm.h-223-\t * of vmcb01 and vmcb12\n--\narch/x86/kvm/svm/svm.h-247-\t/* True if VMCB02 has instruction bytes for the current nested exit. */\narch/x86/kvm/svm/svm.h:248:\tbool vmcb02_insn_bytes_valid;\narch/x86/kvm/svm/svm.h-249-};\n--\narch/x86/kvm/svm/svm.h=545=static inline bool vmcb12_is_intercept(struct vmcb_ctrl_area_cached *control, u32 bit)\n--\narch/x86/kvm/svm/svm.h-549-\narch/x86/kvm/svm/svm.h:550:void nested_vmcb02_recalc_intercepts(struct vcpu_svm *svm);\narch/x86/kvm/svm/svm.h-551-\narch/x86/kvm/svm/svm.h=552=static inline void svm_mark_intercepts_dirty(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/svm.h-556-\t/*\narch/x86/kvm/svm/svm.h:557:\t * If L2 is active, recalculate the intercepts for vmcb02 to account\narch/x86/kvm/svm/svm.h-558-\t * for the changes made to vmcb01. All intercept configuration is done\narch/x86/kvm/svm/svm.h:559:\t * for vmcb01 and then propagated to vmcb02 to combine KVM's intercepts\narch/x86/kvm/svm/svm.h-560-\t * with L1's intercepts (from the vmcb12 snapshot).\n--\narch/x86/kvm/svm/svm.h-562-\tif (is_guest_mode(\u0026svm-\u003evcpu))\narch/x86/kvm/svm/svm.h:563:\t\tnested_vmcb02_recalc_intercepts(svm);\narch/x86/kvm/svm/svm.h-564-}\n--\narch/x86/kvm/svm/svm.h=615=static inline struct vmcb *get_vgif_vmcb(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/svm.h-620-\tif (is_guest_mode(\u0026svm-\u003evcpu) \u0026\u0026 !nested_vgif_enabled(svm))\narch/x86/kvm/svm/svm.h:621:\t\treturn svm-\u003enested.vmcb02.ptr;\narch/x86/kvm/svm/svm.h-622-\telse\n--\narch/x86/kvm/svm/svm.h=904=void nested_copy_vmcb_save_to_cache(struct vcpu_svm *svm,\narch/x86/kvm/svm/svm.h-905-\t\t\t\t struct vmcb_save_area *save);\narch/x86/kvm/svm/svm.h:906:void nested_sync_control_from_vmcb02(struct vcpu_svm *svm);\narch/x86/kvm/svm/svm.h-907-void svm_switch_vmcb(struct vcpu_svm *svm, struct kvm_vmcb_info *target_vmcb);\n--\narch/x86/kvm/svm/vmenter.S=37=SYM_FUNC_START(__svm_vcpu_run)\n--\narch/x86/kvm/svm/vmenter.S-80-\t * the state doesn't need to be copied between vmcb01 and\narch/x86/kvm/svm/vmenter.S:81:\t * vmcb02 when switching vmcbs for nested virtualization.\n\n"
} |
| 39/3 | 2026/09/13 07:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 38033 output: 36 thoughts: 49 Thoughts: Here's my take on the thought process, as if it were my own: **Investigating `svm_allocate_nested` and `snp_safe_alloc_page()`** Okay, so I'm looking at `svm_allocate_nested` and it appears to be responsible for allocating `vmcb02_page`. The code says this allocation is happening via `snp_safe_alloc_page()`. Right. `snp_safe_alloc_page()`... that's the key question. I need to understand what that function does to really grasp the implications here, particularly in the context of nested virtualization and SEV-SNP. I need to trace this function to understand the page allocation behavior. What type of security guarantees is it providing? Is it involved in configuring the secure page? Is there anything noteworthy here? Let's go look at the definition of `snp_safe_alloc_page()` and see what we can find. |
| 40/3 | 2026/09/13 07:03 | tool | grepper |
0mArgs:{
"Expression": "snp_safe_alloc_page",
"PathPrefix": "arch/x86/kvm/"
}Results: {
"Output": "arch/x86/kvm/svm/nested.c=1639=int svm_allocate_nested(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/nested.c-1645-\narch/x86/kvm/svm/nested.c:1646:\tvmcb02_page = snp_safe_alloc_page();\narch/x86/kvm/svm/nested.c-1647-\tif (!vmcb02_page)\n--\narch/x86/kvm/svm/sev.c=4862=int sev_vcpu_create(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/sev.c-4875-\t */\narch/x86/kvm/svm/sev.c:4876:\tvmsa_page = snp_safe_alloc_page();\narch/x86/kvm/svm/sev.c-4877-\tif (!vmsa_page)\n--\narch/x86/kvm/svm/sev.c=4948=void sev_vcpu_deliver_sipi_vector(struct kvm_vcpu *vcpu, u8 vector)\n--\narch/x86/kvm/svm/sev.c-4984-\narch/x86/kvm/svm/sev.c:4985:struct page *snp_safe_alloc_page_node(int node, gfp_t gfp)\narch/x86/kvm/svm/sev.c-4986-{\n--\narch/x86/kvm/svm/svm.c=616=static int svm_cpu_init(int cpu)\n--\narch/x86/kvm/svm/svm.c-622-\tmemset(sd, 0, sizeof(struct svm_cpu_data));\narch/x86/kvm/svm/svm.c:623:\tsave_area_page = snp_safe_alloc_page_node(cpu_to_node(cpu), GFP_KERNEL);\narch/x86/kvm/svm/svm.c-624-\tif (!save_area_page)\n--\narch/x86/kvm/svm/svm.c=1315=static int svm_vcpu_create(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/svm.c-1324-\terr = -ENOMEM;\narch/x86/kvm/svm/svm.c:1325:\tvmcb01_page = snp_safe_alloc_page();\narch/x86/kvm/svm/svm.c-1326-\tif (!vmcb01_page)\n--\narch/x86/kvm/svm/svm.c=5348=static void *svm_alloc_apic_backing_page(struct kvm_vcpu *vcpu)\narch/x86/kvm/svm/svm.c-5349-{\narch/x86/kvm/svm/svm.c:5350:\tstruct page *page = snp_safe_alloc_page();\narch/x86/kvm/svm/svm.c-5351-\n--\narch/x86/kvm/svm/svm.h=995=int sev_handle_vmgexit(struct kvm_vcpu *vcpu);\n--\narch/x86/kvm/svm/svm.h-998-\narch/x86/kvm/svm/svm.h:999:struct page *snp_safe_alloc_page_node(int node, gfp_t gfp);\narch/x86/kvm/svm/svm.h:1000:static inline struct page *snp_safe_alloc_page(void)\narch/x86/kvm/svm/svm.h-1001-{\narch/x86/kvm/svm/svm.h:1002:\treturn snp_safe_alloc_page_node(numa_node_id(), GFP_KERNEL_ACCOUNT);\narch/x86/kvm/svm/svm.h-1003-}\n--\narch/x86/kvm/svm/svm.h=1022=void sev_free_decrypted_vmsa(struct kvm_vcpu *vcpu, struct vmcb_save_area *vmsa);\narch/x86/kvm/svm/svm.h-1023-#else\narch/x86/kvm/svm/svm.h:1024:static inline struct page *snp_safe_alloc_page_node(int node, gfp_t gfp)\narch/x86/kvm/svm/svm.h-1025-{\n--\narch/x86/kvm/svm/svm.h-1028-\narch/x86/kvm/svm/svm.h:1029:static inline struct page *snp_safe_alloc_page(void)\narch/x86/kvm/svm/svm.h-1030-{\narch/x86/kvm/svm/svm.h:1031:\treturn snp_safe_alloc_page_node(numa_node_id(), GFP_KERNEL_ACCOUNT);\narch/x86/kvm/svm/svm.h-1032-}\n"
} |
| 41/3 | 2026/09/13 07:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 39671 output: 43 thoughts: 45 Thoughts: Here's my analysis of what I'm looking at: **Diving into `snp_safe_alloc_page_node`** Okay, so I'm digging into `snp_safe_alloc_page_node`. I need to understand this function. Given the naming convention, it's almost certainly related to secure memory allocation, likely within the context of AMD's Secure Encrypted Virtualization (SEV). My immediate focus is on how it handles page allocation, especially in a secure environment. I'm going to look specifically at line 4985 of `arch/x86/kvm/svm/sev.c` as a starting point. That file, and that line, should be where the core logic of this allocation process begins. I'm expecting to see calls to lower-level memory allocation routines, likely with specific flags to ensure the allocation is compliant with SEV requirements – things like memory encryption or page protection. I'm anticipating that this is where the interesting aspects of the Secure Nested Paging memory management are handled. |
| 42/3 | 2026/09/13 07:03 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/svm/sev.c",
"FirstLine": 4985,
"LineCount": 25
}Results: {
"Contents": "4985:\tstruct page *snp_safe_alloc_page_node(int node, gfp_t gfp)\n4986:\t{\n4987:\t\tunsigned long pfn;\n4988:\t\tstruct page *p;\n4989:\t\n4990:\t\tif (!cc_platform_has(CC_ATTR_HOST_SEV_SNP))\n4991:\t\t\treturn alloc_pages_node(node, gfp | __GFP_ZERO, 0);\n4992:\t\n4993:\t\t/*\n4994:\t\t * Allocate an SNP-safe page to workaround the SNP erratum where\n4995:\t\t * the CPU will incorrectly signal an RMP violation #PF if a\n4996:\t\t * hugepage (2MB or 1GB) collides with the RMP entry of a\n4997:\t\t * 2MB-aligned VMCB, VMSA, or AVIC backing page.\n4998:\t\t *\n4999:\t\t * Allocate one extra page, choose a page which is not\n5000:\t\t * 2MB-aligned, and free the other.\n5001:\t\t */\n5002:\t\tp = alloc_pages_node(node, gfp | __GFP_ZERO, 1);\n5003:\t\tif (!p)\n5004:\t\t\treturn NULL;\n5005:\t\n5006:\t\tsplit_page(p, 1);\n5007:\t\n5008:\t\tpfn = page_to_pfn(p);\n5009:\t\tif (IS_ALIGNED(pfn, PTRS_PER_PMD))\n"
} |
| 43/3 | 2026/09/13 07:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 40404 output: 36 thoughts: 747 Thoughts: **Analyzing the KVM Patch for AMD CPUID DecodeAssists** Okay, first thing I'm noticing is the use of `__GFP_ZERO`. Good practice, keeps things clean. Let's dig into this patch and see what's changed, line by line, function by function. Starting with `arch/x86/kvm/cpuid.c`, I see a new feature flag added: `VENDOR_F(DECODEASSISTS)` within `kvm_initialize_cpu_caps`. Aha! This is exposing the AMD CPUID DecodeAssists feature, specifically Fn8000_000A_EDX bit 7. Makes sense, that's the common way of enabling it. Moving on to `arch/x86/kvm/emulate.c`. I'm seeing changes in how instructions are intercepted, specifically with `get_invlpg_linear_addr`. The inclusion of this function, along with the modification of `struct x86_instruction_info` is important. The fields in the struct are now zero-initialized by a designated initializer, ensuring no undefined behavior. Now, that `get_invlpg_linear_addr` function is interesting. It's calling `__linearize` to determine the linear address for `INVLPG`. Let's scrutinize that `__linearize` function. I see that `unsigned long linear = 0;` ensures that if `__linearize` fails, we default to zero. If successful, `__linearize` writes to `linear`. I need to review the `__linearize` implementation in detail to understand exactly how it's calculating the linear address and what failure conditions might be. I need to be sure the logic is correct, efficient, and that potential issues are addressed. |
| 44/3 | 2026/09/13 07:03 | tool | grepper |
0mArgs:{
"Expression": "__linearize",
"PathPrefix": "arch/x86/kvm/emulate.c"
}Results: {
"Output": "arch/x86/kvm/emulate.c=624=static unsigned insn_alignment(struct x86_emulate_ctxt *ctxt, unsigned size)\n--\narch/x86/kvm/emulate.c-641-\narch/x86/kvm/emulate.c:642:static __always_inline int __linearize(struct x86_emulate_ctxt *ctxt,\narch/x86/kvm/emulate.c-643-\t\t\t\t struct segmented_address addr,\n--\narch/x86/kvm/emulate.c=709=static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,\n--\narch/x86/kvm/emulate.c-717-\narch/x86/kvm/emulate.c:718:\t__linearize(ctxt, ctxt-\u003esrc.addr.mem, \u0026max_size, 1, ctxt-\u003emode,\narch/x86/kvm/emulate.c-719-\t\t \u0026linear, X86EMUL_F_INVLPG);\n--\narch/x86/kvm/emulate.c=723=static int linearize(struct x86_emulate_ctxt *ctxt,\n--\narch/x86/kvm/emulate.c-728-\tunsigned max_size;\narch/x86/kvm/emulate.c:729:\treturn __linearize(ctxt, addr, \u0026max_size, size, ctxt-\u003emode, linear,\narch/x86/kvm/emulate.c-730-\t\t\t write ? X86EMUL_F_WRITE : 0);\n--\narch/x86/kvm/emulate.c=733=static inline int assign_eip(struct x86_emulate_ctxt *ctxt, ulong dst)\n--\narch/x86/kvm/emulate.c-742-\t\taddr.ea = dst \u0026 ((1UL \u003c\u003c (ctxt-\u003eop_bytes \u003c\u003c 3)) - 1);\narch/x86/kvm/emulate.c:743:\trc = __linearize(ctxt, addr, \u0026max_size, 1, ctxt-\u003emode, \u0026linear,\narch/x86/kvm/emulate.c-744-\t\t\t X86EMUL_F_FETCH);\n--\narch/x86/kvm/emulate.c=861=static int __do_insn_fetch_bytes(struct x86_emulate_ctxt *ctxt, int op_size)\n--\narch/x86/kvm/emulate.c-871-\t * We do not know exactly how many bytes will be needed, and\narch/x86/kvm/emulate.c:872:\t * __linearize is expensive, so fetch as much as possible. We\narch/x86/kvm/emulate.c-873-\t * just have to avoid going beyond the 15 byte limit, the end\n--\narch/x86/kvm/emulate.c-875-\t *\narch/x86/kvm/emulate.c:876:\t * __linearize is called with size 0 so that it does not do any\narch/x86/kvm/emulate.c-877-\t * boundary check itself. Instead, we use max_size to check\n--\narch/x86/kvm/emulate.c-879-\t */\narch/x86/kvm/emulate.c:880:\trc = __linearize(ctxt, addr, \u0026max_size, 0, ctxt-\u003emode, \u0026linear,\narch/x86/kvm/emulate.c-881-\t\t\t X86EMUL_F_FETCH);\n--\narch/x86/kvm/emulate.c=3452=static int em_invlpg(struct x86_emulate_ctxt *ctxt)\n--\narch/x86/kvm/emulate.c-3457-\narch/x86/kvm/emulate.c:3458:\trc = __linearize(ctxt, ctxt-\u003esrc.addr.mem, \u0026max_size, 1, ctxt-\u003emode,\narch/x86/kvm/emulate.c-3459-\t\t\t \u0026linear, X86EMUL_F_INVLPG);\n"
} |
| 45/3 | 2026/09/13 07:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 42678 output: 32 thoughts: 2916 Thoughts: **Analyzing Instruction Byte Handling in Nested SVM** Okay, let's break this down. My focus here is on understanding how instruction bytes are handled within the nested SVM (Secure Virtual Machine) context, specifically how the `insn_bytes` and `insn_len` fields are managed across different VMCBs (Virtual Machine Control Blocks). I'm seeing a potential issue with uninitialized or stale data within `vmcb12` and its implications for guest memory. First, I've noticed the initialization of `linear = 0;` which while seemingly straightforward is the jumping off point for the whole system. The function `x86_emulator_copy_insn_bytes` is the starting point in L1. Then I jump to `nested_svm_copy_insn_bytes` and `nested_svm_invalidate_insn_bytes`. It's crucial, and also a potential pitfall. I want to know where and how `insn_bytes` is populated. `vmcb12` is mapped to guest physical memory. This means it's memory allocated by the L1 guest. The code copies instruction bytes from `vmcb02` to `vmcb12` using `memcpy`. However, the code's only handling the copying of `insn_len` bytes. My immediate concern is: Does this leave the *remaining* bytes of `vmcb12->control.insn_bytes` in an undefined state? This could lead to a security vulnerability if stale data from a previous instruction or event is left uninitialized. Now, tracing the sources of `vmcb02`'s `insn_bytes` and `insn_len`. I've found that it's populated in two main cases: 1. **Hardware VM-exit (DecodeAssists Enabled):** The hardware itself writes `insn_len` and `insn_bytes` directly to `vmcb02` when a hardware event intercepts with decode assists enabled on the host CPU. This looks relatively safe. The hardware sets these directly, but the code still needs to be sure it is validating the length. `nested_svm_set_vmcb02_insn_bytes_valid` appears to validate this based on hardware features. I need to understand when, and under which exit code the hardware will invoke this process. 2. **Synthesized Instruction Bytes (`nested_svm_prepare_synthesized_insn_bytes`):** This is where it gets interesting. `x86_emulator_copy_insn_bytes` is called here. It attempts to copy instruction bytes from the emulator to `vmcb02`. The next call `nested_svm_fetch_insn_bytes` reads additional bytes if the initial copy failed. Here's the key: if `kvm_fetch_guest_virt` fails to fetch an entire chunk of bytes (due to a guest page fault, for example), the loop breaks, and a partial `count` is returned and becomes the `insn_len`. I'm starting to see a pattern. `nested_svm_copy_insn_bytes` copies data from `vmcb02` (which could have incomplete data in the `insn_bytes` array) to `vmcb12` (guest memory), based on a `count` (length) which could also be incomplete. `nested_svm_update_vmcb12_insn_bytes` looks like it *can* fetch a full page size, but can also set `insn_len = 0`. So, `vmcb12` can contain uninitialized bytes, or data that is only partially filled. Now, if there is a partially filled `vmcb02` then that can lead to a partially filled `vmcb12`. That data could leak information and become a vulnerability. Finally, I'm checking the `svm_check_intercept` function. This is where I'm asking myself, are any fields in `info` uninitialized when `svm_check_intercept` is called? The check is enabled if decode assists are enabled. If some fields in the `info` are not initialized, I see a possible vulnerability. The next steps are: 1. **Thoroughly Review `kvm_fetch_guest_virt`:** Understand all failure paths and how they influence `count`. 2. **Examine Callers of `svm_check_intercept`:** Identify the cases in which the `info` structure is populated, and ensure they are always fully initialized before the function is called. 3. **Perform Formal Analysis:** Write a formal proof that `vmcb12`'s `insn_bytes` either contains valid instruction data or is zeroed out. 4. ** |
| 46/3 | 2026/09/13 07:03 | tool | grepper |
0mArgs:{
"Expression": "check_intercept",
"PathPrefix": "arch/x86/kvm/"
}Results: {
"Output": "arch/x86/kvm/emulate.c=413=static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,\n--\narch/x86/kvm/emulate.c-415-\narch/x86/kvm/emulate.c:416:static int emulator_check_intercept(struct x86_emulate_ctxt *ctxt,\narch/x86/kvm/emulate.c-417-\t\t\t\t enum x86_intercept intercept,\n--\narch/x86/kvm/emulate.c=5314=void init_decode_cache(struct x86_emulate_ctxt *ctxt)\n--\narch/x86/kvm/emulate.c-5330-\narch/x86/kvm/emulate.c:5331:int x86_emulate_insn(struct x86_emulate_ctxt *ctxt, bool check_intercepts)\narch/x86/kvm/emulate.c-5332-{\n--\narch/x86/kvm/emulate.c-5396-\narch/x86/kvm/emulate.c:5397:\t\tif (unlikely(check_intercepts) \u0026\u0026 ctxt-\u003eintercept) {\narch/x86/kvm/emulate.c:5398:\t\t\trc = emulator_check_intercept(ctxt, ctxt-\u003eintercept,\narch/x86/kvm/emulate.c-5399-\t\t\t\t\t\t X86_ICPT_PRE_EXCEPT);\n--\narch/x86/kvm/emulate.c-5425-\narch/x86/kvm/emulate.c:5426:\t\tif (unlikely(check_intercepts) \u0026\u0026 (ctxt-\u003ed \u0026 Intercept)) {\narch/x86/kvm/emulate.c:5427:\t\t\trc = emulator_check_intercept(ctxt, ctxt-\u003eintercept,\narch/x86/kvm/emulate.c-5428-\t\t\t\t\t\t X86_ICPT_POST_EXCEPT);\n--\narch/x86/kvm/emulate.c-5479-\narch/x86/kvm/emulate.c:5480:\tif (unlikely(check_intercepts) \u0026\u0026 (ctxt-\u003ed \u0026 Intercept)) {\narch/x86/kvm/emulate.c:5481:\t\trc = emulator_check_intercept(ctxt, ctxt-\u003eintercept,\narch/x86/kvm/emulate.c-5482-\t\t\t\t\t X86_ICPT_POST_MEMACCESS);\n--\narch/x86/kvm/kvm_emulate.h=539=void init_decode_cache(struct x86_emulate_ctxt *ctxt);\narch/x86/kvm/kvm_emulate.h:540:int x86_emulate_insn(struct x86_emulate_ctxt *ctxt, bool check_intercepts);\narch/x86/kvm/kvm_emulate.h-541-int emulator_task_switch(struct x86_emulate_ctxt *ctxt,\n--\narch/x86/kvm/svm/svm.c=4774=static const struct __x86_intercept {\n--\narch/x86/kvm/svm/svm.c-4830-\narch/x86/kvm/svm/svm.c:4831:static int svm_check_intercept(struct kvm_vcpu *vcpu,\narch/x86/kvm/svm/svm.c-4832-\t\t\t struct x86_instruction_info *info,\n--\narch/x86/kvm/svm/svm.c=5358=struct kvm_x86_ops svm_x86_ops __initdata = {\n--\narch/x86/kvm/svm/svm.c-5458-\narch/x86/kvm/svm/svm.c:5459:\t.check_intercept = svm_check_intercept,\narch/x86/kvm/svm/svm.c-5460-\t.handle_exit_irqoff = svm_handle_exit_irqoff,\n--\narch/x86/kvm/vmx/main.c=888=struct kvm_x86_ops vt_x86_ops __initdata = {\n--\narch/x86/kvm/vmx/main.c-1001-\narch/x86/kvm/vmx/main.c:1002:\t.check_intercept = vmx_check_intercept,\narch/x86/kvm/vmx/main.c-1003-\t.handle_exit_irqoff = vmx_handle_exit_irqoff,\n--\narch/x86/kvm/vmx/vmx.c=8142=static bool vmx_is_io_intercepted(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/vmx/vmx.c-8187-\narch/x86/kvm/vmx/vmx.c:8188:int vmx_check_intercept(struct kvm_vcpu *vcpu,\narch/x86/kvm/vmx/vmx.c-8189-\t\t\tstruct x86_instruction_info *info,\n--\narch/x86/kvm/vmx/x86_ops.h=41=int vmx_check_emulate_instruction(struct kvm_vcpu *vcpu, int emul_type,\narch/x86/kvm/vmx/x86_ops.h-42-\t\t\t\t void *insn, int insn_len);\narch/x86/kvm/vmx/x86_ops.h:43:int vmx_check_intercept(struct kvm_vcpu *vcpu,\narch/x86/kvm/vmx/x86_ops.h-44-\t\t\tstruct x86_instruction_info *info,\n--\narch/x86/kvm/x86.c=5714=static int emulator_intercept(struct x86_emulate_ctxt *ctxt,\n--\narch/x86/kvm/x86.c-5717-{\narch/x86/kvm/x86.c:5718:\treturn kvm_x86_call(check_intercept)(emul_to_vcpu(ctxt), info, stage,\narch/x86/kvm/x86.c-5719-\t\t\t\t\t \u0026ctxt-\u003eexception);\n"
} |
| 47/3 | 2026/09/13 07:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 48334 output: 34 thoughts: 413 Thoughts: **My Initial Thoughts on `emulator_check_intercept`** Okay, so I'm looking at `emulator_check_intercept`, and my primary focus right now is understanding how the instruction information is structured. It looks like it's building a `struct x86_instruction_info` called `info`. Good, that's the central data structure here. I see it's using the designated initializer syntax, so I'm confident that the struct is being fully initialized, with any fields I *don't* explicitly set defaulting to zero. That's a clean and safe initialization strategy. I can see that most of the fields are being populated directly from the `ctxt` structure, which I assume is some kind of emulation context. Fields like `intercept`, `rep_prefix`, `modrm_mod`, etc. are all coming from `ctxt`. This makes sense; `ctxt` is likely where the instruction's decoded state is stored. The source and destination operand values and sizes (`src_val`, `dst_bytes`, etc.), and their types are also coming from `ctxt`. I see the `get_invlpg_linear_addr` function is used for something related to the `invlpg` instruction. Finally, the instruction pointer (`rip`, `next_rip`) is being pulled from `ctxt` as well. I need to confirm if my assumptions about the role of `ctxt` are correct, especially regarding how the instruction details are populated. I vaguely recall there's a function that handles this, perhaps `x86_decode_insn`, but just to be sure, it's worth double-checking what `init_decode_cache` is doing as well. |
| 48/3 | 2026/09/13 07:03 | tool | grepper |
0mArgs:{
"Expression": "init_decode_cache",
"PathPrefix": "arch/x86/kvm/"
}Results: {
"Output": "arch/x86/kvm/emulate.c=5308=static void fetch_possible_mmx_operand(struct operand *op)\n--\narch/x86/kvm/emulate.c-5313-\narch/x86/kvm/emulate.c:5314:void init_decode_cache(struct x86_emulate_ctxt *ctxt)\narch/x86/kvm/emulate.c-5315-{\n--\narch/x86/kvm/kvm_emulate.h=534=bool x86_page_table_writing_insn(struct x86_emulate_ctxt *ctxt);\n--\narch/x86/kvm/kvm_emulate.h-538-#define EMULATION_INTERCEPTED 2\narch/x86/kvm/kvm_emulate.h:539:void init_decode_cache(struct x86_emulate_ctxt *ctxt);\narch/x86/kvm/kvm_emulate.h-540-int x86_emulate_insn(struct x86_emulate_ctxt *ctxt, bool check_intercepts);\n--\narch/x86/kvm/x86.c=5949=static void init_emulate_ctxt(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/x86.c-5971-\narch/x86/kvm/x86.c:5972:\tinit_decode_cache(ctxt);\narch/x86/kvm/x86.c-5973-\tvcpu-\u003earch.emulate_regs_need_sync_from_vcpu = false;\n"
} |
| 49/3 | 2026/09/13 07:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 49582 output: 42 thoughts: 27 Thoughts: Here's my take on the thought process, as requested: **Diving into `init_decode_cache` in `arch/x86/kvm/emulate.c`** Okay, time to investigate `init_decode_cache` within the KVM x86 emulation code. Specifically, I'm focusing on `arch/x86/kvm/emulate.c`. I need to get a better handle on how the instruction decoding cache is initialized. Understanding this is crucial for performance analysis and optimization, because it has such a direct impact on the speed of guest instruction execution. I want to see how the cache is structured, what key data structures are involved, and what initialization routines are called. This should offer insight into whether this is an area where I could further improve performance. |
| 50/3 | 2026/09/13 07:03 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/emulate.c",
"FirstLine": 5314,
"LineCount": 20
}Results: {
"Contents": "5314:\tvoid init_decode_cache(struct x86_emulate_ctxt *ctxt)\n5315:\t{\n5316:\t\t/* Clear fields that are set conditionally but read without a guard. */\n5317:\t\tctxt-\u003erip_relative = false;\n5318:\t\tctxt-\u003erex_prefix = REX_NONE;\n5319:\t\tctxt-\u003erex_bits = 0;\n5320:\t\tctxt-\u003elock_prefix = 0;\n5321:\t\tctxt-\u003eop_prefix = false;\n5322:\t\tctxt-\u003erep_prefix = 0;\n5323:\t\tctxt-\u003eregs_valid = 0;\n5324:\t\tctxt-\u003eregs_dirty = 0;\n5325:\t\n5326:\t\tctxt-\u003eio_read.pos = 0;\n5327:\t\tctxt-\u003eio_read.end = 0;\n5328:\t\tctxt-\u003emem_read.end = 0;\n5329:\t}\n5330:\t\n5331:\tint x86_emulate_insn(struct x86_emulate_ctxt *ctxt, bool check_intercepts)\n5332:\t{\n5333:\t\tconst struct x86_emulate_ops *ops = ctxt-\u003eops;\n"
} |
| 51/3 | 2026/09/13 07:03 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 50162 output: 32 thoughts: 822 Thoughts: **Diving Deep into KVM Code** Okay, let's break this down. First, I'm examining a change to `kvm_fetch_guest_virt`. It's a straightforward instruction fetch function, but I want to make sure the exception handling is bulletproof. The `memset(exception, 0, sizeof(*exception))` is crucial; I need to ensure the `exception` struct is completely initialized, which is a good practice to avoid any undefined behavior later on. Now, I'm shifting gears to `svm.c`. I'm focusing on how CR0 access is handled. The `exit_info_1` field within the `vmcb->control` structure is what I am interested in, especially in the context of the `SVM_EXIT_READ_CR0` and `SVM_EXIT_WRITE_CR0` cases. I see it's being set to a value derived from some bit manipulation and based on the `intercept` value. It seems to be encoding information about the CR access, and I want to understand exactly what information is being encoded here. Finally, I'm back in `nested.c` and focusing on the `vmcb` structure, specifically the handling of instruction bytes. The `nested_svm_invalidate_insn_bytes` and `nested_svm_copy_insn_bytes` functions caught my eye. The `insn_len` field seems to control how much of `insn_bytes` is valid. I see that there is a `WARN_ON_ONCE` which protects against a length greater than the size of the buffer. The `memcpy` operation itself isn't a surprise, but the context within `nested.c` suggests this is about nested virtualization. This makes me curious about `vmcb->control.insn_bytes`. What is its role in this context? I need to track down the definition of the `struct vmcb_control_area` to fully understand what's going on here. I need to understand its layout, what's stored in `insn_bytes`, and how it's used within the nested SVM context. Time to go search for the struct definition. |
| 52/3 | 2026/09/13 07:04 | tool | grepper |
0mArgs:{
"Expression": "insn_bytes",
"PathPrefix": "arch/x86/include/"
}Results: {
"Output": "arch/x86/include/asm/svm.h=124=struct __attribute__ ((__packed__)) vmcb_control_area {\n--\narch/x86/include/asm/svm.h-155-\tu8 insn_len;\narch/x86/include/asm/svm.h:156:\tu8 insn_bytes[15];\narch/x86/include/asm/svm.h-157-\tu64 avic_backing_page;\t/* Offset 0xe0 */\n"
} |
| 53/3 | 2026/09/13 07:04 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 51623 output: 40 thoughts: 1839 Thoughts: **Analyzing Potential Uninitialized Memory in Nested SVM Instruction Byte Handling** Okay, let's break this down. I'm looking at the `struct vmcb_control_area` and its `insn_len` and `insn_bytes` members, specifically within the context of nested SVM. My immediate concern is that in the `nested_svm_copy_insn_bytes` function, we're doing a `memcpy` from `from->control.insn_bytes` to `to->control.insn_bytes`, using `from->control.insn_len` as the length. The question that immediately comes to mind is: can `from->control.insn_bytes` contain uninitialized memory? So, where does `from` – which, in this context, is `vmcb02` – get its instruction bytes and length? Let's trace it back. The key function here seems to be `nested_svm_update_vmcb12_insn_bytes`. This function is responsible for populating the instruction bytes in the vmcb12, copying them over from vmcb02. `vmcb02` is only valid under the following conditions: 1. A hardware VMEXIT, and then `nested_svm_set_vmcb02_insn_bytes_valid(svm)` is called (after checking `X86_FEATURE_DECODEASSISTS` and `nested_svm_vmexit_supports_insn_bytes`). If these conditions are met, then the AMD CPU has provided decode assists and populated `vmcb02->control.insn_len` and `vmcb02->control.insn_bytes` during VMEXIT! This means the memory will be initialized. 2. `nested_svm_prepare_synthesized_insn_bytes` is called. In this case, `x86_emulator_copy_insn_bytes` writes the instruction bytes into the `vmcb02->control.insn_bytes` buffer. The code writes up to `count` bytes into the buffer, and there's a subsequent call to `nested_svm_fetch_insn_bytes` that completes the initialization. The instruction bytes will be populated in this case too. Crucially, in all *other* cases, `svm->nested.vmcb02_insn_bytes_valid` is set to `false`. Also, `nested_vmcb02_prepare_control(svm)` sets the `insn_len = 0` and invalidates the validity flag, and the same happens in the end of `nested_svm_update_vmcb12_insn_bytes`. This means that at the time `nested_svm_copy_insn_bytes` is called, `vmcb02` has populated instruction bytes. Alright, what about `vmcb12` itself? It resides in the guest's physical memory (L1's guest memory), not kernel space. In `nested_svm_update_vmcb12_insn_bytes`, instruction bytes are either copied or fetched into `vmcb12->control.insn_bytes` and its length is set. It never copies uninitialized kernel memory to user space! The bytes copied to `vmcb12` come from either the hardware VMCB02, the guest instruction stream from its memory, or the emulator instruction buffer (also from guest memory). Finally, I need to consider state saving/restoring (e.g., via `KVM_GET_NESTED_STATE` / `KVM_SET_NESTED_STATE`). I need to check if the nested state ioctl is involved in any way. Let me run a `git grep` for `struct svm_nested_state` to be certain. |
| 54/3 | 2026/09/13 07:04 | tool | grepper |
0mArgs:{
"Expression": "vmcb02_insn_bytes_valid",
"PathPrefix": "arch/x86/kvm/"
}Results: {
"Output": "arch/x86/kvm/svm/nested.c=57=static bool nested_svm_vmexit_supports_insn_bytes(const struct vmcb *vmcb)\n--\narch/x86/kvm/svm/nested.c-67-\narch/x86/kvm/svm/nested.c:68:static void nested_svm_set_vmcb02_insn_bytes_valid(struct vcpu_svm *svm)\narch/x86/kvm/svm/nested.c-69-{\narch/x86/kvm/svm/nested.c:70:\tsvm-\u003enested.vmcb02_insn_bytes_valid =\narch/x86/kvm/svm/nested.c-71-\t\tstatic_cpu_has(X86_FEATURE_DECODEASSISTS) \u0026\u0026\n--\narch/x86/kvm/svm/nested.c=75=static void nested_svm_invalidate_vmcb02_insn_bytes(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/nested.c-77-\tnested_svm_invalidate_insn_bytes(svm-\u003enested.vmcb02.ptr);\narch/x86/kvm/svm/nested.c:78:\tsvm-\u003enested.vmcb02_insn_bytes_valid = false;\narch/x86/kvm/svm/nested.c-79-}\n--\narch/x86/kvm/svm/nested.c=128=static void nested_svm_prepare_synthesized_insn_bytes(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/nested.c-149-\narch/x86/kvm/svm/nested.c:150:\tsvm-\u003enested.vmcb02_insn_bytes_valid = true;\narch/x86/kvm/svm/nested.c-151-}\n--\narch/x86/kvm/svm/nested.c=153=static void nested_svm_update_vmcb12_insn_bytes(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/svm/nested.c-167-\narch/x86/kvm/svm/nested.c:168:\tif (svm-\u003enested.vmcb02_insn_bytes_valid) {\narch/x86/kvm/svm/nested.c-169-\t\tnested_svm_copy_insn_bytes(vmcb12, vmcb02);\n--\narch/x86/kvm/svm/nested.c-182-out:\narch/x86/kvm/svm/nested.c:183:\tsvm-\u003enested.vmcb02_insn_bytes_valid = false;\narch/x86/kvm/svm/nested.c-184-}\n--\narch/x86/kvm/svm/nested.c=186=static void nested_svm_inject_npf_exit(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/svm/nested.c-221-\tif (from_hardware)\narch/x86/kvm/svm/nested.c:222:\t\tnested_svm_set_vmcb02_insn_bytes_valid(svm);\narch/x86/kvm/svm/nested.c-223-\telse if (from_emulation \u0026\u0026 !(fault-\u003eerror_code \u0026 PFERR_FETCH_MASK))\n--\narch/x86/kvm/svm/nested.c-225-\telse\narch/x86/kvm/svm/nested.c:226:\t\tsvm-\u003enested.vmcb02_insn_bytes_valid = false;\narch/x86/kvm/svm/nested.c-227-\tnested_svm_vmexit(svm);\n--\narch/x86/kvm/svm/nested.c=1805=int nested_svm_exit_handled(struct vcpu_svm *svm)\n--\narch/x86/kvm/svm/nested.c-1811-\tif (vmexit == NESTED_EXIT_DONE) {\narch/x86/kvm/svm/nested.c:1812:\t\tnested_svm_set_vmcb02_insn_bytes_valid(svm);\narch/x86/kvm/svm/nested.c-1813-\t\tnested_svm_vmexit(svm);\n--\narch/x86/kvm/svm/svm.h=213=struct svm_nested_state {\n--\narch/x86/kvm/svm/svm.h-247-\t/* True if VMCB02 has instruction bytes for the current nested exit. */\narch/x86/kvm/svm/svm.h:248:\tbool vmcb02_insn_bytes_valid;\narch/x86/kvm/svm/svm.h-249-};\n"
} |
| 55/3 | 2026/09/13 07:04 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 55583 output: 40 thoughts: 60 Thoughts: **Investigating the Allocation of `svm_nested_state`** Okay, let's dive into this. I need to understand how the `svm_nested_state` struct is allocated and initialized. The architecture indicates that it's a member of the `vcpu_svm` struct. So, the question becomes: where and how is a `vcpu_svm` instantiated? That's the key to understanding the lifecycle of `svm_nested_state`. Time to trace the allocation path. Let's start by examining the `svm_create_vcpu` function; that's the most likely place to begin unraveling this allocation puzzle. |
| 56/3 | 2026/09/13 07:04 | tool | grepper |
0mError:no matches Args: {
"Expression": "kmem_cache_zalloc.*svm",
"PathPrefix": "arch/x86/kvm/svm/"
}Results: {
"error": "no matches"
} |
| 57/3 | 2026/09/13 07:04 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 55835 output: 37 thoughts: 12 Thoughts: Okay, here's my attempt at summarizing the thought process, as if I were an expert: **Investigating `svm_vcpu_create`** Right, let's take a look at `svm_vcpu_create`. That's a key function, of course. It's the point where we're spinning up the virtual CPUs within the Secure Virtual Machine environment. I need to understand what's happening here, specifically in the context of [mention specific area relevant to your expertise, e.g., memory management, interrupt handling, or security hardening]. My main concerns are [list key concerns, e.g., resource allocation efficiency, potential race conditions, or vulnerability points]. I'll need to trace the call flow, see what data structures are being initialized, and pay close attention to the error handling. Is there any obvious place for improvements, based on previous audits or performance benchmarks? And how does this interact with the [mention relevant hardware component or software module]? I should also compare this implementation to the standard x86 and ARM implementations, looking for any key differences and potential areas for exploitation. Let's see... first, pull up the source code and start stepping through it. |
| 58/3 | 2026/09/13 07:04 | tool | grepper |
0mArgs:{
"Expression": "svm_vcpu_create",
"PathPrefix": "arch/x86/kvm/svm/"
}Results: {
"Output": "arch/x86/kvm/svm/svm.c=1309=void svm_switch_vmcb(struct vcpu_svm *svm, struct kvm_vmcb_info *target_vmcb)\n--\narch/x86/kvm/svm/svm.c-1314-\narch/x86/kvm/svm/svm.c:1315:static int svm_vcpu_create(struct kvm_vcpu *vcpu)\narch/x86/kvm/svm/svm.c-1316-{\n--\narch/x86/kvm/svm/svm.c=5358=struct kvm_x86_ops svm_x86_ops __initdata = {\n--\narch/x86/kvm/svm/svm.c-5369-\t.vcpu_precreate = avic_vcpu_precreate,\narch/x86/kvm/svm/svm.c:5370:\t.vcpu_create = svm_vcpu_create,\narch/x86/kvm/svm/svm.c-5371-\t.vcpu_free = svm_vcpu_free,\n"
} |
| 59/3 | 2026/09/13 07:04 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 56435 output: 34 thoughts: 28 Thoughts: Here's my thought process, as requested: **Tracing `vcpu` Allocation in KVM Core** Alright, time to get my hands dirty. I need to understand exactly how the `vcpu` structure gets allocated within the KVM kernel module. I suspect this is core to understanding how virtual CPUs are represented and managed, which, as a specialist, I know is crucial. I'm going to start by zeroing in on `kvm_main.c` and potentially `x86.c`, as those are the likely candidates for the initial setup. I figure the main initialization logic is most likely there, and that's where I need to start my search. I need to pinpoint the exact function or code block that handles the memory allocation and initialization of these structures. This seems like a good starting point for a deeper dive into KVM's internal mechanisms. |
| 60/3 | 2026/09/13 07:04 | tool | grepper |
0mArgs:{
"Expression": "vcpu_create\\(",
"PathPrefix": "arch/x86/kvm/"
}Results: {
"Output": "arch/x86/kvm/svm/sev.c=4842=void sev_init_vmcb(struct vcpu_svm *svm, bool init_event)\n--\narch/x86/kvm/svm/sev.c-4861-\narch/x86/kvm/svm/sev.c:4862:int sev_vcpu_create(struct kvm_vcpu *vcpu)\narch/x86/kvm/svm/sev.c-4863-{\n--\narch/x86/kvm/svm/svm.c=1309=void svm_switch_vmcb(struct vcpu_svm *svm, struct kvm_vmcb_info *target_vmcb)\n--\narch/x86/kvm/svm/svm.c-1314-\narch/x86/kvm/svm/svm.c:1315:static int svm_vcpu_create(struct kvm_vcpu *vcpu)\narch/x86/kvm/svm/svm.c-1316-{\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=1004=void sev_snp_reload_vmsa(struct kvm_vcpu *vcpu);\narch/x86/kvm/svm/svm.h-1005-\narch/x86/kvm/svm/svm.h:1006:int sev_vcpu_create(struct kvm_vcpu *vcpu);\narch/x86/kvm/svm/svm.h-1007-void sev_free_vcpu(struct kvm_vcpu *vcpu);\n--\narch/x86/kvm/svm/svm.h=1029=static inline struct page *snp_safe_alloc_page(void)\n--\narch/x86/kvm/svm/svm.h-1033-\narch/x86/kvm/svm/svm.h:1034:static inline int sev_vcpu_create(struct kvm_vcpu *vcpu) { return 0; }\narch/x86/kvm/svm/svm.h-1035-static inline void sev_free_vcpu(struct kvm_vcpu *vcpu) {}\n--\narch/x86/kvm/vmx/main.c=65=static int vt_vcpu_precreate(struct kvm *kvm)\n--\narch/x86/kvm/vmx/main.c-72-\narch/x86/kvm/vmx/main.c:73:static int vt_vcpu_create(struct kvm_vcpu *vcpu)\narch/x86/kvm/vmx/main.c-74-{\narch/x86/kvm/vmx/main.c-75-\tif (is_td_vcpu(vcpu))\narch/x86/kvm/vmx/main.c:76:\t\treturn tdx_vcpu_create(vcpu);\narch/x86/kvm/vmx/main.c-77-\narch/x86/kvm/vmx/main.c:78:\treturn vmx_vcpu_create(vcpu);\narch/x86/kvm/vmx/main.c-79-}\n--\narch/x86/kvm/vmx/tdx.c=618=int tdx_vm_init(struct kvm *kvm)\n--\narch/x86/kvm/vmx/tdx.c-667-\narch/x86/kvm/vmx/tdx.c:668:int tdx_vcpu_create(struct kvm_vcpu *vcpu)\narch/x86/kvm/vmx/tdx.c-669-{\n--\narch/x86/kvm/vmx/tdx.c=827=void tdx_vcpu_put(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/vmx/tdx.c-841- * 3. KVM_CREATE_VCPU ioctl. (only when TD is TD_STATE_INITIALIZED).\narch/x86/kvm/vmx/tdx.c:842: * 3.1 tdx_vcpu_create() transitions vCPU state to VCPU_TD_STATE_UNINITIALIZED.\narch/x86/kvm/vmx/tdx.c:843: * 3.2 vcpu_load() and vcpu_put() in kvm_arch_vcpu_create().\narch/x86/kvm/vmx/tdx.c:844: * 3.3 (conditional) if any error encountered after kvm_arch_vcpu_create()\narch/x86/kvm/vmx/tdx.c-845- * kvm_arch_vcpu_destroy() --\u003e tdx_vcpu_free().\n--\narch/x86/kvm/vmx/vmx.c=7644=void vmx_vcpu_free(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/vmx/vmx.c-7658-\narch/x86/kvm/vmx/vmx.c:7659:int vmx_vcpu_create(struct kvm_vcpu *vcpu)\narch/x86/kvm/vmx/vmx.c-7660-{\n--\narch/x86/kvm/vmx/x86_ops.h=22=int vmx_vcpu_precreate(struct kvm *kvm);\narch/x86/kvm/vmx/x86_ops.h:23:int vmx_vcpu_create(struct kvm_vcpu *vcpu);\narch/x86/kvm/vmx/x86_ops.h-24-fastpath_t vmx_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags);\n--\narch/x86/kvm/vmx/x86_ops.h=134=int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);\narch/x86/kvm/vmx/x86_ops.h-135-\narch/x86/kvm/vmx/x86_ops.h:136:int tdx_vcpu_create(struct kvm_vcpu *vcpu);\narch/x86/kvm/vmx/x86_ops.h-137-void tdx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event);\n--\narch/x86/kvm/x86.c=9316=int kvm_arch_vcpu_precreate(struct kvm *kvm, unsigned int id)\n--\narch/x86/kvm/x86.c-9330-\t/*\narch/x86/kvm/x86.c:9331:\t * Note, any actions done by .vcpu_create() must be idempotent with\narch/x86/kvm/x86.c-9332-\t * respect to creating multiple vCPUs, and therefore are not undone if\n--\narch/x86/kvm/x86.c-9354-\narch/x86/kvm/x86.c:9355:int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)\narch/x86/kvm/x86.c-9356-{\n"
} |
| 61/3 | 2026/09/13 07:04 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 58669 output: 38 thoughts: 631 Thoughts: **Analyzing Potential Information Leaks in a KVM Patch** Okay, let's break this down systematically. I see a few key areas of interest in this KVM patch, specifically concerning potential information leakage, considering zero-allocation and memory copies. First, `kmem_cache_zalloc` is being used to allocate the vCPU, meaning the initial contents of the `struct vcpu_svm` are zeroed out. That's a good starting point for security, but we need to verify no later operations overwrite this. The call to `nested_svm_invalidate_vmcb02_insn_bytes` is specifically clearing `vmcb02_insn_bytes_valid`, which is promising. Furthermore, `vmcb02_page` is being zero-allocated, which is good. My primary concern now revolves around potential `copy_to_user` calls within the patch's modified files. I need to make sure we aren't inadvertently exposing sensitive data to userspace. Let's look closely at the diff. The patch touches several files, including `arch/x86/kvm/cpuid.c` (vendor decode assist), `arch/x86/kvm/emulate.c`, `arch/x86/kvm/kvm_emulate.h`, `arch/x86/kvm/svm/nested.c`, `arch/x86/kvm/svm/svm.c`, `arch/x86/kvm/svm/svm.h`, and `arch/x86/kvm/x86.c`. Specifically, in `emulate.c`, the functions `get_invlpg_linear_addr` returns a `linear` which is initialized to 0, which looks safe. The `x86_emulator_copy_insn_bytes` function is a potential point of concern, so I'll need to double check the context of what it's copying and to where. In `nested.c` we are working with `vmcb12->control.insn_bytes` and `vmcb12->control.insn_len` for nested SVM, which I'll have to examine carefully. In `svm.c`, there's `svm_check_intercept`, and also the setting of a cpu cap with `kvm_cpu_cap_check_and_set`. In `x86.c`, `kvm_fetch_guest_virt` is exported, and sets `exception` to 0 via `memset`, which looks safe. The presence of tests in `tools/testing/selftests/kvm/` is encouraging, but doesn't preclude a possible vulnerability. The next question: does `vmcb12` (the L1's VMCB for L2) ever make its way to userspace? That's the critical question here. It's stored in guest memory via `svm->nested.vmcb12_gpa`. That's an important detail. If userspace can somehow directly access or control the contents of this memory, then we have a potential problem. Therefore, *how* can userspace get access to the information in `vmcb12`? That points me directly to the `KVM_GET_NESTED_STATE` ioctl. I need to look closely at what `KVM_GET_NESTED_STATE` does specifically for the SVM implementation. Does it leak `vmcb12` information? This is the most pressing question. |
| 62/3 | 2026/09/13 07:04 | tool | grepper |
0mArgs:{
"Expression": "svm_get_nested_state",
"PathPrefix": "arch/x86/kvm/svm/"
}Results: {
"Output": "arch/x86/kvm/svm/nested.c=2002=static void nested_copy_vmcb_cache_to_control(struct vmcb_control_area *dst,\n--\narch/x86/kvm/svm/nested.c-2036-\narch/x86/kvm/svm/nested.c:2037:static int svm_get_nested_state(struct kvm_vcpu *vcpu,\narch/x86/kvm/svm/nested.c-2038-\t\t\t\tstruct kvm_nested_state __user *user_kvm_nested_state,\n--\narch/x86/kvm/svm/nested.c=2107=static int svm_set_nested_state(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/svm/nested.c-2267-\narch/x86/kvm/svm/nested.c:2268:static bool svm_get_nested_state_pages(struct kvm_vcpu *vcpu)\narch/x86/kvm/svm/nested.c-2269-{\n--\narch/x86/kvm/svm/nested.c=2321=struct kvm_x86_nested_ops svm_nested_ops __initdata = {\n--\narch/x86/kvm/svm/nested.c-2326-\t.triple_fault = nested_svm_triple_fault,\narch/x86/kvm/svm/nested.c:2327:\t.get_nested_state_pages = svm_get_nested_state_pages,\narch/x86/kvm/svm/nested.c:2328:\t.get_state = svm_get_nested_state,\narch/x86/kvm/svm/nested.c-2329-\t.set_state = svm_set_nested_state,\n--\narch/x86/kvm/svm/svm.c=4484=static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags)\n--\narch/x86/kvm/svm/svm.c-4649-\t *\narch/x86/kvm/svm/svm.c:4650:\t * FIXME: Rework svm_get_nested_state() to not pull data from the\narch/x86/kvm/svm/svm.c-4651-\t * cache (except for maybe int_ctl).\n"
} |
| 63/3 | 2026/09/13 07:04 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 60745 output: 43 thoughts: 30 Thoughts: Here's my take on those thoughts, tailored for an expert audience: **Diving into the SVM Nested State** Alright, let's get down to brass tacks. I'm going to crack open `svm_get_nested_state` in `arch/x86/kvm/svm/nested.c`. My immediate thought is, this is where the *real* fun begins with nested virtualization on AMD. I need to understand how we're pulling the nested state information. Specifically, I want to see how the code handles the transitions and data exchange between the host and guest hypervisors, and how it manages the subtleties of nested paging and EPT within the SVM environment. I'm anticipating that this function is where the magic (or the pain) of performance overhead manifests, and I'm keen to see how efficiently the core hypercall is implemented. I expect to be digging into structures associated with nested guest registers, control registers and potentially nested MSRs. I'm looking for any hints on how the nested guest is emulating hardware that may not be directly available to it and how this state is managed during VCPU execution. I'll be particularly interested in identifying any potential bottlenecks or areas for optimization regarding the SVM nested page tables, and how the overall nesting is structured within the code. Let's see what we've got. |
| 64/3 | 2026/09/13 07:04 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/svm/nested.c",
"FirstLine": 2036,
"LineCount": 70
}Results: {
"Contents": "2036:\t\n2037:\tstatic int svm_get_nested_state(struct kvm_vcpu *vcpu,\n2038:\t\t\t\t\tstruct kvm_nested_state __user *user_kvm_nested_state,\n2039:\t\t\t\t\tu32 user_data_size)\n2040:\t{\n2041:\t\tstruct vcpu_svm *svm;\n2042:\t\tstruct vmcb_control_area *ctl;\n2043:\t\tunsigned long r;\n2044:\t\tstruct kvm_nested_state kvm_state = {\n2045:\t\t\t.flags = 0,\n2046:\t\t\t.format = KVM_STATE_NESTED_FORMAT_SVM,\n2047:\t\t\t.size = sizeof(kvm_state),\n2048:\t\t};\n2049:\t\tstruct vmcb __user *user_vmcb = (struct vmcb __user *)\n2050:\t\t\t\u0026user_kvm_nested_state-\u003edata.svm[0];\n2051:\t\n2052:\t\tif (!vcpu)\n2053:\t\t\treturn kvm_state.size + KVM_STATE_NESTED_SVM_VMCB_SIZE;\n2054:\t\n2055:\t\tsvm = to_svm(vcpu);\n2056:\t\n2057:\t\tif (user_data_size \u003c kvm_state.size)\n2058:\t\t\tgoto out;\n2059:\t\n2060:\t\t/* First fill in the header and copy it out. */\n2061:\t\tif (is_guest_mode(vcpu)) {\n2062:\t\t\tkvm_state.hdr.svm.vmcb_pa = svm-\u003enested.vmcb12_gpa;\n2063:\t\t\tkvm_state.hdr.svm.gpat = 0;\n2064:\t\t\tif (l2_has_separate_pat(vcpu))\n2065:\t\t\t\tkvm_state.hdr.svm.gpat = svm-\u003evmcb-\u003esave.g_pat;\n2066:\t\t\tkvm_state.size += KVM_STATE_NESTED_SVM_VMCB_SIZE;\n2067:\t\t\tkvm_state.flags |= KVM_STATE_NESTED_GUEST_MODE;\n2068:\t\n2069:\t\t\tif (vcpu-\u003earch.nested_run_pending)\n2070:\t\t\t\tkvm_state.flags |= KVM_STATE_NESTED_RUN_PENDING;\n2071:\t\t}\n2072:\t\n2073:\t\tif (gif_set(svm))\n2074:\t\t\tkvm_state.flags |= KVM_STATE_NESTED_GIF_SET;\n2075:\t\n2076:\t\tif (copy_to_user(user_kvm_nested_state, \u0026kvm_state, sizeof(kvm_state)))\n2077:\t\t\treturn -EFAULT;\n2078:\t\n2079:\t\tif (!is_guest_mode(vcpu))\n2080:\t\t\tgoto out;\n2081:\t\n2082:\t\t/*\n2083:\t\t * Copy over the full size of the VMCB rather than just the size\n2084:\t\t * of the structs.\n2085:\t\t */\n2086:\t\tif (clear_user(user_vmcb, KVM_STATE_NESTED_SVM_VMCB_SIZE))\n2087:\t\t\treturn -EFAULT;\n2088:\t\n2089:\t\tctl = kzalloc_obj(*ctl);\n2090:\t\tif (!ctl)\n2091:\t\t\treturn -ENOMEM;\n2092:\t\n2093:\t\tnested_copy_vmcb_cache_to_control(ctl, \u0026svm-\u003enested.ctl);\n2094:\t\tr = copy_to_user(\u0026user_vmcb-\u003econtrol, ctl,\n2095:\t\t\t\t sizeof(user_vmcb-\u003econtrol));\n2096:\t\tkfree(ctl);\n2097:\t\tif (r)\n2098:\t\t\treturn -EFAULT;\n2099:\t\n2100:\t\tif (copy_to_user(\u0026user_vmcb-\u003esave, \u0026svm-\u003evmcb01.ptr-\u003esave,\n2101:\t\t\t\t sizeof(user_vmcb-\u003esave)))\n2102:\t\t\treturn -EFAULT;\n2103:\tout:\n2104:\t\treturn kvm_state.size;\n2105:\t}\n"
} |
| 65/3 | 2026/09/13 07:04 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 62256 output: 41 thoughts: 297 Thoughts: **Analyzing Potential Subtle Issues in the Patch**
Okay, so I've taken a quick glance at this patch and initially it doesn't seem to have any immediately glaring issues with uninitialized memory. I see `ctl = kzalloc_obj(*ctl);` and `clear_user` being called, so it looks like the memory is being properly handled. Plus, it hasn't touched `svm_get_nested_state`, which is good.
But, as always, I need to dig deeper, just to be sure. Let's look for any subtle things. I'll focus on `arch/x86/kvm/emulate.c`. I see this function, `get_invlpg_linear_addr`:
```c
static u64 get_invlpg_linear_addr(struct x86_emulate_ctxt *ctxt,
enum x86_intercept intercept)
{
unsigned int max_size;
unsigned long linear = 0;
if (intercept != x86_intercept_invlpg)
return 0;
__linearize(ctxt, ctxt->src.addr.mem, &max_size, 1, ctxt->mode,
&linear, X86EMUL_F_INVLPG);
return linear;
}
```
Okay, `linear` is initialized to zero before being passed by reference to `__linearize`, which is good. `max_size` is passed as `&max_size` to `__linearize`, but is it initialized before that? Let's check the definition of `__linearize` around line 642 of `emulate.c`. That's where I need to focus my attention now.
|
| 66/3 | 2026/09/13 07:04 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/emulate.c",
"FirstLine": 642,
"LineCount": 65
}Results: {
"Contents": " 642:\tstatic __always_inline int __linearize(struct x86_emulate_ctxt *ctxt,\n 643:\t\t\t\t\t struct segmented_address addr,\n 644:\t\t\t\t\t unsigned *max_size, unsigned size,\n 645:\t\t\t\t\t enum x86emul_mode mode, ulong *linear,\n 646:\t\t\t\t\t unsigned int flags)\n 647:\t{\n 648:\t\tstruct desc_struct desc;\n 649:\t\tbool usable;\n 650:\t\tulong la;\n 651:\t\tu32 lim;\n 652:\t\tu16 sel;\n 653:\t\tu8 va_bits;\n 654:\t\n 655:\t\tla = seg_base(ctxt, addr.seg) + addr.ea;\n 656:\t\t*max_size = 0;\n 657:\t\tswitch (mode) {\n 658:\t\tcase X86EMUL_MODE_PROT64:\n 659:\t\t\t*linear = la = ctxt-\u003eops-\u003eget_untagged_addr(ctxt, la, flags);\n 660:\t\t\tva_bits = ctxt_virt_addr_bits(ctxt);\n 661:\t\t\tif (!__is_canonical_address(la, va_bits))\n 662:\t\t\t\tgoto bad;\n 663:\t\n 664:\t\t\t*max_size = min_t(u64, ~0u, (1ull \u003c\u003c va_bits) - la);\n 665:\t\t\tif (size \u003e *max_size)\n 666:\t\t\t\tgoto bad;\n 667:\t\t\tbreak;\n 668:\t\tdefault:\n 669:\t\t\t*linear = la = (u32)la;\n 670:\t\t\tusable = ctxt-\u003eops-\u003eget_segment(ctxt, \u0026sel, \u0026desc, NULL,\n 671:\t\t\t\t\t\t\taddr.seg);\n 672:\t\t\tif (!usable)\n 673:\t\t\t\tgoto bad;\n 674:\t\t\t/* code segment in protected mode or read-only data segment */\n 675:\t\t\tif ((((ctxt-\u003emode != X86EMUL_MODE_REAL) \u0026\u0026 (desc.type \u0026 8)) || !(desc.type \u0026 2)) \u0026\u0026\n 676:\t\t\t (flags \u0026 X86EMUL_F_WRITE))\n 677:\t\t\t\tgoto bad;\n 678:\t\t\t/* unreadable code segment */\n 679:\t\t\tif (!(flags \u0026 X86EMUL_F_FETCH) \u0026\u0026 (desc.type \u0026 8) \u0026\u0026 !(desc.type \u0026 2))\n 680:\t\t\t\tgoto bad;\n 681:\t\t\tlim = desc_limit_scaled(\u0026desc);\n 682:\t\t\tif (!(desc.type \u0026 8) \u0026\u0026 (desc.type \u0026 4)) {\n 683:\t\t\t\t/* expand-down segment */\n 684:\t\t\t\tif (addr.ea \u003c= lim)\n 685:\t\t\t\t\tgoto bad;\n 686:\t\t\t\tlim = desc.d ? 0xffffffff : 0xffff;\n 687:\t\t\t}\n 688:\t\t\tif (addr.ea \u003e lim)\n 689:\t\t\t\tgoto bad;\n 690:\t\t\tif (lim == 0xffffffff)\n 691:\t\t\t\t*max_size = ~0u;\n 692:\t\t\telse {\n 693:\t\t\t\t*max_size = (u64)lim + 1 - addr.ea;\n 694:\t\t\t\tif (size \u003e *max_size)\n 695:\t\t\t\t\tgoto bad;\n 696:\t\t\t}\n 697:\t\t\tbreak;\n 698:\t\t}\n 699:\t\tif (la \u0026 (insn_alignment(ctxt, size) - 1))\n 700:\t\t\treturn emulate_gp(ctxt, 0);\n 701:\t\treturn X86EMUL_CONTINUE;\n 702:\tbad:\n 703:\t\tif (addr.seg == VCPU_SREG_SS)\n 704:\t\t\treturn emulate_ss(ctxt, 0);\n 705:\t\telse\n 706:\t\t\treturn emulate_gp(ctxt, 0);\n"
} |
| 67/3 | 2026/09/13 07:04 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 64036 output: 43 thoughts: 2356 Thoughts: **Analysis of Instruction Byte Handling in Nested SVM** Okay, let's break this down systematically. My initial thought is to trace the flow of instruction bytes within the nested SVM architecture, paying particular attention to potential data leaks or uninitialized memory reads. First, I see immediate writes to `*max_size` and `*linear` on line 656, this is not an issue I'll focus on the instruction bytes. I need to understand how the instruction bytes are fetched and copied. `x86_emulator_copy_insn_bytes` is the first point of interest, it copies bytes from `ctxt->fetch.data`. It uses `ctxt->fetch.end` to determine the valid data region. I need to make sure this region is always properly initialized. A check reveals that `ctxt->fetch.data` is always initialized up to `ctxt->fetch.end`. Next, the `nested_svm_fetch_insn_bytes` function shows how bytes are fetched from guest virtual memory. The important part is that `kvm_fetch_guest_virt` is called. It guarantees that the memory pointed to by `bytes + count` is initialized before the `count` is incremented. Moving on, `nested_svm_prepare_synthesized_insn_bytes` uses both `x86_emulator_copy_insn_bytes` and `nested_svm_fetch_insn_bytes` to populate `vmcb02->control.insn_bytes`. It is important to remember that `vmcb02` is allocated with `__GFP_ZERO`. `nested_svm_copy_insn_bytes` then copies these bytes from `vmcb02` to `vmcb12`. Crucially, only `insn_len` bytes are copied. What if `insn_len` is 0, nothing is copied. This means that if `vmcb02` is synthesized, then the correct length and content is propagated. The question then arises if the memory in `vmcb12` can contain old values. However, I recall that `vmcb02` is allocated with `snp_safe_alloc_page()` which uses `__GFP_ZERO`. I'm going to have to make sure that there are no ways that KVM could write to the data section of the `vmcb12` to affect the contents. Also, it is crucial to remember that the AMD SVM specification describes that hardware itself writes up to 15 bytes. Finally, a key concern is that any uninitialized kernel memory might leak to host userspace. To address this, I need to check whether any ioctl functions ever return data from `vmcb02` or `vmcb12`. It appears that `vmcb02` is not leaked to userspace and that `nested_svm_copy_vmcb_cache_to_control` does not copy `insn_bytes`. Thus, it looks like there isn't a vulnerability here. The memory pointed to by the `insn_bytes` field are not written to by KVM. I have to confirm this is correct. I need to make sure this is the case. |
| 68/3 | 2026/09/13 07:04 | tool | grepper |
0mArgs:{
"Expression": "nested_copy_vmcb_cache_to_control",
"PathPrefix": "arch/x86/kvm/svm/"
}Results: {
"Output": "arch/x86/kvm/svm/nested.c=1991=void nested_svm_update_tsc_ratio_msr(struct kvm_vcpu *vcpu)\n--\narch/x86/kvm/svm/nested.c-2001-/* Inverse operation of nested_copy_vmcb_control_to_cache(). asid is copied too. */\narch/x86/kvm/svm/nested.c:2002:static void nested_copy_vmcb_cache_to_control(struct vmcb_control_area *dst,\narch/x86/kvm/svm/nested.c-2003-\t\t\t\t\t struct vmcb_ctrl_area_cached *from)\n--\narch/x86/kvm/svm/nested.c=2037=static int svm_get_nested_state(struct kvm_vcpu *vcpu,\n--\narch/x86/kvm/svm/nested.c-2092-\narch/x86/kvm/svm/nested.c:2093:\tnested_copy_vmcb_cache_to_control(ctl, \u0026svm-\u003enested.ctl);\narch/x86/kvm/svm/nested.c-2094-\tr = copy_to_user(\u0026user_vmcb-\u003econtrol, ctl,\n"
} |
| 69/3 | 2026/09/13 07:04 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 67452 output: 43 thoughts: 35 Thoughts: Here's my summary, as if I were thinking these thoughts: **Diving into the Nested VMCB Cache Function** Alright, time to dig into `nested_copy_vmcb_cache_to_control` in `arch/x86/kvm/svm/nested.c`. I need to get a better grasp of how the nested virtualization handles the VMCB cache transfer to control structures. This is a critical area, especially with the complexity of nested SVM, and I want to understand the specifics of this function. I'm hoping to get a clearer picture of the cache's role in the transition and data handling. Let's see how this function orchestrates that. |
| 70/3 | 2026/09/13 07:04 | tool | read-file |
0mArgs:{
"File": "arch/x86/kvm/svm/nested.c",
"FirstLine": 2002,
"LineCount": 34
}Results: {
"Contents": "2002:\tstatic void nested_copy_vmcb_cache_to_control(struct vmcb_control_area *dst,\n2003:\t\t\t\t\t\t struct vmcb_ctrl_area_cached *from)\n2004:\t{\n2005:\t\tunsigned int i;\n2006:\t\n2007:\t\tmemset(dst, 0, sizeof(struct vmcb_control_area));\n2008:\t\n2009:\t\tfor (i = 0; i \u003c MAX_INTERCEPT; i++)\n2010:\t\t\tdst-\u003eintercepts[i] = from-\u003eintercepts[i];\n2011:\t\n2012:\t\tdst-\u003eiopm_base_pa = from-\u003eiopm_base_pa;\n2013:\t\tdst-\u003emsrpm_base_pa = from-\u003emsrpm_base_pa;\n2014:\t\tdst-\u003etsc_offset = from-\u003etsc_offset;\n2015:\t\tdst-\u003easid = from-\u003easid;\n2016:\t\tdst-\u003etlb_ctl = from-\u003etlb_ctl;\n2017:\t\tdst-\u003eerap_ctl = from-\u003eerap_ctl;\n2018:\t\tdst-\u003eint_ctl = from-\u003eint_ctl;\n2019:\t\tdst-\u003eint_vector = from-\u003eint_vector;\n2020:\t\tdst-\u003eint_state = from-\u003eint_state;\n2021:\t\tdst-\u003eexit_code = from-\u003eexit_code;\n2022:\t\tdst-\u003eexit_info_1 = from-\u003eexit_info_1;\n2023:\t\tdst-\u003eexit_info_2 = from-\u003eexit_info_2;\n2024:\t\tdst-\u003eexit_int_info = from-\u003eexit_int_info;\n2025:\t\tdst-\u003eexit_int_info_err = from-\u003eexit_int_info_err;\n2026:\t\tdst-\u003emisc_ctl\t\t = from-\u003emisc_ctl;\n2027:\t\tdst-\u003eevent_inj = from-\u003eevent_inj;\n2028:\t\tdst-\u003eevent_inj_err = from-\u003eevent_inj_err;\n2029:\t\tdst-\u003enext_rip = from-\u003enext_rip;\n2030:\t\tdst-\u003enested_cr3\t\t = from-\u003enested_cr3;\n2031:\t\tdst-\u003emisc_ctl2\t\t = from-\u003emisc_ctl2;\n2032:\t\tdst-\u003epause_filter_count = from-\u003epause_filter_count;\n2033:\t\tdst-\u003epause_filter_thresh = from-\u003epause_filter_thresh;\n2034:\t\t/* 'clean' and 'hv_enlightenments' are not changed by KVM */\n2035:\t}\n"
} |
| 71/3 | 2026/09/13 07:04 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 68377 output: 459 thoughts: 3355 Thoughts: **KMSAN Assessment: Nested SVM DecodeAssists in KVM** Alright, let's break this down. My task is to determine if this patch, enabling AMD SVM DecodeAssists for nested virtualization, introduces any potential KMSAN issues. I start by carefully dissecting the code, focusing on the changes made and the potential impact on uninitialized memory access. First, I see the core functionality: it's all about handling intercepts, fetching instruction bytes from the guest, and updating the L1 VMCB12 control area. The initial `memset` call is the first sign, ensuring the VMCB control area (`dst`) is fully zeroed. My primary concern is if any subsequent operations might leave fields uninitialized that could later be read. I note specifically that `insn_bytes` and `insn_len` are *not* even in the `vmcb_ctrl_area_cached` and therefore are zeroed directly. I consider all the KVM APIs (like `KVM_GET_REGS`, `KVM_GET_SREGS`, etc.). I verify that none of these APIs involve access or manipulation of `insn_bytes` and `exit_info_1` which are the areas of potential concern. I also look at `vcpu->run` to see if it exposes data from `svm_check_intercept` or other emulator logic that would impact the state that might be uninitialized. Next, I delve into the nested exit scenarios, paying close attention to the `nested_svm_vmexit` function. It switches VMCBs but never exposes anything to userspace via the `vcpu->run` interface. I then check the `trace_kvm_nested_vmexit_inject` tracepoint and confirm that it only involves standard `vmcb12->control` fields. This is important to ensure these fields don't accidentally expose uninitialized data. Then, I review the KMSAN guidelines. I zero in on items related to uninitialized variables in conditionals, kernel data structures sent to userspace, or data structure initialization. I systematically examine each code snippet introduced or modified by this patch. I check `get_invlpg_linear_addr`. I see that the `intercept` is passed by caller, `linear` is initialized to 0 and `__linearize` guarantees its initialized. `x86_emulator_copy_insn_bytes` is checked, verifying that `ctxt->fetch.data` and `ctxt->fetch.end` are managed and the `eip` is an argument, ensuring everything is under control. For `nested_svm_copy_insn_bytes`, I note `insn_len`’s initialization process. I see that it is zero-allocated (`__GFP_ZERO`), and that `vmcb02->control.insn_len = 0;` and hardware also write its value. This confirms the initial state. `nested_svm_vmexit_supports_insn_bytes` uses `vmcb->control.exit_code` and `vmcb->control.exit_info_1`. I see they are written by hardware on VM-exit or set by KVM when injecting exit, so they are not uninitialized. The `nested_svm_set_vmcb02_insn_bytes_valid` and other functions are checked and verified. The `nested_svm_get_insn_bytes_len` checks are inspected, ensuring initialized variables. I drill into `nested_svm_fetch_insn_bytes`. I confirm all variables are initialized, with `e` explicitly zeroed by `kvm_fetch_guest_virt`. I analyze the changes in `svm.c`. I check for cases around CR0, DR, SWINT, INVLPG, and INVLPGA handling, verifying the proper setting of `vmcb->control.exit_info_1` and verifying initialized state. I turn my focus to item 3 and the newly introduced `bool vmcb02_insn_bytes_valid;` in `struct svm_nested_state`. I recall that `vcpu_svm` is allocated with `kmem_cache_zalloc`, meaning it begins zeroed (false). I track that `nested_vmcb02_prepare_control` also sets it to false and that `nested_svm_update_vmcb12_insn_bytes` resets it to false. I check that `invlpg_linear_addr` in `struct x86_instruction_info` is explicitly initialized. Finally, I address item 4: checking for bounds checks that could lead to uninitialized memory access. I confirm that functions such as `nested_svm_copy_insn_bytes`, `x86_emulator_copy_ins |
| 72/3 | 2026/09/13 07:05 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch implements nested SVM DecodeAssists support in KVM on AMD x86. This entails advertising the CPUID capability, populating EXITINFO1 during emulated intercepts, synthesizing/propagating instruction bytes into VMCB02 and VMCB12 on nested VM-exits, and adding helper functions for instruction byte extraction from the emulator cache or guest virtual memory.\n\nKMSAN vs KASAN applicability:\n- No kernel-to-user information leaks: The data copied is written to guest memory (VMCB12) or hardware-allocated VMCB02 (allocated with `__GFP_ZERO`). There are no new or modified `copy_to_user`, `put_user`, socket, netlink, or ioctl user-facing buffers.\n- All new and modified structures and local variables are properly initialized before being read: `x86_instruction_info` uses designated initializers (zeroing omitted fields), `linear` in `get_invlpg_linear_addr` is explicitly initialized to 0, `e` in `nested_svm_fetch_insn_bytes` is cleared via `memset` in `kvm_fetch_guest_virt`, and `vmcb02_insn_bytes_valid` is zero-initialized with the vCPU allocation and properly invalidated across transitions.\n- Bounds and length checks: The instruction length calculations in `nested_svm_get_insn_bytes_len`, `nested_svm_fetch_insn_bytes`, and `x86_emulator_copy_insn_bytes` ensure memory is copied strictly within valid bounds and only up to the count of successfully fetched bytes.\n- Memory corruption or out-of-bounds risks (e.g. in `insn_bytes` copying or limit checks) are spatial access violations that are detected by standard KASAN, not uninitialized memory reads.\n\nTherefore, this patch series does not introduce uninitialized memory risks or info-leaks, and a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch implements nested SVM DecodeAssists support in KVM on AMD x86. This entails advertising the CPUID capability, populating EXITINFO1 during emulated intercepts, synthesizing/propagating instruction bytes into VMCB02 and VMCB12 on nested VM-exits, and adding helper functions for instruction byte extraction from the emulator cache or guest virtual memory.\n\nKMSAN vs KASAN applicability:\n- No kernel-to-user information leaks: The data copied is written to guest memory (VMCB12) or hardware-allocated VMCB02 (allocated with `__GFP_ZERO`). There are no new or modified `copy_to_user`, `put_user`, socket, netlink, or ioctl user-facing buffers.\n- All new and modified structures and local variables are properly initialized before being read: `x86_instruction_info` uses designated initializers (zeroing omitted fields), `linear` in `get_invlpg_linear_addr` is explicitly initialized to 0, `e` in `nested_svm_fetch_insn_bytes` is cleared via `memset` in `kvm_fetch_guest_virt`, and `vmcb02_insn_bytes_valid` is zero-initialized with the vCPU allocation and properly invalidated across transitions.\n- Bounds and length checks: The instruction length calculations in `nested_svm_get_insn_bytes_len`, `nested_svm_fetch_insn_bytes`, and `x86_emulator_copy_insn_bytes` ensure memory is copied strictly within valid bounds and only up to the count of successfully fetched bytes.\n- Memory corruption or out-of-bounds risks (e.g. in `insn_bytes` copying or limit checks) are spatial access violations that are detected by standard KASAN, not uninitialized memory reads.\n\nTherefore, this patch series does not introduce uninitialized memory risks or info-leaks, and a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|