Skip to contentSkip to conversation

Move memory acceptance x86 arch code into EFI stub

PATCH Superseded Ard Biesheuvel 5 participants 20 messages Sep 14 – Sep 25

Subsystems (3)

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore

Ard Biesheuvel

author maintainer
#0 lore raw

cover 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]

Cc Kiryl Shutsemau (Meta) <[email protected]>
Cc Borislav Petkov <[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
author maintainer
#1 lore raw

PATCH 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
index 1655aa56a0a5..5fc36c8b35db 100644
@@ -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
index f904a636d449..a9a16d0fb5c2 100644
@@ -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
index f20e91d7ac35..e5785258e547 100644
@@ -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
maintainer
#2 lore raw

Re: [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-netiquette
maintainer
#3 lore raw

Re: [PATCH 1/3] x86/tdx: Share tdx_panic() with the EFI stub

In reply to Ard Biesheuvel
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() 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.

Quoted 6 lines
---
 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-netiquette
author maintainer
#4 lore raw

Re: [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() 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.

Quoted 6 lines
---
 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;
author maintainer
#5 lore raw

PATCH 2/3 x86/boot: Move unaccepted memory handling out of the decompressor

In reply to Ard Biesheuvel

arch_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
index 0e9f84ab4bdc..1721af3a8039 100644
@@ -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
index 22637b416b46..62e50c2e71ed 100644
@@ -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
index 9e7a077c445d..843bf463d14d 100644
@@ -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
index cef32e2c82d8..5009623e4a37 100644
@@ -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
author maintainer
#6 lore raw

PATCH 3/3 x86/boot: Drop unused implementation of panic()

In reply to Ard Biesheuvel

The 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
index 19a8251de506..ce5ed7d8265e 100644
@@ -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
index 31f9e080d61a..87062dea9a20 100644
@@ -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
maintainer
#7 lore raw
In reply to Ard Biesheuvel

+ 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 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]

Cc Kiryl Shutsemau (Meta) <[email protected]>
Cc Borislav Petkov <[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
Signature · 4 lines
Regards/Gruss,
    Boris.

https://people.kernel.org/tglx/notes-about-netiquette
#8 lore raw
In reply to Ard Biesheuvel

Kiryl,

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 as error().

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 handled
TDX_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.

maintainer
#9 lore raw
Quoted 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 as error().

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 handled
TDX_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.

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
#10 lore raw

+Yan

Quoted 10 lines · Kiryl Shutsemau
Quoted 5 lines

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.

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.

author maintainer
#11 lore raw
Quoted 33 lines · Rick P

+Yan

Quoted 10 lines · Kiryl Shutsemau
Quoted 5 lines

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.

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.

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?

#12 lore raw
Quoted 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 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?

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.

#13 lore raw
In reply to Edgecombe, Rick P
Quoted 9 lines · Rick P

+Yan

Quoted 6 lines · Kiryl Shutsemau
Quoted 5 lines

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.

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?

#14 lore raw
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_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.

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.

Quoted 5 lines

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?

Case (a) should be prevented by the unaccepted_memory_lock in accept_memory(), right?

maintainer
#15 lore raw
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_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.

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.

Quoted 7 lines
Quoted 5 lines

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?

Case (a) should be prevented by the unaccepted_memory_lock in accept_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
author maintainer
#16 lore raw
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_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.

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.

Quoted 7 lines
Quoted 5 lines

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?

Case (a) should be prevented by the unaccepted_memory_lock in accept_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().

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_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.

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().

maintainer
#17 lore raw
Quoted 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_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.

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.

Quoted 7 lines
Quoted 5 lines

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?

Case (a) should be prevented by the unaccepted_memory_lock in accept_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().

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_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.

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 to snprintf().

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
#18 lore raw
Quoted 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 to snprintf().

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

author maintainer
#19 lore raw
Quoted 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 to snprintf().

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.