Skip to content

Commit f4738f5

Browse files
Kuppuswamy Sathyanarayanandjbw
authored andcommitted
virt: tdx-guest: Add Quote generation support using TSM_REPORTS
In TDX guest, the attestation process is used to verify the TDX guest trustworthiness to other entities before provisioning secrets to the guest. The first step in the attestation process is TDREPORT generation, which involves getting the guest measurement data in the format of TDREPORT, which is further used to validate the authenticity of the TDX guest. TDREPORT by design is integrity-protected and can only be verified on the local machine. To support remote verification of the TDREPORT in a SGX-based attestation, the TDREPORT needs to be sent to the SGX Quoting Enclave (QE) to convert it to a remotely verifiable Quote. SGX QE by design can only run outside of the TDX guest (i.e. in a host process or in a normal VM) and guest can use communication channels like vsock or TCP/IP to send the TDREPORT to the QE. But for security concerns, the TDX guest may not support these communication channels. To handle such cases, TDX defines a GetQuote hypercall which can be used by the guest to request the host VMM to communicate with the SGX QE. More details about GetQuote hypercall can be found in TDX Guest-Host Communication Interface (GHCI) for Intel TDX 1.0, section titled "TDG.VP.VMCALL<GetQuote>". Trusted Security Module (TSM) [1] exposes a common ABI for Confidential Computing Guest platforms to get the measurement data via ConfigFS. Extend the TSM framework and add support to allow an attestation agent to get the TDX Quote data (included usage example below). report=/sys/kernel/config/tsm/report/report0 mkdir $report dd if=/dev/urandom bs=64 count=1 > $report/inblob hexdump -C $report/outblob rmdir $report GetQuote TDVMCALL requires TD guest pass a 4K aligned shared buffer with TDREPORT data as input, which is further used by the VMM to copy the TD Quote result after successful Quote generation. To create the shared buffer, allocate a large enough memory and mark it shared using set_memory_decrypted() in tdx_guest_init(). This buffer will be re-used for GetQuote requests in the TDX TSM handler. Although this method reserves a fixed chunk of memory for GetQuote requests, such one time allocation can help avoid memory fragmentation related allocation failures later in the uptime of the guest. Since the Quote generation process is not time-critical or frequently used, the current version uses a polling model for Quote requests and it also does not support parallel GetQuote requests. Link: https://lore.kernel.org/lkml/169342399185.3934343.3035845348326944519.stgit@dwillia2-xfh.jf.intel.com/ [1] Signed-off-by: Kuppuswamy Sathyanarayanan <[email protected]> Reviewed-by: Erdem Aktas <[email protected]> Tested-by: Kuppuswamy Sathyanarayanan <[email protected]> Tested-by: Peter Gonda <[email protected]> Reviewed-by: Tom Lendacky <[email protected]> Signed-off-by: Dan Williams <[email protected]>
1 parent f479067 commit f4738f5

File tree

5 files changed

+253
-1
lines changed

5 files changed

+253
-1
lines changed

arch/x86/coco/tdx/tdx.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,27 @@ int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport)
104104
}
105105
EXPORT_SYMBOL_GPL(tdx_mcall_get_report0);
106106

