]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
s390/zcrypt: Improve EP11 CPRB length and overflow checks
authorHarald Freudenberger <freude@linux.ibm.com>
Mon, 3 Aug 2026 08:33:36 +0000 (10:33 +0200)
committerVasily Gorbik <gor@linux.ibm.com>
Wed, 5 Aug 2026 13:12:22 +0000 (15:12 +0200)
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: e2c6d91eb8b1 ("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>
drivers/s390/crypto/zcrypt_msgtype6.c

index 3e19e79d747cf0880012f8ba36348d46ffd9d507..7e1f76c935ee180e336f26f4b0f00ebbf39cf922 100644 (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;
-
-       if (CEIL4(xcrb->req_len) < xcrb->req_len)
-               return -EINVAL; /* overflow after alignment*/
+       size_t req_len, rep_len, pld_len;
+       unsigned char *pld;
+       int offs = 0, i;
+       unsigned int u;
 
-       /* 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)