From 91361b5175d2b3704f7e436d0071893c839e1199 Mon Sep 17 00:00:00 2001 From: Russell Currey Date: Fri, 10 Feb 2023 19:03:58 +1100 Subject: powerpc/pseries: Pass PLPKS password on kexec Before interacting with the PLPKS, we ask the hypervisor to generate a password for the current boot, which is then required for most further PLPKS operations. If we kexec into a new kernel, the new kernel will try and fail to generate a new password, as the password has already been set. Pass the password through to the new kernel via the device tree, in /chosen/ibm,plpks-pw. Check for the presence of this property before trying to generate a new password - if it exists, use the existing password and remove it from the device tree. This only works with the kexec_file_load() syscall, not the older kexec_load() syscall, however if you're using Secure Boot then you want to be using kexec_file_load() anyway. Signed-off-by: Russell Currey Signed-off-by: Andrew Donnellan Signed-off-by: Michael Ellerman Link: https://lore.kernel.org/r/20230210080401.345462-24-ajd@linux.ibm.com --- arch/powerpc/include/asm/plpks.h | 14 ++++++++ arch/powerpc/kernel/prom.c | 4 +++ arch/powerpc/kexec/file_load_64.c | 18 +++++++--- arch/powerpc/platforms/pseries/plpks.c | 61 ++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 5 deletions(-) (limited to 'arch') diff --git a/arch/powerpc/include/asm/plpks.h b/arch/powerpc/include/asm/plpks.h index 757313e00521..23b77027c916 100644 --- a/arch/powerpc/include/asm/plpks.h +++ b/arch/powerpc/include/asm/plpks.h @@ -176,6 +176,20 @@ u64 plpks_get_signedupdatealgorithms(void); */ u16 plpks_get_passwordlen(void); +/** + * Called in early init to retrieve and clear the PLPKS password from the DT. + */ +void plpks_early_init_devtree(void); + +/** + * Populates the FDT with the PLPKS password to prepare for kexec. + */ +int plpks_populate_fdt(void *fdt); +#else // CONFIG_PSERIES_PLPKS +static inline bool plpks_is_available(void) { return false; } +static inline u16 plpks_get_passwordlen(void) { BUILD_BUG(); } +static inline void plpks_early_init_devtree(void) { } +static inline int plpks_populate_fdt(void *fdt) { BUILD_BUG(); } #endif // CONFIG_PSERIES_PLPKS #endif // _ASM_POWERPC_PLPKS_H diff --git a/arch/powerpc/kernel/prom.c b/arch/powerpc/kernel/prom.c index f318e8e1f3fe..9d9ee4e9e1a1 100644 --- a/arch/powerpc/kernel/prom.c +++ b/arch/powerpc/kernel/prom.c @@ -56,6 +56,7 @@ #include #include #include +#include #include @@ -887,6 +888,9 @@ void __init early_init_devtree(void *params) powerpc_firmware_features |= FW_FEATURE_PS3_POSSIBLE; #endif + /* If kexec left a PLPKS password in the DT, get it and clear it */ + plpks_early_init_devtree(); + tm_init(); DBG(" <- early_init_devtree()\n"); diff --git a/arch/powerpc/kexec/file_load_64.c b/arch/powerpc/kexec/file_load_64.c index 9be3e818a240..ab80c492da31 100644 --- a/arch/powerpc/kexec/file_load_64.c +++ b/arch/powerpc/kexec/file_load_64.c @@ -28,6 +28,7 @@ #include #include #include +#include struct umem_info { u64 *buf; /* data buffer for usable-memory property */ @@ -978,12 +979,17 @@ static unsigned int cpu_node_size(void) */ unsigned int kexec_extra_fdt_size_ppc64(struct kimage *image) { - unsigned int cpu_nodes, extra_size; + unsigned int cpu_nodes, extra_size = 0; struct device_node *dn; u64 usm_entries; + // Budget some space for the password blob. There's already extra space + // for the key name + if (plpks_is_available()) + extra_size += (unsigned int)plpks_get_passwordlen(); + if (image->type != KEXEC_TYPE_CRASH) - return 0; + return extra_size; /* * For kdump kernel, account for linux,usable-memory and @@ -993,9 +999,7 @@ unsigned int kexec_extra_fdt_size_ppc64(struct kimage *image) if (drmem_lmb_size()) { usm_entries = ((memory_hotplug_max() / drmem_lmb_size()) + (2 * (resource_size(&crashk_res) / drmem_lmb_size()))); - extra_size = (unsigned int)(usm_entries * sizeof(u64)); - } else { - extra_size = 0; + extra_size += (unsigned int)(usm_entries * sizeof(u64)); } /* @@ -1234,6 +1238,10 @@ int setup_new_fdt_ppc64(const struct kimage *image, void *fdt, } } + // If we have PLPKS active, we need to provide the password to the new kernel + if (plpks_is_available()) + ret = plpks_populate_fdt(fdt); + out: kfree(rmem); kfree(umem); diff --git a/arch/powerpc/platforms/pseries/plpks.c b/arch/powerpc/platforms/pseries/plpks.c index 671a10acaebf..cdf09e5bd741 100644 --- a/arch/powerpc/platforms/pseries/plpks.c +++ b/arch/powerpc/platforms/pseries/plpks.c @@ -16,6 +16,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -128,6 +131,12 @@ static int plpks_gen_password(void) u8 *password, consumer = PLPKS_OS_OWNER; int rc; + // If we booted from kexec, we could be reusing an existing password already + if (ospassword) { + pr_debug("Password of length %u already in use\n", ospasswordlength); + return 0; + } + // The password must not cross a page boundary, so we align to the next power of 2 password = kzalloc(roundup_pow_of_two(maxpwsize), GFP_KERNEL); if (!password) @@ -621,6 +630,58 @@ int plpks_read_bootloader_var(struct plpks_var *var) return plpks_read_var(PLPKS_BOOTLOADER_OWNER, var); } +int plpks_populate_fdt(void *fdt) +{ + int chosen_offset = fdt_path_offset(fdt, "/chosen"); + + if (chosen_offset < 0) { + pr_err("Can't find chosen node: %s\n", + fdt_strerror(chosen_offset)); + return chosen_offset; + } + + return fdt_setprop(fdt, chosen_offset, "ibm,plpks-pw", ospassword, ospasswordlength); +} + +// Once a password is registered with the hypervisor it cannot be cleared without +// rebooting the LPAR, so to keep using the PLPKS across kexec boots we need to +// recover the previous password from the FDT. +// +// There are a few challenges here. We don't want the password to be visible to +// users, so we need to clear it from the FDT. This has to be done in early boot. +// Clearing it from the FDT would make the FDT's checksum invalid, so we have to +// manually cause the checksum to be recalculated. +void __init plpks_early_init_devtree(void) +{ + void *fdt = initial_boot_params; + int chosen_node = fdt_path_offset(fdt, "/chosen"); + const u8 *password; + int len; + + if (chosen_node < 0) + return; + + password = fdt_getprop(fdt, chosen_node, "ibm,plpks-pw", &len); + if (len <= 0) { + pr_debug("Couldn't find ibm,plpks-pw node.\n"); + return; + } + + ospassword = memblock_alloc_raw(len, SMP_CACHE_BYTES); + if (!ospassword) { + pr_err("Error allocating memory for password.\n"); + goto out; + } + + memcpy(ospassword, password, len); + ospasswordlength = (u16)len; + +out: + fdt_nop_property(fdt, chosen_node, "ibm,plpks-pw"); + // Since we've cleared the password, we must update the FDT checksum + early_init_dt_verify(fdt); +} + static __init int pseries_plpks_init(void) { int rc; -- cgit v1.2.3