107+
/**
108+
* tdx_hcall_get_quote() - Wrapper to request TD Quote using GetQuote
109+
* hypercall.
110+
* @buf: Address of the directly mapped shared kernel buffer which
111+
* contains TDREPORT. The same buffer will be used by VMM to
112+
* store the generated TD Quote output.
113+
* @size: size of the tdquote buffer (4KB-aligned).
114+
*
115+
* Refer to section titled "TDG.VP.VMCALL<GetQuote>" in the TDX GHCI
116+
* v1.0 specification for more information on GetQuote hypercall.
117+
* It is used in the TDX guest driver module to get the TD Quote.
118+
*
119+
* Return 0 on success or error code on failure.
120+
*/
121+
u64 tdx_hcall_get_quote(u8 *buf, size_t size)
122+
{
123+
/* Since buf is a shared memory, set the shared (decrypted) bits */
124+
return _tdx_hypercall(TDVMCALL_GET_QUOTE, cc_mkdec(virt_to_phys(buf)), size, 0, 0);
125+
}
126+
EXPORT_SYMBOL_GPL(tdx_hcall_get_quote);
127+
107128
static void __noreturn tdx_panic(const char *msg)
108129
{
109130
struct tdx_hypercall_args args = {

arch/x86/include/asm/shared/tdx.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
/* TDX hypercall Leaf IDs */
2424
#define TDVMCALL_MAP_GPA 0x10001
25+
#define TDVMCALL_GET_QUOTE 0x10002
2526
#define TDVMCALL_REPORT_FATAL_ERROR 0x10003
2627

2728
#ifndef __ASSEMBLY__

arch/x86/include/asm/tdx.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ bool tdx_early_handle_ve(struct pt_regs *regs);
5252

5353
int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport);
5454

55+
u64 tdx_hcall_get_quote(u8 *buf, size_t size);
56+
5557
#else
5658

5759
static inline void tdx_early_init(void) { };

drivers/virt/coco/tdx-guest/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
config TDX_GUEST_DRIVER
22
tristate "TDX Guest driver"
33
depends on INTEL_TDX_GUEST
4+
select TSM_REPORTS
45
help
56
The driver provides userspace interface to communicate with
67
the TDX module to request the TDX guest details like attestation

drivers/virt/coco/tdx-guest/tdx-guest.c

Lines changed: 228 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,60 @@
1212
#include <linux/mod_devicetable.h>
1313
#include <linux/string.h>
1414
#include <linux/uaccess.h>
15+
#include <linux/set_memory.h>
16+
#include <linux/io.h>
17+
#include <linux/delay.h>
18+
#include <linux/tsm.h>
19+
#include <linux/sizes.h>
1520

1621
#include <uapi/linux/tdx-guest.h>
1722

1823
#include <asm/cpu_device_id.h>
1924
#include <asm/tdx.h>
2025

26+
/*
27+
* Intel's SGX QE implementation generally uses Quote size less
28+
* than 8K (2K Quote data + ~5K of certificate blob).
29+
*/
30+
#define GET_QUOTE_BUF_SIZE SZ_8K
31+
32+
#define GET_QUOTE_CMD_VER 1
33+
34+
/* TDX GetQuote status codes */
35+
#define GET_QUOTE_SUCCESS 0
36+
#define GET_QUOTE_IN_FLIGHT 0xffffffffffffffff
37+
38+
/* struct tdx_quote_buf: Format of Quote request buffer.
39+
* @version: Quote format version, filled by TD.
40+
* @status: Status code of Quote request, filled by VMM.
41+
* @in_len: Length of TDREPORT, filled by TD.
42+
* @out_len: Length of Quote data, filled by VMM.
43+
* @data: Quote data on output or TDREPORT on input.
44+
*
45+
* More details of Quote request buffer can be found in TDX
46+
* Guest-Host Communication Interface (GHCI) for Intel TDX 1.0,
47+
* section titled "TDG.VP.VMCALL<GetQuote>"
48+
*/
49+
struct tdx_quote_buf {
50+
u64 version;
51+
u64 status;
52+
u32 in_len;
53+
u32 out_len;
54+
u8 data[];
55+
};
56+
57+
/* Quote data buffer */
58+
static void *quote_data;
59+
60+
/* Lock to streamline quote requests */
61+
static DEFINE_MUTEX(quote_lock);
62+
63+
/*
64+
* GetQuote request timeout in seconds. Expect that 30 seconds
65+
* is enough time for QE to respond to any Quote requests.
66+
*/
67+
static u32 getquote_timeout = 30;
68+
2169
static long tdx_get_report0(struct tdx_report_req __user *req)
2270
{
2371
u8 *reportdata, *tdreport;
@@ -53,6 +101,154 @@ static long tdx_get_report0(struct tdx_report_req __user *req)
53101
return ret;
54102
}
55103

104+
static void free_quote_buf(void *buf)
105+
{
106+
size_t len = PAGE_ALIGN(GET_QUOTE_BUF_SIZE);
107+
unsigned int count = len >> PAGE_SHIFT;
108+
109+
if (set_memory_encrypted((unsigned long)buf, count)) {
110+
pr_err("Failed to restore encryption mask for Quote buffer, leak it\n");
111+
return;
112+
}
113+
114+
free_pages_exact(buf, len);
115+
}
116+
117+
static void *alloc_quote_buf(void)
118+
{
119+
size_t len = PAGE_ALIGN(GET_QUOTE_BUF_SIZE);
120+
unsigned int count = len >> PAGE_SHIFT;
121+
void *addr;
122+
123+
addr = alloc_pages_exact(len, GFP_KERNEL | __GFP_ZERO);
124+
if (!addr)
125+
return NULL;
126+
127+
if (set_memory_decrypted((unsigned long)addr, count)) {
128+
free_pages_exact(addr, len);
129+
return NULL;
130+
}
131+
132+
return addr;
133+
}
134+
135+
/*
136+
* wait_for_quote_completion() - Wait for Quote request completion
137+
* @quote_buf: Address of Quote buffer.
138+
* @timeout: Timeout in seconds to wait for the Quote generation.
139+
*
140+
* As per TDX GHCI v1.0 specification, sec titled "TDG.VP.VMCALL<GetQuote>",
141+
* the status field in the Quote buffer will be set to GET_QUOTE_IN_FLIGHT
142+
* while VMM processes the GetQuote request, and will change it to success
143+
* or error code after processing is complete. So wait till the status
144+
* changes from GET_QUOTE_IN_FLIGHT or the request being timed out.
145+
*/
146+
static int wait_for_quote_completion(struct tdx_quote_buf *quote_buf, u32 timeout)
147+
{
148+
int i = 0;
149+
150+
/*
151+
* Quote requests usually take a few seconds to complete, so waking up
152+
* once per second to recheck the status is fine for this use case.
153+
*/
154+
while (quote_buf->status == GET_QUOTE_IN_FLIGHT && i++ < timeout) {
155+
if (msleep_interruptible(MSEC_PER_SEC))
156+
return -EINTR;
157+
}
158+
159+
return (i == timeout) ? -ETIMEDOUT : 0;
160+
}
161+
162+
static int tdx_report_new(struct tsm_report *report, void *data)
163+
{
164+
u8 *buf, *reportdata = NULL, *tdreport = NULL;
165+
struct tdx_quote_buf *quote_buf = quote_data;
166+
struct tsm_desc *desc = &report->desc;
167+
int ret;
168+
u64 err;
169+
170+
/* TODO: switch to guard(mutex_intr) */
171+
if (mutex_lock_interruptible(&quote_lock))
172+
return -EINTR;
173+
174+
/*
175+
* If the previous request is timedout or interrupted, and the
176+
* Quote buf status is still in GET_QUOTE_IN_FLIGHT (owned by
177+
* VMM), don't permit any new request.
178+
*/
179+
if (quote_buf->status == GET_QUOTE_IN_FLIGHT) {
180+
ret = -EBUSY;
181+
goto done;
182+
}
183+
184+
if (desc->inblob_len != TDX_REPORTDATA_LEN) {
185+
ret = -EINVAL;
186+
goto done;
187+
}
188+
189+
reportdata = kmalloc(TDX_REPORTDATA_LEN, GFP_KERNEL);
190+
if (!reportdata) {
191+
ret = -ENOMEM;
192+
goto done;
193+
}
194+
195+
tdreport = kzalloc(TDX_REPORT_LEN, GFP_KERNEL);
196+
if (!tdreport) {
197+
ret = -ENOMEM;
198+
goto done;
199+
}
200+
201+
memcpy(reportdata, desc->inblob, desc->inblob_len);
202+
203+
/* Generate TDREPORT0 using "TDG.MR.REPORT" TDCALL */
204+
ret = tdx_mcall_get_report0(reportdata, tdreport);
205+
if (ret) {
206+
pr_err("GetReport call failed\n");
207+
goto done;
208+
}
209+
210+
memset(quote_data, 0, GET_QUOTE_BUF_SIZE);
211+
212+
/* Update Quote buffer header */
213+
quote_buf->version = GET_QUOTE_CMD_VER;
214+
quote_buf->in_len = TDX_REPORT_LEN;
215+
216+
memcpy(quote_buf->data, tdreport, TDX_REPORT_LEN);
217+
218+
err = tdx_hcall_get_quote(quote_data, GET_QUOTE_BUF_SIZE);
219+
if (err) {
220+
pr_err("GetQuote hypercall failed, status:%llx\n", err);
221+
ret = -EIO;
222+
goto done;
223+
}
224+
225+
ret = wait_for_quote_completion(quote_buf, getquote_timeout);
226+
if (ret) {
227+
pr_err("GetQuote request timedout\n");
228+
goto done;
229+
}
230+
231+
buf = kvmemdup(quote_buf->data, quote_buf->out_len, GFP_KERNEL);
232+
if (!buf) {
233+
ret = -ENOMEM;
234+
goto done;
235+
}
236+
237+
report->outblob = buf;
238+
report->outblob_len = quote_buf->out_len;
239+
240+
/*
241+
* TODO: parse the PEM-formatted cert chain out of the quote buffer when
242+
* provided
243+
*/
244+
done:
245+
mutex_unlock(&quote_lock);
246+
kfree(reportdata);
247+
kfree(tdreport);
248+
249+
return ret;
250+
}
251+
56252
static long tdx_guest_ioctl(struct file *file, unsigned int cmd,
57253
unsigned long arg)
58254
{
@@ -82,17 +278,48 @@ static const struct x86_cpu_id tdx_guest_ids[] = {
82278
};
83279
MODULE_DEVICE_TABLE(x86cpu, tdx_guest_ids);
84280

281+
static const struct tsm_ops tdx_tsm_ops = {
282+
.name = KBUILD_MODNAME,
283+
.report_new = tdx_report_new,
284+
};
285+
85286
static int __init tdx_guest_init(void)
86287
{
288+
int ret;
289+
87290
if (!x86_match_cpu(tdx_guest_ids))
88291
return -ENODEV;
89292

90-
return misc_register(&tdx_misc_dev);
293+
ret = misc_register(&tdx_misc_dev);
294+
if (ret)
295+
return ret;
296+
297+
quote_data = alloc_quote_buf();
298+
if (!quote_data) {
299+
pr_err("Failed to allocate Quote buffer\n");
300+
ret = -ENOMEM;
301+
goto free_misc;
302+
}
303+
304+
ret = tsm_register(&tdx_tsm_ops, NULL, NULL);
305+
if (ret)
306+
goto free_quote;
307+
308+
return 0;
309+
310+
free_quote:
311+
free_quote_buf(quote_data);
312+
free_misc:
313+
misc_deregister(&tdx_misc_dev);
314+
315+
return ret;
91316
}
92317
module_init(tdx_guest_init);
93318

94319
static void __exit tdx_guest_exit(void)
95320
{
321+
tsm_unregister(&tdx_tsm_ops);
322+
free_quote_buf(quote_data);
96323
misc_deregister(&tdx_misc_dev);
97324
}
98325
module_exit(tdx_guest_exit);

0 commit comments

Comments
 (0)