@@ -42,6 +42,17 @@ ABI. However, in the future, if the TDX Module supports more than one subtype,
a new IOCTL CMD will be created to handle it. To keep the IOCTL naming
consistent, a subtype index is added as part of the IOCTL CMD.
+2.2 TDX_CMD_GET_QUOTE
+----------------------
+
+:Input parameters: struct tdx_quote_req
+:Output: Return 0 on success, -EIO on TDCALL failure or standard error number
+ on common failures. Upon successful execution, Quote data is copied
+ to tdx_quote_req.buf.
+
+The TDX_CMD_GET_QUOTE IOCTL can be used by attestation software to generate
+Quote for the given TDREPORT using TDG.VP.VMCALL<GetQuote> hypercall.
+
Reference
---------
@@ -33,6 +33,7 @@
#define TDVMCALL_MAP_GPA 0x10001
#define TDVMCALL_REPORT_FATAL_ERROR 0x10003
#define TDVMCALL_SETUP_NOTIFY_INTR 0x10004
+#define TDVMCALL_GET_QUOTE 0x10002
/* MMIO direction */
#define EPT_READ 0
@@ -198,6 +199,40 @@ static void __noreturn tdx_panic(const char *msg)
__tdx_hypercall(&args);
}
+/**
+ * tdx_hcall_get_quote() - Wrapper to request TD Quote using GetQuote
+ * hypercall.
+ * @buf: Address of the directly mapped shared kernel buffer which
+ * contains TDREPORT data. The same buffer will be used by
+ * VMM to store the generated TD Quote output.
+ * @size: size of the tdquote buffer (4KB-aligned).
+ *
+ * Refer to section titled "TDG.VP.VMCALL<GetQuote>" in the TDX GHCI
+ * v1.0 specification for more information on GetQuote hypercall.
+ * It is used in the TDX guest driver module to get the TD Quote.
+ *
+ * Return 0 on success or error code on failure.
+ */
+int tdx_hcall_get_quote(u8 *buf, size_t size)
+{
+ struct tdx_hypercall_args args = {0};
+
+ args.r10 = TDX_HYPERCALL_STANDARD;
+ args.r11 = TDVMCALL_GET_QUOTE;
+ /* Since buf is a shared memory, set the shared (decrypted) bits */
+ args.r12 = cc_mkdec(virt_to_phys(buf));
+ args.r13 = size;
+
+ /*
+ * Pass the physical address of TDREPORT to the VMM and
+ * trigger the Quote generation. It is not a blocking
+ * call, hence completion of this request will be notified to
+ * the TD guest via a callback interrupt.
+ */
+ return __tdx_hypercall(&args);
+}
+EXPORT_SYMBOL_GPL(tdx_hcall_get_quote);
+
static void tdx_parse_tdinfo(u64 *cc_mask)
{
struct tdx_module_output out;
@@ -75,6 +75,8 @@ int tdx_register_event_irq_cb(tdx_event_irq_cb_t handler, void *data);
int tdx_unregister_event_irq_cb(tdx_event_irq_cb_t handler, void *data);
+int tdx_hcall_get_quote(u8 *buf, size_t size);
+
#else
static inline void tdx_early_init(void) { };
@@ -12,12 +12,106 @@
#include <linux/mod_devicetable.h>
#include <linux/string.h>
#include <linux/uaccess.h>
+#include <linux/set_memory.h>
#include <uapi/linux/tdx-guest.h>
#include <asm/cpu_device_id.h>
#include <asm/tdx.h>
+/*
+ * Intel's SGX QE implementation generally uses Quote size less
+ * than 8K; Use 16K as MAX size to handle future updates and other
+ * 3rd party implementations.
+ */
+#define GET_QUOTE_MAX_SIZE (4 * PAGE_SIZE)
+
+/**
+ * struct quote_entry - Quote request struct
+ * @valid: Flag to check validity of the GetQuote request.
+ * @buf: Kernel buffer to share data with VMM (size is page aligned).
+ * @buf_len: Size of the buf in bytes.
+ * @compl: Completion object to track completion of GetQuote request.
+ */
+struct quote_entry {
+ bool valid;
+ void *buf;
+ size_t buf_len;
+ struct completion compl;
+};
+
+/* Quote data entry */
+static struct quote_entry *qentry;
+
+/* Lock to streamline quote requests */
+static DEFINE_MUTEX(quote_lock);
+
+static int quote_cb_handler(void *dev_id)
+{
+ struct quote_entry *entry = dev_id;
+ struct tdx_quote_buf *quote_buf = entry->buf;
+
+ if (entry->valid && quote_buf->status != GET_QUOTE_IN_FLIGHT)
+ complete(&entry->compl);
+
+ return 0;
+}
+
+static void free_shared_pages(void *buf, size_t len)
+{
+ unsigned int count = PAGE_ALIGN(len) >> PAGE_SHIFT;
+
+ set_memory_encrypted((unsigned long)buf, count);
+
+ free_pages_exact(buf, PAGE_ALIGN(len));
+}
+
+static void *alloc_shared_pages(size_t len)
+{
+ unsigned int count = PAGE_ALIGN(len) >> PAGE_SHIFT;
+ void *addr;
+ int ret;
+
+ addr = alloc_pages_exact(len, GFP_KERNEL);
+ if (!addr)
+ return NULL;
+
+ ret = set_memory_decrypted((unsigned long)addr, count);
+ if (ret) {
+ free_pages_exact(addr, PAGE_ALIGN(len));
+ return NULL;
+ }
+
+ return addr;
+}
+
+static struct quote_entry *alloc_quote_entry(size_t len)
+{
+ struct quote_entry *entry = NULL;
+
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return NULL;
+
+ entry->buf = alloc_shared_pages(len);
+ if (!entry->buf) {
+ kfree(entry);
+ return NULL;
+ }
+
+ entry->buf_len = PAGE_ALIGN(len);
+ init_completion(&entry->compl);
+ entry->valid = false;
+
+ return entry;
+}
+
+static void free_quote_entry(struct quote_entry *entry)
+{
+ free_shared_pages(entry->buf, entry->buf_len);
+ kfree(entry);
+}
+
static long tdx_get_report0(struct tdx_report_req __user *req)
{
u8 *reportdata, *tdreport;
@@ -53,12 +147,65 @@ static long tdx_get_report0(struct tdx_report_req __user *req)
return ret;
}
+static long tdx_get_quote(struct tdx_quote_req __user *ureq)
+{
+ struct tdx_quote_req req;
+ long ret;
+
+ if (copy_from_user(&req, ureq, sizeof(req)))
+ return -EFAULT;
+
+ mutex_lock("e_lock);
+
+ if (!req.len || req.len > qentry->buf_len) {
+ ret = -EINVAL;
+ goto quote_failed;
+ }
+
+ memset(qentry->buf, 0, qentry->buf_len);
+ reinit_completion(&qentry->compl);
+ qentry->valid = true;
+
+ if (copy_from_user(qentry->buf, (void __user *)req.buf, req.len)) {
+ ret = -EFAULT;
+ goto quote_failed;
+ }
+
+ /* Submit GetQuote Request using GetQuote hypercall */
+ ret = tdx_hcall_get_quote(qentry->buf, qentry->buf_len);
+ if (ret) {
+ pr_err("GetQuote hypercall failed, status:%lx\n", ret);
+ ret = -EIO;
+ goto quote_failed;
+ }
+
+ /*
+ * Although the GHCI specification does not state explicitly that
+ * the VMM must not wait indefinitely for the Quote request to be
+ * completed, a sane VMM should always notify the guest after a
+ * certain time, regardless of whether the Quote generation is
+ * successful or not. For now just assume the VMM will do so.
+ */
+ wait_for_completion(&qentry->compl);
+
+ if (copy_to_user((void __user *)req.buf, qentry->buf, req.len))
+ ret = -EFAULT;
+
+quote_failed:
+ qentry->valid = false;
+ mutex_unlock("e_lock);
+
+ return ret;
+}
+
static long tdx_guest_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
case TDX_CMD_GET_REPORT0:
return tdx_get_report0((struct tdx_report_req __user *)arg);
+ case TDX_CMD_GET_QUOTE:
+ return tdx_get_quote((struct tdx_quote_req *)arg);
default:
return -ENOTTY;
}
@@ -84,15 +231,41 @@ MODULE_DEVICE_TABLE(x86cpu, tdx_guest_ids);
static int __init tdx_guest_init(void)
{
+ int ret;
+
if (!x86_match_cpu(tdx_guest_ids))
return -ENODEV;
- return misc_register(&tdx_misc_dev);
+ ret = misc_register(&tdx_misc_dev);
+ if (ret)
+ return ret;
+
+ qentry = alloc_quote_entry(GET_QUOTE_MAX_SIZE);
+ if (!qentry) {
+ pr_err("Failed to allocate Quote buffer\n");
+ ret = -ENOMEM;
+ goto free_misc;
+ }
+
+ ret = tdx_register_event_irq_cb(quote_cb_handler, qentry);
+ if (ret)
+ goto free_quote;
+
+ return 0;
+
+free_quote:
+ free_quote_entry(qentry);
+free_misc:
+ misc_deregister(&tdx_misc_dev);
+
+ return ret;
}
module_init(tdx_guest_init);
static void __exit tdx_guest_exit(void)
{
+ tdx_unregister_event_irq_cb(quote_cb_handler, qentry);
+ free_quote_entry(qentry);
misc_deregister(&tdx_misc_dev);
}
module_exit(tdx_guest_exit);
@@ -17,6 +17,12 @@
/* Length of TDREPORT used in TDG.MR.REPORT TDCALL */
#define TDX_REPORT_LEN 1024
+/* TD Quote status codes */
+#define GET_QUOTE_SUCCESS 0
+#define GET_QUOTE_IN_FLIGHT 0xffffffffffffffff
+#define GET_QUOTE_ERROR 0x8000000000000000
+#define GET_QUOTE_SERVICE_UNAVAILABLE 0x8000000000000001
+
/**
* struct tdx_report_req - Request struct for TDX_CMD_GET_REPORT0 IOCTL.
*
@@ -30,6 +36,36 @@ struct tdx_report_req {
__u8 tdreport[TDX_REPORT_LEN];
};
+/* struct tdx_quote_buf: Format of Quote request buffer.
+ * @version: Quote format version, filled by TD.
+ * @status: Status code of Quote request, filled by VMM.
+ * @in_len: Length of TDREPORT, filled by TD.
+ * @out_len: Length of Quote data, filled by VMM.
+ * @data: Quote data on output or TDREPORT on input.
+ *
+ * More details of Quote request buffer can be found in TDX
+ * Guest-Host Communication Interface (GHCI) for Intel TDX 1.0,
+ * section titled "TDG.VP.VMCALL<GetQuote>"
+ */
+struct tdx_quote_buf {
+ __u64 version;
+ __u64 status;
+ __u32 in_len;
+ __u32 out_len;
+ __u64 data[];
+};
+
+/* struct tdx_quote_req: Request struct for TDX_CMD_GET_QUOTE IOCTL.
+ * @buf: Address of user buffer in the format of struct tdx_quote_buf.
+ * Upon successful completion of IOCTL, output is copied back to
+ * the same buffer (in struct tdx_quote_buf.data).
+ * @len: Length of the Quote buffer.
+ */
+struct tdx_quote_req {
+ __u64 buf;
+ __u64 len;
+};
+
/*
* TDX_CMD_GET_REPORT0 - Get TDREPORT0 (a.k.a. TDREPORT subtype 0) using
* TDCALL[TDG.MR.REPORT]
@@ -39,4 +75,12 @@ struct tdx_report_req {
*/
#define TDX_CMD_GET_REPORT0 _IOWR('T', 1, struct tdx_report_req)
+/*
+ * TDX_CMD_GET_QUOTE - Get TD Guest Quote from QE/QGS using GetQuote
+ * TDVMCALL.
+ *
+ * Returns 0 on success or standard errno on other failures.
+ */
+#define TDX_CMD_GET_QUOTE _IOWR('T', 2, struct tdx_quote_req)
+
#endif /* _UAPI_LINUX_TDX_GUEST_H_ */