Move memory acceptance x86 arch code into EFI stub
Subsystems (3)
Ard Biesheuvel
maintainercover Move memory acceptance x86 arch code into EFI stub
This is a follow-up to [0].
Move arch_accept_memory(), which is only called by the EFI stub and never by the decompressor on a non-EFI boot, into the EFI stub, and avoid relying directly on decompressor APIs such as error().
Instead, call tdx_panic() on a failure to accept memory in a TDX guest.
This makes the decompressor's implementation of panic() obsolete, and allows it to be removed. This is a prerequisite for dropping the implementation of snprintf() from the EFI stub entirely, which is what the series containing [0] implements.
[0] http://lore.kernel.org/r/[email protected]
Ard Biesheuvel (3):
x86/tdx: Share tdx_panic() with the EFI stub
x86/boot: Move unaccepted memory handling out of the decompressor x86/boot: Drop unused implementation of panic()
arch/x86/boot/compressed/error.c | 19 --------- arch/x86/boot/compressed/error.h | 1 - arch/x86/boot/compressed/mem.c | 42 -------------------- arch/x86/boot/compressed/sev.h | 2 - arch/x86/coco/tdx/tdx-shared.c | 35 ++++++++++++++++ arch/x86/coco/tdx/tdx.c | 35 ---------------- arch/x86/include/asm/sev.h | 2 + arch/x86/include/asm/shared/tdx.h | 1 + drivers/firmware/efi/libstub/x86-stub.c | 40 +++++++++++++++++++ 9 files changed, 78 insertions(+), 99 deletions(-)
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
Signature · 1 line
2.47.3
Ard Biesheuvel
maintainerPATCH 1/3 x86/tdx: Share tdx_panic() with the EFI stub
Move the implementation of tdx_panic() into the source file that is shared with the decompressor and the EFI stub.
Use memcpy() and strnlen() instead of strtomem_pad(), as the latter does not exist in the early boot code.
Note that __tdx_hypercall() may call __tdx_hypercall_failed() if the hypercall returns with an error (while it should never return to begin with). __tdx_hypercall_failed() calls the decompressor's error() routine, which prints a message and then loops forever.
When called from the EFI stub, this error() call may attempt to use port I/O to the default serial port rather than the TDX hypercalls which the decompressor uses normally to print diagnostics to the console, but this is fine: given that this situation only occurs after a catastrophic error, and a subsequent spurious return from tdx_panic(), whether error() uses port I/O or not is rather moot at that point, as long as it never returns.
--- arch/x86/coco/tdx/tdx-shared.c | 35 ++++++++++++++++++++ arch/x86/coco/tdx/tdx.c | 35 -------------------- arch/x86/include/asm/shared/tdx.h | 1 + 3 files changed, 36 insertions(+), 35 deletions(-)
arch/x86/coco/tdx/tdx-shared.c +35 -0
@@ -89,3 +89,38 @@ noinstr u64 __tdx_hypercall(struct tdx_module_args *args)/* TDVMCALL leaf return code is in R10 */ return args->r10; } + +void __noreturn tdx_panic (const char *msg) +{ +struct tdx_module_args args = { + .r10 =TDX_HYPERCALL_STANDARD , + .r11 =TDVMCALL_REPORT_FATAL_ERROR , + .r12 =0 ,/* Error code: 0 is Panic */ + }; +union { +/* Define register order according to the GHCI */ +struct {u64 r14, r15, rbx, rdi, rsi, r8, r9, rdx; }; + + char bytes[64] __nonstring; + } message = {}; + +/* VMM assumes '\0' in byte 65, if the message took all 64 bytes */ +memcpy (message.bytes, msg,strnlen (msg,sizeof (message))); + + args.r8 = message.r8; + args.r9 = message.r9; + args.r14 = message.r14; + args.r15 = message.r15; + args.rdi = message.rdi; + args.rsi = message.rsi; + args.rbx = message.rbx; + args.rdx = message.rdx; + +/* +* This hypercall should never return and it is not safe +* to keep the guest running. Call it forever if it +* happens to return. +*/ +while (1 ) +__tdx_hypercall (&args); +}
arch/x86/coco/tdx/tdx.c +0 -35
@@ -198,41 +198,6 @@ u64 tdx_hcall_get_quote(u8 *buf, size_t size) }EXPORT_SYMBOL_GPL (tdx_hcall_get_quote); -static void __noreturn tdx_panic (const char *msg) -{ -struct tdx_module_args args = { - .r10 =TDX_HYPERCALL_STANDARD , - .r11 =TDVMCALL_REPORT_FATAL_ERROR , - .r12 =0 ,/* Error code: 0 is Panic */ - }; -union { -/* Define register order according to the GHCI */ -struct {u64 r14, r15, rbx, rdi, rsi, r8, r9, rdx; }; - - char bytes[64] __nonstring; - } message; - -/* VMM assumes '\0' in byte 65, if the message took all 64 bytes */ -strtomem_pad (message.bytes, msg,'\0' ); - - args.r8 = message.r8; - args.r9 = message.r9; - args.r14 = message.r14; - args.r15 = message.r15; - args.rdi = message.rdi; - args.rsi = message.rsi; - args.rbx = message.rbx; - args.rdx = message.rdx; - -/* -* This hypercall should never return and it is not safe -* to keep the guest running. Call it forever if it -* happens to return. -*/ -while (1 ) -__tdx_hypercall (&args); -} -/* * The kernel cannot handle #VEs when accessing normal kernel memory. Ensure * that no #VE will be delivered for accesses to TD-private memory.
arch/x86/include/asm/shared/tdx.h +1 -0
@@ -171,6 +171,7 @@ static inline u64 _tdx_hypercall(u64 fn, u64 r12, u64 r13, u64 r14, u64 r15)return __tdx_hypercall (&args); } +void __noreturn tdx_panic (const char *msg);/* Called from __tdx_hypercall() for unrecoverable failure */ void __noreturn __tdx_hypercall_failed (void );
Signature · 1 line
2.47.3
Borislav Petkov
maintainerRe: [PATCH 1/3] x86/tdx: Share tdx_panic() with the EFI stub
Quoted 6 lines · Ard Biesheuvel
arch/x86/coco/tdx/tdx-shared.c +6
-0+void __noreturn tdx_panic (const char *msg) +{ +struct tdx_module_args args = { + .r10 =TDX_HYPERCALL_STANDARD , + .r11 =TDVMCALL_REPORT_FATAL_ERROR , + .r12 =0 ,/* Error code: 0 is Panic */
arch/x86/coco/tdx/tdx-shared.c noinstr u64 __tdx_hypercall(struct tdx_module_args *args) · line 108
Quoted 10 lines
arch/x86/coco/tdx/tdx-shared.c +10
-0+ }; +union { +/* Define register order according to the GHCI */ +struct {u64 r14, r15, rbx, rdi, rsi, r8, r9, rdx; }; + + char bytes[64] __nonstring; + } message = {}; + +/* VMM assumes '\0' in byte 65, if the message took all 64 bytes */ +memcpy (message.bytes, msg,strnlen (msg,sizeof (message)));
I'd probably do here:
#define MSG_SZ 64
right above them in the function and use that everywhere, esp. instead of sizeof(message).
Signature · 4 lines
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquetteBorislav Petkov
maintainerRe: [PATCH 1/3] x86/tdx: Share tdx_panic() with the EFI stub
In reply to Ard BiesheuvelQuoted 19 lines · Ard Biesheuvel
Move the implementation of
tdx_panic()into the source file that is shared with the decompressor and the EFI stub.Use
memcpy()andstrnlen()instead ofstrtomem_pad(), as the latter does not exist in the early boot code.Note that
__tdx_hypercall()may call__tdx_hypercall_failed()if the hypercall returns with an error (while it should never return to begin with).__tdx_hypercall_failed()calls the decompressor'serror()routine, which prints a message and then loops forever.When called from the EFI stub, this
error()call may attempt to use port I/O to the default serial port rather than the TDX hypercalls which the decompressor uses normally to print diagnostics to the console, but this is fine: given that this situation only occurs after a catastrophic error, and a subsequent spurious return fromtdx_panic(), whethererror()uses port I/O or not is rather moot at that point, as long as it never returns.
Quoted 6 lines
Signed-off-by Ard Biesheuvel <[email protected]>--- arch/x86/coco/tdx/tdx-shared.c | 35 ++++++++++++++++++++ arch/x86/coco/tdx/tdx.c | 35 -------------------- arch/x86/include/asm/shared/tdx.h | 1 + 3 files changed, 36 insertions(+), 35 deletions(-)
Nah, that doesn't work:
ld: arch/x86/boot/compressed/tdx-shared.o: in function `tdx_panic':
tdx-shared.c:(.text+0x2a3): undefined reference to `__fortify_panic'
ld: tdx-shared.c:(.text+0x2b2): undefined reference to `__fortify_panic'
ld: arch/x86/boot/compressed/vmlinux: hidden symbol `__SCK__WARN_trap' isn't defined
ld: final link failed: bad value
make[3]: *** [arch/x86/boot/compressed/Makefile:117: arch/x86/boot/compressed/vmlinux] Error 1 make[2]: *** [arch/x86/boot/Makefile:96: arch/x86/boot/compressed/vmlinux] Error 2 make[1]: *** [arch/x86/Makefile:304: bzImage] Error 2
make: *** [Makefile:248: __sub-make] Error 2
Signature · 4 lines
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquetteArd Biesheuvel
maintainerRe: [PATCH 1/3] x86/tdx: Share tdx_panic() with the EFI stub
Quoted 29 lines · Borislav Petkov
Quoted 19 lines · Ard Biesheuvel
Move the implementation of
tdx_panic()into the source file that is shared with the decompressor and the EFI stub.Use
memcpy()andstrnlen()instead ofstrtomem_pad(), as the latter does not exist in the early boot code.Note that
__tdx_hypercall()may call__tdx_hypercall_failed()if the hypercall returns with an error (while it should never return to begin with).__tdx_hypercall_failed()calls the decompressor'serror()routine, which prints a message and then loops forever.When called from the EFI stub, this
error()call may attempt to use port I/O to the default serial port rather than the TDX hypercalls which the decompressor uses normally to print diagnostics to the console, but this is fine: given that this situation only occurs after a catastrophic error, and a subsequent spurious return fromtdx_panic(), whethererror()uses port I/O or not is rather moot at that point, as long as it never returns.Quoted 6 lines
Signed-off-by Ard Biesheuvel <[email protected]>--- arch/x86/coco/tdx/tdx-shared.c | 35 ++++++++++++++++++++ arch/x86/coco/tdx/tdx.c | 35 -------------------- arch/x86/include/asm/shared/tdx.h | 1 + 3 files changed, 36 insertions(+), 35 deletions(-)Nah, that doesn't work:
Quoted 13 lines
ld:
arch/x86/boot/compressed/tdx-shared.o: in function `tdx_panic':
tdx-shared.c:(.text+0x2a3): undefined reference to `__fortify_panic'
ld: tdx-shared.c:(.text+0x2b2): undefined reference to `__fortify_panic'
ld:arch/x86/boot/compressed/vmlinux: hidden symbol `__SCK__WARN_trap' isn't defined
ld: final link failed: bad value
make[3]: *** [arch/x86/boot/compressed/Makefile:117:arch/x86/boot/compressed/vmlinux] Error 1
make[2]: *** [arch/x86/boot/Makefile:96:arch/x86/boot/compressed/vmlinux] Error 2
make[1]: *** [arch/x86/Makefile:304: bzImage] Error 2
make: *** [Makefile:248: __sub-make] Error 2
The bot hit this error too, but without your change so it is something I will need to address anyway, e.g., by doing
arch/x86/boot/compressed/tdx-shared.c +1 -0
@@ -1,2 +1,3 @@ +#define __NO_FORTIFY#include "error.h" #include "../../coco/tdx/tdx-shared.c"
But I realized the whole union thing is redundant in the first place. We might just do
arch/x86/coco/tdx/tdx-shared.c +4 -6
@@ -97,15 +97,13 @@ void __noreturn tdx_panic(const char *msg) .r11 = TDVMCALL_REPORT_FATAL_ERROR, .r12 = 0, /* Error code: 0 is Panic */ }; -union { -/* Define register order according to the GHCI */ -struct {u64 r14, r15, rbx, rdi, rsi, r8, r9, rdx; }; - - char bytes[64] __nonstring; +/* Define register order according to the GHCI */ +struct { +u64 r14, r15, rbx, rdi, rsi, r8, r9, rdx; } message = {};/* VMM assumes '\0' in byte 65, if the message took all 64 bytes */ -memcpy (message.bytes, msg,strnlen (msg,sizeof (message))); +memcpy (&message, msg,strnlen (msg,sizeof (message))); args.r8 = message.r8; args.r9 = message.r9;
Ard Biesheuvel
maintainerPATCH 2/3 x86/boot: Move unaccepted memory handling out of the decompressor
In reply to Ard Biesheuvelarch_accept_memory() is an arch-specific hook that is required by the EFI stub when processing memory that the firmware reports to the OS as EFI_UNACCEPTED_MEMORY.
This hook is called after ExitBootServices() has been called, as before that point, the EFI memory map may get updated behind the back of the running EFI stub, making it difficult to get a stable view on it while iterating over the entries.
Currently, the x86 version of this hook is implemented in its decompressor rather than in the EFI stub itself, in a manner that is
problematic: when an error occurs, it calls the decompressor's error() routine, but without having gone through the decompressor initialization code. This means it will resort to direct port I/O rather than the hypercall based interface that TDX guests would use otherwise.
Conceptually, code that is only called from the EFI stub, and never by the decompressor when doing legacy boot, belongs in the EFI stub and not in the decompressor.
So move it into the x86-specific EFI stub code, replacing the error() on the TDX path with tdx_panic(), and dropping the error() when no CC support is detected - the kernel can decide what to do in this case after it has booted.
--- arch/x86/boot/compressed/mem.c | 42 -------------------- arch/x86/boot/compressed/sev.h | 2 - arch/x86/include/asm/sev.h | 2 + drivers/firmware/efi/libstub/x86-stub.c | 40 +++++++++++++++++++ 4 files changed, 42 insertions(+), 44 deletions(-)
arch/x86/boot/compressed/mem.c +0 -42
@@ -2,48 +2,6 @@#include "error.h" #include "misc.h" -#include "tdx.h" -#include "sev.h" -#include <asm/shared/tdx.h> - -/* -* accept_memory() and process_unaccepted_memory() called from EFI stub which -* runs before decompressor and its early_tdx_detect(). -* -* Enumerate TDX directly from the early users. -*/ -static bool early_is_tdx_guest (void ) -{ -static bool once; -static bool is_tdx; - -if (!IS_ENABLED (CONFIG_INTEL_TDX_GUEST )) -return false; - -if (!once) { -u32 eax, sig[3 ]; - -cpuid_count (TDX_CPUID_LEAF_ID ,0 , &eax, - &sig[0 ], &sig[2 ], &sig[1 ]); - is_tdx = !memcmp (TDX_IDENT , sig,sizeof (sig)); - once = true; - } - -return is_tdx; -} - -void arch_accept_memory (phys_addr_t start,phys_addr_t end) -{ -/* Platform-specific memory-acceptance call goes here */ -if (early_is_tdx_guest ()) { -if (!tdx_accept_memory (start, end)) -panic ("TDX: Failed to accept memory\n" ); - }else if (early_is_sevsnp_guest ()) { -snp_accept_memory (start, end); - }else { -error ("Cannot accept memory: unknown platform\n" ); - } -}bool init_unaccepted_memory (void ) {
arch/x86/boot/compressed/sev.h +0 -2
@@ -14,7 +14,6 @@void snp_accept_memory (phys_addr_t start,phys_addr_t end);u64 sev_get_status (void ); -bool early_is_sevsnp_guest (void );static inline u64 sev_es_rd_ghcb_msr (void ) {
@@ -37,7 +36,6 @@ static inline void sev_es_wr_ghcb_msr(u64 val)static inline void snp_accept_memory (phys_addr_t start,phys_addr_t end) { }static inline u64 sev_get_status (void ) {return 0 ; } -static inline bool early_is_sevsnp_guest (void ) {return false; }#endif
arch/x86/include/asm/sev.h +2 -0
@@ -464,6 +464,8 @@ static __always_inline void sev_es_nmi_complete(void)extern int __init sev_es_efi_map_ghcbs_cas (pgd_t *pgd);extern void sev_enable (struct boot_params *bp); +bool early_is_sevsnp_guest (void ); +/* * RMPADJUST modifies the RMP permissions of a page of a lesser- * privileged (numerically higher) VMPL.
drivers/firmware/efi/libstub/x86-stub.c +40 -0
@@ -10,6 +10,7 @@#include <linux/pci.h> #include <linux/stddef.h> +#include <asm/cpuid/api.h> #include <asm/efi.h> #include <asm/e820/types.h> #include <asm/setup.h>
@@ -17,6 +18,7 @@#include <asm/boot.h> #include <asm/kaslr.h> #include <asm/sev.h> +#include <asm/shared/tdx.h> #include "efistub.h" #include "x86-stub.h"
@@ -1068,3 +1070,41 @@ void efi64_stub_entry(efi_handle_t handle, efi_system_table_t *sys_table_arg, struct boot_params *boot_params);#endif #endif + +#ifdef CONFIG_UNACCEPTED_MEMORY +/* +* process_unaccepted_memory() is called after ExitBootServices(), and so these +* memory acceptance routines cannot rely on EFI protocols for detecting the +* presence of TDX or SEV-SNP, or emit any kind of output if any error +* conditions are detected. +*/ +static bool early_is_tdx_guest (void ) +{ +static bool once; +static bool is_tdx; + +if (!IS_ENABLED (CONFIG_INTEL_TDX_GUEST )) +return false; + +if (!once) { +u32 eax, sig[3 ]; + +cpuid_count (TDX_CPUID_LEAF_ID ,0 , &eax, + &sig[0 ], &sig[2 ], &sig[1 ]); + is_tdx = !memcmp (TDX_IDENT , sig,sizeof (sig)); + once = true; + } + +return is_tdx; +} + +void arch_accept_memory (phys_addr_t start,phys_addr_t end) +{ +if (early_is_tdx_guest ()) { +if (!tdx_accept_memory (start, end)) +tdx_panic ("Failed to accept memory" ); + }else if (early_is_sevsnp_guest ()) { +snp_accept_memory (start, end); + } +} +#endif
Signature · 1 line
2.47.3
Ard Biesheuvel
maintainerPATCH 3/3 x86/boot: Drop unused implementation of panic()
In reply to Ard BiesheuvelThe decompressor has its own implementation of panic(), which is based on the vsnprintf() routine provided by the EFI stub.
The decompressor's panic() has no remaining users, and is implemented in terms of the EFI stub's snprintf() implementation, which is going away (and relying on the EFI stub from code that does not execute in the context of the EFI boot services is a bad idea in general).
So drop it.
--- arch/x86/boot/compressed/error.c | 19 ------------------- arch/x86/boot/compressed/error.h | 1 - 2 files changed, 20 deletions(-)
arch/x86/boot/compressed/error.c +0 -19
@@ -22,22 +22,3 @@ void error(char *m)while (1 ) asm("hlt" ); } - -/* EFI libstub provides vsnprintf() */ -#ifdef CONFIG_EFI_STUB -void panic (const char *fmt, ...) -{ -static char buf[1024 ]; -va_list args; -int len; - -va_start (args, fmt); - len =vsnprintf (buf,sizeof (buf), fmt, args); -va_end (args); - -if (len && buf[len -1 ] =='\n' ) - buf[len -1 ] ='\0' ; - -error (buf); -} -#endif
arch/x86/boot/compressed/error.h +0 -1
@@ -6,6 +6,5 @@void warn (const char *m);void error (char *m)__noreturn ; -void panic (const char *fmt, ...)__noreturn __cold ;#endif /* BOOT_COMPRESSED_ERROR_H */
Signature · 1 line
2.47.3
Borislav Petkov
maintainer+ Rick and linux-coco
Quoted 39 lines · Ard Biesheuvel
This is a follow-up to [0].
Move
arch_accept_memory(), which is only called by the EFI stub and never by the decompressor on a non-EFI boot, into the EFI stub, and avoid relying directly on decompressor APIs such aserror().Instead, call
tdx_panic()on a failure to accept memory in a TDX guest.This makes the decompressor's implementation of
panic()obsolete, and allows it to be removed. This is a prerequisite for dropping the implementation ofsnprintf()from the EFI stub entirely, which is what the series containing [0] implements.[0] http://lore.kernel.org/r/[email protected]
Cc Kiryl Shutsemau (Meta) <[email protected]>Cc Borislav Petkov <[email protected]>Ard Biesheuvel (3):
x86/tdx: Sharetdx_panic()with the EFI stub
x86/boot: Move unaccepted memory handling out of the decompressor x86/boot: Drop unused implementation ofpanic()arch/x86/boot/compressed/error.c | 19 --------- arch/x86/boot/compressed/error.h | 1 - arch/x86/boot/compressed/mem.c | 42 -------------------- arch/x86/boot/compressed/sev.h | 2 - arch/x86/coco/tdx/tdx-shared.c | 35 ++++++++++++++++ arch/x86/coco/tdx/tdx.c | 35 ---------------- arch/x86/include/asm/sev.h | 2 + arch/x86/include/asm/shared/tdx.h | 1 + drivers/firmware/efi/libstub/x86-stub.c | 40 +++++++++++++++++++ 9 files changed, 78 insertions(+), 99 deletions(-)base-commit:
cee9395acd8043be0644b25c34bfa86623f2b935Signature · 1 line
2.47.3
Signature · 4 lines
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquetteEdgecombe, Rick P
maintainerKiryl,
Quoted 7 lines · Ard Biesheuvel
This is a follow-up to [0].
Move
arch_accept_memory(), which is only called by the EFI stub and never by the decompressor on a non-EFI boot, into the EFI stub, and avoid relying directly on decompressor APIs such aserror().Instead, call
tdx_panic()on a failure to accept memory in a TDX guest.
The TDG accept call can return:
TDX_OPERAND_INVALID - That would be a bug in Linux TDX code, we don't need to pass it to the caller. Probably don't need to handle it. TDX_PAGE_ALREADY_ACCEPTED - Potential security sensitive error that is the guests fault.TDX_PAGE_SIZE_MISMATCH - Already handled. Sort of. Not sure if it is robust to S-EPT page size changes?TDX_SUCCESS - Already handledTDX_OPERAND_BUSY - Can happen from transient host side S-EPT locking. Or it could be the guest's fault if they are accepting the same page from different vCPUs at the same time, so the guest case is similar to TDX_PAGE_ALREADY_ACCEPTED.
I guess the caller could care about TDX_PAGE_ALREADY_ACCEPTED errors. But SNP doesn't do anything for this case. It seems like part of the problem is that we are passing errors back that the caller can't feasibly handle.
Kiryl Shutsemau
maintainerQuoted 9 lines · Rick P
Kiryl,
Quoted 6 lines · Ard Biesheuvel
This is a follow-up to [0].
Move
arch_accept_memory(), which is only called by the EFI stub and never by the decompressor on a non-EFI boot, into the EFI stub, and avoid relying directly on decompressor APIs such aserror().
Quoted 8 lines
Quoted 1 line
Instead, call
tdx_panic()on a failure to accept memory in a TDX guest.The TDG accept call can return:
TDX_OPERAND_INVALID- That would be a bug in Linux TDX code, we don't need to pass it to the caller. Probably don't need to handle it.TDX_PAGE_ALREADY_ACCEPTED- Potential security sensitive error that is the guests fault.
Yes, but it can also be safe in some contexts as we discussed before.
Quoted 11 lines
TDX_PAGE_SIZE_MISMATCH- Already handled. Sort of. Not sure if it is robust to S-EPT page size changes?TDX_SUCCESS- Already handledTDX_OPERAND_BUSY- Can happen from transient host side S-EPT locking. Or it could be the guest's fault if they are accepting the same page from different vCPUs at the same time, so the guest case is similar toTDX_PAGE_ALREADY_ACCEPTED.I guess the caller could care about
TDX_PAGE_ALREADY_ACCEPTEDerrors. But SNP doesn't do anything for this case. It seems like part of the problem is that we are passing errors back that the caller can't feasibly handle.
Maybe. I don't understand SNP model and why they don't care about errors here.
Do you have a proposal here?
Signature · 1 line
Kiryl Shutsemau / Kirill A. Shutemov
Edgecombe, Rick P
maintainer+Yan
Quoted 10 lines · Kiryl Shutsemau
Quoted 5 lines
I guess the caller could care about
TDX_PAGE_ALREADY_ACCEPTEDerrors. But SNP
doesn't do anything for this case. It seems like part of the problem is that we
are passing errors back that the caller can't feasibly handle.Maybe. I don't understand SNP model and why they don't care about errors here.
Do you have a proposal here?
Yan pointed out that future TDX modules will not take an S-EPT entry lock on accepting a NP S-EPT entry. However, I think this won't prevent guest caused busys on re-accept attempts?
Here is a strawman proposal. Please consider critically.
TDX_PAGE_ALREADY_ACCEPTED: Zero memory that was re-accepted and treat it as a success.
TDX_OPERAND_BUSY host contention: Loop and blame the host for any infinite loops. It can be not unlike if the host zap's the EPT and the guest gets stuck on infinite EPT violations. Except that the host does not get any notification of the trouble it is causing... Some sort of TDX arch change to notify the host of heavy guest-host contention could be worked on in the background?
TDX_OPERAND_BUSY guest contention: RW lock around accept. Normally take the lock for read, and a single fallback attempt takes the lock for write.
Then I think all errors accept() would want to return to the callers are handled.
The only thing I'm not sure about is TDX_PAGE_ALREADY_ACCEPTED. Some callers may want a warning on that case. I think a warning is more in line with what we have done in similar situations, rather than panic. But a warming would throw off the EFI stub callers apparently.
Ard Biesheuvel
maintainerQuoted 33 lines · Rick P
+Yan
Quoted 10 lines · Kiryl Shutsemau
Quoted 5 lines
I guess the caller could care about
TDX_PAGE_ALREADY_ACCEPTEDerrors. But SNP
doesn't do anything for this case. It seems like part of the problem is that we
are passing errors back that the caller can't feasibly handle.Maybe. I don't understand SNP model and why they don't care about errors here.
Do you have a proposal here?
Yan pointed out that future TDX modules will not take an S-EPT entry lock on accepting a NP S-EPT entry. However, I think this won't prevent guest caused busys on re-accept attempts?
Here is a strawman proposal. Please consider critically.
TDX_PAGE_ALREADY_ACCEPTED: Zero memory that was re-accepted and treat it as a success.
TDX_OPERAND_BUSYhost contention: Loop and blame the host for any infinite loops. It can be not unlike if the host zap's the EPT and the guest gets stuck on infinite EPT violations. Except that the host does not get any notification of the trouble it is causing... Some sort of TDX arch change to notify the host of heavy guest-host contention could be worked on in the background?
TDX_OPERAND_BUSYguest contention: RW lock around accept. Normally take the lock for read, and a single fallback attempt takes the lock for write.
Quoted 7 lines
Then I think all errors
accept()would want to return to the callers are handled.The only thing I'm not sure about is
TDX_PAGE_ALREADY_ACCEPTED. Some callers may want a warning on that case. I think a warning is more in line with what we have done in similar situations, rather than panic. But a warming would throw off the EFI stub callers apparently.
The memory acceptance occurs after ExitBootServices(), and so there is little the EFI stub can do at this point except maybe set a variable with an error message and reset the system. If that is better than a panic(), this is something we can consider.
However, I'd like to avoid going off into the weeds here with something that was intended to simply move some logic from one place to the other. The current code prints an error to the UART I/O port (rather than the PV console TDX uses when running in the decompressor) and then loops forever. Is that something we could retain for this series, and then continue the above discussion in the context of a separate series based on top of that?
Edgecombe, Rick P
maintainerQuoted 11 lines · Ard Biesheuvel
The memory acceptance occurs after
ExitBootServices(), and so there is little the EFI stub can do at this point except maybe set a variable with an error message and reset the system. If that is better than apanic(), this is something we can consider.However, I'd like to avoid going off into the weeds here with something that was intended to simply move some logic from one place to the other. The current code prints an error to the UART I/O port (rather than the PV console TDX uses when running in the decompressor) and then loops forever. Is that something we could retain for this series, and then continue the above discussion in the context of a separate series based on top of that?
Uhh, yea. I guess I don't see a strong reason. This definitely has some aspects unrelated to removing panic() from that EFI stub. I thought we might find an alternative simpler solution quickly.
Quoted 9 lines · Rick P
+Yan
Quoted 6 lines · Kiryl Shutsemau
Quoted 5 lines
I guess the caller could care about
TDX_PAGE_ALREADY_ACCEPTEDerrors. But SNP
doesn't do anything for this case. It seems like part of the problem is that we
are passing errors back that the caller can't feasibly handle.
Quoted 8 lines
Quoted 4 lines
Maybe. I don't understand SNP model and why they don't care about errors here.
Do you have a proposal here?
Yan pointed out that future TDX modules will not take an S-EPT entry lock on accepting a NP S-EPT entry. However, I think this won't prevent guest caused busys on re-accept attempts?
Re-accept attempts may occur due to:
(a) two concurrent ACCEPT TDCALLs, where the one that arrives slightly later returns either TDACCEPT_ALREADY_ACCEPTED or TDX_OPERAND_BUSY.
(b) two successive ACCEPT TDCALLs on the same GPA.
Since Linux guest always invokes ACCEPT TDCALL before a memory access, and accept_memory() always checks the unaccepted_table->bitmap before invoking the ACCEPT TDCALL, case (b) should be impossible in practice, right?
Is case (a) a valid scenario, and does it actually occur in a Linux guest?
Quoted 23 lines · Yan Zhao
Quoted 9 lines · Rick P
+Yan
Quoted 6 lines · Kiryl Shutsemau
Quoted 5 lines
I guess the caller could care about
TDX_PAGE_ALREADY_ACCEPTEDerrors. But SNP
doesn't do anything for this case. It seems like part of the problem is that we
are passing errors back that the caller can't feasibly handle.Quoted 8 lines
Quoted 4 lines
Maybe. I don't understand SNP model and why they don't care about errors here.
Do you have a proposal here?
Yan pointed out that future TDX modules will not take an S-EPT entry lock on accepting a NP S-EPT entry. However, I think this won't prevent guest caused busys on re-accept attempts?
Re-accept attempts may occur due to:
(a) two concurrent ACCEPT TDCALLs, where the one that arrives slightly later returns eitherTDACCEPT_ALREADY_ACCEPTEDorTDX_OPERAND_BUSY.
(b) two successive ACCEPT TDCALLs on the same GPA.
Quoted 5 lines
Since Linux guest always invokes ACCEPT TDCALL before a memory access, and
accept_memory()always checks theunaccepted_table->bitmapbefore invoking the ACCEPT TDCALL, case (b) should be impossible in practice, right?Is case (a) a valid scenario, and does it actually occur in a Linux guest?
Case (a) should be prevented by the unaccepted_memory_lock in accept_memory(), right?
Kiryl Shutsemau
maintainerQuoted 24 lines · Yan Zhao
Quoted 23 lines · Yan Zhao
Quoted 9 lines · Rick P
+Yan
Quoted 6 lines · Kiryl Shutsemau
Quoted 5 lines
I guess the caller could care about
TDX_PAGE_ALREADY_ACCEPTEDerrors. But SNP
doesn't do anything for this case. It seems like part of the problem is that we
are passing errors back that the caller can't feasibly handle.Quoted 8 lines
Quoted 4 lines
Maybe. I don't understand SNP model and why they don't care about errors here.
Do you have a proposal here?
Yan pointed out that future TDX modules will not take an S-EPT entry lock on accepting a NP S-EPT entry. However, I think this won't prevent guest caused busys on re-accept attempts?
Re-accept attempts may occur due to:
(a) two concurrent ACCEPT TDCALLs, where the one that arrives slightly later returns eitherTDACCEPT_ALREADY_ACCEPTEDorTDX_OPERAND_BUSY.
(b) two successive ACCEPT TDCALLs on the same GPA.
Quoted 7 lines
Quoted 5 lines
Since Linux guest always invokes ACCEPT TDCALL before a memory access, and
accept_memory()always checks theunaccepted_table->bitmapbefore invoking the ACCEPT TDCALL, case (b) should be impossible in practice, right?Is case (a) a valid scenario, and does it actually occur in a Linux guest?
Case (a) should be prevented by the
unaccepted_memory_lockinaccept_memory(), right?
Right, for accept_memory().
The lock itself is dropped around the TDCALL, but the range stays on accepting_list until the bits are cleared, and the overlap check is done in unit_size granularity, so a second caller for the same unit spins until the first one is done and then finds the bits clear. Both (a) and
(b) are covered there.
There is a second ACCEPT issuer that does not look at the bitmap at
all: shared->private conversion in tdx_enc_status_changed().
It does not need the bitmap because the memory came from the page allocator or memblock and got accepted before it was handed out. It also has no per-range serialization; mem_enc_lock is only taken for read. So ALREADY_ACCEPTED on that path means either the guest converted the same range twice (a double-free class of bug) or the VMM acked MapGPA(shared) but never removed the private page. Either way the page there is the one the guest accepted earlier, and the failure already comes back as -EIO rather than a panic.
Signature · 1 line
Kiryl Shutsemau / Kirill A. Shutemov
Ard Biesheuvel
maintainerQuoted 44 lines · Kiryl Shutsemau
Quoted 24 lines · Yan Zhao
Quoted 23 lines · Yan Zhao
Quoted 9 lines · Rick P
+Yan
Quoted 6 lines · Kiryl Shutsemau
Quoted 5 lines
I guess the caller could care about
TDX_PAGE_ALREADY_ACCEPTEDerrors. But SNP
doesn't do anything for this case. It seems like part of the problem is that we
are passing errors back that the caller can't feasibly handle.Quoted 8 lines
Quoted 4 lines
Maybe. I don't understand SNP model and why they don't care about errors here.
Do you have a proposal here?
Yan pointed out that future TDX modules will not take an S-EPT entry lock on accepting a NP S-EPT entry. However, I think this won't prevent guest caused busys on re-accept attempts?
Re-accept attempts may occur due to:
(a) two concurrent ACCEPT TDCALLs, where the one that arrives slightly later returns eitherTDACCEPT_ALREADY_ACCEPTEDorTDX_OPERAND_BUSY.
(b) two successive ACCEPT TDCALLs on the same GPA.Quoted 7 lines
Quoted 5 lines
Since Linux guest always invokes ACCEPT TDCALL before a memory access, and
accept_memory()always checks theunaccepted_table->bitmapbefore invoking the ACCEPT TDCALL, case (b) should be impossible in practice, right?Is case (a) a valid scenario, and does it actually occur in a Linux guest?
Case (a) should be prevented by the
unaccepted_memory_lockinaccept_memory(), right?Right, for
accept_memory().The lock itself is dropped around the TDCALL, but the range stays on
accepting_listuntil the bits are cleared, and the overlap check is done inunit_sizegranularity, so a second caller for the same unit spins until the first one is done and then finds the bits clear. Both (a) and
(b) are covered there.There is a second ACCEPT issuer that does not look at the bitmap at
all:shared->privateconversion intdx_enc_status_changed().
Quoted 9 lines
It does not need the bitmap because the memory came from the page allocator or memblock and got accepted before it was handed out. It also has no per-range serialization;
mem_enc_lockis only taken for read. SoALREADY_ACCEPTEDon that path means either the guest converted the same range twice (a double-free class of bug) or the VMM acked MapGPA(shared) but never removed the private page. Either way the page there is the one the guest accepted earlier, and the failure already comes back as-EIOrather than a panic.
Is there any way we could make some progress here?
Ultimately, the thing I am after is to get rid of the call from the decompressor to EFI stub's snprintf(), which I want to remove. For the time being, I am more than happy leaving the memory acceptance where it is, and just dropping the call to snprintf().
Kiryl Shutsemau
maintainerQuoted 57 lines · Ard Biesheuvel
Quoted 44 lines · Kiryl Shutsemau
Quoted 24 lines · Yan Zhao
Quoted 23 lines · Yan Zhao
Quoted 9 lines · Rick P
+Yan
Quoted 6 lines · Kiryl Shutsemau
Quoted 5 lines
I guess the caller could care about
TDX_PAGE_ALREADY_ACCEPTEDerrors. But SNP
doesn't do anything for this case. It seems like part of the problem is that we
are passing errors back that the caller can't feasibly handle.Quoted 8 lines
Quoted 4 lines
Maybe. I don't understand SNP model and why they don't care about errors here.
Do you have a proposal here?
Yan pointed out that future TDX modules will not take an S-EPT entry lock on accepting a NP S-EPT entry. However, I think this won't prevent guest caused busys on re-accept attempts?
Re-accept attempts may occur due to:
(a) two concurrent ACCEPT TDCALLs, where the one that arrives slightly later returns eitherTDACCEPT_ALREADY_ACCEPTEDorTDX_OPERAND_BUSY.
(b) two successive ACCEPT TDCALLs on the same GPA.Quoted 7 lines
Quoted 5 lines
Since Linux guest always invokes ACCEPT TDCALL before a memory access, and
accept_memory()always checks theunaccepted_table->bitmapbefore invoking the ACCEPT TDCALL, case (b) should be impossible in practice, right?Is case (a) a valid scenario, and does it actually occur in a Linux guest?
Case (a) should be prevented by the
unaccepted_memory_lockinaccept_memory(), right?Right, for
accept_memory().The lock itself is dropped around the TDCALL, but the range stays on
accepting_listuntil the bits are cleared, and the overlap check is done inunit_sizegranularity, so a second caller for the same unit spins until the first one is done and then finds the bits clear. Both (a) and
(b) are covered there.There is a second ACCEPT issuer that does not look at the bitmap at
all:shared->privateconversion intdx_enc_status_changed().Quoted 9 lines
It does not need the bitmap because the memory came from the page allocator or memblock and got accepted before it was handed out. It also has no per-range serialization;
mem_enc_lockis only taken for read. SoALREADY_ACCEPTEDon that path means either the guest converted the same range twice (a double-free class of bug) or the VMM acked MapGPA(shared) but never removed the private page. Either way the page there is the one the guest accepted earlier, and the failure already comes back as-EIOrather than a panic.
Quoted 6 lines
Is there any way we could make some progress here?
Ultimately, the thing I am after is to get rid of the call from the decompressor to EFI stub's
snprintf(), which I want to remove. For the time being, I am more than happy leaving the memory acceptance where it is, and just dropping the call tosnprintf().
Please go ahead with the move. Rick's questions are about what tdx_accept_memory() should return, not about where the caller lives, and can be sorted out separately.
Signature · 1 line
Kiryl Shutsemau / Kirill A. Shutemov
Edgecombe, Rick P
maintainerQuoted 10 lines · Kiryl Shutsemau
Quoted 6 lines
Is there any way we could make some progress here?
Ultimately, the thing I am after is to get rid of the call from the decompressor to EFI stub's
snprintf(), which I want to remove. For the time being, I am more than happy leaving the memory acceptance where it is, and just dropping the call tosnprintf().Please go ahead with the move. Rick's questions are about what
tdx_accept_memory()should return, not about where the caller lives, and can be sorted out separately.
Yep. I tried to do a quick smoke test and got a similar build error as Boris. I guess you will send a new revision?
ld: warning: orphan section .altinstructions' from drivers/firmware/efi/libstub/x86-stub.stub.o' being placed in section `.altinstructions'
ld: warning: orphan section .altinstr_replacement' from drivers/firmware/efi/libstub/x86-stub.stub.o' being placed in section `.altinstr_replacement'
ld: Unexpected GOT/PLT entries detected!
ld: Unexpected run-time procedure linkages detected!
ld: Unexpected run-time relocations (.rela) detected!
ld: arch/x86/boot/compressed/tdx-shared.o: in function `tdx_panic.cold':
tdx-shared.c:(.text.unlikely+0xc): undefined reference to `__fortify_panic'
ld: tdx-shared.c:(.text.unlikely+0x1e): undefined reference to `__fortify_panic'
ld: drivers/firmware/efi/libstub/x86-stub.stub.o: in function `arch_accept_memory':
x86-stub.c:(.text+0x17f5): undefined reference to `pv_ops'
ld: drivers/firmware/efi/libstub/x86-stub.stub.o:(.altinstr_replacement+0x1):
undefined reference to `BUG_func'
ld: arch/x86/boot/compressed/vmlinux: hidden symbol `__SCK__WARN_trap' isn't defined
ld: final link failed: bad value
Ard Biesheuvel
maintainerQuoted 8 lines · Rick P
Quoted 7 lines · Kiryl Shutsemau
Quoted 6 lines
Is there any way we could make some progress here?
Ultimately, the thing I am after is to get rid of the call from the decompressor to EFI stub's
snprintf(), which I want to remove. For the time being, I am more than happy leaving the memory acceptance where it is, and just dropping the call tosnprintf().
Quoted 7 lines
Quoted 3 lines
Please go ahead with the move. Rick's questions are about what
tdx_accept_memory()should return, not about where the caller lives, and can be sorted out separately.Yep. I tried to do a quick smoke test and got a similar build error as Boris. I guess you will send a new revision?
Yes, thanks for testing.