Merge tag 'tsm-for-6.7' of git://git.kernel.org/pub/scm/linux/kernel/git/djbw/linux
authorLinus Torvalds <torvalds@linux-foundation.org>
Sun, 5 Nov 2023 01:58:13 +0000 (15:58 -1000)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sun, 5 Nov 2023 01:58:13 +0000 (15:58 -1000)
Pull unified attestation reporting from Dan Williams:
 "In an ideal world there would be a cross-vendor standard attestation
  report format for confidential guests along with a common device
  definition to act as the transport.

  In the real world the situation ended up with multiple platform
  vendors inventing their own attestation report formats with the
  SEV-SNP implementation being a first mover to define a custom
  sev-guest character device and corresponding ioctl(). Later, this
  configfs-tsm proposal intercepted an attempt to add a tdx-guest
  character device and a corresponding new ioctl(). It also anticipated
  ARM and RISC-V showing up with more chardevs and more ioctls().

  The proposal takes for granted that Linux tolerates the vendor report
  format differentiation until a standard arrives. From talking with
  folks involved, it sounds like that standardization work is unlikely
  to resolve anytime soon. It also takes the position that kernfs ABIs
  are easier to maintain than ioctl(). The result is a shared configfs
  mechanism to return per-vendor report-blobs with the option to later
  support a standard when that arrives.

  Part of the goal here also is to get the community into the
  "uncomfortable, but beneficial to the long term maintainability of the
  kernel" state of talking to each other about their differentiation and
  opportunities to collaborate. Think of this like the device-driver
  equivalent of the common memory-management infrastructure for
  confidential-computing being built up in KVM.

  As for establishing an "upstream path for cross-vendor
  confidential-computing device driver infrastructure" this is something
  I want to discuss at Plumbers. At present, the multiple vendor
  proposals for assigning devices to confidential computing VMs likely
  needs a new dedicated repository and maintainer team, but that is a
  discussion for v6.8.

  For now, Greg and Thomas have acked this approach and this is passing
  is AMD, Intel, and Google tests.

  Summary:

   - Introduce configfs-tsm as a shared ABI for confidential computing
     attestation reports

   - Convert sev-guest to additionally support configfs-tsm alongside
     its vendor specific ioctl()

   - Added signed attestation report retrieval to the tdx-guest driver
     forgoing a new vendor specific ioctl()

   - Misc cleanups and a new __free() annotation for kvfree()"

* tag 'tsm-for-6.7' of git://git.kernel.org/pub/scm/linux/kernel/git/djbw/linux:
  virt: tdx-guest: Add Quote generation support using TSM_REPORTS
  virt: sevguest: Add TSM_REPORTS support for SNP_GET_EXT_REPORT
  mm/slab: Add __free() support for kvfree
  virt: sevguest: Prep for kernel internal get_ext_report()
  configfs-tsm: Introduce a shared ABI for attestation reports
  virt: coco: Add a coco/Makefile and coco/Kconfig
  virt: sevguest: Fix passing a stack buffer as a scatterlist target

18 files changed:
Documentation/ABI/testing/configfs-tsm [new file with mode: 0644]
MAINTAINERS
arch/x86/coco/tdx/tdx.c
arch/x86/include/asm/shared/tdx.h
arch/x86/include/asm/tdx.h
drivers/virt/Kconfig
drivers/virt/Makefile
drivers/virt/coco/Kconfig [new file with mode: 0644]
drivers/virt/coco/Makefile [new file with mode: 0644]
drivers/virt/coco/sev-guest/Kconfig
drivers/virt/coco/sev-guest/sev-guest.c
drivers/virt/coco/tdx-guest/Kconfig
drivers/virt/coco/tdx-guest/tdx-guest.c
drivers/virt/coco/tsm.c [new file with mode: 0644]
include/linux/slab.h
include/linux/tsm.h [new file with mode: 0644]
include/uapi/linux/psp-sev.h
include/uapi/linux/sev-guest.h

diff --git a/Documentation/ABI/testing/configfs-tsm b/Documentation/ABI/testing/configfs-tsm
new file mode 100644 (file)
index 0000000..dd24202
--- /dev/null
@@ -0,0 +1,82 @@
+What:          /sys/kernel/config/tsm/report/$name/inblob
+Date:          September, 2023
+KernelVersion: v6.7
+Contact:       linux-coco@lists.linux.dev
+Description:
+               (WO) Up to 64 bytes of user specified binary data. For replay
+               protection this should include a nonce, but the kernel does not
+               place any restrictions on the content.
+
+What:          /sys/kernel/config/tsm/report/$name/outblob
+Date:          September, 2023
+KernelVersion: v6.7
+Contact:       linux-coco@lists.linux.dev
+Description:
+               (RO) Binary attestation report generated from @inblob and other
+               options The format of the report is implementation specific
+               where the implementation is conveyed via the @provider
+               attribute.
+
+What:          /sys/kernel/config/tsm/report/$name/auxblob
+Date:          October, 2023
+KernelVersion: v6.7
+Contact:       linux-coco@lists.linux.dev
+Description:
+               (RO) Optional supplemental data that a TSM may emit, visibility
+               of this attribute depends on TSM, and may be empty if no
+               auxiliary data is available.
+
+               When @provider is "sev_guest" this file contains the
+               "cert_table" from SEV-ES Guest-Hypervisor Communication Block
+               Standardization v2.03 Section 4.1.8.1 MSG_REPORT_REQ.
+               https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56421.pdf
+
+What:          /sys/kernel/config/tsm/report/$name/provider
+Date:          September, 2023
+KernelVersion: v6.7
+Contact:       linux-coco@lists.linux.dev
+Description:
+               (RO) A name for the format-specification of @outblob like
+               "sev_guest" [1] or "tdx_guest" [2] in the near term, or a
+               common standard format in the future.
+
+               [1]: SEV Secure Nested Paging Firmware ABI Specification
+               Revision 1.55 Table 22
+               https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf
+
+               [2]: IntelĀ® Trust Domain Extensions Data Center Attestation
+               Primitives : Quote Generation Library and Quote Verification
+               Library Revision 0.8 Appendix 4,5
+               https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_TDX_DCAP_Quoting_Library_API.pdf
+
+What:          /sys/kernel/config/tsm/report/$name/generation
+Date:          September, 2023
+KernelVersion: v6.7
+Contact:       linux-coco@lists.linux.dev
+Description:
+               (RO) The value in this attribute increments each time @inblob or
+               any option is written. Userspace can detect conflicts by
+               checking generation before writing to any attribute and making
+               sure the number of writes matches expectations after reading
+               @outblob, or it can prevent conflicts by creating a report
+               instance per requesting context.
+
+What:          /sys/kernel/config/tsm/report/$name/privlevel
+Date:          September, 2023
+KernelVersion: v6.7
+Contact:       linux-coco@lists.linux.dev
+Description:
+               (WO) Attribute is visible if a TSM implementation provider
+               supports the concept of attestation reports for TVMs running at
+               different privilege levels, like SEV-SNP "VMPL", specify the
+               privilege level via this attribute.  The minimum acceptable
+               value is conveyed via @privlevel_floor and the maximum
+               acceptable value is TSM_PRIVLEVEL_MAX (3).
+
+What:          /sys/kernel/config/tsm/report/$name/privlevel_floor
+Date:          September, 2023
+KernelVersion: v6.7
+Contact:       linux-coco@lists.linux.dev
+Description:
+               (RO) Indicates the minimum permissible value that can be written
+               to @privlevel.
index d42727703adeb51e552668622f8f88ef645cfcc9..796182c8e1702fdf8497a9810cd4c4da4d167c43 100644 (file)
@@ -22058,6 +22058,14 @@ W:     https://github.com/srcres258/linux-doc
 T:     git git://github.com/srcres258/linux-doc.git doc-zh-tw
 F:     Documentation/translations/zh_TW/
 
