From: Russell Currey <ruscur@xxxxxxxxxx> 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. Signed-off-by: Russell Currey <ruscur@xxxxxxxxxx> Signed-off-by: Andrew Donnellan <ajd@xxxxxxxxxxxxx> --- v3: New patch v4: Fix compile when CONFIG_PSERIES_PLPKS=n (snowpatch) Fix error handling on fdt_path_offset() call (ruscur) v5: Fix DT property name in commit message (npiggin) Clear prop in FDT during init to prevent password exposure (mpe) Rework to remove ifdefs from C code (npiggin) --- arch/powerpc/include/asm/plpks.h | 14 ++++++ arch/powerpc/kernel/prom.c | 4 ++ arch/powerpc/kexec/file_load_64.c | 15 +++++-- arch/powerpc/platforms/pseries/plpks.c | 60 ++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 3 deletions(-) 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 4f1c920aa13e..8a13b378770f 100644 --- a/arch/powerpc/kernel/prom.c +++ b/arch/powerpc/kernel/prom.c @@ -56,6 +56,7 @@ #include <asm/drmem.h> #include <asm/ultravisor.h> #include <asm/prom.h> +#include <asm/plpks.h> #include <mm/mmu_decl.h> @@ -893,6 +894,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 af8854f9eae3..3f5740fb01a4 100644 --- a/arch/powerpc/kexec/file_load_64.c +++ b/arch/powerpc/kexec/file_load_64.c @@ -27,6 +27,7 @@ #include <asm/kexec_ranges.h> #include <asm/crashdump-ppc64.h> #include <asm/prom.h> +#include <asm/plpks.h> struct umem_info { u64 *buf; /* data buffer for usable-memory property */ @@ -977,12 +978,16 @@ 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; + // Make room for the PLPKS password, plus node overhead for ibm,plpks-pw. + if (plpks_is_available()) + extra_size += (unsigned int)plpks_get_passwordlen() + 32; + if (image->type != KEXEC_TYPE_CRASH) - return 0; + return extra_size; /* * For kdump kernel, account for linux,usable-memory and @@ -992,7 +997,7 @@ unsigned int kexec_extra_fdt_size_ppc64(struct kimage *image) usm_entries = ((memblock_end_of_DRAM() / drmem_lmb_size()) + (2 * (resource_size(&crashk_res) / drmem_lmb_size()))); - extra_size = (unsigned int)(usm_entries * sizeof(u64)); + extra_size += (unsigned int)(usm_entries * sizeof(u64)); /* * Get the number of CPU nodes in the current DT. This allows to @@ -1230,6 +1235,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 6940280ae94a..481a669845c5 100644 --- a/arch/powerpc/platforms/pseries/plpks.c +++ b/arch/powerpc/platforms/pseries/plpks.c @@ -16,6 +16,9 @@ #include <linux/slab.h> #include <linux/string.h> #include <linux/types.h> +#include <linux/of_fdt.h> +#include <linux/libfdt.h> +#include <linux/memblock.h> #include <asm/hvcall.h> #include <asm/machdep.h> #include <asm/plpks.h> @@ -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,57 @@ 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"); + u8 *password; + int len; + + if (chosen_node < 0) + return; + + password = (u8 *)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; -- 2.39.1