]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
s390/zcrypt: Improve EP11 CPRB domain handling with ASN.1 parsing
authorHarald Freudenberger <freude@linux.ibm.com>
Mon, 3 Aug 2026 08:33:37 +0000 (10:33 +0200)
committerVasily Gorbik <gor@linux.ibm.com>
Wed, 5 Aug 2026 13:12:22 +0000 (15:12 +0200)
The zcrypt_msgtype6_send_ep11_cprb() function uses fragile struct
overlays to access and modify the domain field in the EP11 CPRB
payload, creating maintainability and security concerns:
1. Struct overlay approach (pld_hdr) assumes fixed payload structure
   and doesn't validate the actual ASN.1 encoding.
2. Complex length format detection logic is error-prone and doesn't
   properly validate bounds at each parsing step.
3. Direct struct member access bypasses proper ASN.1 validation.

Fix by replacing struct overlays with explicit ASN.1 parsing that
validates each field (payload tag/length, function tag/length/value,
optional domain tag/length/value) with proper bounds checking at every
step. Add asn1_int_encode() helper function to safely write integer
values with correct endianness conversion. This makes the code
consistent with the validation pattern introduced with the rework of
the xcrb_msg_to_type6_ep11cprb_msgx() function.

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 7e1f76c935ee180e336f26f4b0f00ebbf39cf922..e79e68b4dd41d08f43372a3c6e12c72aeefa8e5e 100644 (file)
@@ -1226,6 +1226,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.
@@ -1238,51 +1260,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->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:
+       if (!(msg->cprbx.flags & 0x80)) {
+               int i, offs = 0;
+               size_t pld_len;
+               u8 *pld;
+               u32 u;
+
+               /* 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);
        }
 
        /*