Re: [PATCH v4 4/6] pseries/plpks: add HCALLs for PowerVM Key Wrapping Module

Nayna Jain
2026-01-15T20:45:55Z

On 1/15/26 5:05 AM, Srish Srinivasan wrote:
> The hypervisor generated wrapping key is an AES-GCM-256 symmetric key which
> is stored in a non-volatile, secure, and encrypted storage called the Power
> LPAR Platform KeyStore. It has policy based protections that prevent it
> from being read out or exposed to the user.
>
> Implement H_PKS_GEN_KEY, H_PKS_WRAP_OBJECT, and H_PKS_UNWRAP_OBJECT HCALLs
> to enable using the PowerVM Key Wrapping Module (PKWM) as a new trust
> source for trusted keys. Disallow H_PKS_READ_OBJECT, H_PKS_SIGNED_UPDATE,
> and H_PKS_WRITE_OBJECT for objects with the 'wrapping key' policy set.
> Capture the availability status for the H_PKS_WRAP_OBJECT interface.

Reviewed-by: Nayna Jain <nayna@linux.ibm.com>
>
> Signed-off-by: Srish Srinivasan <ssrish@linux.ibm.com>
> ---
> Documentation/arch/powerpc/papr_hcalls.rst | 43 +++
> arch/powerpc/include/asm/plpks.h | 10 +
> arch/powerpc/platforms/pseries/plpks.c | 342 ++++++++++++++++++++-
> 3 files changed, 393 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/arch/powerpc/papr_hcalls.rst b/Documentation/arch/powerpc/papr_hcalls.rst
> index 805e1cb9bab9..14e39f095a1c 100644
> --- a/Documentation/arch/powerpc/papr_hcalls.rst
> +++ b/Documentation/arch/powerpc/papr_hcalls.rst
> @@ -300,6 +300,49 @@ H_HTM supports setup, configuration, control and dumping of Hardware Trace
> Macro (HTM) function and its data. HTM buffer stores tracing data for functions
> like core instruction, core LLAT and nest.
>
> +**H_PKS_GEN_KEY**
> +
> +| Input: authorization, objectlabel, objectlabellen, policy, out, outlen
> +| Out: *Hypervisor Generated Key, or None when the wrapping key policy is set*
> +| Return Value: *H_SUCCESS, H_Function, H_State, H_R_State, H_Parameter, H_P2,
> + H_P3, H_P4, H_P5, H_P6, H_Authority, H_Nomem, H_Busy, H_Resource,
> + H_Aborted*
> +
> +H_PKS_GEN_KEY is used to have the hypervisor generate a new random key.
> +This key is stored as an object in the Power LPAR Platform KeyStore with
> +the provided object label. With the wrapping key policy set the key is only
> +visible to the hypervisor, while the key's label would still be visible to
> +the user. Generation of wrapping keys is supported only for a key size of
> +32 bytes.
> +
> +**H_PKS_WRAP_OBJECT**
> +
> +| Input: authorization, wrapkeylabel, wrapkeylabellen, objectwrapflags, in,
> +| inlen, out, outlen, continue-token
> +| Out: *continue-token, byte size of wrapped object, wrapped object*
> +| Return Value: *H_SUCCESS, H_Function, H_State, H_R_State, H_Parameter, H_P2,
> + H_P3, H_P4, H_P5, H_P6, H_P7, H_P8, H_P9, H_Authority, H_Invalid_Key,
> + H_NOT_FOUND, H_Busy, H_LongBusy, H_Aborted*
> +
> +H_PKS_WRAP_OBJECT is used to wrap an object using a wrapping key stored in the
> +Power LPAR Platform KeyStore and return the wrapped object to the caller. The
> +caller provides a label to a wrapping key with the 'wrapping key' policy set,
> +which must have been previously created with H_PKS_GEN_KEY. The provided object
> +is then encrypted with the wrapping key and additional metadata and the result
> +is returned to the caller.
> +
> +
> +**H_PKS_UNWRAP_OBJECT**
> +
> +| Input: authorization, objectwrapflags, in, inlen, out, outlen, continue-token
> +| Out: *continue-token, byte size of unwrapped object, unwrapped object*
> +| Return Value: *H_SUCCESS, H_Function, H_State, H_R_State, H_Parameter, H_P2,
> + H_P3, H_P4, H_P5, H_P6, H_P7, H_Authority, H_Unsupported, H_Bad_Data,
> + H_NOT_FOUND, H_Invalid_Key, H_Busy, H_LongBusy, H_Aborted*
> +
> +H_PKS_UNWRAP_OBJECT is used to unwrap an object that was previously warapped with
> +H_PKS_WRAP_OBJECT.
> +
> References
> ==========
> .. [1] "Power Architecture Platform Reference"
> diff --git a/arch/powerpc/include/asm/plpks.h b/arch/powerpc/include/asm/plpks.h
> index 8f034588fdf7..e87f90e40d4e 100644
> --- a/arch/powerpc/include/asm/plpks.h
> +++ b/arch/powerpc/include/asm/plpks.h
> @@ -113,6 +113,16 @@ void plpks_early_init_devtree(void);
> int plpks_populate_fdt(void *fdt);
>
> int plpks_config_create_softlink(struct kobject *from);
> +
> +bool plpks_wrapping_is_supported(void);
> +
> +int plpks_gen_wrapping_key(void);
> +
> +int plpks_wrap_object(u8 **input_buf, u32 input_len, u16 wrap_flags,
> + u8 **output_buf, u32 *output_len);
> +
> +int plpks_unwrap_object(u8 **input_buf, u32 input_len,
> + u8 **output_buf, u32 *output_len);
> #else // CONFIG_PSERIES_PLPKS
> static inline bool plpks_is_available(void) { return false; }
> static inline u16 plpks_get_passwordlen(void) { BUILD_BUG(); }
> diff --git a/arch/powerpc/platforms/pseries/plpks.c b/arch/powerpc/platforms/pseries/plpks.c
> index 4a08f51537c8..b97b7750f6a8 100644
> --- a/arch/powerpc/platforms/pseries/plpks.c
> +++ b/arch/powerpc/platforms/pseries/plpks.c
> @@ -9,6 +9,32 @@
>
> #define pr_fmt(fmt) "plpks: " fmt
>
> +#define PLPKS_WRAPKEY_COMPONENT "PLPKSWR"
> +#define PLPKS_WRAPKEY_NAME "default-wrapping-key"
> +
> +/*
> + * To 4K align the {input, output} buffers to the {UN}WRAP H_CALLs
> + */
> +#define PLPKS_WRAPPING_BUF_ALIGN 4096
> +
> +/*
> + * To ensure the output buffer's length is at least 1024 bytes greater
> + * than the input buffer's length during the WRAP H_CALL
> + */
> +#define PLPKS_WRAPPING_BUF_DIFF 1024
> +
> +#define PLPKS_WRAP_INTERFACE_BIT 3
> +#define PLPKS_WRAPPING_KEY_LENGTH 32
> +
> +#define WRAPFLAG_BE_BIT_SET(be_bit) \
> + BIT_ULL(63 - (be_bit))
> +
> +#define WRAPFLAG_BE_GENMASK(be_bit_hi, be_bit_lo) \
> + GENMASK_ULL(63 - (be_bit_hi), 63 - (be_bit_lo))
> +
> +#define WRAPFLAG_BE_FIELD_PREP(be_bit_hi, be_bit_lo, val) \
> + FIELD_PREP(WRAPFLAG_BE_GENMASK(be_bit_hi, be_bit_lo), (val))
> +
> #include <linux/delay.h>
> #include <linux/errno.h>
> #include <linux/io.h>
> @@ -39,6 +65,7 @@ static u32 supportedpolicies;
> static u32 maxlargeobjectsize;
> static u64 signedupdatealgorithms;
> static u64 wrappingfeatures;
> +static bool wrapsupport;
>
> struct plpks_auth {
> u8 version;
> @@ -283,6 +310,7 @@ static int _plpks_get_config(void)
> maxlargeobjectsize = be32_to_cpu(config->maxlargeobjectsize);
> signedupdatealgorithms = be64_to_cpu(config->signedupdatealgorithms);
> wrappingfeatures = be64_to_cpu(config->wrappingfeatures);
> + wrapsupport = config->flags & PPC_BIT8(PLPKS_WRAP_INTERFACE_BIT);
>
> // Validate that the numbers we get back match the requirements of the spec
> if (maxpwsize < 32) {
> @@ -614,6 +642,9 @@ int plpks_signed_update_var(struct plpks_var *var, u64 flags)
> if (!(var->policy & PLPKS_SIGNEDUPDATE))
> return -EINVAL;
>
> + if (var->policy & PLPKS_WRAPPINGKEY)
> + return -EINVAL;
> +
> // Signed updates need the component to be NULL.
> if (var->component)
> return -EINVAL;
> @@ -696,6 +727,9 @@ int plpks_write_var(struct plpks_var var)
> if (var.policy & PLPKS_SIGNEDUPDATE)
> return -EINVAL;
>
> + if (var.policy & PLPKS_WRAPPINGKEY)
> + return -EINVAL;
> +
> auth = construct_auth(PLPKS_OS_OWNER);
> if (IS_ERR(auth))
> return PTR_ERR(auth);
> @@ -790,6 +824,9 @@ static int plpks_read_var(u8 consumer, struct plpks_var *var)
> if (var->namelen > PLPKS_MAX_NAME_SIZE)
> return -EINVAL;
>
> + if (var->policy & PLPKS_WRAPPINGKEY)
> + return -EINVAL;
> +
> auth = construct_auth(consumer);
> if (IS_ERR(auth))
> return PTR_ERR(auth);
> @@ -845,8 +882,309 @@ static int plpks_read_var(u8 consumer, struct plpks_var *var)
> }
>
> /**
> - * plpks_read_os_var() - Fetch the data for the specified variable that is
> - * owned by the OS consumer.
> + * plpks_wrapping_is_supported() - Get the H_PKS_WRAP_OBJECT interface
> + * availability status for the LPAR.
> + *
> + * Successful execution of the H_PKS_GET_CONFIG HCALL during initialization
> + * sets bit 3 of the flags variable in the PLPKS config structure if the
> + * H_PKS_WRAP_OBJECT interface is supported.
> + *
> + * Returns: true if the H_PKS_WRAP_OBJECT interface is supported, false if not.
> + */
> +bool plpks_wrapping_is_supported(void)
> +{
> + return wrapsupport;
> +}
> +
> +/**
> + * plpks_gen_wrapping_key() - Generate a new random key with the 'wrapping key'
> + * policy set.
> + *
> + * The H_PKS_GEN_KEY HCALL makes the hypervisor generate a new random key and
> + * store the key in a PLPKS object with the provided object label. With the
> + * 'wrapping key' policy set, only the label to the newly generated random key
> + * would be visible to the user.
> + *
> + * Possible reasons for the returned errno values:
> + *
> + * -ENXIO if PLPKS is not supported
> + * -EIO if PLPKS access is blocked due to the LPAR's state
> + * if PLPKS modification is blocked due to the LPAR's state
> + * if an error occurred while processing the request
> + * -EINVAL if invalid authorization parameter
> + * if invalid object label parameter
> + * if invalid object label len parameter
> + * if invalid or unsupported policy declaration
> + * if invalid output buffer parameter
> + * if invalid output buffer length parameter
> + * -EPERM if access is denied
> + * -ENOMEM if there is inadequate memory to perform this operation
> + * -EBUSY if unable to handle the request
> + * -EEXIST if the object label already exists
> + *
> + * Returns: On success 0 is returned, a negative errno if not.
> + */
> +int plpks_gen_wrapping_key(void)
> +{
> + unsigned long retbuf[PLPAR_HCALL_BUFSIZE] = { 0 };
> + struct plpks_auth *auth;
> + struct label *label;
> + int rc = 0, pseries_status = 0;
> + struct plpks_var var = {
> + .name = PLPKS_WRAPKEY_NAME,
> + .namelen = strlen(var.name),
> + .policy = PLPKS_WRAPPINGKEY,
> + .os = PLPKS_VAR_LINUX,
> + .component = PLPKS_WRAPKEY_COMPONENT
> + };
> +
> + auth = construct_auth(PLPKS_OS_OWNER);
> + if (IS_ERR(auth))
> + return PTR_ERR(auth);
> +
> + label = construct_label(var.component, var.os, var.name, var.namelen);
> + if (IS_ERR(label)) {
> + rc = PTR_ERR(label);
> + goto out;
> + }
> +
> + rc = plpar_hcall(H_PKS_GEN_KEY, retbuf,
> + virt_to_phys(auth), virt_to_phys(label),
> + label->size, var.policy,
> + NULL, PLPKS_WRAPPING_KEY_LENGTH);
> +
> + if (!rc)
> + rc = plpks_confirm_object_flushed(label, auth);
> +
> + pseries_status = rc;
> + rc = pseries_status_to_err(rc);
> +
> + if (rc && rc != -EEXIST) {
> + pr_err("H_PKS_GEN_KEY failed. pseries_status=%d, rc=%d",
> + pseries_status, rc);
> + } else {
> + rc = 0;
> + }
> +
> + kfree(label);
> +out:
> + kfree(auth);
> + return rc;
> +}
> +EXPORT_SYMBOL_GPL(plpks_gen_wrapping_key);
> +
> +/**
> + * plpks_wrap_object() - Wrap an object using the default wrapping key stored in
> + * the PLPKS.
> + * @input_buf: buffer containing the data to be wrapped
> + * @input_len: length of the input buffer
> + * @wrap_flags: object wrapping flags
> + * @output_buf: buffer to store the wrapped data
> + * @output_len: length of the output buffer
> + *
> + * The H_PKS_WRAP_OBJECT HCALL wraps an object using a wrapping key stored in
> + * the PLPKS and returns the wrapped object to the caller. The caller provides a
> + * label to the wrapping key with the 'wrapping key' policy set that must have
> + * been previously created with the H_PKS_GEN_KEY HCALL. The provided object is
> + * then encrypted with the wrapping key and additional metadata and the result
> + * is returned to the user. The metadata includes the wrapping algorithm and the
> + * wrapping key name so those parameters are not required during unwrap.
> + *
> + * Possible reasons for the returned errno values:
> + *
> + * -ENXIO if PLPKS is not supported
> + * -EIO if PLPKS access is blocked due to the LPAR's state
> + * if PLPKS modification is blocked due to the LPAR's state
> + * if an error occurred while processing the request
> + * -EINVAL if invalid authorization parameter
> + * if invalid wrapping key label parameter
> + * if invalid wrapping key label length parameter
> + * if invalid or unsupported object wrapping flags
> + * if invalid input buffer parameter
> + * if invalid input buffer length parameter
> + * if invalid output buffer parameter
> + * if invalid output buffer length parameter
> + * if invalid continue token parameter
> + * if the wrapping key is not compatible with the wrapping
> + * algorithm
> + * -EPERM if access is denied
> + * -ENOENT if the requested wrapping key was not found
> + * -EBUSY if unable to handle the request or long running operation
> + * initiated, retry later.
> + *
> + * Returns: On success 0 is returned, a negative errno if not.
> + */
> +int plpks_wrap_object(u8 **input_buf, u32 input_len, u16 wrap_flags,
> + u8 **output_buf, u32 *output_len)
> +{
> + unsigned long retbuf[PLPAR_HCALL9_BUFSIZE] = { 0 };
> + struct plpks_auth *auth;
> + struct label *label;
> + u64 continuetoken = 0;
> + u64 objwrapflags = 0;
> + int rc = 0, pseries_status = 0;
> + bool sb_audit_or_enforce_bit = wrap_flags & BIT(0);
> + bool sb_enforce_bit = wrap_flags & BIT(1);
> + struct plpks_var var = {
> + .name = PLPKS_WRAPKEY_NAME,
> + .namelen = strlen(var.name),
> + .os = PLPKS_VAR_LINUX,
> + .component = PLPKS_WRAPKEY_COMPONENT
> + };
> +
> + auth = construct_auth(PLPKS_OS_OWNER);
> + if (IS_ERR(auth))
> + return PTR_ERR(auth);
> +
> + label = construct_label(var.component, var.os, var.name, var.namelen);
> + if (IS_ERR(label)) {
> + rc = PTR_ERR(label);
> + goto out;
> + }
> +
> + /* Set the consumer password requirement bit. A must have. */
> + objwrapflags |= WRAPFLAG_BE_BIT_SET(3);
> +
> + /* Set the wrapping algorithm bit. Just one algorithm option for now */
> + objwrapflags |= WRAPFLAG_BE_FIELD_PREP(60, 63, 0x1);
> +
> + if (sb_audit_or_enforce_bit & sb_enforce_bit) {
> + pr_err("Cannot set both audit/enforce and enforce bits.");
> + rc = -EINVAL;
> + goto out_free_label;
> + } else if (sb_audit_or_enforce_bit) {
> + objwrapflags |= WRAPFLAG_BE_BIT_SET(1);
> + } else if (sb_enforce_bit) {
> + objwrapflags |= WRAPFLAG_BE_BIT_SET(2);
> + }
> +
> + *output_len = input_len + PLPKS_WRAPPING_BUF_DIFF;
> +
> + *output_buf = kzalloc(ALIGN(*output_len, PLPKS_WRAPPING_BUF_ALIGN),
> + GFP_KERNEL);
> + if (!(*output_buf)) {
> + pr_err("Output buffer allocation failed. Returning -ENOMEM.");
> + rc = -ENOMEM;
> + goto out_free_label;
> + }
> +
> + do {
> + rc = plpar_hcall9(H_PKS_WRAP_OBJECT, retbuf,
> + virt_to_phys(auth), virt_to_phys(label),
> + label->size, objwrapflags,
> + virt_to_phys(*input_buf), input_len,
> + virt_to_phys(*output_buf), *output_len,
> + continuetoken);
> +
> + continuetoken = retbuf[0];
> + pseries_status = rc;
> + rc = pseries_status_to_err(rc);
> + } while (rc == -EBUSY);
> +
> + if (rc) {
> + pr_err("H_PKS_WRAP_OBJECT failed. pseries_status=%d, rc=%d",
> + pseries_status, rc);
> + kfree(*output_buf);
> + *output_buf = NULL;
> + } else {
> + *output_len = retbuf[1];
> + }
> +
> +out_free_label:
> + kfree(label);
> +out:
> + kfree(auth);
> + return rc;
> +}
> +EXPORT_SYMBOL_GPL(plpks_wrap_object);
> +
> +/**
> + * plpks_unwrap_object() - Unwrap an object using the default wrapping key
> + * stored in the PLPKS.
> + * @input_buf: buffer containing the data to be unwrapped
> + * @input_len: length of the input buffer
> + * @output_buf: buffer to store the unwrapped data
> + * @output_len: length of the output buffer
> + *
> + * The H_PKS_UNWRAP_OBJECT HCALL unwraps an object that was previously wrapped
> + * using the H_PKS_WRAP_OBJECT HCALL.
> + *
> + * Possible reasons for the returned errno values:
> + *
> + * -ENXIO if PLPKS is not supported
> + * -EIO if PLPKS access is blocked due to the LPAR's state
> + * if PLPKS modification is blocked due to the LPAR's state
> + * if an error occurred while processing the request
> + * -EINVAL if invalid authorization parameter
> + * if invalid or unsupported object unwrapping flags
> + * if invalid input buffer parameter
> + * if invalid input buffer length parameter
> + * if invalid output buffer parameter
> + * if invalid output buffer length parameter
> + * if invalid continue token parameter
> + * if the wrapping key is not compatible with the wrapping
> + * algorithm
> + * if the wrapped object's format is not supported
> + * if the wrapped object is invalid
> + * -EPERM if access is denied
> + * -ENOENT if the wrapping key for the provided object was not found
> + * -EBUSY if unable to handle the request or long running operation
> + * initiated, retry later.
> + *
> + * Returns: On success 0 is returned, a negative errno if not.
> + */
> +int plpks_unwrap_object(u8 **input_buf, u32 input_len, u8 **output_buf,
> + u32 *output_len)
> +{
> + unsigned long retbuf[PLPAR_HCALL9_BUFSIZE] = { 0 };
> + struct plpks_auth *auth;
> + u64 continuetoken = 0;
> + u64 objwrapflags = 0;
> + int rc = 0, pseries_status = 0;
> +
> + auth = construct_auth(PLPKS_OS_OWNER);
> + if (IS_ERR(auth))
> + return PTR_ERR(auth);
> +
> + *output_len = input_len - PLPKS_WRAPPING_BUF_DIFF;
> + *output_buf = kzalloc(ALIGN(*output_len, PLPKS_WRAPPING_BUF_ALIGN),
> + GFP_KERNEL);
> + if (!(*output_buf)) {
> + pr_err("Output buffer allocation failed. Returning -ENOMEM.");
> + rc = -ENOMEM;
> + goto out;
> + }
> +
> + do {
> + rc = plpar_hcall9(H_PKS_UNWRAP_OBJECT, retbuf,
> + virt_to_phys(auth), objwrapflags,
> + virt_to_phys(*input_buf), input_len,
> + virt_to_phys(*output_buf), *output_len,
> + continuetoken);
> +
> + continuetoken = retbuf[0];
> + pseries_status = rc;
> + rc = pseries_status_to_err(rc);
> + } while (rc == -EBUSY);
> +
> + if (rc) {
> + pr_err("H_PKS_UNWRAP_OBJECT failed. pseries_status=%d, rc=%d",
> + pseries_status, rc);
> + kfree(*output_buf);
> + *output_buf = NULL;
> + } else {
> + *output_len = retbuf[1];
> + }
> +
> +out:
> + kfree(auth);
> + return rc;
> +}
> +EXPORT_SYMBOL_GPL(plpks_unwrap_object);
> +
> +/**
> + * plpks_read_os_var() - Fetch the data for the specified variable that is owned
> + * by the OS consumer.
> * @var: variable to be read from the PLPKS
> *
> * The consumer or the owner of the object is the os kernel. The