s390/zcrypt: Improve EP11 CPRB length and overflow checks

The xcrb_msg_to_type6_ep11cprb_msgx() function lacks proper input
validation, creating security vulnerabilities:
1. Missing minimum size validation: The ep11_cprb structure and
   subsequent payload fields (pld_tag, pld_lenfmt) are copied from
   userspace without verifying sufficient buffer length.
2. Arithmetic overflow in length calculations: CEIL4 alignment could
   overflow, bypassing size checks and enabling buffer overflows.
3. The payload is asn1 encoded but the function just uses a simple c
   struct overlay to access some fields of the payload.

Fix by using size_t for length calculations, adding U32_MAX boundary
checks after alignment, and validating minimum request size and
minimum reply size before copying from userspace. Do a very simple
asn1 parsing of the payload up to the function value field.

Fixes: e2c6d91eb8 ("s390/zcrypt: Rework domain processing within zcrypt device driver")
Signed-off-by: Harald Freudenberger <freude@linux.ibm.com>
Cc: stable@vger.kernel.org # 7.1+
Reviewed-by: Holger Dengler <dengler@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
This commit is contained in:
Harald Freudenberger 2026-08-03 10:33:36 +02:00 committed by Vasily Gorbik
parent 35ac6754ba
commit 17ac0bc866

View File

