Re: [PATCH v8 1/5] x86/tdx: Add TDX Guest attestation interface driver

From: Dave Hansen
Date: Fri Jun 24 2022 - 12:52:40 EST


On 6/8/22 19:52, Kuppuswamy Sathyanarayanan wrote:
> TDREPORT can only be verified on local platform as the MAC key is bound
> to the platform. To support remote verification of the TDREPORT, TDX
> leverages Intel SGX Quote Enclave (QE) to verify the TDREPORT locally
> and convert it to a remote verifiable Quote.
>
> After getting the TDREPORT, the second step of the attestation process
> is to send it to the QE to generate the Quote. TDX doesn't support SGX
> inside the TD, so the QE can be deployed in the host, or in another
> legacy VM with SGX support. How to send the TDREPORT to QE and receive
> the Quote is implementation and deployment specific.

On high-level comment: this whole series is quite agnostic about what is
actually attested. On some level, I guess it doesn't matter. But, it
makes me a bit uneasy. There needs to be at least *some* kind of claim
about that somewhere, preferably a sentence in the cover letter and
sentence or two here.

Second, how can someone test this code? It appears that they need to
assemble a veritable Rube Goldberg machine. The least we could do is
have a selftest that just calls the ioctl() and makes sure that
something halfway sane comes out of it.

> In such
> case, since REPORTDATA is a secret, using sysfs to share it is insecure
> compared to sending it via IOCTL.

Huh? How is sysfs "insecure"?

> + /*
> + * Generate TDREPORT using "TDG.MR.REPORT" TDCALL.
> + *
> + * Get the TDREPORT using REPORTDATA as input. Refer to
> + * section 22.3.3 TDG.MR.REPORT leaf in the TDX Module 1.0
> + * Specification for detailed information.
> + */
> + ret = __tdx_module_call(TDX_GET_REPORT, virt_to_phys(tdreport),
> + virt_to_phys(reportdata), 0, 0, NULL);

One of those 0's is a "Report sub type". Those bits in the module call
are presumably because it won't *always* be zero. But, if there's ever
a new report type, we'll need another user/kernel ABI. That doesn't
seem great.

The TDX module spec doesn't even give a name to "sub report type 0".
That is not super helpful for deciding what it *means*.

Right now, the entire ABI is basically:

ret = ioctl(TDX_GET_REPORT, &buffer);

That _implies_ a ton of stuff:

1. report sub type 0
2. a specific input length REPORTDATA
3. a specific output length

I don't want to over-engineer this thing, but it would make a lot of
sense to me to feed the ioctl() with something like this:

struct tdx_report
{
u8 report_sub_type;
u64 report_input_data;
u64 report_input_data_len;

u64 report_output_data;
u64 report_output_data_len;
}

That makes *everything* explicit. It makes it utterly clear what goes
where, *AND* it makes userspace declare it.

But, you have:

> +/**
> + * struct tdx_report_req: Get TDREPORT using REPORTDATA as input.
> + *
> + * @reportdata : User-defined 64-Byte REPORTDATA to be included into
> + * TDREPORT. Typically it can be some nonce provided by
> + * attestation service, so the generated TDREPORT can be
> + * uniquely verified.
> + * @tdreport : TDREPORT output from TDCALL[TDG.MR.REPORT] of size
> + * TDX_REPORT_LEN.
> + *
> + * Used in TDX_CMD_GET_REPORT IOCTL request.
> + */
> +struct tdx_report_req {
> + union {
> + __u8 reportdata[TDX_REPORTDATA_LEN];
> + __u8 tdreport[TDX_REPORT_LEN];
> + };
> +};
> +

and a bunch of code copying in and out of this structure:

> +static long tdx_get_report(void __user *argp)
> +{
> + void *reportdata = NULL, *tdreport = NULL;
...
> + /* Copy REPORTDATA from the user buffer */
> + if (copy_from_user(reportdata, argp, TDX_REPORTDATA_LEN)) {
> + ret = -EFAULT;
> + goto out;
> + }

But none of that code even bothers to *use* the structure!

What's with the union? Are you really just trying to save 64 bytes of
space? Is that worth it?

How many of these "drivers" are we going to need which are thinly veiled
ioctl()s that are only TDX module call wrappers?