Merge tag 's390-7.2-7' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux

Pull s390 fixes from Vasily Gorbik:

 - Fix potential uninitialized memory reads and buffer overflows from
   malformed zcrypt CCA and EP11 requests by properly validating lengths
   and payloads

 - Fix possible out of bounds accesses in zcrypt EP11 domain handling by
   replacing fixed payload layout assumptions with parsing ASN.1 fields
   with bounds checks

 - Fix zcrypt CCA and EP11 request and reply buffer allocations missing
   required 4-byte padding, and scrub the full allocation on release

 - Fix zcrypt CCA and EP11 messages leaking up to 3 uninitialized bytes
   of memory by zeroing trailing alignment padding

* tag 's390-7.2-7' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux:
  s390/zcrypt: Pad trailing CCA or EP11 message with zeros
  s390/zcrypt: Improve EP11 CPRB domain handling with ASN.1 parsing
  s390/zcrypt: Improve EP11 CPRB length and overflow checks
  s390/zcrypt: Improve CCA CPRB length and overflow checks
  s390/zcrypt: Fix CPRB memory allocation in zcrypt misc code
This commit is contained in:
Linus Torvalds
2026-08-09 06:31:16 -07:00
3 changed files with 277 additions and 123 deletions

View File

@@ -15,6 +15,7 @@
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <linux/align.h>
#include <asm/zcrypt.h>
#include <asm/pkey.h>
@@ -267,6 +268,10 @@ EXPORT_SYMBOL(cca_check_sececckeytoken);
* block, reply CPRB and reply param block and fill in values
* for the common fields. Returns 0 on success or errno value
* on failure.
* It is guaranteed that request and a possible param block
* are aligned to a 4 byte boundary. Furthermore if a param
* block is used, the memory allocated for this is rounded up to
* the next multiple of 4 bytes.
*/
static int alloc_and_prep_cprbmem(size_t paramblen,
u8 **p_cprb_mem,
@@ -275,7 +280,8 @@ static int alloc_and_prep_cprbmem(size_t paramblen,
u32 xflags)
{
u8 *cprbmem = NULL;
size_t cprbplusparamblen = sizeof(struct CPRBX) + paramblen;
size_t cprbplusparamblen =
ALIGN(sizeof(struct CPRBX), 4) + ALIGN(paramblen, 4);
size_t len = 2 * cprbplusparamblen;
struct CPRBX *preqcblk, *prepcblk;
@@ -302,10 +308,10 @@ static int alloc_and_prep_cprbmem(size_t paramblen,
memcpy(preqcblk->func_id, "T2", 2);
preqcblk->rpl_msgbl = cprbplusparamblen;
if (paramblen) {
preqcblk->req_parmb =
((u8 __user *)preqcblk) + sizeof(struct CPRBX);
preqcblk->rpl_parmb =
((u8 __user *)prepcblk) + sizeof(struct CPRBX);
preqcblk->req_parmb = ((u8 __user *)preqcblk) +
ALIGN(sizeof(struct CPRBX), 4);
preqcblk->rpl_parmb = ((u8 __user *)prepcblk) +
ALIGN(sizeof(struct CPRBX), 4);
}
*p_cprb_mem = cprbmem;
@@ -323,8 +329,10 @@ static int alloc_and_prep_cprbmem(size_t paramblen,
*/
static void free_cprbmem(void *mem, size_t paramblen, bool scrub, u32 xflags)
{
size_t cprblen = ALIGN(sizeof(struct CPRBX), 4) + ALIGN(paramblen, 4);
if (mem && scrub)
memzero_explicit(mem, 2 * (sizeof(struct CPRBX) + paramblen));
memzero_explicit(mem, 2 * cprblen);
if (xflags & ZCRYPT_XFLAG_NOMEMALLOC)
mempool_free(mem, cprb_mempool);

View File

@@ -14,6 +14,7 @@
#include <linux/module.h>
#include <linux/random.h>
#include <linux/slab.h>
#include <linux/align.h>
#include <asm/zcrypt.h>
#include <asm/pkey.h>
#include <crypto/aes.h>
@@ -355,21 +356,24 @@ EXPORT_SYMBOL(ep11_check_aes_key);
/*
* Allocate and prepare ep11 cprb plus additional payload.
* It is guaranteed that the memory is aligned to a 4 byte boundary.
* Furthermore the memory allocation is rounded up to the next
* multiple of 4 bytes (with taking the payload_len into account).
*/
static void *alloc_cprbmem(size_t payload_len, u32 xflags)
{
size_t len = sizeof(struct ep11_cprb) + payload_len;
size_t memlen = ALIGN(sizeof(struct ep11_cprb) + payload_len, 4);
struct ep11_cprb *cprb = NULL;
if (xflags & ZCRYPT_XFLAG_NOMEMALLOC) {
if (len <= CPRB_MEMPOOL_ITEM_SIZE)
if (memlen <= CPRB_MEMPOOL_ITEM_SIZE)
cprb = mempool_alloc_preallocated(cprb_mempool);
} else {
cprb = kmalloc(len, GFP_KERNEL);
cprb = kmalloc(memlen, GFP_KERNEL);
}
if (!cprb)
return NULL;
memset(cprb, 0, len);
memset(cprb, 0, memlen);
cprb->cprb_len = sizeof(struct ep11_cprb);
cprb->cprb_ver_id = 0x04;
@@ -385,8 +389,10 @@ static void *alloc_cprbmem(size_t payload_len, u32 xflags)
*/
static void free_cprbmem(void *mem, size_t payload_len, bool scrub, u32 xflags)
{
size_t memlen = ALIGN(sizeof(struct ep11_cprb) + payload_len, 4);
if (mem && scrub)
memzero_explicit(mem, sizeof(struct ep11_cprb) + payload_len);
memzero_explicit(mem, memlen);
if (xflags & ZCRYPT_XFLAG_NOMEMALLOC)
mempool_free(mem, cprb_mempool);

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");
@@ -342,49 +346,39 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg,
};
} __packed * msg = ap_msg->msg;
int rcblen = CEIL4(xcrb->request_control_blk_length);
int req_sumlen, resp_sumlen;
char *req_data = ap_msg->msg + sizeof(struct type6_hdr) + rcblen;
char *function_code;
size_t req_cblen, rep_cblen, req_sumlen, rep_sumlen;
char *function_code, *req_data;
if (CEIL4(xcrb->request_control_blk_length) <
xcrb->request_control_blk_length)
return -EINVAL; /* overflow after alignment*/
/* length checks */
ap_msg->len = sizeof(struct type6_hdr) +
CEIL4(xcrb->request_control_blk_length) +
xcrb->request_data_length;
/* request length and overflow checks */
if (xcrb->request_control_blk_length < sizeof(struct CPRBX))
return -EINVAL;
req_cblen = CEIL4((size_t)xcrb->request_control_blk_length);
if (req_cblen > U32_MAX)
return -EINVAL;
req_sumlen = req_cblen + xcrb->request_data_length;
if (req_sumlen > U32_MAX)
return -EINVAL;
ap_msg->len = sizeof(struct type6_hdr) + req_sumlen;
if (ap_msg->len > ap_msg->bufsize)
return -EINVAL;
/*
* Overflow check
* sum must be greater (or equal) than the largest operand
*/
req_sumlen = CEIL4(xcrb->request_control_blk_length) +
xcrb->request_data_length;
if ((CEIL4(xcrb->request_control_blk_length) <=
xcrb->request_data_length) ?
if (req_cblen <= xcrb->request_data_length ?
req_sumlen < xcrb->request_data_length :
req_sumlen < CEIL4(xcrb->request_control_blk_length)) {
req_sumlen < req_cblen) {
return -EINVAL;
}
if (CEIL4(xcrb->reply_control_blk_length) <
xcrb->reply_control_blk_length)
return -EINVAL; /* overflow after alignment*/
/*
* Overflow check
* sum must be greater (or equal) than the largest operand
*/
resp_sumlen = CEIL4(xcrb->reply_control_blk_length) +
xcrb->reply_data_length;
if ((CEIL4(xcrb->reply_control_blk_length) <=
xcrb->reply_data_length) ?
resp_sumlen < xcrb->reply_data_length :
resp_sumlen < CEIL4(xcrb->reply_control_blk_length)) {
/* reply length and overflow checks */
if (xcrb->reply_control_blk_length < sizeof(struct CPRBX))
return -EINVAL;
rep_cblen = CEIL4((size_t)xcrb->reply_control_blk_length);
if (rep_cblen > U32_MAX)
return -EINVAL;
rep_sumlen = rep_cblen + xcrb->reply_data_length;
if (rep_sumlen > U32_MAX)
return -EINVAL;
if (rep_cblen <= xcrb->reply_data_length ?
rep_sumlen < xcrb->reply_data_length :
rep_sumlen < rep_cblen) {
return -EINVAL;
}
@@ -393,7 +387,7 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg,
memcpy(msg->hdr.agent_id, &xcrb->agent_ID, sizeof(xcrb->agent_ID));
msg->hdr.tocardlen1 = xcrb->request_control_blk_length;
if (xcrb->request_data_length) {
msg->hdr.offset2 = msg->hdr.offset1 + rcblen;
msg->hdr.offset2 = msg->hdr.offset1 + req_cblen;
msg->hdr.tocardlen2 = xcrb->request_data_length;
}
msg->hdr.fromcardlen1 = xcrb->reply_control_blk_length;
@@ -404,8 +398,12 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg,
xcrb->request_control_blk_addr,
xcrb->request_control_blk_length))
return -EFAULT;
if (msg->cprbx.cprb_len + sizeof(msg->hdr.function_code) >
xcrb->request_control_blk_length)
/* pad tail with 0 up to req_cblen */
if (xcrb->request_control_blk_length < req_cblen)
memset(msg->userdata + xcrb->request_control_blk_length,
0, req_cblen - xcrb->request_control_blk_length);
/* copy subfunction code into AP msg type 6 function code field */
if (msg->cprbx.cprb_len > req_cblen - sizeof(msg->hdr.function_code))
return -EINVAL;
function_code = ((unsigned char *)&msg->cprbx) + msg->cprbx.cprb_len;
memcpy(msg->hdr.function_code, function_code,
@@ -437,20 +435,70 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg,
}
/* copy data block */
if (xcrb->request_data_length &&
z_copy_from_user(userspace, req_data, xcrb->request_data_address,
xcrb->request_data_length))
return -EFAULT;
if (xcrb->request_data_length) {
req_data = ap_msg->msg + sizeof(struct type6_hdr) + req_cblen;
if (z_copy_from_user(userspace, req_data,
xcrb->request_data_address,
xcrb->request_data_length))
return -EFAULT;
}
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},
@@ -462,34 +510,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;
@@ -498,26 +544,55 @@ 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;
}
/* pad tail with 0 up to req_len */
if (xcrb->req_len < req_len)
memset(msg->userdata + xcrb->req_len, 0,
req_len - xcrb->req_len);
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)
@@ -1159,6 +1234,28 @@ int prep_ep11_ap_msg(bool userspace, struct ep11_urb *xcrb,
func_code, domain);
}
/*
* Simple asn1 int writer/encoder 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_encode(u8 *buf, size_t intlen, u32 u)
{
switch (intlen) {
case 1:
*buf = (u8)u;
return 1;
case 2:
put_unaligned_be16((u16)u, buf);
return 2;
case 4:
put_unaligned_be32((u32)u, buf);
return 4;
default:
return -EINVAL;
}
}
/*
* The request distributor calls this function if it picked the CEX4P
* device to handle a send_ep11_cprb request.
@@ -1171,51 +1268,94 @@ static long zcrypt_msgtype6_send_ep11_cprb(bool userspace, struct zcrypt_queue *
struct ap_message *ap_msg)
{
int rc;
unsigned int lfmt;
struct ap_response_type *resp_type = &ap_msg->response;
struct {
struct type6_hdr hdr;
struct ep11_cprb cprbx;
unsigned char pld_tag; /* fixed value 0x30 */
unsigned char pld_lenfmt; /* payload length format */
} __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;
/*
* The target domain field within the cprb body/payload block will be
* replaced by the usage domain for non-management commands only.
* Therefore we check the first bit of the 'flags' parameter for
* management command indication.
* 0 - non management command
* 1 - management command
*/
if (!((msg->cprbx.flags & 0x80) == 0x80)) {
msg->cprbx.target_id = (unsigned int)
AP_QID_QUEUE(zq->queue->qid);
if (!(msg->cprbx.flags & 0x80)) {
int i, offs = 0;
size_t pld_len;
u8 *pld;
u32 u;
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:
/* update target field in ep11_cprb */
msg->cprbx.target_id = (u32)AP_QID_QUEUE(zq->queue->qid);
/* ptr and length to payload */
pld = ap_msg->msg +
sizeof(struct type6_hdr) + sizeof(struct ep11_cprb);
pld_len = msg->cprbx.payload_len;
if (pld_len < MIN_EP11_PAYLOAD_SIZE)
return -EINVAL;
/*
* Parse the asn1 payload, at least we have
* pld tag (1 octet)
* payload length (1-5 octets)
* function tag (1 octet)
* function length (1-5 octets)
* function value (1-4 octets)
* ----- optional fields -----
* domain tag (1 octet)
* domain length (1-5 octets)
* domain value (1-4 octets)
* ... maybe much more data ...
*/
/* 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 (u > pld_len - offs)
return -EINVAL;
/* skip over the function value */
offs += u;
/* is there some payload left which could hold a domain value ? */
if (offs < pld_len && pld_len - offs >= 3) {
/* domain tag */
if (pld[offs++] != 0x04)
return -EINVAL;
}
} else {
lfmt = 1; /* length format #1 */
/* domain length */
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;
/*
* pld[offs] is now at the start of the domain value
* with the value sprawled in u octets.
*/
i = asn1_int_encode(pld + offs, u,
AP_QID_QUEUE(zq->queue->qid));
if (i < 0)
return -EINVAL;
offs += i;
}
payload_hdr = (struct pld_hdr *)((&msg->pld_lenfmt) + lfmt);
payload_hdr->dom_val = AP_QID_QUEUE(zq->queue->qid);
}
/*