Re: [PATCH v7 05/20] x86/virt/tdx: Implement functions to make SEAMCALL
From: Huang, Kai
Date: Wed Nov 23 2022 - 05:55:30 EST
On Tue, 2022-11-22 at 10:20 -0800, Dave Hansen wrote:
> On 11/20/22 16:26, Kai Huang wrote:
> > TDX introduces a new CPU mode: Secure Arbitration Mode (SEAM). This
> > mode runs only the TDX module itself or other code to load the TDX
> > module.
> >
> > The host kernel communicates with SEAM software via a new SEAMCALL
> > instruction. This is conceptually similar to a guest->host hypercall,
> > except it is made from the host to SEAM software instead.
> >
> > The TDX module defines a set of SEAMCALL leaf functions to allow the
> > host to initialize it, and to create and run protected VMs. SEAMCALL
> > leaf functions use an ABI different from the x86-64 system-v ABI.
> > Instead, they share the same ABI with the TDCALL leaf functions.
>
> I may have suggested this along the way, but the mention of the sysv ABI
> is just confusing here. This is enough for a changelog:
>
> The TDX module establishes a new SEAMCALL ABI which allows the
> host to initialize the module and to and to manage VMs.
>
> Kill the rest.
Thanks will do.
>
> > Implement a function __seamcall() to allow the host to make SEAMCALL
> > to SEAM software using the TDX_MODULE_CALL macro which is the common
> > assembly for both SEAMCALL and TDCALL.
>
> In general, I dislike mentioning function names in changelogs. Keep
> this high-level, like:
>
> Add infrastructure to make SEAMCALLs. The SEAMCALL ABI is very
> similar to the TDCALL ABI and leverages much TDCALL
> infrastructure.
Will do.
>
> > SEAMCALL instruction causes #GP when SEAMRR isn't enabled, and #UD when
> > CPU is not in VMX operation. The current TDX_MODULE_CALL macro doesn't
> > handle any of them. There's no way to check whether the CPU is in VMX
> > operation or not.
>
> What is SEAMRR?
Sorry it is a leftover. Should be "when TDX isn't enabled".
>
> Why even mention this behavior in the changelog. Is this a problem?
> Does it have a solution?
My intention was to provide some background information why to extend
TDX_MODULE_CALL macro to handle #UD and #GP as mentioned below.
>
> > Initializing the TDX module is done at runtime on demand, and it depends
> > on the caller to ensure CPU is in VMX operation before making SEAMCALL.
> > To avoid getting Oops when the caller mistakenly tries to initialize the
> > TDX module when CPU is not in VMX operation, extend the TDX_MODULE_CALL
> > macro to handle #UD (and also #GP, which can theoretically still happen
> > when TDX isn't actually enabled by the BIOS, i.e. due to BIOS bug).
>
> I'm not completely sure this is worth it. If the BIOS lies, we oops.
> There are lots of ways that the BIOS lying can make the kernel oops.
> What's one more?
I agree. But if we want to handle #UD, then #GP won't cause oops any more, so I
just added error code for #GP too.
Or perhaps we can change to below: ?
"... extend the TDX_MODULE_CALL to handle #UD (and opportunistically #GP since
they share the same assembly)."
Or other suggestions?
>
> > Introduce two new TDX error codes for #UD and #GP respectively so the
> > caller can distinguish. Also, Opportunistically put the new TDX error
> > codes and the existing TDX_SEAMCALL_VMFAILINVALID into INTEL_TDX_HOST
> > Kconfig option as they are only used when it is on.
> >
> > As __seamcall() can potentially return multiple error codes, besides the
> > actual SEAMCALL leaf function return code, also introduce a wrapper
> > function seamcall() to convert the __seamcall() error code to the kernel
> > error code, so the caller doesn't need to duplicate the code to check
> > return value of __seamcall() and return kernel error code accordingly.
>
>
[...]
> > +/*
> > + * Wrapper of __seamcall() to convert SEAMCALL leaf function error code
> > + * to kernel error code. @seamcall_ret and @out contain the SEAMCALL
> > + * leaf function return code and the additional output respectively if
> > + * not NULL.
> > + */
> > +static int __always_unused seamcall(u64 fn, u64 rcx, u64 rdx, u64 r8, u64 r9,
> > + u64 *seamcall_ret,
> > + struct tdx_module_output *out)
> > +{
> > + u64 sret;
> > +
> > + sret = __seamcall(fn, rcx, rdx, r8, r9, out);
> > +
> > + /* Save SEAMCALL return code if caller wants it */
> > + if (seamcall_ret)
> > + *seamcall_ret = sret;
> > +
> > + /* SEAMCALL was successful */
> > + if (!sret)
> > + return 0;
> > +
> > + switch (sret) {
> > + case TDX_SEAMCALL_GP:
> > + /*
> > + * platform_tdx_enabled() is checked to be true
> > + * before making any SEAMCALL.
> > + */
>
> This doesn't make any sense. "platform_tdx_enabled() is checked"???
>
> Do you mean that it *should* be checked and probably wasn't which is
> what caused the error?
I meant tdx_enable() already calls platform_tdx_enabled() to check whether BIOS
has enabled TDX at the very beginning before making any SEAMCALL, so
theoretically #GP should not happen unless there's BIOS bug. I thought a WARN()
can help to catch.
>
> > + WARN_ON_ONCE(1);
> > + fallthrough;
> > + case TDX_SEAMCALL_VMFAILINVALID:
> > + /* Return -ENODEV if the TDX module is not loaded. */
> > + return -ENODEV;
>
> Pro tip: you don't need to rewrite code in comments. If the code
> literally says, "return -ENODEV", there is very little value in writing
> virtually identical bytes "Return -ENODEV" in the comment.
>
Indeed. Thanks for the tip! I'll update those comments.