+TRUSTED SECURITY MODULE (TSM) ATTESTATION REPORTS
+M:     Dan Williams <dan.j.williams@intel.com>
+L:     linux-coco@lists.linux.dev
+S:     Maintained
+F:     Documentation/ABI/testing/configfs-tsm
+F:     drivers/virt/coco/tsm.c
+F:     include/linux/tsm.h
+
 TTY LAYER AND SERIAL DRIVERS
 M:     Greg Kroah-Hartman <gregkh@linuxfoundation.org>
 M:     Jiri Slaby <jirislaby@kernel.org>
index d11206ceff3b1668b474f227a7965bcfafc157f9..1b5d17a9f70dde9f711c53ea2db97762d73eadf3 100644 (file)
@@ -106,6 +106,27 @@ int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport)
 }
 EXPORT_SYMBOL_GPL(tdx_mcall_get_report0);
 
+/**
+ * tdx_hcall_get_quote() - Wrapper to request TD Quote using GetQuote
+ *                         hypercall.
+ * @buf: Address of the directly mapped shared kernel buffer which
+ *       contains TDREPORT. 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.
+ */
+u64 tdx_hcall_get_quote(u8 *buf, size_t size)
+{
+       /* Since buf is a shared memory, set the shared (decrypted) bits */
+       return _tdx_hypercall(TDVMCALL_GET_QUOTE, cc_mkdec(virt_to_phys(buf)), size, 0, 0);
+}
+EXPORT_SYMBOL_GPL(tdx_hcall_get_quote);
+
 static void __noreturn tdx_panic(const char *msg)
 {
        struct tdx_module_args args = {
index f74695dea2176b4cd76340b7f6f03fa430c78829..ccce7ebd8677287359c4de9271fe29ecf6793d31 100644 (file)
@@ -23,6 +23,7 @@
 
 /* TDX hypercall Leaf IDs */
 #define TDVMCALL_MAP_GPA               0x10001
+#define TDVMCALL_GET_QUOTE             0x10002
 #define TDVMCALL_REPORT_FATAL_ERROR    0x10003
 
 #define TDVMCALL_STATUS_RETRY          1
index adcbe3f1de30b78109b50a6be284e83cc836c22c..f3d5305a60fc50b13708d80ffecab0a529239ee0 100644 (file)
@@ -56,6 +56,8 @@ bool tdx_early_handle_ve(struct pt_regs *regs);
 
 int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport);
 
+u64 tdx_hcall_get_quote(u8 *buf, size_t size);
+
 #else
 
 static inline void tdx_early_init(void) { };
index f79ab13a5c28b0cd7a71d41ba8d3177914946351..40129b6f0eca41ced6c6213776571a5ae8374ebe 100644 (file)
@@ -48,10 +48,6 @@ source "drivers/virt/nitro_enclaves/Kconfig"
 
 source "drivers/virt/acrn/Kconfig"
 
-source "drivers/virt/coco/efi_secret/Kconfig"
-
-source "drivers/virt/coco/sev-guest/Kconfig"
-
-source "drivers/virt/coco/tdx-guest/Kconfig"
+source "drivers/virt/coco/Kconfig"
 
 endif
index e9aa6fc96fab7242a9963d9a834722af24e4b6a2..f29901bd782058d3552cdec2c2128ad47ce6fe27 100644 (file)
@@ -9,6 +9,4 @@ obj-y                           += vboxguest/
 
 obj-$(CONFIG_NITRO_ENCLAVES)   += nitro_enclaves/
 obj-$(CONFIG_ACRN_HSM)         += acrn/
-obj-$(CONFIG_EFI_SECRET)       += coco/efi_secret/
-obj-$(CONFIG_SEV_GUEST)                += coco/sev-guest/
-obj-$(CONFIG_INTEL_TDX_GUEST)  += coco/tdx-guest/
+obj-y                          += coco/
diff --git a/drivers/virt/coco/Kconfig b/drivers/virt/coco/Kconfig
new file mode 100644 (file)
index 0000000..87d142c
--- /dev/null
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Confidential computing related collateral
+#
+
+config TSM_REPORTS
+       select CONFIGFS_FS
+       tristate
+
+source "drivers/virt/coco/efi_secret/Kconfig"
+
+source "drivers/virt/coco/sev-guest/Kconfig"
+
+source "drivers/virt/coco/tdx-guest/Kconfig"
diff --git a/drivers/virt/coco/Makefile b/drivers/virt/coco/Makefile
new file mode 100644 (file)
index 0000000..18c1aba
--- /dev/null
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Confidential computing related collateral
+#
+obj-$(CONFIG_TSM_REPORTS)      += tsm.o
+obj-$(CONFIG_EFI_SECRET)       += efi_secret/
+obj-$(CONFIG_SEV_GUEST)                += sev-guest/
+obj-$(CONFIG_INTEL_TDX_GUEST)  += tdx-guest/
index da2d7ca531f0fb7cdcc7fbd9a1d660bb72ac950d..1cffc72c41cb1461020dff76db8b84ffb7527795 100644 (file)
@@ -5,6 +5,7 @@ config SEV_GUEST
        select CRYPTO
        select CRYPTO_AEAD2
        select CRYPTO_GCM
+       select TSM_REPORTS
        help
          SEV-SNP firmware provides the guest a mechanism to communicate with
          the PSP without risk from a malicious hypervisor who wishes to read,
index 97dbe715e96adfab478a255354f731c1dfdd55ee..bc564adcf499526aacdbc4fbc35e276f4cbdfee4 100644 (file)
 #include <linux/miscdevice.h>
 #include <linux/set_memory.h>
 #include <linux/fs.h>
+#include <linux/tsm.h>
 #include <crypto/aead.h>
 #include <linux/scatterlist.h>
 #include <linux/psp-sev.h>
+#include <linux/sockptr.h>
+#include <linux/cleanup.h>
+#include <linux/uuid.h>
 #include <uapi/linux/sev-guest.h>
 #include <uapi/linux/psp-sev.h>
 
@@ -57,6 +61,11 @@ struct snp_guest_dev {
 
        struct snp_secrets_page_layout *layout;
        struct snp_req_data input;
+       union {
+               struct snp_report_req report;
+               struct snp_derived_key_req derived_key;
+               struct snp_ext_report_req ext_report;
+       } req;
        u32 *os_area_msg_seqno;
        u8 *vmpck;
 };
@@ -470,11 +479,16 @@ static int handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code,
        return 0;
 }
 
+struct snp_req_resp {
+       sockptr_t req_data;
+       sockptr_t resp_data;
+};
+
 static int get_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg)
 {
        struct snp_guest_crypto *crypto = snp_dev->crypto;
+       struct snp_report_req *req = &snp_dev->req.report;
        struct snp_report_resp *resp;
-       struct snp_report_req req;
        int rc, resp_len;
 
        lockdep_assert_held(&snp_cmd_mutex);
@@ -482,7 +496,7 @@ static int get_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_io
        if (!arg->req_data || !arg->resp_data)
                return -EINVAL;
 
-       if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req)))
+       if (copy_from_user(req, (void __user *)arg->req_data, sizeof(*req)))
                return -EFAULT;
 
        /*
@@ -496,7 +510,7 @@ static int get_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_io
                return -ENOMEM;
 
        rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg,
-                                 SNP_MSG_REPORT_REQ, &req, sizeof(req), resp->data,
+                                 SNP_MSG_REPORT_REQ, req, sizeof(*req), resp->data,
                                  resp_len);
        if (rc)
                goto e_free;
@@ -511,9 +525,9 @@ e_free:
 
 static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg)
 {
+       struct snp_derived_key_req *req = &snp_dev->req.derived_key;
        struct snp_guest_crypto *crypto = snp_dev->crypto;
        struct snp_derived_key_resp resp = {0};
-       struct snp_derived_key_req req;
        int rc, resp_len;
        /* Response data is 64 bytes and max authsize for GCM is 16 bytes. */
        u8 buf[64 + 16];
@@ -532,11 +546,11 @@ static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_reque
        if (sizeof(buf) < resp_len)
                return -ENOMEM;
 
-       if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req)))
+       if (copy_from_user(req, (void __user *)arg->req_data, sizeof(*req)))
                return -EFAULT;
 
        rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg,
-                                 SNP_MSG_KEY_REQ, &req, sizeof(req), buf, resp_len);
+                                 SNP_MSG_KEY_REQ, req, sizeof(*req), buf, resp_len);
        if (rc)
                return rc;
 
@@ -550,31 +564,39 @@ static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_reque
        return rc;
 }
 
-static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg)
+static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg,
+                         struct snp_req_resp *io)
+
 {
+       struct snp_ext_report_req *req = &snp_dev->req.ext_report;
        struct snp_guest_crypto *crypto = snp_dev->crypto;
-       struct snp_ext_report_req req;
        struct snp_report_resp *resp;
        int ret, npages = 0, resp_len;
+       sockptr_t certs_address;
 
        lockdep_assert_held(&snp_cmd_mutex);
 
-       if (!arg->req_data || !arg->resp_data)
+       if (sockptr_is_null(io->req_data) || sockptr_is_null(io->resp_data))
                return -EINVAL;
 
-       if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req)))
+       if (copy_from_sockptr(req, io->req_data, sizeof(*req)))
                return -EFAULT;
 
-       /* userspace does not want certificate data */
-       if (!req.certs_len || !req.certs_address)
+       /* caller does not want certificate data */
+       if (!req->certs_len || !req->certs_address)
                goto cmd;
 
-       if (req.certs_len > SEV_FW_BLOB_MAX_SIZE ||
-           !IS_ALIGNED(req.certs_len, PAGE_SIZE))
+       if (req->certs_len > SEV_FW_BLOB_MAX_SIZE ||
+           !IS_ALIGNED(req->certs_len, PAGE_SIZE))
                return -EINVAL;
 
-       if (!access_ok((const void __user *)req.certs_address, req.certs_len))
-               return -EFAULT;
+       if (sockptr_is_kernel(io->resp_data)) {
+               certs_address = KERNEL_SOCKPTR((void *)req->certs_address);
+       } else {
+               certs_address = USER_SOCKPTR((void __user *)req->certs_address);
+               if (!access_ok(certs_address.user, req->certs_len))
+                       return -EFAULT;
+       }
 
        /*
         * Initialize the intermediate buffer with all zeros. This buffer
@@ -582,8 +604,8 @@ static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_reques
         * the host. If host does not supply any certs in it, then copy
         * zeros to indicate that certificate data was not provided.
         */
-       memset(snp_dev->certs_data, 0, req.certs_len);
-       npages = req.certs_len >> PAGE_SHIFT;
+       memset(snp_dev->certs_data, 0, req->certs_len);
+       npages = req->certs_len >> PAGE_SHIFT;
 cmd:
        /*
         * The intermediate response buffer is used while decrypting the
@@ -597,28 +619,26 @@ cmd:
 
        snp_dev->input.data_npages = npages;
        ret = handle_guest_request(snp_dev, SVM_VMGEXIT_EXT_GUEST_REQUEST, arg,
-                                  SNP_MSG_REPORT_REQ, &req.data,
-                                  sizeof(req.data), resp->data, resp_len);
+                                  SNP_MSG_REPORT_REQ, &req->data,
+                                  sizeof(req->data), resp->data, resp_len);
 
        /* If certs length is invalid then copy the returned length */
        if (arg->vmm_error == SNP_GUEST_VMM_ERR_INVALID_LEN) {
-               req.certs_len = snp_dev->input.data_npages << PAGE_SHIFT;
+               req->certs_len = snp_dev->input.data_npages << PAGE_SHIFT;
 
-               if (copy_to_user((void __user *)arg->req_data, &req, sizeof(req)))
+               if (copy_to_sockptr(io->req_data, req, sizeof(*req)))
                        ret = -EFAULT;
        }
 
        if (ret)
                goto e_free;
 
