From: Xu Yilun <yilun.xu@linux.intel.com>
To: x86@kernel.org, kvm@vger.kernel.org, linux-coco@lists.linux.dev,
linux-kernel@vger.kernel.org
Cc: djbw@kernel.org, kas@kernel.org, rick.p.edgecombe@intel.com,
yilun.xu@linux.intel.com, yilun.xu@intel.com,
xiaoyao.li@intel.com, sohil.mehta@intel.com,
adrian.hunter@intel.com, kishen.maloor@intel.com,
tony.lindgren@linux.intel.com, peter.fang@intel.com,
baolu.lu@linux.intel.com, zhenzhong.duan@intel.com,
dave.hansen@intel.com, dave.hansen@linux.intel.com,
seanjc@google.com
Subject: [PATCH v2 16/17] KVM: TDX: Add in-kernel Quote generation
Date: Thu, 18 Jun 2026 16:13:54 +0800 [thread overview]
Message-ID: <20260618081355.3253581-17-yilun.xu@linux.intel.com> (raw)
In-Reply-To: <20260618081355.3253581-1-yilun.xu@linux.intel.com>
From: Peter Fang <peter.fang@intel.com>
Provide an in-kernel path for Quote generation when handling
TDG.VP.VMCALL<GetQuote>, without requiring an exit to userspace.
Use the core TDX API for Quote generation when the Quoting extension is
available. For simplicity, KVM checks its availability once per guest
during initialization. KVM does not handle Quoting service disruptions
or switch between the in-kernel and userspace paths.
Update the KVM API and TDX documentation to describe this new Quoting
capability.
Signed-off-by: Peter Fang <peter.fang@intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
Documentation/arch/x86/tdx.rst | 19 ++---
Documentation/virt/kvm/api.rst | 3 +
arch/x86/include/asm/tdx.h | 9 +++
arch/x86/kvm/vmx/tdx.h | 6 ++
arch/x86/kvm/vmx/tdx.c | 135 ++++++++++++++++++++++++++++++++-
virt/kvm/kvm_main.c | 1 +
6 files changed, 163 insertions(+), 10 deletions(-)
diff --git a/Documentation/arch/x86/tdx.rst b/Documentation/arch/x86/tdx.rst
index 3303499ad4c6..f02bb6919d91 100644
--- a/Documentation/arch/x86/tdx.rst
+++ b/Documentation/arch/x86/tdx.rst
@@ -522,15 +522,16 @@ provided by attestation service so the TDREPORT can be verified uniquely.
More details about the TDREPORT can be found in Intel TDX Module
specification, section titled "TDG.MR.REPORT Leaf".
-After getting the TDREPORT, the second step of the attestation process
-is to send it to the Quoting Enclave (QE) to generate the Quote. TDREPORT
-by design can only be verified on the local platform as the MAC key is
-bound to the platform. To support remote verification of the TDREPORT,
-TDX leverages Intel SGX Quoting Enclave to verify the TDREPORT locally
-and convert it to a remotely verifiable Quote. Method of sending TDREPORT
-to QE is implementation specific. Attestation software can choose
-whatever communication channel available (i.e. vsock or TCP/IP) to
-send the TDREPORT to QE and receive the Quote.
+After getting the TDREPORT, the second step of the attestation process is to
+convert it to a Quote. A TDREPORT by design can only be verified on the local
+platform, as the MAC key is bound to the platform. A Quote makes the TDREPORT
+remotely verifiable. It can be generated either through a Quoting Enclave
+(QE) in userspace or through the Quoting service in kernel space. In
+userspace, the Intel SGX Quoting Enclave verifies the TDREPORT locally and
+converts it to a Quote. The method of sending the TDREPORT to the QE and
+receiving the Quote is implementation-specific. If the TDX module supports the
+Quoting service, the kernel can convert a TDREPORT to a Quote directly through
+a SEAMCALL. In this case, the Quote is generated entirely by the TDX module.
References
==========
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 52bbbb553ce1..4a3b69b2e602 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -7335,6 +7335,9 @@ inputs and outputs of the TDVMCALL. Currently the following values of
queued successfully, the TDX guest can poll the status field in the
shared-memory area to check whether the Quote generation is completed or
not. When completed, the generated Quote is returned via the same buffer.
+ If the host kernel generates Quotes through the Quoting service provided by
+ the TDX module, KVM processes the GetQuote request and it will not appear in
+ userspace.
* ``TDVMCALL_GET_TD_VM_CALL_INFO``: the guest has requested the support
status of TDVMCALLs. The output values for the given leaf should be
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 24bce7512de3..b9a24104415c 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -86,6 +86,15 @@ struct tdx_quote_req {
u8 data[];
};
+#define TDX_QUOTE_REQ_HDR_SIZE (offsetof(struct tdx_quote_req, data))
+
+/*
+ * TDG.VP.VMCALL<GetQuote> Status Codes
+ */
+#define TDX_QUOTE_STATUS_SUCCESS 0x0000000000000000ULL
+#define TDX_QUOTE_STATUS_ERROR 0x8000000000000000ULL
+#define TDX_QUOTE_STATUS_UNAVAILABLE 0x8000000000000001ULL
+
#ifdef CONFIG_INTEL_TDX_GUEST
void __init tdx_early_init(void);
diff --git a/arch/x86/kvm/vmx/tdx.h b/arch/x86/kvm/vmx/tdx.h
index ac8323a68b16..5e4b3aee0577 100644
--- a/arch/x86/kvm/vmx/tdx.h
+++ b/arch/x86/kvm/vmx/tdx.h
@@ -47,6 +47,12 @@ struct kvm_tdx {
* Set/unset is protected with kvm->mmu_lock.
*/
bool wait_for_sept_zap;
+
+ /*
+ * Whether to get the quote directly in kernel, without exiting to
+ * userspace.
+ */
+ bool get_quote_in_kernel;
};
/* TDX module vCPU states */
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index 9f7c39e0d4b5..20558b0185b6 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -1538,11 +1538,133 @@ static int tdx_get_quote_user(struct kvm_vcpu *vcpu, u64 gpa, u64 size)
return 0;
}
+static bool write_quote_status_to_guest(struct kvm_vcpu *vcpu, u64 status,
+ gpa_t gpa)
+{
+ if (kvm_vcpu_write_guest(vcpu,
+ gpa + offsetof(struct tdx_quote_req, status),
+ &status, sizeof(status)))
+ return false;
+
+ return true;
+}
+
+static bool write_quote_to_guest(struct kvm_vcpu *vcpu, void *quote_data,
+ u32 quote_len, gpa_t gpa)
+{
+ if (kvm_vcpu_write_guest(vcpu,
+ gpa + TDX_QUOTE_REQ_HDR_SIZE,
+ quote_data, quote_len))
+ return false;
+
+ if (kvm_vcpu_write_guest(vcpu,
+ gpa + offsetof(struct tdx_quote_req, out_len),
+ "e_len, sizeof(quote_len)))
+ return false;
+
+ return true;
+}
+
+static u64 get_quote_kernel(struct kvm_vcpu *vcpu, struct tdx_quote_req *req,
+ gpa_t req_gpa, size_t total_len)
+{
+ struct tdx_td *td = &to_kvm_tdx(vcpu->kvm)->td;
+
+ /* Only support version 1 as defined in the GHCI spec */
+ if (req->version != 1)
+ return TDX_QUOTE_STATUS_ERROR;
+
+ /* Header + input data must fit in the page read from guest memory */
+ if ((size_t)req->in_len + TDX_QUOTE_REQ_HDR_SIZE > PAGE_SIZE)
+ return TDX_QUOTE_STATUS_ERROR;
+
+ /* Caller owns the requested quote */
+ void *quote_data __free(kvfree) =
+ tdx_quote_generate(td, req->data, req->in_len, &req->out_len);
+
+ if (!quote_data)
+ return TDX_QUOTE_STATUS_UNAVAILABLE;
+
+ if ((size_t)req->out_len + TDX_QUOTE_REQ_HDR_SIZE > total_len)
+ return TDX_QUOTE_STATUS_ERROR;
+
+ if (!write_quote_to_guest(vcpu, quote_data, req->out_len, req_gpa))
+ return TDX_QUOTE_STATUS_ERROR;
+
+ return TDX_QUOTE_STATUS_SUCCESS;
+}
+
+static u64 tdx_get_quote_check_args(struct kvm_vcpu *vcpu, u64 gpa, u64 size)
+{
+ gfn_t gfn_start, gfn_end;
+ u64 end;
+
+ if (!size)
+ return TDVMCALL_STATUS_INVALID_OPERAND;
+
+ if (!PAGE_ALIGNED(gpa) || !PAGE_ALIGNED(size))
+ return TDVMCALL_STATUS_ALIGN_ERROR;
+
+ if (check_add_overflow(gpa, size, &end))
+ return TDVMCALL_STATUS_INVALID_OPERAND;
+
+ gfn_start = gpa_to_gfn(gpa);
+ gfn_end = gpa_to_gfn(end);
+
+ /*
+ * Reject if the guest didn't explicitly convert its quote pages to
+ * shared.
+ */
+ if (!kvm_range_has_memory_attributes(vcpu->kvm, gfn_start, gfn_end,
+ KVM_MEMORY_ATTRIBUTE_PRIVATE, 0))
+ return TDVMCALL_STATUS_INVALID_OPERAND;
+
+ return TDVMCALL_STATUS_SUCCESS;
+}
+
+static int tdx_get_quote_kernel(struct kvm_vcpu *vcpu, u64 gpa, u64 size)
+{
+ void *first_page = NULL;
+ u64 err, qerr;
+
+ err = tdx_get_quote_check_args(vcpu, gpa, size);
+ if (err != TDVMCALL_STATUS_SUCCESS)
+ goto out;
+
+ err = TDVMCALL_STATUS_INVALID_OPERAND;
+
+ first_page = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!first_page)
+ goto out;
+
+ /*
+ * Read the first GetQuote page for its header + input data. The check
+ * above ensures that this GetQuote message is at least one page in
+ * size. in_data spanning more than a page is not supported.
+ */
+ if (kvm_vcpu_read_guest(vcpu, gpa, first_page, PAGE_SIZE))
+ goto out;
+
+ qerr = get_quote_kernel(vcpu, first_page, (gpa_t)gpa, size);
+
+ if (write_quote_status_to_guest(vcpu, qerr, (gpa_t)gpa) &&
+ qerr == TDX_QUOTE_STATUS_SUCCESS)
+ err = TDVMCALL_STATUS_SUCCESS;
+
+out:
+ kfree(first_page);
+ tdvmcall_set_return_code(vcpu, err);
+
+ return 1;
+}
+
static int tdx_get_quote(struct kvm_vcpu *vcpu)
{
+ struct kvm_tdx *kvm_tdx = to_kvm_tdx(vcpu->kvm);
struct vcpu_tdx *tdx = to_tdx(vcpu);
u64 gpa = tdx->vp_enter_args.r12;
u64 size = tdx->vp_enter_args.r13;
+ int ret;
/* The gpa of buffer must have shared bit set. */
if (vt_is_tdx_private_gpa(vcpu->kvm, gpa)) {
@@ -1552,7 +1674,12 @@ static int tdx_get_quote(struct kvm_vcpu *vcpu)
gpa &= ~gfn_to_gpa(kvm_gfn_direct_bits(vcpu->kvm));
- return tdx_get_quote_user(vcpu, gpa, size);
+ if (kvm_tdx->get_quote_in_kernel)
+ ret = tdx_get_quote_kernel(vcpu, gpa, size);
+ else
+ ret = tdx_get_quote_user(vcpu, gpa, size);
+
+ return ret;
}
static int tdx_setup_event_notify_interrupt(struct kvm_vcpu *vcpu)
@@ -2751,6 +2878,12 @@ static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd)
else
kvm->arch.gfn_direct_bits = TDX_SHARED_BIT_PWL_4;
+ /*
+ * Check only once at TD creation. Switching between userspace and
+ * in-kernel quoting is not supported.
+ */
+ kvm_tdx->get_quote_in_kernel = tdx_quote_enabled();
+
kvm_tdx->state = TD_STATE_INITIALIZED;
out:
/* kfree() accepts NULL. */
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 89489996fbc1..599f88a13071 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -2461,6 +2461,7 @@ bool kvm_range_has_memory_attributes(struct kvm *kvm, gfn_t start, gfn_t end,
return true;
}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_range_has_memory_attributes);
static __always_inline void kvm_handle_gfn_range(struct kvm *kvm,
struct kvm_mmu_notifier_range *range)
--
2.25.1
next prev parent reply other threads:[~2026-06-18 8:40 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-18 8:13 [PATCH v2 00/17] Enable DICE-based TDX Quoting Extension Xu Yilun
2026-06-18 8:13 ` [PATCH v2 01/17] x86/virt/tdx: Embed version info in SEAMCALL leaf function definitions Xu Yilun
2026-06-18 14:45 ` Dave Hansen
2026-06-18 8:13 ` [PATCH v2 02/17] x86/virt/tdx: Configure add-on features on TDX module init and update Xu Yilun
2026-06-18 15:04 ` Dave Hansen
2026-06-18 8:13 ` [PATCH v2 03/17] x86/virt/tdx: Detect if the extensions initialization is required Xu Yilun
2026-06-18 8:13 ` [PATCH v2 04/17] x86/virt/tdx: Add extra memory to TDX module for the extensions Xu Yilun
2026-06-18 8:13 ` [PATCH v2 05/17] x86/virt/tdx: Make TDX module initialize " Xu Yilun
2026-06-18 8:13 ` [PATCH v2 06/17] x86/virt/tdx: Re-initialize the extensions on runtime TDX module update Xu Yilun
2026-06-18 8:13 ` [PATCH v2 07/17] x86/virt/tdx: Initialize Quoting extension Xu Yilun
2026-06-18 8:13 ` [PATCH v2 08/17] x86/virt/tdx: Prepare Quote buffer during extension bringup Xu Yilun
2026-06-18 8:13 ` [PATCH v2 09/17] x86/virt/tdx: Add interface to check Quoting availability Xu Yilun
2026-06-18 8:13 ` [PATCH v2 10/17] x86/virt/tdx: Move tdx_tdr_pa() up in the file Xu Yilun
2026-06-18 8:13 ` [PATCH v2 11/17] x86/virt/tdx: Add interface to generate a Quote Xu Yilun
2026-06-18 8:13 ` [PATCH v2 12/17] x86/virt/tdx: Reinitialize the Quoting extension after TDX module update Xu Yilun
2026-06-18 8:13 ` [PATCH v2 13/17] x86/virt/tdx: Enable Quoting extension Xu Yilun
2026-06-18 8:13 ` [PATCH v2 14/17] x86/tdx: Move and rename Quote request structure Xu Yilun
2026-06-18 8:13 ` [PATCH v2 15/17] KVM: TDX: Factor out userspace return path from tdx_get_quote() Xu Yilun
2026-06-18 8:13 ` Xu Yilun [this message]
2026-06-18 8:13 ` [PATCH v2 17/17] KVM: TDX: Support event-notify interrupts only with userspace Quoting Xu Yilun
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260618081355.3253581-17-yilun.xu@linux.intel.com \
--to=yilun.xu@linux.intel.com \
--cc=adrian.hunter@intel.com \
--cc=baolu.lu@linux.intel.com \
--cc=dave.hansen@intel.com \
--cc=dave.hansen@linux.intel.com \
--cc=djbw@kernel.org \
--cc=kas@kernel.org \
--cc=kishen.maloor@intel.com \
--cc=kvm@vger.kernel.org \
--cc=linux-coco@lists.linux.dev \
--cc=linux-kernel@vger.kernel.org \
--cc=peter.fang@intel.com \
--cc=rick.p.edgecombe@intel.com \
--cc=seanjc@google.com \
--cc=sohil.mehta@intel.com \
--cc=tony.lindgren@linux.intel.com \
--cc=x86@kernel.org \
--cc=xiaoyao.li@intel.com \
--cc=yilun.xu@intel.com \
--cc=zhenzhong.duan@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox