x86/sev: Add callback to apply RMP table fixups for kexec
authorAshish Kalra <ashish.kalra@amd.com>
Fri, 26 Apr 2024 00:43:18 +0000 (00:43 +0000)
committerBorislav Petkov (AMD) <bp@alien8.de>
Mon, 29 Apr 2024 09:21:09 +0000 (11:21 +0200)
Handle cases where the RMP table placement in the BIOS is not 2M aligned
and the kexec-ed kernel could try to allocate from within that chunk
which then causes a fatal RMP fault.

The kexec failure is illustrated below:

  SEV-SNP: RMP table physical range [0x0000007ffe800000 - 0x000000807f0fffff]
  BIOS-provided physical RAM map:
  BIOS-e820: [mem 0x0000000000000000-0x000000000008efff] usable
  BIOS-e820: [mem 0x000000000008f000-0x000000000008ffff] ACPI NVS
  ...
  BIOS-e820: [mem 0x0000004080000000-0x0000007ffe7fffff] usable
  BIOS-e820: [mem 0x0000007ffe800000-0x000000807f0fffff] reserved
  BIOS-e820: [mem 0x000000807f100000-0x000000807f1fefff] usable

As seen here in the e820 memory map, the end range of the RMP table is not
aligned to 2MB and not reserved but it is usable as RAM.

Subsequently, kexec -s (KEXEC_FILE_LOAD syscall) loads it's purgatory
code and boot_param, command line and other setup data into this RAM
region as seen in the kexec logs below, which leads to fatal RMP fault
during kexec boot.

  Loaded purgatory at 0x807f1fa000
  Loaded boot_param, command line and misc at 0x807f1f8000 bufsz=0x1350 memsz=0x2000
  Loaded 64bit kernel at 0x7ffae00000 bufsz=0xd06200 memsz=0x3894000
  Loaded initrd at 0x7ff6c89000 bufsz=0x4176014 memsz=0x4176014
  E820 memmap:
  0000000000000000-000000000008efff (1)
  000000000008f000-000000000008ffff (4)
  0000000000090000-000000000009ffff (1)
  ...
  0000004080000000-0000007ffe7fffff (1)
  0000007ffe800000-000000807f0fffff (2)
  000000807f100000-000000807f1fefff (1)
  000000807f1ff000-000000807fffffff (2)
  nr_segments = 4
  segment[0]: buf=0x00000000e626d1a2 bufsz=0x4000 mem=0x807f1fa000 memsz=0x5000
  segment[1]: buf=0x0000000029c67bd6 bufsz=0x1350 mem=0x807f1f8000 memsz=0x2000
  segment[2]: buf=0x0000000045c60183 bufsz=0xd06200 mem=0x7ffae00000 memsz=0x3894000
  segment[3]: buf=0x000000006e54f08d bufsz=0x4176014 mem=0x7ff6c89000 memsz=0x4177000
  kexec_file_load: type:0, start:0x807f1fa150 head:0x1184d0002 flags:0x0

Check if RMP table start and end physical range in the e820 tables are
not aligned to 2MB and in that case map this range to reserved in all
the three e820 tables.

  [ bp: Massage. ]

Fixes: c3b86e61b756 ("x86/cpufeatures: Enable/unmask SEV-SNP CPU feature")
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Link: https://lore.kernel.org/r/df6e995ff88565262c2c7c69964883ff8aa6fc30.1714090302.git.ashish.kalra@amd.com
arch/x86/include/asm/sev.h
arch/x86/mm/mem_encrypt.c
arch/x86/virt/svm/sev.c

index 7f57382afee41754beb8164244f199e4ac30a148..93ed60080cfe7b58dc0349ab34fdb7e5704abd81 100644 (file)
@@ -269,6 +269,7 @@ int rmp_make_private(u64 pfn, u64 gpa, enum pg_level level, u32 asid, bool immut
 int rmp_make_shared(u64 pfn, enum pg_level level);
 void snp_leak_pages(u64 pfn, unsigned int npages);
 void kdump_sev_callback(void);
+void snp_fixup_e820_tables(void);
 #else
 static inline bool snp_probe_rmptable_info(void) { return false; }
 static inline int snp_lookup_rmpentry(u64 pfn, bool *assigned, int *level) { return -ENODEV; }
@@ -282,6 +283,7 @@ static inline int rmp_make_private(u64 pfn, u64 gpa, enum pg_level level, u32 as
 static inline int rmp_make_shared(u64 pfn, enum pg_level level) { return -ENODEV; }
 static inline void snp_leak_pages(u64 pfn, unsigned int npages) {}
 static inline void kdump_sev_callback(void) { }
+static inline void snp_fixup_e820_tables(void) {}
 #endif
 
 #endif
index 6f3b3e028718556667c1d86e8f56442eafc78dcc..0a120d85d7bba88b33c59b97f9109a6acde727cd 100644 (file)
@@ -102,6 +102,13 @@ void __init mem_encrypt_setup_arch(void)
        phys_addr_t total_mem = memblock_phys_mem_size();
        unsigned long size;
 
+       /*
+        * Do RMP table fixups after the e820 tables have been setup by
+        * e820__memory_setup().
+        */
+       if (cc_platform_has(CC_ATTR_HOST_SEV_SNP))
+               snp_fixup_e820_tables();
+
        if (!cc_platform_has(CC_ATTR_GUEST_MEM_ENCRYPT))
                return;
 
index ab0e8448bb6eb2bfbc4fab29321cd0ddbe876f7e..0ae10535c699982e15f20026a3e8012e52c41f32 100644 (file)
@@ -163,6 +163,42 @@ bool snp_probe_rmptable_info(void)
        return true;
 }
 
+static void __init __snp_fixup_e820_tables(u64 pa)
+{
+       if (IS_ALIGNED(pa, PMD_SIZE))
+               return;
+
+       /*
+        * Handle cases where the RMP table placement by the BIOS is not
+        * 2M aligned and the kexec kernel could try to allocate
+        * from within that chunk which then causes a fatal RMP fault.
+        *
+        * The e820_table needs to be updated as it is converted to
+        * kernel memory resources and used by KEXEC_FILE_LOAD syscall
+        * to load kexec segments.
+        *
+        * The e820_table_firmware needs to be updated as it is exposed
+        * to sysfs and used by the KEXEC_LOAD syscall to load kexec
+        * segments.
+        *
+        * The e820_table_kexec needs to be updated as it passed to
+        * the kexec-ed kernel.
+        */
+       pa = ALIGN_DOWN(pa, PMD_SIZE);
+       if (e820__mapped_any(pa, pa + PMD_SIZE, E820_TYPE_RAM)) {
+               pr_info("Reserving start/end of RMP table on a 2MB boundary [0x%016llx]\n", pa);
+               e820__range_update(pa, PMD_SIZE, E820_TYPE_RAM, E820_TYPE_RESERVED);
+               e820__range_update_table(e820_table_kexec, pa, PMD_SIZE, E820_TYPE_RAM, E820_TYPE_RESERVED);
+               e820__range_update_table(e820_table_firmware, pa, PMD_SIZE, E820_TYPE_RAM, E820_TYPE_RESERVED);
+       }
+}
+
+void __init snp_fixup_e820_tables(void)
+{
+       __snp_fixup_e820_tables(probed_rmp_base);
+       __snp_fixup_e820_tables(probed_rmp_base + probed_rmp_size);
+}
+
 /*
  * Do the necessary preparations which are verified by the firmware as
  * described in the SNP_INIT_EX firmware command description in the SNP