Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Review Phase 1 TrenchBoot Intel TXT and TPM 1.2 support Rull Requests #9

Open
BeataZdunczyk opened this issue Apr 3, 2023 · 14 comments
Labels
P: default Priority: default. Default priority for new issues, to be replaced given sufficient information. T: feature request Type: feature reguest. A new feature for the project. W: in review Workflow: in review. The issue is being reviewed for completeness.

Comments

@BeataZdunczyk
Copy link
Member

Is your feature request related to a problem? Please describe.

This issue tracks the review, and merge process for the Pull Requests created to
add TPM 1.2 support for Intel TXT in TrenchBoot GRUB2 and to implement Xen
Secure Launch with Intel TXT support in Xen for TrenchBoot as Anti Evil Maid
project
.The problem is
that TrenchBoot, which is used as an anti-evil maid solution in Qubes OS, does
not currently support TPM 1.2 on Intel TXT path and does not have Xen Secure
Launch with Intel TXT support. This limits the hardware compatibility for
TrenchBoot and hinders its ability to protect against certain attacks.

Is your feature request related to a new idea or technology that
would benefit the project? Please describe.

The feature request is to add TPM 1.2 support for Intel TXT in TrenchBoot GRUB2
and to implement Xen Secure Launch with Intel TXT support in Xen for TrenchBoot.
This would allow TrenchBoot to support older Intel hardware with Intel TXT and
launch Xen directly via DRTM on Intel hardware, improving its hardware
compatibility and security.

Describe the solution you'd like

Following PRs merged:

  1. Add TPM 1.2 support for Intel TXT in TrenchBoot GRUB2:
    Inteltxt support QubesOS/qubes-grub2#13
  2. Implement Xen Secure Launch with Intel TXT support in Xen for TrenchBoot:
    Inteltxt support QubesOS/qubes-vmm-xen#160

Describe alternatives you've considered
N/A

Additional context

This feature request is Phase 1 for TrenchBoot as Anti Evil Maid project, as
outlined in the documentation: https://docs.dasharo.com/projects/trenchboot-aem/
and https://docs.dasharo.com/projects/trenchboot-aem-v2/

Relevant documentation you've consulted
N/A

@BeataZdunczyk BeataZdunczyk added T: feature request Type: feature reguest. A new feature for the project. P: default Priority: default. Default priority for new issues, to be replaced given sufficient information. W: todo Workflow: todo. The issue is in the initial to do state. labels Apr 3, 2023
@BeataZdunczyk BeataZdunczyk added the W: in progress Workflow: in progress. The issue is actively being worked on. label Nov 15, 2023
@BeataZdunczyk BeataZdunczyk removed the W: todo Workflow: todo. The issue is in the initial to do state. label Dec 15, 2023
@SergiiDmytruk
Copy link
Member

SergiiDmytruk commented Dec 20, 2023

Rebased Xen patches (by now they include TPM 2.0 and processing of DRTM from SLRT) on aem-phase3-rebase of https://github.com/TrenchBoot/xen.

Commits looks a bit different now and most of the code differences are due to taking most of review comments into account (skipped those which might need discussion), especially the one about using a different prefix for SL-related symbols (I went with slaunch_ which was already used in one place and is also used in GRUB changes).

Commits: TrenchBoot/xen@c23e698...aem-phase3-rebase

Diff of diffs (includes "changes" like line number or hash differences):

diff -bu <( git diff --no-ext-diff cbb35e7..3dc5991) <( git diff --no-ext-diff c23e698..150a0a329a)

--- /dev/fd/63	2023-12-20 19:40:09.799219058 +0200
+++ /dev/fd/62	2023-12-20 19:40:09.799219058 +0200
@@ -1,6 +1,6 @@
 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
 new file mode 100644
-index 0000000000..cfb966b60d
+index 0000000000..5bff1deb9b
 --- /dev/null
 +++ b/.github/workflows/build.yml
 @@ -0,0 +1,17 @@
@@ -17,7 +17,7 @@
 +  qubes-dom0-package:
 +    uses: TrenchBoot/.github/.github/workflows/qubes-dom0-package.yml@master
 +    with:
-+      base-commit: 'cbb35e72802f3a285c382a995ef647b59e5caf2f'
++      base-commit: 'c23e698430f3381304643d59b1ae373e36a318d5'
 +      patch-start: 1300
 +      qubes-component: 'vmm-xen'
 +      spec-pattern: '/^Patch1202:/'
@@ -38,10 +38,10 @@
  xen.gz
  ~~~~~~
 diff --git a/xen/arch/x86/Makefile b/xen/arch/x86/Makefile
-index 177a2ff742..594093fcca 100644
+index 4eec765106..76606f7617 100644
 --- a/xen/arch/x86/Makefile
 +++ b/xen/arch/x86/Makefile
-@@ -55,6 +55,7 @@ obj-y += percpu.o
+@@ -56,6 +56,7 @@ obj-y += percpu.o
  obj-y += physdev.o
  obj-$(CONFIG_COMPAT) += x86_64/physdev.o
  obj-y += psr.o
@@ -49,7 +49,7 @@
  obj-y += setup.o
  obj-y += shutdown.o
  obj-y += smp.o
-@@ -63,6 +64,7 @@ obj-y += spec_ctrl.o
+@@ -64,6 +65,7 @@ obj-y += spec_ctrl.o
  obj-y += srat.o
  obj-y += string.o
  obj-y += time.o
@@ -93,10 +93,10 @@
  
  	for (acpiid = 0; acpiid < ARRAY_SIZE(x86_acpiid_to_apicid); acpiid++)
 diff --git a/xen/arch/x86/apic.c b/xen/arch/x86/apic.c
-index 47e6e5fe41..b33e9070e8 100644
+index fcd8d5e8b2..2ac86a3774 100644
 --- a/xen/arch/x86/apic.c
 +++ b/xen/arch/x86/apic.c
-@@ -989,7 +989,7 @@ __next:
+@@ -985,7 +985,7 @@ __next:
       */
      if (boot_cpu_physical_apicid == -1U)
          boot_cpu_physical_apicid = get_apic_id();
@@ -129,7 +129,7 @@
  	$(LD) $(subst x86_64,i386,$(LDFLAGS_DIRECT)) -N -T $(filter %.lds,$^) -o $@ $<
  
 diff --git a/xen/arch/x86/boot/head.S b/xen/arch/x86/boot/head.S
-index 0fb7dd3029..32f99dfc52 100644
+index 245c859dd7..2cd9a3a0b5 100644
 --- a/xen/arch/x86/boot/head.S
 +++ b/xen/arch/x86/boot/head.S
 @@ -3,6 +3,7 @@
@@ -152,7 +152,7 @@
 +        .long	0x42b651cb  /* UUID3 */
 +        .long   0x00000034  /* MLE header size */
 +        .long	0x00020002  /* MLE version 2.2 */
-+        .long   (sl_stub_entry - start)  /* Linear entry point of MLE (SINIT virt. address) */
++        .long   (slaunch_stub_entry - start)  /* Linear entry point of MLE (SINIT virt. address) */
 +        .long   0x00000000  /* First valid page of MLE */
 +        .long   0x00000000  /* Offset within binary of first byte of MLE */
 +        .long   0x00000000  /* Offset within binary of last byte + 1 of MLE */
@@ -166,7 +166,7 @@
          .section .init.rodata, "a", @progbits
  
  .Lbad_cpu_msg: .asciz "ERR: Not a 64-bit CPU!"
-@@ -425,6 +445,31 @@ __pvh_start:
+@@ -428,6 +448,31 @@ __pvh_start:
  
  #endif /* CONFIG_PVH_GUEST */
  
@@ -190,7 +190,7 @@
 +         * - APs must be brought up by MONITOR or GETSEC[WAKEUP], depending on
 +         *   which is supported by a given SINIT ACM
 +         */
-+sl_stub_entry:
++slaunch_stub_entry:
 +        movl    $SLAUNCH_BOOTLOADER_MAGIC,%eax
 +
 +        /* Fall through to Multiboot entry point. */
@@ -198,7 +198,7 @@
  __start:
          cld
          cli
-@@ -453,6 +498,10 @@ __start:
+@@ -456,6 +501,10 @@ __start:
          /* Bootloaders may set multiboot{1,2}.mem_lower to a nonzero value. */
          xor     %edx,%edx
  
@@ -209,13 +209,13 @@
          /* Check for Multiboot2 bootloader. */
          cmp     $MULTIBOOT2_BOOTLOADER_MAGIC,%eax
          je      .Lmultiboot2_proto
-@@ -468,6 +517,27 @@ __start:
+@@ -471,6 +520,27 @@ __start:
          cmovnz  MB_mem_lower(%ebx),%edx
          jmp     trampoline_bios_setup
  
 +.Lslaunch_proto:
 +        /* Save information that TrenchBoot slaunch was used. */
-+        movl $1, sym_esi(sl_status)
++        movl $1, sym_esi(slaunch_active)
 +
 +        /* Push arguments to stack and call txt_early_tests(). */
 +        push    $sym_offs(__2M_rwdata_end)  /* end of target image */
@@ -237,7 +237,7 @@
  .Lmultiboot2_proto:
          /* Skip Multiboot2 information fixed part. */
          lea     (MB2_fixed_sizeof+MULTIBOOT2_TAG_ALIGN-1)(%ebx),%ecx
-@@ -789,6 +859,14 @@ cmdline_parse_early:
+@@ -792,6 +862,14 @@ cmdline_parse_early:
  reloc:
          .incbin "reloc.bin"
  
@@ -325,7 +325,7 @@
          /* Relocations for trampoline Real Mode segments. */
 diff --git a/xen/arch/x86/boot/txt_early.c b/xen/arch/x86/boot/txt_early.c
 new file mode 100644
-index 0000000000..f94b2c6cc0
+index 0000000000..c52a600e8d
 --- /dev/null
 +++ b/xen/arch/x86/boot/txt_early.c
 @@ -0,0 +1,131 @@
@@ -369,7 +369,7 @@
 +
 +    /* Verify the value of the low PMR base. It should always be 0. */
 +    if (os_sinit->vtd_pmr_lo_base != 0)
-+        txt_reset(SL_ERROR_LO_PMR_BASE);
++        txt_reset(SLAUNCH_ERROR_LO_PMR_BASE);
 +
 +    /*
 +     * Low PMR size should not be 0 on current platforms. There is an ongoing
@@ -377,12 +377,12 @@
 +     * yet supported by the code.
 +     */
 +    if (os_sinit->vtd_pmr_lo_size == 0)
-+        txt_reset(SL_ERROR_LO_PMR_BASE);
++        txt_reset(SLAUNCH_ERROR_LO_PMR_BASE);
 +
 +    /* Check if regions overlap. Treat regions with no hole between as error. */
 +    if (os_sinit->vtd_pmr_hi_size != 0 &&
 +        os_sinit->vtd_pmr_hi_base <= os_sinit->vtd_pmr_lo_size)
-+        txt_reset(SL_ERROR_HI_PMR_BASE);
++        txt_reset(SLAUNCH_ERROR_HI_PMR_BASE);
 +
 +    /* All regions accessed by 32b code must be below 4G. */
 +    if (os_sinit->vtd_pmr_hi_base + os_sinit->vtd_pmr_hi_size <= 0x100000000ull)
@@ -397,17 +397,17 @@
 +
 +    /* Check if all of Xen before relocation is covered by PMR. */
 +    if (!is_in_pmr(os_sinit, load_base_addr, xen_size, check_high_pmr))
-+        txt_reset(SL_ERROR_LO_PMR_MLE);
++        txt_reset(SLAUNCH_ERROR_LO_PMR_MLE);
 +
 +    /* Check if all of Xen after relocation is covered by PMR. */
 +    if (load_base_addr != tgt_base_addr &&
 +        !is_in_pmr(os_sinit, tgt_base_addr, xen_size, check_high_pmr))
-+        txt_reset(SL_ERROR_LO_PMR_MLE);
++        txt_reset(SLAUNCH_ERROR_LO_PMR_MLE);
 +
 +    /* Check if MBI is covered by PMR. MBI starts with 'uint32_t total_size'. */
 +    if (!is_in_pmr(os_sinit, os_mle->boot_params_addr,
 +                   *(uint32_t *)os_mle->boot_params_addr, check_high_pmr))
-+        txt_reset(SL_ERROR_BUFFER_BEYOND_PMR);
++        txt_reset(SLAUNCH_ERROR_BUFFER_BEYOND_PMR);
 +
 +    /* Check if TPM event log (if present) is covered by PMR. */
 +    /*
@@ -431,7 +431,7 @@
 +    if (os_mle->evtlog_addr != 0 && os_mle->evtlog_size != 0 &&
 +        !is_in_pmr(os_sinit, os_mle->evtlog_addr, os_mle->evtlog_size,
 +                   check_high_pmr))
-+        txt_reset(SL_ERROR_BUFFER_BEYOND_PMR);
++        txt_reset(SLAUNCH_ERROR_BUFFER_BEYOND_PMR);
 +    */
 +}
 +
@@ -451,7 +451,7 @@
 +
 +    if (txt_os_mle_data_size(txt_heap) < sizeof(*os_mle) ||
 +        txt_os_sinit_data_size(txt_heap) < sizeof(*os_sinit))
-+        txt_reset(SL_ERROR_GENERIC);
++        txt_reset(SLAUNCH_ERROR_GENERIC);
 +
 +    os_mle = txt_os_mle_data_start(txt_heap);
 +    os_sinit = txt_os_sinit_data_start(txt_heap);
@@ -513,10 +513,10 @@
          BUG     /* start_secondary() shouldn't return. */
  
 diff --git a/xen/arch/x86/cpu/mwait-idle.c b/xen/arch/x86/cpu/mwait-idle.c
-index 5d77672f6b..890b6ba3a9 100644
+index ffdc6fb2fc..1124f2be3d 100644
 --- a/xen/arch/x86/cpu/mwait-idle.c
 +++ b/xen/arch/x86/cpu/mwait-idle.c
-@@ -1093,8 +1093,8 @@ static void __init ivt_idle_state_table_update(void)
+@@ -1213,8 +1213,8 @@ static void __init ivt_idle_state_table_update(void)
  	unsigned int cpu, max_apicid = boot_cpu_physical_apicid;
  
  	for_each_present_cpu(cpu)
@@ -528,10 +528,10 @@
  	case 0: case 1:
  		/* 1 and 2 socket systems use default ivt_cstates */
 diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c
-index 41e1e3f272..34d3631904 100644
+index aca9fa310c..9a8cc4e92e 100644
 --- a/xen/arch/x86/domain.c
 +++ b/xen/arch/x86/domain.c
-@@ -1559,7 +1559,7 @@ long do_vcpu_op(int cmd, unsigned int vcpuid, XEN_GUEST_HANDLE_PARAM(void) arg)
+@@ -1538,7 +1538,7 @@ long do_vcpu_op(int cmd, unsigned int vcpuid, XEN_GUEST_HANDLE_PARAM(void) arg)
              break;
  
          cpu_id.phys_id =
@@ -541,7 +541,7 @@
  
          rc = -EFAULT;
 diff --git a/xen/arch/x86/e820.c b/xen/arch/x86/e820.c
-index b653a19c93..03a0a86902 100644
+index c5911cf48d..f836fec189 100644
 --- a/xen/arch/x86/e820.c
 +++ b/xen/arch/x86/e820.c
 @@ -11,6 +11,7 @@
@@ -556,7 +556,7 @@
      rdmsrl(MSR_MTRRcap, mtrr_cap);
      rdmsrl(MSR_MTRRdefType, mtrr_def);
  
-+    if ( sl_status )
++    if ( slaunch_active )
 +        txt_restore_mtrrs(e820_verbose);
 +
      if ( e820_verbose )
@@ -577,22 +577,23 @@
  
 diff --git a/xen/arch/x86/include/asm/intel_txt.h b/xen/arch/x86/include/asm/intel_txt.h
 new file mode 100644
-index 0000000000..bbf6f566ed
+index 0000000000..8be44fae1a
 --- /dev/null
 +++ b/xen/arch/x86/include/asm/intel_txt.h
-@@ -0,0 +1,397 @@
+@@ -0,0 +1,398 @@
++#include <xen/multiboot.h>
++
 +/*
 + * TXT configuration registers (offsets from TXT_{PUB, PRIV}_CONFIG_REGS_BASE)
 + */
-+
-+#include <xen/multiboot.h>
-+
 +#define TXT_PUB_CONFIG_REGS_BASE	0xfed30000
 +#define TXT_PRIV_CONFIG_REGS_BASE	0xfed20000
 +
++/* Number of pages for each config regs space. */
 +#define NR_TXT_CONFIG_PAGES	((TXT_PUB_CONFIG_REGS_BASE - \
 +				  TXT_PRIV_CONFIG_REGS_BASE) >> PAGE_SHIFT)
 +
++/* Offsets from pub/priv config space. */
 +#define TXTCR_STS			0x0000
 +#define TXTCR_ESTS			0x0008
 +#define TXTCR_ERRORCODE			0x0030
@@ -621,39 +622,39 @@
 + * TXT Specification
 + * Appendix I ACM Error Codes
 + */
-+#define SL_ERROR_GENERIC		0xc0008001
-+#define SL_ERROR_TPM_INIT		0xc0008002
-+#define SL_ERROR_TPM_INVALID_LOG20	0xc0008003
-+#define SL_ERROR_TPM_LOGGING_FAILED	0xc0008004
-+#define SL_ERROR_REGION_STRADDLE_4GB	0xc0008005
-+#define SL_ERROR_TPM_EXTEND		0xc0008006
-+#define SL_ERROR_MTRR_INV_VCNT		0xc0008007
-+#define SL_ERROR_MTRR_INV_DEF_TYPE	0xc0008008
-+#define SL_ERROR_MTRR_INV_BASE		0xc0008009
-+#define SL_ERROR_MTRR_INV_MASK		0xc000800a
-+#define SL_ERROR_MSR_INV_MISC_EN	0xc000800b
-+#define SL_ERROR_INV_AP_INTERRUPT	0xc000800c
-+#define SL_ERROR_INTEGER_OVERFLOW	0xc000800d
-+#define SL_ERROR_HEAP_WALK		0xc000800e
-+#define SL_ERROR_HEAP_MAP		0xc000800f
-+#define SL_ERROR_REGION_ABOVE_4GB	0xc0008010
-+#define SL_ERROR_HEAP_INVALID_DMAR	0xc0008011
-+#define SL_ERROR_HEAP_DMAR_SIZE		0xc0008012
-+#define SL_ERROR_HEAP_DMAR_MAP		0xc0008013
-+#define SL_ERROR_HI_PMR_BASE		0xc0008014
-+#define SL_ERROR_HI_PMR_SIZE		0xc0008015
-+#define SL_ERROR_LO_PMR_BASE		0xc0008016
-+#define SL_ERROR_LO_PMR_MLE		0xc0008017
-+#define SL_ERROR_INITRD_TOO_BIG		0xc0008018
-+#define SL_ERROR_HEAP_ZERO_OFFSET	0xc0008019
-+#define SL_ERROR_WAKE_BLOCK_TOO_SMALL	0xc000801a
-+#define SL_ERROR_MLE_BUFFER_OVERLAP	0xc000801b
-+#define SL_ERROR_BUFFER_BEYOND_PMR	0xc000801c
-+#define SL_ERROR_OS_SINIT_BAD_VERSION	0xc000801d
-+#define SL_ERROR_EVENTLOG_MAP		0xc000801e
-+#define SL_ERROR_TPM_NUMBER_ALGS	0xc000801f
-+#define SL_ERROR_TPM_UNKNOWN_DIGEST	0xc0008020
-+#define SL_ERROR_TPM_INVALID_EVENT	0xc0008021
++#define SLAUNCH_ERROR_GENERIC                0xc0008001
++#define SLAUNCH_ERROR_TPM_INIT               0xc0008002
++#define SLAUNCH_ERROR_TPM_INVALID_LOG20      0xc0008003
++#define SLAUNCH_ERROR_TPM_LOGGING_FAILED     0xc0008004
++#define SLAUNCH_ERROR_REGION_STRADDLE_4GB    0xc0008005
++#define SLAUNCH_ERROR_TPM_EXTEND             0xc0008006
++#define SLAUNCH_ERROR_MTRR_INV_VCNT          0xc0008007
++#define SLAUNCH_ERROR_MTRR_INV_DEF_TYPE      0xc0008008
++#define SLAUNCH_ERROR_MTRR_INV_BASE          0xc0008009
++#define SLAUNCH_ERROR_MTRR_INV_MASK          0xc000800a
++#define SLAUNCH_ERROR_MSR_INV_MISC_EN        0xc000800b
++#define SLAUNCH_ERROR_INV_AP_INTERRUPT       0xc000800c
++#define SLAUNCH_ERROR_INTEGER_OVERFLOW       0xc000800d
++#define SLAUNCH_ERROR_HEAP_WALK              0xc000800e
++#define SLAUNCH_ERROR_HEAP_MAP               0xc000800f
++#define SLAUNCH_ERROR_REGION_ABOVE_4GB       0xc0008010
++#define SLAUNCH_ERROR_HEAP_INVALID_DMAR      0xc0008011
++#define SLAUNCH_ERROR_HEAP_DMAR_SIZE         0xc0008012
++#define SLAUNCH_ERROR_HEAP_DMAR_MAP          0xc0008013
++#define SLAUNCH_ERROR_HI_PMR_BASE            0xc0008014
++#define SLAUNCH_ERROR_HI_PMR_SIZE            0xc0008015
++#define SLAUNCH_ERROR_LO_PMR_BASE            0xc0008016
++#define SLAUNCH_ERROR_LO_PMR_MLE             0xc0008017
++#define SLAUNCH_ERROR_INITRD_TOO_BIG         0xc0008018
++#define SLAUNCH_ERROR_HEAP_ZERO_OFFSET       0xc0008019
++#define SLAUNCH_ERROR_WAKE_BLOCK_TOO_SMALL   0xc000801a
++#define SLAUNCH_ERROR_MLE_BUFFER_OVERLAP     0xc000801b
++#define SLAUNCH_ERROR_BUFFER_BEYOND_PMR      0xc000801c
++#define SLAUNCH_ERROR_OS_SINIT_BAD_VERSION   0xc000801d
++#define SLAUNCH_ERROR_EVENTLOG_MAP           0xc000801e
++#define SLAUNCH_ERROR_TPM_NUMBER_ALGS        0xc000801f
++#define SLAUNCH_ERROR_TPM_UNKNOWN_DIGEST     0xc0008020
++#define SLAUNCH_ERROR_TPM_INVALID_EVENT      0xc0008021
 +
 +#define SLAUNCH_BOOTLOADER_MAGIC	0x4c534254
 +
@@ -662,7 +663,7 @@
 +
 +#ifndef __ASSEMBLY__
 +
-+extern unsigned long sl_status;
++extern bool slaunch_active;
 +
 +extern char txt_ap_entry[];
 +extern uint32_t trampoline_gdt[];
@@ -891,7 +892,7 @@
 +{
 +	/* Check for size overflow. */
 +	if (base + size < base)
-+		txt_reset(SL_ERROR_INTEGER_OVERFLOW);
++        txt_reset(SLAUNCH_ERROR_INTEGER_OVERFLOW);
 +
 +	/* Low range always starts at 0, so its size is also end address. */
 +	if (base >= os_sinit->vtd_pmr_lo_base &&
@@ -901,7 +902,7 @@
 +	if (check_high && os_sinit->vtd_pmr_hi_size != 0) {
 +		if (os_sinit->vtd_pmr_hi_base + os_sinit->vtd_pmr_hi_size <
 +		    os_sinit->vtd_pmr_hi_size)
-+			txt_reset(SL_ERROR_INTEGER_OVERFLOW);
++            txt_reset(SLAUNCH_ERROR_INTEGER_OVERFLOW);
 +		if (base >= os_sinit->vtd_pmr_hi_base &&
 +		    base + size <= os_sinit->vtd_pmr_hi_base + os_sinit->vtd_pmr_hi_size)
 +			return 1;
@@ -978,11 +979,25 @@
 +void tpm_process_drtm_policy(const multiboot_info_t *mbi);
 +
 +#endif /* __ASSEMBLY__ */
+diff --git a/xen/arch/x86/include/asm/mm.h b/xen/arch/x86/include/asm/mm.h
+index d723c7c38f..989fb75038 100644
+--- a/xen/arch/x86/include/asm/mm.h
++++ b/xen/arch/x86/include/asm/mm.h
+@@ -98,6 +98,9 @@
+ #define _PGC_need_scrub   _PGC_allocated
+ #define PGC_need_scrub    PGC_allocated
+ 
++/* How much of the directmap is prebuilt at compile time. */
++#define PREBUILT_MAP_LIMIT (1 << L2_PAGETABLE_SHIFT)
++
+ #ifndef CONFIG_BIGMEM
+ /*
+  * This definition is solely for the use in struct page_info (and
 diff --git a/xen/arch/x86/include/asm/processor.h b/xen/arch/x86/include/asm/processor.h
-index 8e2816fae9..dbf5a53e53 100644
+index f8f757a8a8..592ff38bae 100644
 --- a/xen/arch/x86/include/asm/processor.h
 +++ b/xen/arch/x86/include/asm/processor.h
-@@ -130,6 +130,8 @@ struct cpuinfo_x86 {
+@@ -137,6 +137,8 @@ struct cpuinfo_x86 {
      __u32 phys_proc_id;    /* package ID of each logical CPU */
      __u32 cpu_core_id;     /* core ID of each logical CPU*/
      __u32 compute_unit_id; /* AMD compute unit ID of each logical CPU */
@@ -991,7 +1006,7 @@
      unsigned short x86_clflush_size;
  } __cacheline_aligned;
  
-@@ -142,6 +144,8 @@ extern struct cpuinfo_x86 boot_cpu_data;
+@@ -149,6 +151,8 @@ extern struct cpuinfo_x86 boot_cpu_data;
  extern struct cpuinfo_x86 cpu_data[];
  #define current_cpu_data cpu_data[smp_processor_id()]
  
@@ -1034,10 +1049,10 @@
  extern bool unaccounted_cpus;
 diff --git a/xen/arch/x86/intel_txt.c b/xen/arch/x86/intel_txt.c
 new file mode 100644
-index 0000000000..502a9c35d3
+index 0000000000..75584b17a2
 --- /dev/null
 +++ b/xen/arch/x86/intel_txt.c
-@@ -0,0 +1,159 @@
+@@ -0,0 +1,155 @@
 +#include <xen/types.h>
 +#include <asm/e820.h>
 +#include <xen/string.h>
@@ -1049,17 +1064,13 @@
 +
 +static uint64_t __initdata txt_heap_base, txt_heap_size;
 +
-+unsigned long __initdata sl_status;
-+
-+#define PREBUILT_MAP_LIMIT (1 << L2_PAGETABLE_SHIFT)
++bool __initdata slaunch_active;
 +
 +int __init map_l2(unsigned long paddr, unsigned long size)
 +{
 +    unsigned long aligned_paddr = paddr & ~((1ULL << L2_PAGETABLE_SHIFT) - 1);
 +    unsigned long pages = ((paddr + size) - aligned_paddr);
-+    pages += (1ULL << L2_PAGETABLE_SHIFT) - 1;
-+    pages &= ~((1ULL << L2_PAGETABLE_SHIFT) - 1);
-+    pages >>= PAGE_SHIFT;
++    pages = ROUNDUP(pages, 1ULL << L2_PAGETABLE_SHIFT) >> PAGE_SHIFT;
 +
 +    if ( (aligned_paddr + pages * PAGE_SIZE) <= PREBUILT_MAP_LIMIT )
 +        return 0;
@@ -1226,10 +1237,10 @@
  }
  
 diff --git a/xen/arch/x86/numa.c b/xen/arch/x86/numa.c
-index 627ae8aa95..d4f311742e 100644
+index 322157fab7..393e4108c7 100644
 --- a/xen/arch/x86/numa.c
 +++ b/xen/arch/x86/numa.c
-@@ -321,14 +321,13 @@ custom_param("numa", numa_setup);
+@@ -324,14 +324,13 @@ custom_param("numa", numa_setup);
  /*
   * Setup early cpu_to_node.
   *
@@ -1251,7 +1262,7 @@
   */
  void __init init_cpu_to_node(void)
  {
-@@ -337,7 +336,7 @@ void __init init_cpu_to_node(void)
+@@ -340,7 +339,7 @@ void __init init_cpu_to_node(void)
  
      for ( i = 0; i < nr_cpu_ids; i++ )
      {
@@ -1261,7 +1272,7 @@
              continue;
          node = apicid < MAX_LOCAL_APIC ? apicid_to_node[apicid] : NUMA_NO_NODE;
 diff --git a/xen/arch/x86/platform_hypercall.c b/xen/arch/x86/platform_hypercall.c
-index a7341dc3d7..d39af7c2a2 100644
+index e7deee2268..a63751fff0 100644
 --- a/xen/arch/x86/platform_hypercall.c
 +++ b/xen/arch/x86/platform_hypercall.c
 @@ -588,7 +588,7 @@ ret_t do_platform_op(
@@ -1274,7 +1285,7 @@
              ASSERT(g_info->apic_id != BAD_APICID);
              g_info->flags = 0;
 diff --git a/xen/arch/x86/setup.c b/xen/arch/x86/setup.c
-index e05189f649..d1c233868c 100644
+index 0c00ea875d..808a15c41d 100644
 --- a/xen/arch/x86/setup.c
 +++ b/xen/arch/x86/setup.c
 @@ -55,6 +55,7 @@
@@ -1285,7 +1296,7 @@
  
  /* opt_nosmp: If true, secondary processors are ignored. */
  static bool __initdata opt_nosmp;
-@@ -320,7 +321,7 @@ static void __init init_idle_domain(void)
+@@ -316,7 +317,7 @@ static void __init init_idle_domain(void)
  void srat_detect_node(int cpu)
  {
      nodeid_t node;
@@ -1294,7 +1305,7 @@
  
      node = apicid < MAX_LOCAL_APIC ? apicid_to_node[apicid] : NUMA_NO_NODE;
      if ( node == NUMA_NO_NODE )
-@@ -347,7 +348,7 @@ static void __init normalise_cpu_order(void)
+@@ -343,7 +344,7 @@ static void __init normalise_cpu_order(void)
  
      for_each_present_cpu ( i )
      {
@@ -1303,7 +1314,7 @@
          min_diff = min_cpu = ~0u;
  
          /*
-@@ -358,12 +359,12 @@ static void __init normalise_cpu_order(void)
+@@ -354,12 +355,12 @@ static void __init normalise_cpu_order(void)
                j < nr_cpu_ids;
                j = cpumask_next(j, &cpu_present_map) )
          {
@@ -1318,7 +1329,7 @@
              {
                  min_diff = diff;
                  min_cpu = j;
-@@ -379,9 +380,9 @@ static void __init normalise_cpu_order(void)
+@@ -375,9 +376,9 @@ static void __init normalise_cpu_order(void)
  
          /* Switch the best-matching CPU with the next CPU in logical order. */
          j = cpumask_next(i, &cpu_present_map);
@@ -1331,7 +1342,7 @@
      }
  }
  
-@@ -686,7 +687,7 @@ static void __init noreturn reinit_bsp_stack(void)
+@@ -682,7 +683,7 @@ static void __init noreturn reinit_bsp_stack(void)
      /* Update SYSCALL trampolines */
      percpu_traps_init();
  
@@ -1340,11 +1351,21 @@
  
      rc = setup_cpu_root_pgt(0);
      if ( rc )
-@@ -1140,6 +1141,17 @@ void __init noreturn __start_xen(unsigned long mbi_p)
+@@ -849,9 +850,6 @@ static struct domain *__init create_dom0(const module_t *image,
+     return d;
+ }
+ 
+-/* How much of the directmap is prebuilt at compile time. */
+-#define PREBUILT_MAP_LIMIT (1 << L2_PAGETABLE_SHIFT)
+-
+ void __init noreturn __start_xen(unsigned long mbi_p)
+ {
+     char *memmap_type = NULL;
+@@ -1170,6 +1168,17 @@ void __init noreturn __start_xen(unsigned long mbi_p)
  #endif
      }
  
-+    if ( sl_status )
++    if ( slaunch_active )
 +    {
 +        /* Prepare for TXT-related code. */
 +        map_txt_mem_regions();
@@ -1358,20 +1379,20 @@
      /* Sanitise the raw E820 map to produce a final clean version. */
      max_page = raw_max_page = init_e820(memmap_type, &e820_raw);
  
-@@ -1158,6 +1170,12 @@ void __init noreturn __start_xen(unsigned long mbi_p)
+@@ -1188,6 +1197,12 @@ void __init noreturn __start_xen(unsigned long mbi_p)
      /* Create a temporary copy of the E820 map. */
      memcpy(&boot_e820, &e820, sizeof(e820));
  
 +    /* Process all yet unmeasured DRTM entries after E820 initialization to not
 +     * do this while memory is uncached (too slow). This must also happen before
 +     * fields of Multiboot modules change their format below. */
-+    if ( sl_status )
++    if ( slaunch_active )
 +        tpm_process_drtm_policy(mbi);
 +
      /* Early kexec reservation (explicit static start address). */
      nr_pages = 0;
      for ( i = 0; i < e820.nr_map; i++ )
-@@ -1871,6 +1889,7 @@ void __init noreturn __start_xen(unsigned long mbi_p)
+@@ -1901,6 +1916,7 @@ void __init noreturn __start_xen(unsigned long mbi_p)
       */
      if ( !pv_shim )
      {
@@ -1379,7 +1400,7 @@
          for_each_present_cpu ( i )
          {
              /* Set up cpu_to_node[]. */
-@@ -1878,6 +1897,14 @@ void __init noreturn __start_xen(unsigned long mbi_p)
+@@ -1908,6 +1924,14 @@ void __init noreturn __start_xen(unsigned long mbi_p)
              /* Set up node_to_cpumask based on cpu_to_node[]. */
              numa_add_cpu(i);
  
@@ -1426,7 +1447,7 @@
                  halt();
          }
 diff --git a/xen/arch/x86/smpboot.c b/xen/arch/x86/smpboot.c
-index b46fd9ab18..455fc4fadd 100644
+index 41d9454d53..f8d1e4c76d 100644
 --- a/xen/arch/x86/smpboot.c
 +++ b/xen/arch/x86/smpboot.c
 @@ -39,6 +39,7 @@
@@ -1580,7 +1601,7 @@
      startup_cpu_idle_loop();
  }
  
-+static int sl_wake_aps(unsigned long trampoline_rm)
++static int slaunch_wake_aps(unsigned long trampoline_rm)
 +{
 +    struct txt_sinit_mle_data *sinit_mle =
 +              txt_sinit_mle_data_start(__va(read_txt_reg(TXTCR_HEAP_BASE)));
@@ -1619,8 +1640,8 @@
      if ( tboot_in_measured_env() && !tboot_wake_ap(phys_apicid, start_eip) )
          return 0;
  
-+    if ( sl_status )
-+        return sl_wake_aps(start_eip);
++    if ( slaunch_active )
++        return slaunch_wake_aps(start_eip);
 +
 +    /*
 +     * Use destination shorthand for broadcasting IPIs during boot.
@@ -1857,9 +1878,9 @@
 +    /* Not really used anywhere, but set it just in case. */
 +    set_cpu_state(0, CPU_STATE_ONLINE);
  
-     if ( opt_xpti_hwdom || opt_xpti_domu )
-     {
-@@ -1281,7 +1360,7 @@ void __cpu_disable(void)
+     set_nr_sockets();
+ 
+@@ -1267,7 +1346,7 @@ void __cpu_disable(void)
  {
      int cpu = smp_processor_id();
  
@@ -1868,7 +1889,7 @@
  
      local_irq_disable();
      clear_local_APIC();
-@@ -1306,7 +1385,7 @@ void __cpu_die(unsigned int cpu)
+@@ -1292,7 +1371,7 @@ void __cpu_die(unsigned int cpu)
      unsigned int i = 0;
      enum cpu_state seen_state;
  
@@ -1877,7 +1898,7 @@
      {
          BUG_ON(seen_state != CPU_STATE_DYING);
          mdelay(100);
-@@ -1391,7 +1470,7 @@ int __cpu_up(unsigned int cpu)
+@@ -1377,7 +1456,7 @@ int __cpu_up(unsigned int cpu)
  {
      int apicid, ret;
  
@@ -1886,7 +1907,7 @@
          return -ENODEV;
  
      if ( (!x2apic_enabled && apicid >= APIC_ALL_CPUS) ||
-@@ -1407,7 +1486,7 @@ int __cpu_up(unsigned int cpu)
+@@ -1393,7 +1472,7 @@ int __cpu_up(unsigned int cpu)
  
      time_latch_stamps();
  
@@ -1896,10 +1917,10 @@
      {
          cpu_relax();
 diff --git a/xen/arch/x86/spec_ctrl.c b/xen/arch/x86/spec_ctrl.c
-index 4e53056624..3d8721d912 100644
+index 0ff3c895ac..38375f44dd 100644
 --- a/xen/arch/x86/spec_ctrl.c
 +++ b/xen/arch/x86/spec_ctrl.c
-@@ -583,7 +583,7 @@ static bool __init check_smt_enabled(void)
+@@ -601,7 +601,7 @@ static bool __init check_smt_enabled(void)
       * has a non-zero thread id component indicates that SMT is active.
       */
      for_each_present_cpu ( cpu )
@@ -1909,10 +1930,10 @@
  
      return false;
 diff --git a/xen/arch/x86/sysctl.c b/xen/arch/x86/sysctl.c
-index 716525f72f..7792ce5302 100644
+index 42dc360ad6..de9b75c1ec 100644
 --- a/xen/arch/x86/sysctl.c
 +++ b/xen/arch/x86/sysctl.c
-@@ -90,7 +90,7 @@ static long cf_check smt_up_down_helper(void *data)
+@@ -59,7 +59,7 @@ static long cf_check smt_up_down_helper(void *data)
      for_each_present_cpu ( cpu )
      {
          /* Skip primary siblings (those whose thread id is 0). */
@@ -1922,10 +1943,42 @@
  
          if ( !up && core_parking_remove(cpu) )
 diff --git a/xen/arch/x86/tboot.c b/xen/arch/x86/tboot.c
-index fe1abfdf08..a1559a0545 100644
+index fe1abfdf08..9730f12b06 100644
 --- a/xen/arch/x86/tboot.c
 +++ b/xen/arch/x86/tboot.c
-@@ -254,9 +254,9 @@ static int mfn_in_guarded_stack(unsigned long mfn)
+@@ -14,6 +14,7 @@
+ #include <asm/e820.h>
+ #include <asm/tboot.h>
+ #include <asm/setup.h>
++#include <asm/intel_txt.h>
+ #include <crypto/vmac.h>
+ 
+ /* tboot=<physical address of shared page> */
+@@ -31,23 +32,6 @@ static vmac_t frametable_mac; /* MAC for frame table during S3 */
+ static uint64_t __initdata txt_heap_base, __initdata txt_heap_size;
+ static uint64_t __initdata sinit_base, __initdata sinit_size;
+ 
+-/*
+- * TXT configuration registers (offsets from TXT_{PUB, PRIV}_CONFIG_REGS_BASE)
+- */
+-
+-#define TXT_PUB_CONFIG_REGS_BASE       0xfed30000
+-#define TXT_PRIV_CONFIG_REGS_BASE      0xfed20000
+-
+-/* # pages for each config regs space - used by fixmap */
+-#define NR_TXT_CONFIG_PAGES     ((TXT_PUB_CONFIG_REGS_BASE -                \
+-                                  TXT_PRIV_CONFIG_REGS_BASE) >> PAGE_SHIFT)
+-
+-/* offsets from pub/priv config space */
+-#define TXTCR_SINIT_BASE            0x0270
+-#define TXTCR_SINIT_SIZE            0x0278
+-#define TXTCR_HEAP_BASE             0x0300
+-#define TXTCR_HEAP_SIZE             0x0308
+-
+ #define SHA1_SIZE      20
+ typedef uint8_t   sha1_hash_t[SHA1_SIZE];
+ 
+@@ -254,9 +238,9 @@ static int mfn_in_guarded_stack(unsigned long mfn)
  
      for ( i = 0; i < nr_cpu_ids; i++ )
      {
@@ -1939,10 +1992,10 @@
              return -1;
 diff --git a/xen/arch/x86/tpm.c b/xen/arch/x86/tpm.c
 new file mode 100644
-index 0000000000..bb27123571
+index 0000000000..139aaa7a0f
 --- /dev/null
 +++ b/xen/arch/x86/tpm.c
-@@ -0,0 +1,1163 @@
+@@ -0,0 +1,1164 @@
 +/*
 + * Copyright (c) 2022 3mdeb Sp. z o.o. All rights reserved.
 + *
@@ -1974,12 +2027,15 @@
 +
 +#include "boot/defs.h"
 +#include "include/asm/intel_txt.h"
-+#include <xen/slr_table.h>
 +#ifdef __va
 +#error "__va defined in non-paged mode!"
 +#endif
 +#define __va(x)     _p(x)
 +
++/*
++ * The implementation is necessary if compiler chooses to not use an inline
++ * builtin.
++ */
 +void *memset(void *dest, int c, size_t n)
 +{
 +  uint8_t *d = dest;
@@ -1989,7 +2045,6 @@
 +
 +  return dest;
 +}
-+
 +void *memcpy(void *dest, const void *src, size_t n)
 +{
 +  const uint8_t *s = src;
@@ -2033,6 +2088,7 @@
 +
 +#define swap16(x)       __builtin_bswap16(x)
 +#define swap32(x)       __builtin_bswap32(x)
++#define memset(s, c, n) __builtin_memset(s, c, n)
 +#define memcpy(d, s, n) __builtin_memcpy(d, s, n)
 +
 +static inline volatile uint32_t tis_read32(unsigned reg)
@@ -2729,12 +2785,10 @@
 +{
 +    uint32_t rc;
 +    unsigned i;
-+    unsigned j;
 +    struct tpm2_log_hashes supported_hashes = {0};
 +
 +    request_locality(loc);
 +
-+    j = 0;
 +    for ( i = 0; i < log_hashes->count; ++i ) {
 +        const struct tpm2_log_hash *hash = &log_hashes->hashes[i];
 +        if ( !tpm_supports_hash(loc, hash) ) {
@@ -3107,10 +3161,10 @@
 +}
 +#endif
 diff --git a/xen/arch/x86/traps.c b/xen/arch/x86/traps.c
-index 7207390118..ae19056ee0 100644
+index e65cc60041..7471da5d2e 100644
 --- a/xen/arch/x86/traps.c
 +++ b/xen/arch/x86/traps.c
-@@ -616,9 +616,9 @@ void show_stack_overflow(unsigned int cpu, const struct cpu_user_regs *regs)
+@@ -623,9 +623,9 @@ void show_stack_overflow(unsigned int cpu, const struct cpu_user_regs *regs)
      unsigned long curr_stack_base = esp & ~(STACK_SIZE - 1);
      unsigned long esp_top, esp_bottom;
  

sl_status is now a bool and called slaunch_active. By the way, I noticed that x86/boot/txt_early: add early TXT tests and restore MBI pointer commit adds declaration of (now) slaunch_active without definition, so Xen doesn't compile. Don't know if it's worth fixing by adding essentially empty intel_txt.c in that commit.

Also, there should be no tabulation in the changes anymore.

@BeataZdunczyk
Copy link
Member Author

@SergiiDmytruk great news! Thank you for the comparison of the diff of diffs, it is helpful. @krystian-hebel please take a look.

@krystian-hebel
Copy link

sl_status is now a bool

I'm not sure how I feel about this change. To the best of my knowledge, size of bool is not defined. There is BOOL_WIDTH in limits.h since C23, I haven't found anything similar for earlier standards. This may be important because in assembly we assume that it is 32b.

On top of that, we will soon be implementing AMD counterpart, so we may need a way of differentiating between those two, which would require more bits than a single bool provides.

Xen doesn't compile. Don't know if it's worth fixing by adding essentially empty intel_txt.c in that commit.

There was a recent discussion about how Xen can't be easily bisected because of problems like this, so please fix it.

@SergiiDmytruk
Copy link
Member

I'm not sure how I feel about this change. To the best of my knowledge, size of bool is not defined. There is BOOL_WIDTH in limits.h since C23, I haven't found anything similar for earlier standards. This may be important because in assembly we assume that it is 32b.

I did the change because it was suggested in the review, can revert (I even have aem-phase3-rebase-before-sl_status-rename branch locally). I think bool is 1 byte and I didn't notice that assembly treats the variable as 4 bytes. Can add a static assert for the size if bool will remain.

On top of that, we will soon be implementing AMD counterpart, so we may need a way of differentiating between those two, which would require more bits than a single bool provides.

Is there a need to pack it in a single variable? It can be slaunch_active and slaunch_type.

There was a recent discussion about how Xen can't be easily bisected because of problems like this, so please fix it.

Will do.

@krystian-hebel
Copy link

Is there a need to pack it in a single variable? It can be slaunch_active and slaunch_type.

Actually, this may be even better. Often tasks have to be done for all vendors, and SLRT should make it similar between them, we just have to get its address differently. In fact, everything that has to be done on AMD that isn't required for Intel is already done without slaunch_type.

@SergiiDmytruk
Copy link
Member

@krystian-hebel
Copy link

Changes look good now, and it seems to work on minimal Xen+Alpine setup, I think QubesOS/qubes-vmm-xen#160 can be updated with those now.

@SergiiDmytruk
Copy link
Member

I think QubesOS/qubes-vmm-xen#160 can be updated with those now.

To avoid a potential deadlock: I'm not the one who should be updating it, right?

@SergiiDmytruk
Copy link
Member

Updated QubesOS/qubes-vmm-xen#160 and commented on all review comments which were addressed or which I knew how to answer. There are some more left to answer, some to do (like define constants and add log messages). @krystian-hebel:

  1. You might already know an answer to Inteltxt support QubesOS/qubes-vmm-xen#160 (comment).
  2. Don't know if Inteltxt support QubesOS/qubes-vmm-xen#160 (comment) will be addressed in GRUB.
  3. Inteltxt support QubesOS/qubes-vmm-xen#160 (comment), is this why there is volatile on read_txt_reg()?
  4. Not sure if we should replace hash implementations per Inteltxt support QubesOS/qubes-vmm-xen#160 (comment)
  5. Is Inteltxt support QubesOS/qubes-vmm-xen#160 (comment) safe because MTRRs are measured?
  6. Do we want to fail in cases like Inteltxt support QubesOS/qubes-vmm-xen#160 (comment)? If SL isn't used, that code shouldn't be called and if it's used, conditions must always succeed? So maybe use BUG macro?

@krystian-hebel
Copy link

@SergiiDmytruk I replied to the first 4 comments you listed.

As for 5, it depends on what layer of security that question is about. They are measured, and they won't be less secure than it would be without TB because of that. Xen can choose to ignore it and set it's own MTRRs (perhaps it already does, haven't checked), but by restoring them at this point we get decent execution speed. Booting from UC is slooooow.

For 6, if BUG prints something that would be visible for end users, definitely use it. Otherwise this would still be silent, but most likely something else would break if TXT heap or SINIT can't be found, so maybe that would also be good reason for using BUG. If nothing else, this would at least make code clearer by having one less indentation level.

@SergiiDmytruk
Copy link
Member

Thanks, @krystian-hebel.

Integrated your comments for 1 and 2 to corresponding commits.

For 6, if BUG prints something that would be visible for end users, definitely use it. Otherwise this would still be silent, but most likely something else would break if TXT heap or SINIT can't be found, so maybe that would also be good reason for using BUG. If nothing else, this would at least make code clearer by having one less indentation level.

It prints source code location and halts, so I guess it'll do.

Didn't update the PR yet, but pushed the changes. Their difference: https://github.com/TrenchBoot/xen/compare/0c8fec9a0638f61996b6e0f35c48ef35f1837c8b..37d1ba6086f103010cab4e90e83eb9f1033894ae

@SergiiDmytruk
Copy link
Member

(Looks like last time I wrote a comment here I confused its preview for an actual comment and closed the browser. Not much was lost anyway.)

Last changes (constants and a comment): https://github.com/TrenchBoot/xen/compare/37d1ba6086f103010cab4e90e83eb9f1033894ae..81ac532e6c77290b6ecf6e4caf76915f350ca6d3

I think this addresses all outstanding comments on QubesOS/qubes-vmm-xen#160 (several without reply are covered by a reply to a similar comment nearby).

@krystian-hebel
Copy link

Review of GRUB has been moved to TrenchBoot/grub#16. Original approach from QubesOS/qubes-grub2#13 proved to be impractical - review of patches to patches, indirect building, and most annoyingly, GH had problems with showing so many comments at once, some of us were greeted by unicorn of death instead of PR page:
image

@krystian-hebel
Copy link

After review and a lot of debugging TrenchBoot/grub#16 was finally positively tested and reviewed. New set of patches was created and sent back to the land of unicorns QubesOS/qubes-grub2#13.

@BeataZdunczyk BeataZdunczyk added W: in review Workflow: in review. The issue is being reviewed for completeness. and removed W: in progress Workflow: in progress. The issue is actively being worked on. labels Feb 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P: default Priority: default. Default priority for new issues, to be replaced given sufficient information. T: feature request Type: feature reguest. A new feature for the project. W: in review Workflow: in review. The issue is being reviewed for completeness.
Projects
None yet
Development

No branches or pull requests

3 participants