-       if (npages &&
-           copy_to_user((void __user *)req.certs_address, snp_dev->certs_data,
-                        req.certs_len)) {
+       if (npages && copy_to_sockptr(certs_address, snp_dev->certs_data, req->certs_len)) {
                ret = -EFAULT;
                goto e_free;
        }
 
-       if (copy_to_user((void __user *)arg->resp_data, resp, sizeof(*resp)))
+       if (copy_to_sockptr(io->resp_data, resp, sizeof(*resp)))
                ret = -EFAULT;
 
 e_free:
@@ -631,6 +651,7 @@ static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long
        struct snp_guest_dev *snp_dev = to_snp_dev(file);
        void __user *argp = (void __user *)arg;
        struct snp_guest_request_ioctl input;
+       struct snp_req_resp io;
        int ret = -ENOTTY;
 
        if (copy_from_user(&input, argp, sizeof(input)))
@@ -659,7 +680,14 @@ static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long
                ret = get_derived_key(snp_dev, &input);
                break;
        case SNP_GET_EXT_REPORT:
-               ret = get_ext_report(snp_dev, &input);
+               /*
+                * As get_ext_report() may be called from the ioctl() path and a
+                * kernel internal path (configfs-tsm), decorate the passed
+                * buffers as user pointers.
+                */
+               io.req_data = USER_SOCKPTR((void __user *)input.req_data);
+               io.resp_data = USER_SOCKPTR((void __user *)input.resp_data);
+               ret = get_ext_report(snp_dev, &input, &io);
                break;
        default:
                break;
@@ -743,6 +771,130 @@ static u8 *get_vmpck(int id, struct snp_secrets_page_layout *layout, u32 **seqno
        return key;
 }
 
+struct snp_msg_report_resp_hdr {
+       u32 status;
+       u32 report_size;
+       u8 rsvd[24];
+};
+
+struct snp_msg_cert_entry {
+       guid_t guid;
+       u32 offset;
+       u32 length;
+};
+
+static int sev_report_new(struct tsm_report *report, void *data)
+{
+       struct snp_msg_cert_entry *cert_table;
+       struct tsm_desc *desc = &report->desc;
+       struct snp_guest_dev *snp_dev = data;
+       struct snp_msg_report_resp_hdr hdr;
+       const u32 report_size = SZ_4K;
+       const u32 ext_size = SEV_FW_BLOB_MAX_SIZE;
+       u32 certs_size, i, size = report_size + ext_size;
+       int ret;
+
+       if (desc->inblob_len != SNP_REPORT_USER_DATA_SIZE)
+               return -EINVAL;
+
+       void *buf __free(kvfree) = kvzalloc(size, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       guard(mutex)(&snp_cmd_mutex);
+
+       /* Check if the VMPCK is not empty */
+       if (is_vmpck_empty(snp_dev)) {
+               dev_err_ratelimited(snp_dev->dev, "VMPCK is disabled\n");
+               return -ENOTTY;
+       }
+
+       cert_table = buf + report_size;
+       struct snp_ext_report_req ext_req = {
+               .data = { .vmpl = desc->privlevel },
+               .certs_address = (__u64)cert_table,
+               .certs_len = ext_size,
+       };
+       memcpy(&ext_req.data.user_data, desc->inblob, desc->inblob_len);
+
+       struct snp_guest_request_ioctl input = {
+               .msg_version = 1,
+               .req_data = (__u64)&ext_req,
+               .resp_data = (__u64)buf,
+               .exitinfo2 = 0xff,
+       };
+       struct snp_req_resp io = {
+               .req_data = KERNEL_SOCKPTR(&ext_req),
+               .resp_data = KERNEL_SOCKPTR(buf),
+       };
+
+       ret = get_ext_report(snp_dev, &input, &io);
+       if (ret)
+               return ret;
+
+       memcpy(&hdr, buf, sizeof(hdr));
+       if (hdr.status == SEV_RET_INVALID_PARAM)
+               return -EINVAL;
+       if (hdr.status == SEV_RET_INVALID_KEY)
+               return -EINVAL;
+       if (hdr.status)
+               return -ENXIO;
+       if ((hdr.report_size + sizeof(hdr)) > report_size)
+               return -ENOMEM;
+
+       void *rbuf __free(kvfree) = kvzalloc(hdr.report_size, GFP_KERNEL);
+       if (!rbuf)
+               return -ENOMEM;
+
+       memcpy(rbuf, buf + sizeof(hdr), hdr.report_size);
+       report->outblob = no_free_ptr(rbuf);
+       report->outblob_len = hdr.report_size;
+
+       certs_size = 0;
+       for (i = 0; i < ext_size / sizeof(struct snp_msg_cert_entry); i++) {
+               struct snp_msg_cert_entry *ent = &cert_table[i];
+
+               if (guid_is_null(&ent->guid) && !ent->offset && !ent->length)
+                       break;
+               certs_size = max(certs_size, ent->offset + ent->length);
+       }
+
+       /* Suspicious that the response populated entries without populating size */
+       if (!certs_size && i)
+               dev_warn_ratelimited(snp_dev->dev, "certificate slots conveyed without size\n");
+
+       /* No certs to report */
+       if (!certs_size)
+               return 0;
+
+       /* Suspicious that the certificate blob size contract was violated
+        */
+       if (certs_size > ext_size) {
+               dev_warn_ratelimited(snp_dev->dev, "certificate data truncated\n");
+               certs_size = ext_size;
+       }
+
+       void *cbuf __free(kvfree) = kvzalloc(certs_size, GFP_KERNEL);
+       if (!cbuf)
+               return -ENOMEM;
+
+       memcpy(cbuf, cert_table, certs_size);
+       report->auxblob = no_free_ptr(cbuf);
+       report->auxblob_len = certs_size;
+
+       return 0;
+}
+
+static const struct tsm_ops sev_tsm_ops = {
+       .name = KBUILD_MODNAME,
+       .report_new = sev_report_new,
+};
+
+static void unregister_sev_tsm(void *data)
+{
+       tsm_unregister(&sev_tsm_ops);
+}
+
 static int __init sev_guest_probe(struct platform_device *pdev)
 {
        struct snp_secrets_page_layout *layout;
@@ -816,6 +968,14 @@ static int __init sev_guest_probe(struct platform_device *pdev)
        snp_dev->input.resp_gpa = __pa(snp_dev->response);
        snp_dev->input.data_gpa = __pa(snp_dev->certs_data);
 
+       ret = tsm_register(&sev_tsm_ops, snp_dev, &tsm_report_extra_type);
+       if (ret)
+               goto e_free_cert_data;
+
+       ret = devm_add_action_or_reset(&pdev->dev, unregister_sev_tsm, NULL);
+       if (ret)
+               goto e_free_cert_data;
+
        ret =  misc_register(misc);
        if (ret)
                goto e_free_cert_data;
index 14246fc2fb02b661e1f1aabc1f419563e3f58c38..22dd59e194315a754472b510465af35a9f8c3b27 100644 (file)
@@ -1,6 +1,7 @@
 config TDX_GUEST_DRIVER
        tristate "TDX Guest driver"
        depends on INTEL_TDX_GUEST
+       select TSM_REPORTS
        help
          The driver provides userspace interface to communicate with
          the TDX module to request the TDX guest details like attestation
index 5e44a0fa69bd8ab958abbc7aca6f9fe492d59f8e..1253bf76b57031884420a35d808483a9cd7267be 100644 (file)
 #include <linux/mod_devicetable.h>
 #include <linux/string.h>
 #include <linux/uaccess.h>
+#include <linux/set_memory.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/tsm.h>
+#include <linux/sizes.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 (2K Quote data + ~5K of certificate blob).
+ */
+#define GET_QUOTE_BUF_SIZE             SZ_8K
+
+#define GET_QUOTE_CMD_VER              1
+
+/* TDX GetQuote status codes */
+#define GET_QUOTE_SUCCESS              0
+#define GET_QUOTE_IN_FLIGHT            0xffffffffffffffff
+
+/* 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;
+       u8 data[];
+};
+
+/* Quote data buffer */
+static void *quote_data;
+
+/* Lock to streamline quote requests */
+static DEFINE_MUTEX(quote_lock);
+
+/*
+ * GetQuote request timeout in seconds. Expect that 30 seconds
+ * is enough time for QE to respond to any Quote requests.
+ */
+static u32 getquote_timeout = 30;
+
 static long tdx_get_report0(struct tdx_report_req __user *req)
 {
        u8 *reportdata, *tdreport;
@@ -53,6 +101,154 @@ out:
        return ret;
 }
 
+static void free_quote_buf(void *buf)
+{
+       size_t len = PAGE_ALIGN(GET_QUOTE_BUF_SIZE);
+       unsigned int count = len >> PAGE_SHIFT;
+
+       if (set_memory_encrypted((unsigned long)buf, count)) {
+               pr_err("Failed to restore encryption mask for Quote buffer, leak it\n");
+               return;
+       }
+
+       free_pages_exact(buf, len);
+}
+
+static void *alloc_quote_buf(void)
+{
+       size_t len = PAGE_ALIGN(GET_QUOTE_BUF_SIZE);
+       unsigned int count = len >> PAGE_SHIFT;
+       void *addr;
+
+       addr = alloc_pages_exact(len, GFP_KERNEL | __GFP_ZERO);
+       if (!addr)
+               return NULL;
+
+       if (set_memory_decrypted((unsigned long)addr, count)) {
+               free_pages_exact(addr, len);
+               return NULL;
+       }
+
+       return addr;
+}
+
+/*
+ * wait_for_quote_completion() - Wait for Quote request completion
+ * @quote_buf: Address of Quote buffer.
+ * @timeout: Timeout in seconds to wait for the Quote generation.
+ *
+ * As per TDX GHCI v1.0 specification, sec titled "TDG.VP.VMCALL<GetQuote>",
+ * the status field in the Quote buffer will be set to GET_QUOTE_IN_FLIGHT
+ * while VMM processes the GetQuote request, and will change it to success
+ * or error code after processing is complete. So wait till the status
+ * changes from GET_QUOTE_IN_FLIGHT or the request being timed out.
+ */
+static int wait_for_quote_completion(struct tdx_quote_buf *quote_buf, u32 timeout)
+{
+       int i = 0;
+
+       /*
+        * Quote requests usually take a few seconds to complete, so waking up
+        * once per second to recheck the status is fine for this use case.
+        */
+       while (quote_buf->status == GET_QUOTE_IN_FLIGHT && i++ < timeout) {
+               if (msleep_interruptible(MSEC_PER_SEC))
+                       return -EINTR;
+       }
+
+       return (i == timeout) ? -ETIMEDOUT : 0;
+}
+
+static int tdx_report_new(struct tsm_report *report, void *data)
+{
+       u8 *buf, *reportdata = NULL, *tdreport = NULL;
+       struct tdx_quote_buf *quote_buf = quote_data;
+       struct tsm_desc *desc = &report->desc;
+       int ret;
+       u64 err;
+
+       /* TODO: switch to guard(mutex_intr) */
+       if (mutex_lock_interruptible(&quote_lock))
+               return -EINTR;
+
+       /*
+        * If the previous request is timedout or interrupted, and the
+        * Quote buf status is still in GET_QUOTE_IN_FLIGHT (owned by
+        * VMM), don't permit any new request.
+        */
+       if (quote_buf->status == GET_QUOTE_IN_FLIGHT) {
+               ret = -EBUSY;
+               goto done;
+       }
+
+       if (desc->inblob_len != TDX_REPORTDATA_LEN) {
+               ret = -EINVAL;
+               goto done;
+       }
+
+       reportdata = kmalloc(TDX_REPORTDATA_LEN, GFP_KERNEL);
+       if (!reportdata) {
+               ret = -ENOMEM;
+               goto done;
+       }
+
+       tdreport = kzalloc(TDX_REPORT_LEN, GFP_KERNEL);
+       if (!tdreport) {
+               ret = -ENOMEM;
+               goto done;
+       }
+
+       memcpy(reportdata, desc->inblob, desc->inblob_len);
+
+       /* Generate TDREPORT0 using "TDG.MR.REPORT" TDCALL */
+       ret = tdx_mcall_get_report0(reportdata, tdreport);
+       if (ret) {
+               pr_err("GetReport call failed\n");
+               goto done;
+       }
+
+       memset(quote_data, 0, GET_QUOTE_BUF_SIZE);
+
+       /* Update Quote buffer header */
+       quote_buf->version = GET_QUOTE_CMD_VER;
+       quote_buf->in_len = TDX_REPORT_LEN;
+
+       memcpy(quote_buf->data, tdreport, TDX_REPORT_LEN);
+
+       err = tdx_hcall_get_quote(quote_data, GET_QUOTE_BUF_SIZE);
+       if (err) {
+               pr_err("GetQuote hypercall failed, status:%llx\n", err);
+               ret = -EIO;
+               goto done;
+       }
+
+       ret = wait_for_quote_completion(quote_buf, getquote_timeout);
+       if (ret) {
+               pr_err("GetQuote request timedout\n");
+               goto done;
+       }
+
+       buf = kvmemdup(quote_buf->data, quote_buf->out_len, GFP_KERNEL);
+       if (!buf) {
+               ret = -ENOMEM;
+               goto done;
+       }
+
+       report->outblob = buf;
+       report->outblob_len = quote_buf->out_len;
+
+       /*
+        * TODO: parse the PEM-formatted cert chain out of the quote buffer when
+        * provided
+        */
+done:
+       mutex_unlock(&quote_lock);
+       kfree(reportdata);
+       kfree(tdreport);
+
+       return ret;
+}
+
 static long tdx_guest_ioctl(struct file *file, unsigned int cmd,
                            unsigned long arg)
 {
@@ -82,17 +278,48 @@ static const struct x86_cpu_id tdx_guest_ids[] = {
 };
 MODULE_DEVICE_TABLE(x86cpu, tdx_guest_ids);
 
+static const struct tsm_ops tdx_tsm_ops = {
+       .name = KBUILD_MODNAME,
+       .report_new = tdx_report_new,
+};
+
 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;
+
+       quote_data = alloc_quote_buf();
+       if (!quote_data) {
+               pr_err("Failed to allocate Quote buffer\n");
+               ret = -ENOMEM;
+               goto free_misc;
+       }
+
+       ret = tsm_register(&tdx_tsm_ops, NULL, NULL);
+       if (ret)
+               goto free_quote;
+
+       return 0;
+
+free_quote:
+       free_quote_buf(quote_data);
+free_misc:
+       misc_deregister(&tdx_misc_dev);
+
+       return ret;
 }
 module_init(tdx_guest_init);
 
 static void __exit tdx_guest_exit(void)
 {
+       tsm_unregister(&tdx_tsm_ops);
+       free_quote_buf(quote_data);
        misc_deregister(&tdx_misc_dev);
 }
 module_exit(tdx_guest_exit);
diff --git a/drivers/virt/coco/tsm.c b/drivers/virt/coco/tsm.c
new file mode 100644 (file)
index 0000000..d1c2db8
--- /dev/null
@@ -0,0 +1,425 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright(c) 2023 Intel Corporation. All rights reserved. */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/tsm.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/rwsem.h>
+#include <linux/string.h>
+#include <linux/module.h>
+#include <linux/cleanup.h>
+#include <linux/configfs.h>
+
+static struct tsm_provider {
+       const struct tsm_ops *ops;
+       const struct config_item_type *type;
+       void *data;
+} provider;
+static DECLARE_RWSEM(tsm_rwsem);
+
+/**
+ * DOC: Trusted Security Module (TSM) Attestation Report Interface
+ *
+ * The TSM report interface is a common provider of blobs that facilitate
+ * attestation of a TVM (confidential computing guest) by an attestation
+ * service. A TSM report combines a user-defined blob (likely a public-key with
+ * a nonce for a key-exchange protocol) with a signed attestation report. That
+ * combined blob is then used to obtain secrets provided by an agent that can
+ * validate the attestation report. The expectation is that this interface is
+ * invoked infrequently, however configfs allows for multiple agents to
+ * own their own report generation instances to generate reports as
+ * often as needed.
+ *
+ * The attestation report format is TSM provider specific, when / if a standard
+ * materializes that can be published instead of the vendor layout. Until then
+ * the 'provider' attribute indicates the format of 'outblob', and optionally
+ * 'auxblob'.
+ */
+
+struct tsm_report_state {
+       struct tsm_report report;
+       unsigned long write_generation;
+       unsigned long read_generation;
+       struct config_item cfg;
+};
+
+enum tsm_data_select {
+       TSM_REPORT,
+       TSM_CERTS,
+};
+
+static struct tsm_report *to_tsm_report(struct config_item *cfg)
+{
+       struct tsm_report_state *state =
+               container_of(cfg, struct tsm_report_state, cfg);
+
+       return &state->report;
+}
+
+static struct tsm_report_state *to_state(struct tsm_report *report)
+{
+       return container_of(report, struct tsm_report_state, report);
+}
+
+static int try_advance_write_generation(struct tsm_report *report)
+{
+       struct tsm_report_state *state = to_state(report);
+
+       lockdep_assert_held_write(&tsm_rwsem);
+
+       /*
+        * Malicious or broken userspace has written enough times for
+        * read_generation == write_generation by modular arithmetic without an
+        * interim read. Stop accepting updates until the current report
+        * configuration is read.
+        */
+       if (state->write_generation == state->read_generation - 1)
+               return -EBUSY;
+       state->write_generation++;
+       return 0;
+}
+
+static ssize_t tsm_report_privlevel_store(struct config_item *cfg,
+                                         const char *buf, size_t len)
+{
+       struct tsm_report *report = to_tsm_report(cfg);
+       unsigned int val;
+       int rc;
+
+       rc = kstrtouint(buf, 0, &val);
+       if (rc)
+               return rc;
+
+       /*
+        * The valid privilege levels that a TSM might accept, if it accepts a
+        * privilege level setting at all, are a max of TSM_PRIVLEVEL_MAX (see
+        * SEV-SNP GHCB) and a minimum of a TSM selected floor value no less
+        * than 0.
+        */
+       if (provider.ops->privlevel_floor > val || val > TSM_PRIVLEVEL_MAX)
+               return -EINVAL;
+
+       guard(rwsem_write)(&tsm_rwsem);
+       rc = try_advance_write_generation(report);
+       if (rc)
+               return rc;
+       report->desc.privlevel = val;
+
+       return len;
+}
+CONFIGFS_ATTR_WO(tsm_report_, privlevel);
+
+static ssize_t tsm_report_privlevel_floor_show(struct config_item *cfg,
+                                              char *buf)
+{
+       guard(rwsem_read)(&tsm_rwsem);
+       return sysfs_emit(buf, "%u\n", provider.ops->privlevel_floor);
+}
+CONFIGFS_ATTR_RO(tsm_report_, privlevel_floor);
+
+static ssize_t tsm_report_inblob_write(struct config_item *cfg,
+                                      const void *buf, size_t count)
+{
+       struct tsm_report *report = to_tsm_report(cfg);
+       int rc;
+
+       guard(rwsem_write)(&tsm_rwsem);
+       rc = try_advance_write_generation(report);
+       if (rc)
+               return rc;
+
+       report->desc.inblob_len = count;
+       memcpy(report->desc.inblob, buf, count);
+       return count;
+}
+CONFIGFS_BIN_ATTR_WO(tsm_report_, inblob, NULL, TSM_INBLOB_MAX);
+
+static ssize_t tsm_report_generation_show(struct config_item *cfg, char *buf)
+{
+       struct tsm_report *report = to_tsm_report(cfg);
+       struct tsm_report_state *state = to_state(report);
+
+       guard(rwsem_read)(&tsm_rwsem);
+       return sysfs_emit(buf, "%lu\n", state->write_generation);
+}
+CONFIGFS_ATTR_RO(tsm_report_, generation);
+
+static ssize_t tsm_report_provider_show(struct config_item *cfg, char *buf)
+{
+       guard(rwsem_read)(&tsm_rwsem);
+       return sysfs_emit(buf, "%s\n", provider.ops->name);
+}
+CONFIGFS_ATTR_RO(tsm_report_, provider);
+
+static ssize_t __read_report(struct tsm_report *report, void *buf, size_t count,
+                            enum tsm_data_select select)
+{
+       loff_t offset = 0;
+       ssize_t len;
+       u8 *out;
+
+       if (select == TSM_REPORT) {
+               out = report->outblob;
+               len = report->outblob_len;
+       } else {
+               out = report->auxblob;
+               len = report->auxblob_len;
+       }
+
+       /*
+        * Recall that a NULL @buf is configfs requesting the size of
+        * the buffer.
+        */
+       if (!buf)
+               return len;
+       return memory_read_from_buffer(buf, count, &offset, out, len);
+}
+
+static ssize_t read_cached_report(struct tsm_report *report, void *buf,
+                                 size_t count, enum tsm_data_select select)
+{
+       struct tsm_report_state *state = to_state(report);
+
+       guard(rwsem_read)(&tsm_rwsem);
+       if (!report->desc.inblob_len)
+               return -EINVAL;
+
+       /*
+        * A given TSM backend always fills in ->outblob regardless of
+        * whether the report includes an auxblob or not.
+        */
+       if (!report->outblob ||
+           state->read_generation != state->write_generation)
+               return -EWOULDBLOCK;
+
+       return __read_report(report, buf, count, select);
+}
+
+static ssize_t tsm_report_read(struct tsm_report *report, void *buf,
+                              size_t count, enum tsm_data_select select)
+{
+       struct tsm_report_state *state = to_state(report);
+       const struct tsm_ops *ops;
+       ssize_t rc;
+
+       /* try to read from the existing report if present and valid... */
+       rc = read_cached_report(report, buf, count, select);
+       if (rc >= 0 || rc != -EWOULDBLOCK)
+               return rc;
+
+       /* slow path, report may need to be regenerated... */
+       guard(rwsem_write)(&tsm_rwsem);
+       ops = provider.ops;
+       if (!ops)
+               return -ENOTTY;
+       if (!report->desc.inblob_len)
+               return -EINVAL;
+
+       /* did another thread already generate this report? */
+       if (report->outblob &&
+           state->read_generation == state->write_generation)
+               goto out;
+
+       kvfree(report->outblob);
+       kvfree(report->auxblob);
+       report->outblob = NULL;
+       report->auxblob = NULL;
+       rc = ops->report_new(report, provider.data);
+       if (rc < 0)
+               return rc;
+       state->read_generation = state->write_generation;
+out:
+       return __read_report(report, buf, count, select);
+}
+
+static ssize_t tsm_report_outblob_read(struct config_item *cfg, void *buf,
+                                      size_t count)
+{
+       struct tsm_report *report = to_tsm_report(cfg);
+
+       return tsm_report_read(report, buf, count, TSM_REPORT);
+}
+CONFIGFS_BIN_ATTR_RO(tsm_report_, outblob, NULL, TSM_OUTBLOB_MAX);
+
+static ssize_t tsm_report_auxblob_read(struct config_item *cfg, void *buf,
+                                      size_t count)
+{
+       struct tsm_report *report = to_tsm_report(cfg);
+
+       return tsm_report_read(report, buf, count, TSM_CERTS);
+}
+CONFIGFS_BIN_ATTR_RO(tsm_report_, auxblob, NULL, TSM_OUTBLOB_MAX);
+
+#define TSM_DEFAULT_ATTRS() \
+       &tsm_report_attr_generation, \
+       &tsm_report_attr_provider
+
+static struct configfs_attribute *tsm_report_attrs[] = {
+       TSM_DEFAULT_ATTRS(),
+       NULL,
+};
+
+static struct configfs_attribute *tsm_report_extra_attrs[] = {
+       TSM_DEFAULT_ATTRS(),
+       &tsm_report_attr_privlevel,
+       &tsm_report_attr_privlevel_floor,
+       NULL,
+};
+
+#define TSM_DEFAULT_BIN_ATTRS() \
+       &tsm_report_attr_inblob, \
+       &tsm_report_attr_outblob
+
+static struct configfs_bin_attribute *tsm_report_bin_attrs[] = {
+       TSM_DEFAULT_BIN_ATTRS(),
+       NULL,
+};
+
+static struct configfs_bin_attribute *tsm_report_bin_extra_attrs[] = {
+       TSM_DEFAULT_BIN_ATTRS(),
+       &tsm_report_attr_auxblob,
+       NULL,
+};
+
+static void tsm_report_item_release(struct config_item *cfg)
+{
+       struct tsm_report *report = to_tsm_report(cfg);
+       struct tsm_report_state *state = to_state(report);
+
+       kvfree(report->auxblob);
+       kvfree(report->outblob);
+       kfree(state);
+}
+
+static struct configfs_item_operations tsm_report_item_ops = {
+       .release = tsm_report_item_release,
+};
+
+const struct config_item_type tsm_report_default_type = {
+       .ct_owner = THIS_MODULE,
+       .ct_bin_attrs = tsm_report_bin_attrs,
+       .ct_attrs = tsm_report_attrs,
+       .ct_item_ops = &tsm_report_item_ops,
+};
+EXPORT_SYMBOL_GPL(tsm_report_default_type);
+
+const struct config_item_type tsm_report_extra_type = {
+       .ct_owner = THIS_MODULE,
+       .ct_bin_attrs = tsm_report_bin_extra_attrs,
+       .ct_attrs = tsm_report_extra_attrs,
+       .ct_item_ops = &tsm_report_item_ops,
+};
+EXPORT_SYMBOL_GPL(tsm_report_extra_type);
+
+static struct config_item *tsm_report_make_item(struct config_group *group,
+                                               const char *name)
+{
+       struct tsm_report_state *state;
+
+       guard(rwsem_read)(&tsm_rwsem);
+       if (!provider.ops)
+               return ERR_PTR(-ENXIO);
+
+       state = kzalloc(sizeof(*state), GFP_KERNEL);
+       if (!state)
+               return ERR_PTR(-ENOMEM);
+
+       config_item_init_type_name(&state->cfg, name, provider.type);
+       return &state->cfg;
+}
+
+static struct configfs_group_operations tsm_report_group_ops = {
+       .make_item = tsm_report_make_item,
+};
+
+static const struct config_item_type tsm_reports_type = {
+       .ct_owner = THIS_MODULE,
+       .ct_group_ops = &tsm_report_group_ops,
+};
+
+static const struct config_item_type tsm_root_group_type = {
+       .ct_owner = THIS_MODULE,
+};
+
+static struct configfs_subsystem tsm_configfs = {
+       .su_group = {
+               .cg_item = {
+                       .ci_namebuf = "tsm",
+                       .ci_type = &tsm_root_group_type,
+               },
+       },
+       .su_mutex = __MUTEX_INITIALIZER(tsm_configfs.su_mutex),
+};
+
+int tsm_register(const struct tsm_ops *ops, void *priv,
+                const struct config_item_type *type)
+{
+       const struct tsm_ops *conflict;
+
+       if (!type)
+               type = &tsm_report_default_type;
+       if (!(type == &tsm_report_default_type || type == &tsm_report_extra_type))
+               return -EINVAL;
+
+       guard(rwsem_write)(&tsm_rwsem);
+       conflict = provider.ops;
+       if (conflict) {
+               pr_err("\"%s\" ops already registered\n", conflict->name);
+               return -EBUSY;
+       }
+
+       provider.ops = ops;
+       provider.data = priv;
+       provider.type = type;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(tsm_register);
+
+int tsm_unregister(const struct tsm_ops *ops)
+{
+       guard(rwsem_write)(&tsm_rwsem);
+       if (ops != provider.ops)
+               return -EBUSY;
+       provider.ops = NULL;
+       provider.data = NULL;
+       provider.type = NULL;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(tsm_unregister);
+
+static struct config_group *tsm_report_group;
+
+static int __init tsm_init(void)
+{
+       struct config_group *root = &tsm_configfs.su_group;
+       struct config_group *tsm;
+       int rc;
+
+       config_group_init(root);
+       rc = configfs_register_subsystem(&tsm_configfs);
+       if (rc)
+               return rc;
+
+       tsm = configfs_register_default_group(root, "report",
+                                             &tsm_reports_type);
+       if (IS_ERR(tsm)) {
+               configfs_unregister_subsystem(&tsm_configfs);
+               return PTR_ERR(tsm);
+       }
+       tsm_report_group = tsm;
+
+       return 0;
+}
+module_init(tsm_init);
+
+static void __exit tsm_exit(void)
+{
+       configfs_unregister_default_group(tsm_report_group);
+       configfs_unregister_subsystem(&tsm_configfs);
+}
+module_exit(tsm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Provide Trusted Security Module attestation reports via configfs");
index ff56ab804bf6dbedf9664696d9e25abcbcd3879d..d6d6ffeeb9a2af2f3ff497b48c08e2c19f8d0efb 100644 (file)
@@ -764,6 +764,8 @@ static inline __alloc_size(1, 2) void *kvcalloc(size_t n, size_t size, gfp_t fla
 extern void *kvrealloc(const void *p, size_t oldsize, size_t newsize, gfp_t flags)
                      __realloc_size(3);
 extern void kvfree(const void *addr);
+DEFINE_FREE(kvfree, void *, if (_T) kvfree(_T))
+
 extern void kvfree_sensitive(const void *addr, size_t len);
 
 unsigned int kmem_cache_size(struct kmem_cache *s);
diff --git a/include/linux/tsm.h b/include/linux/tsm.h
new file mode 100644 (file)
index 0000000..de8324a
--- /dev/null
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __TSM_H
+#define __TSM_H
+
+#include <linux/sizes.h>
+#include <linux/types.h>
+
+#define TSM_INBLOB_MAX 64
+#define TSM_OUTBLOB_MAX SZ_32K
+
+/*
+ * Privilege level is a nested permission concept to allow confidential
+ * guests to partition address space, 4-levels are supported.
+ */
+#define TSM_PRIVLEVEL_MAX 3
+
+/**
+ * struct tsm_desc - option descriptor for generating tsm report blobs
+ * @privlevel: optional privilege level to associate with @outblob
+ * @inblob_len: sizeof @inblob
+ * @inblob: arbitrary input data
+ */
+struct tsm_desc {
+       unsigned int privlevel;
+       size_t inblob_len;
+       u8 inblob[TSM_INBLOB_MAX];
+};
+
+/**
+ * struct tsm_report - track state of report generation relative to options
+ * @desc: input parameters to @report_new()
+ * @outblob_len: sizeof(@outblob)
+ * @outblob: generated evidence to provider to the attestation agent
+ * @auxblob_len: sizeof(@auxblob)
+ * @auxblob: (optional) auxiliary data to the report (e.g. certificate data)
+ */
+struct tsm_report {
+       struct tsm_desc desc;
+       size_t outblob_len;
+       u8 *outblob;
+       size_t auxblob_len;
+       u8 *auxblob;
+};
+
+/**
+ * struct tsm_ops - attributes and operations for tsm instances
+ * @name: tsm id reflected in /sys/kernel/config/tsm/report/$report/provider
+ * @privlevel_floor: convey base privlevel for nested scenarios
+ * @report_new: Populate @report with the report blob and auxblob
+ * (optional), return 0 on successful population, or -errno otherwise
+ *
+ * Implementation specific ops, only one is expected to be registered at
+ * a time i.e. only one of "sev-guest", "tdx-guest", etc.
+ */
+struct tsm_ops {
+       const char *name;
+       const unsigned int privlevel_floor;
+       int (*report_new)(struct tsm_report *report, void *data);
+};
+
+extern const struct config_item_type tsm_report_default_type;
+
+/* publish @privlevel, @privlevel_floor, and @auxblob attributes */
+extern const struct config_item_type tsm_report_extra_type;
+
+int tsm_register(const struct tsm_ops *ops, void *priv,
+                const struct config_item_type *type);
+int tsm_unregister(const struct tsm_ops *ops);
+#endif /* __TSM_H */
index 1c9da485318f93fe50e11a47f284d8fe81106066..b44ba7dcdefcadc62444e97721ea7ef00ade6a74 100644 (file)
@@ -68,6 +68,7 @@ typedef enum {
        SEV_RET_INVALID_PARAM,
        SEV_RET_RESOURCE_LIMIT,
        SEV_RET_SECURE_DATA_INVALID,
+       SEV_RET_INVALID_KEY = 0x27,
        SEV_RET_MAX,
 } sev_ret_code;
 
index 2aa39112cf8dd37e3ee265f6128973c8698145df..154a87a1eca978baf5fccf2280936d103a82ebbb 100644 (file)
 
 #include <linux/types.h>
 
+#define SNP_REPORT_USER_DATA_SIZE 64
+
 struct snp_report_req {
        /* user data that should be included in the report */
-       __u8 user_data[64];
+       __u8 user_data[SNP_REPORT_USER_DATA_SIZE];
 
        /* The vmpl level to be included in the report */
        __u32 vmpl;