@ -19,6 +19,7 @@
#include <linux/slab.h>
#include <linux/atomic.h>
#include <linux/uaccess.h>
#include <linux/unaligned.h>
#include "ap_bus.h"
#include "zcrypt_api.h"
@ -34,6 +35,9 @@
#define CEXXC_RESPONSE_TYPE_XCRB 1
#define CEXXC_RESPONSE_TYPE_EP11 2
/* smallest possible EP11 payload size */
#define MIN_EP11_PAYLOAD_SIZE 5
MODULE_AUTHOR("IBM Corporation");
MODULE_DESCRIPTION("Cryptographic Coprocessor (message type 6), " \
"Copyright IBM Corp. 2001, 2023");
@ -438,12 +442,59 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg,
return 0;
}
/*
* Simple asn1 int reader/decoder helper function
* Returns number of bytes processed or < 0 on failure
* Only accepts int length values of 1, 2 or 4.
*/
static inline int asn1_int_decode(const u8 *buf, size_t intlen, u32 *u)
{
switch (intlen) {
case 1:
*u = (u32)(*buf);
return 1;
case 2:
*u = (u32)get_unaligned_be16(buf);
return 2;
case 4:
*u = (u32)get_unaligned_be32(buf);
return 4;
default:
return -EINVAL;
}
}
/*
* Simple asn1 length parse helper function
* Returns number of bytes processed or < 0 on failure
* Only accepts length encoded within the length octet
* or for long form 1, 2 or 4 octet length bytes.
*/
static inline int asn1_length_decode(const u8 *buf, size_t buflen, u32 *u)
{
int i;
if (buflen < 1)
return -EINVAL;
if (*buf < 128) {
*u = (u32)(*buf & 0x7F);
return 1;
}
i = *buf & 0x7F;
if (--buflen < i)
return -EINVAL;
i = asn1_int_decode(++buf, i, u);
return i < 0 ? i : i + 1;
}
static int xcrb_msg_to_type6_ep11cprb_msgx(bool userspace, struct ap_message *ap_msg,
struct ep11_urb *xcrb,
unsigned int *fcode,
unsigned int *domain)
{
unsigned int lfmt;
static struct type6_hdr static_type6_ep11_hdr = {
.type = 0x06,
.rqid = {0x00, 0x01},
@ -455,34 +506,32 @@ static int xcrb_msg_to_type6_ep11cprb_msgx(bool userspace, struct ap_message *ap
struct {
struct type6_hdr hdr;
union {
struct {
struct ep11_cprb cprbx;
unsigned char pld_tag; /* fixed value 0x30 */
unsigned char pld_lenfmt; /* length format */
} __packed;
struct ep11_cprb cprbx;
DECLARE_FLEX_ARRAY(u8, userdata);
};
} __packed * msg = ap_msg->msg;
struct pld_hdr {
unsigned char func_tag; /* fixed value 0x4 */
unsigned char func_len; /* fixed value 0x4 */
unsigned int func_val; /* function ID */
unsigned char dom_tag; /* fixed value 0x4 */
unsigned char dom_len; /* fixed value 0x4 */
unsigned int dom_val; /* domain id */
} __packed * payload_hdr = NULL;
size_t req_len, rep_len, pld_len;
unsigned char *pld;
int offs = 0, i;
unsigned int u;
if (CEIL4(xcrb->req_len) < xcrb->req_len)
return -EINVAL; /* overflow after alignment*/
/* length checks */
ap_msg->len = sizeof(struct type6_hdr) + CEIL4(xcrb->req_len);
/* request length and overflow checks */
if (xcrb->req_len < sizeof(struct ep11_cprb) + MIN_EP11_PAYLOAD_SIZE)
return -EINVAL;
req_len = CEIL4(xcrb->req_len);
if (req_len < xcrb->req_len || req_len > U32_MAX)
return -EINVAL;
ap_msg->len = sizeof(struct type6_hdr) + req_len;
if (ap_msg->len > ap_msg->bufsize)
return -EINVAL;
if (CEIL4(xcrb->resp_len) < xcrb->resp_len)
return -EINVAL; /* overflow after alignment*/
/* reply length and overflow checks */
if (xcrb->resp_len < sizeof(struct ep11_cprb))
return -EINVAL;
rep_len = CEIL4(xcrb->resp_len);
if (rep_len < xcrb->resp_len || rep_len > U32_MAX)
return -EINVAL;
/* prepare type6 header */
msg->hdr = static_type6_ep11_hdr;
@ -491,26 +540,51 @@ static int xcrb_msg_to_type6_ep11cprb_msgx(bool userspace, struct ap_message *ap
/* Import CPRB data from the ioctl input parameter */
if (z_copy_from_user(userspace, msg->userdata,
(char __force __user *)xcrb->req, xcrb->req_len)) {
(char __force __user *)xcrb->req, xcrb->req_len))
return -EFAULT;
}
if ((msg->pld_lenfmt & 0x80) == 0x80) { /*ext.len.fmt 2 or 3*/
switch (msg->pld_lenfmt & 0x03) {
case 1:
lfmt = 2;
break;
case 2:
lfmt = 3;
break;
default:
return -EINVAL;
}
} else {
lfmt = 1; /* length format #1 */
}
payload_hdr = (struct pld_hdr *)((&msg->pld_lenfmt) + lfmt);
*fcode = payload_hdr->func_val & 0xFFFF;
pld = msg->userdata + sizeof(struct ep11_cprb);
pld_len = msg->cprbx.payload_len;
if (pld_len != xcrb->req_len - sizeof(struct ep11_cprb))
return -EINVAL;
/*
* At this point pld_len is always >= MIN_EP11_PAYLOAD_SIZE
* and the smallest supported asn1 payload is:
* payload tag (1 octet)
* payload length (1-5 octets)
* function tag (1 octet)
* function length (1-5 octets)
* function value (1-4 octets)
*/
/* payload tag */
if (pld[offs++] != 0x30)
return -EINVAL;
/* payload length field */
i = asn1_length_decode(pld + offs, pld_len - offs, &u);
if (i < 0)
return -EINVAL;
offs += i;
if (offs >= pld_len || u > pld_len - offs)
return -EINVAL;
/* function tag */
if (pld[offs++] != 0x04)
return -EINVAL;
/* function length */
if (offs >= pld_len)
return -EINVAL;
i = asn1_length_decode(pld + offs, pld_len - offs, &u);
if (i < 0)
return -EINVAL;
offs += i;
if (offs >= pld_len || u > pld_len - offs)
return -EINVAL;
/* function value */
i = asn1_int_decode(pld + offs, u, &u);
if (i < 0)
return -EINVAL;
offs += i;
*fcode = 0xFFFF & u;
/* enable special processing based on the cprbs flags special bit */
if (msg->cprbx.flags & 0x20)