[31662] in CVS-changelog-for-Kerberos-V5

home help back first fref pref prev next nref lref last post

krb5 commit [krb5-1.22]: Enforce XDR decode size limits

daemon@ATHENA.MIT.EDU (ghudson@mit.edu)
Mon Nov 9 12:01:20 2026

From: ghudson@mit.edu
To: cvs-krb5@mit.edu
Message-Id: <20261109170112.7BE1C105839@krbdev.mit.edu>
Date: Mon,  9 Nov 2026 12:01:12 -0500 (EST)
MIME-Version: 1.0
Reply-To: krbdev@mit.edu
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Errors-To: cvs-krb5-bounces@mit.edu

https://github.com/krb5/krb5/commit/b6ab8218368c5eaa7a321474fd56c74e1a0280fc
commit b6ab8218368c5eaa7a321474fd56c74e1a0280fc
Author: Julien Rische <jrische@redhat.com>
Date:   Wed Jul 8 02:03:25 2026 +0200

    Enforce XDR decode size limits
    
    xdr_array(), xdr_bytes(), and xdr_string() allocate memory based on a
    count or length read from the XDR stream.  Many callers pass UINT_MAX
    as the maxsize parameter, effectively disabling bounds checking.  This
    allows memory exhaustion in kadmind, and can be leveraged without
    authentication via the legacy AUTH_GSSAPI handshake path.
    
    Add three constants in gssrpc/xdr.h:
    
    * GSSRPC_MAX_MESSAGE_SIZE (64 MiB) for the maximum size of a wrapped
      RPC message
    * GSSRPC_MAX_ELEMENT_SIZE (64 KiB) for individual xdr_bytes and
      xdr_string fields
    * GSSRPC_MAX_ARRAY_COUNT (8192) for xdr_array element counts
    
    Enforce GSSRPC_MAX_MESSAGE_SIZE in auth_gssapi_misc.c and
    authgss_prot.c.  Use the other two constants in the kadmin RPC
    type functions.
    
    Do not enforce a limit in xdr_gprincs_ret(), which decodes the result
    of a list_principals RPC, as the number of principal names returned
    could be large.
    
    Do not enforce limits in the iprop protocol.  Variable allocations in
    iprop are only used when decoding messages from the primary KDC to a
    replica KDC.
    
    [ghudson@mit.edu: made style changes; backed out changes to iprop;
    edited comments and commit message]
    
    CVE-2026-112319:
    
    The GSS-RPC layer in MIT Kerberos 5 (aka krb5) does not enforce size
    limits on XDR-decoded data, which allows remote authenticated users to
    cause a denial of service (memory exhaustion or excessive CPU
    consumption in kadmind) via crafted RPC requests that trigger
    unbounded memory allocations during XDR decoding.
    
    (cherry picked from commit 6946435bc3e66b1cccef0c41105bbd1e7f8c1a92)
    
    ticket: 9257
    version_fixed: 1.22.3

 src/include/gssrpc/xdr.h       | 14 +++++++++++
 src/lib/kadm5/kadm_rpc_xdr.c   | 54 +++++++++++++++++++++++++++---------------
 src/lib/kadm5/srv/adb_xdr.c    |  9 +++----
 src/lib/rpc/auth_gssapi_misc.c | 14 ++++-------
 src/lib/rpc/authgss_prot.c     | 19 ++++++++++-----
 src/lib/rpc/xdr.c              |  2 +-
 6 files changed, 72 insertions(+), 40 deletions(-)

diff --git a/src/include/gssrpc/xdr.h b/src/include/gssrpc/xdr.h
index 4e5c29bdc..ccbb284c7 100644
--- a/src/include/gssrpc/xdr.h
+++ b/src/include/gssrpc/xdr.h
@@ -84,6 +84,20 @@ enum xdr_op {
 	XDR_FREE=2
 };
 
+/* Maximum byte size for a message wrapped with RPCSEC_GSS. */
+#define GSSRPC_MAX_MESSAGE_SIZE	(64U * 1024U * 1024U)
+
+/*
+ * Suggested maximum byte size for a single XDR element (xdr_bytes,
+ * xdr_string), to limit up-front allocation during decoding.  This limit
+ * applies to xdr_wrapstring().
+ */
+#define GSSRPC_MAX_ELEMENT_SIZE	(64U * 1024U)
+
+/* Suggested maximum element count for XDR arrays (xdr_array), to limit
+ * up-front allocation during decoding. */
+#define GSSRPC_MAX_ARRAY_COUNT	8192U
+
 /*
  * This is the number of bytes per unit of external data.
  */
diff --git a/src/lib/kadm5/kadm_rpc_xdr.c b/src/lib/kadm5/kadm_rpc_xdr.c
index 8a2def8ff..6a706dbd7 100644
--- a/src/lib/kadm5/kadm_rpc_xdr.c
+++ b/src/lib/kadm5/kadm_rpc_xdr.c
@@ -49,9 +49,10 @@ bool_t xdr_nullstring(XDR *xdrs, char **objp)
 	  else
 	       size = strlen(*objp) + 1;
      }
-     if (! xdr_u_int(xdrs, &size)) {
+     if (!xdr_u_int(xdrs, &size) ||
+	 size > GSSRPC_MAX_ELEMENT_SIZE && xdrs->x_op != XDR_FREE) {
 	  return FALSE;
-	}
+     }
      switch (xdrs->x_op) {
      case XDR_DECODE:
 	  if (size == 0) {
@@ -276,12 +277,12 @@ bool_t xdr_krb5_key_data_nocontents(XDR *xdrs, krb5_key_data *objp)
      if (xdrs->x_op == XDR_FREE) {
 	  tmp = (unsigned int) objp->key_data_length[0];
 	  if (!xdr_bytes(xdrs, (char **) &objp->key_data_contents[0],
-			 &tmp, ~0))
+			 &tmp, GSSRPC_MAX_ELEMENT_SIZE))
 	       return FALSE;
 
 	  tmp = (unsigned int) objp->key_data_length[1];
 	  if (!xdr_bytes(xdrs, (char **) &objp->key_data_contents[1],
-			 &tmp, ~0))
+			 &tmp, GSSRPC_MAX_ELEMENT_SIZE))
 	       return FALSE;
      }
 
@@ -328,7 +329,8 @@ bool_t xdr_krb5_tl_data(XDR *xdrs, krb5_tl_data **tl_data_head)
 	       if (!xdr_krb5_int16(xdrs, &tl->tl_data_type))
 		    return FALSE;
 	       len = tl->tl_data_length;
-	       if (!xdr_bytes(xdrs, (char **) &tl->tl_data_contents, &len, ~0))
+	       if (!xdr_bytes(xdrs, (char **) &tl->tl_data_contents, &len,
+			      GSSRPC_MAX_ELEMENT_SIZE))
 		    return FALSE;
 	       tl = tl->tl_data_next;
 	  }
@@ -347,7 +349,8 @@ bool_t xdr_krb5_tl_data(XDR *xdrs, krb5_tl_data **tl_data_head)
 	       memset(tl2, 0, sizeof(krb5_tl_data));
 	       if (!xdr_krb5_int16(xdrs, &tl2->tl_data_type))
 		    return FALSE;
-	       if (!xdr_bytes(xdrs, (char **)&tl2->tl_data_contents, &len, ~0))
+	       if (!xdr_bytes(xdrs, (char **)&tl2->tl_data_contents, &len,
+			      GSSRPC_MAX_ELEMENT_SIZE))
 		    return FALSE;
 	       tl2->tl_data_length = len;
 
@@ -575,7 +578,8 @@ xdr_cprinc3_arg(XDR *xdrs, cprinc3_arg *objp)
 		return (FALSE);
 	}
 	if (!xdr_array(xdrs, (caddr_t *)&objp->ks_tuple,
-		       (unsigned int *)&objp->n_ks_tuple, ~0,
+		       (unsigned int *)&objp->n_ks_tuple,
+		       GSSRPC_MAX_ARRAY_COUNT,
 		       sizeof(krb5_key_salt_tuple),
 		       (xdrproc_t)xdr_krb5_key_salt_tuple)) {
 		return (FALSE);
@@ -667,6 +671,8 @@ xdr_gprincs_ret(XDR *xdrs, gprincs_ret *objp)
 	  if (!xdr_int(xdrs, &objp->count)) {
 	       return (FALSE);
 	  }
+	  /* xdr_gprincs_ret() is not used by kadmind, and may contain many
+	   * entries for a large KDB; accept unbound length. */
 	  if (!xdr_array(xdrs, (caddr_t *) &objp->princs,
 			 (unsigned int *) &objp->count, ~0,
 			 sizeof(char *), (xdrproc_t)xdr_nullstring)) {
@@ -705,7 +711,8 @@ xdr_chpass3_arg(XDR *xdrs, chpass3_arg *objp)
 		return (FALSE);
 	}
 	if (!xdr_array(xdrs, (caddr_t *)&objp->ks_tuple,
-		       (unsigned int*)&objp->n_ks_tuple, ~0,
+		       (unsigned int*)&objp->n_ks_tuple,
+		       GSSRPC_MAX_ARRAY_COUNT,
 		       sizeof(krb5_key_salt_tuple),
 		       (xdrproc_t)xdr_krb5_key_salt_tuple)) {
 		return (FALSE);
@@ -726,7 +733,8 @@ xdr_setkey_arg(XDR *xdrs, setkey_arg *objp)
 		return (FALSE);
 	}
 	if (!xdr_array(xdrs, (caddr_t *) &objp->keyblocks,
-		       (unsigned int *) &objp->n_keys, ~0,
+		       (unsigned int *) &objp->n_keys,
+		       GSSRPC_MAX_ARRAY_COUNT,
 		       sizeof(krb5_keyblock), (xdrproc_t)xdr_krb5_keyblock)) {
 		return (FALSE);
 	}
@@ -746,13 +754,15 @@ xdr_setkey3_arg(XDR *xdrs, setkey3_arg *objp)
 		return (FALSE);
 	}
 	if (!xdr_array(xdrs, (caddr_t *) &objp->ks_tuple,
-		       (unsigned int *) &objp->n_ks_tuple, ~0,
+		       (unsigned int *) &objp->n_ks_tuple,
+		       GSSRPC_MAX_ARRAY_COUNT,
 		       sizeof(krb5_key_salt_tuple),
 		       (xdrproc_t)xdr_krb5_key_salt_tuple)) {
 		return (FALSE);
 	}
 	if (!xdr_array(xdrs, (caddr_t *) &objp->keyblocks,
-		       (unsigned int *) &objp->n_keys, ~0,
+		       (unsigned int *) &objp->n_keys,
+		       GSSRPC_MAX_ARRAY_COUNT,
 		       sizeof(krb5_keyblock), (xdrproc_t)xdr_krb5_keyblock)) {
 		return (FALSE);
 	}
@@ -772,7 +782,8 @@ xdr_setkey4_arg(XDR *xdrs, setkey4_arg *objp)
 		return FALSE;
 	}
 	if (!xdr_array(xdrs, (caddr_t *) &objp->key_data,
-		       (unsigned int *) &objp->n_key_data, ~0,
+		       (unsigned int *) &objp->n_key_data,
+		       GSSRPC_MAX_ARRAY_COUNT,
 		       sizeof(kadm5_key_data),
 		       (xdrproc_t)xdr_kadm5_key_data)) {
 		return FALSE;
@@ -805,7 +816,8 @@ xdr_chrand3_arg(XDR *xdrs, chrand3_arg *objp)
 		return (FALSE);
 	}
 	if (!xdr_array(xdrs, (caddr_t *)&objp->ks_tuple,
-		       (unsigned int*)&objp->n_ks_tuple, ~0,
+		       (unsigned int*)&objp->n_ks_tuple,
+		       GSSRPC_MAX_ARRAY_COUNT,
 		       sizeof(krb5_key_salt_tuple),
 		       (xdrproc_t)xdr_krb5_key_salt_tuple)) {
 		return (FALSE);
@@ -824,7 +836,8 @@ xdr_chrand_ret(XDR *xdrs, chrand_ret *objp)
 	}
 	if (objp->code == KADM5_OK) {
 		if (!xdr_array(xdrs, (char **)&objp->keys,
-			       (unsigned int *)&objp->n_keys, ~0,
+			       (unsigned int *)&objp->n_keys,
+			       GSSRPC_MAX_ARRAY_COUNT,
 			       sizeof(krb5_keyblock),
 			       (xdrproc_t)xdr_krb5_keyblock))
 			return FALSE;
@@ -968,7 +981,8 @@ xdr_gpols_ret(XDR *xdrs, gpols_ret *objp)
 	       return (FALSE);
 	  }
 	  if (!xdr_array(xdrs, (caddr_t *) &objp->pols,
-			 (unsigned int *) &objp->count, ~0,
+			 (unsigned int *) &objp->count,
+			 GSSRPC_MAX_ARRAY_COUNT,
 			 sizeof(char *), (xdrproc_t)xdr_nullstring)) {
 	       return (FALSE);
 	  }
@@ -1032,7 +1046,8 @@ xdr_gstrings_ret(XDR *xdrs, gstrings_ret *objp)
 			return (FALSE);
 		}
 		if (!xdr_array(xdrs, (caddr_t *) &objp->strings,
-			       (unsigned int *) &objp->count, ~0,
+			       (unsigned int *) &objp->count,
+			       GSSRPC_MAX_ARRAY_COUNT,
 			       sizeof(krb5_string_attr),
 			       (xdrproc_t)xdr_krb5_string_attr)) {
 			return (FALSE);
@@ -1141,7 +1156,7 @@ xdr_krb5_keyblock(XDR *xdrs, krb5_keyblock *objp)
    if(!xdr_krb5_enctype(xdrs, &objp->enctype))
       return FALSE;
    cp = (char *)objp->contents;
-   if(!xdr_bytes(xdrs, &cp, &objp->length, ~0))
+   if (!xdr_bytes(xdrs, &cp, &objp->length, GSSRPC_MAX_ELEMENT_SIZE))
       return FALSE;
    objp->contents = (uint8_t *)cp;
    return TRUE;
@@ -1170,7 +1185,7 @@ xdr_kadm5_key_data(XDR *xdrs, kadm5_key_data *objp)
 	if (!xdr_krb5_int16(xdrs, &objp->salt.type))
 		return FALSE;
 	if (!xdr_bytes(xdrs, &objp->salt.data.data,
-		       &objp->salt.data.length, ~0))
+		       &objp->salt.data.length, GSSRPC_MAX_ELEMENT_SIZE))
 		return FALSE;
 	return TRUE;
 }
@@ -1201,7 +1216,8 @@ xdr_getpkeys_ret(XDR *xdrs, getpkeys_ret *objp)
 	}
 	if (objp->code == KADM5_OK) {
 		if (!xdr_array(xdrs, (caddr_t *) &objp->key_data,
-			       (unsigned int *) &objp->n_key_data, ~0,
+			       (unsigned int *) &objp->n_key_data,
+			       GSSRPC_MAX_ARRAY_COUNT,
 			       sizeof(kadm5_key_data),
 			       (xdrproc_t)xdr_kadm5_key_data)) {
 		    return FALSE;
diff --git a/src/lib/kadm5/srv/adb_xdr.c b/src/lib/kadm5/srv/adb_xdr.c
index b14cb96ee..47b4d3c0b 100644
--- a/src/lib/kadm5/srv/adb_xdr.c
+++ b/src/lib/kadm5/srv/adb_xdr.c
@@ -34,14 +34,14 @@ xdr_krb5_key_data(XDR *xdrs, krb5_key_data *objp)
 
     tmp = (unsigned int) objp->key_data_length[0];
     if (!xdr_bytes(xdrs, (char **) &objp->key_data_contents[0],
-		   &tmp, ~0))
+		   &tmp, GSSRPC_MAX_ELEMENT_SIZE))
 	return FALSE;
     if (tmp != objp->key_data_length[0])
 	return FALSE;
 
     tmp = (unsigned int) objp->key_data_length[1];
     if (!xdr_bytes(xdrs, (char **) &objp->key_data_contents[1],
-		   &tmp, ~0))
+		   &tmp, GSSRPC_MAX_ELEMENT_SIZE))
 	return FALSE;
     if (tmp != objp->key_data_length[1])
 	return FALSE;
@@ -56,7 +56,7 @@ bool_t
 xdr_osa_pw_hist_ent(XDR *xdrs, osa_pw_hist_ent *objp)
 {
     if (!xdr_array(xdrs, (caddr_t *) &objp->key_data,
-		   (u_int *) &objp->n_key_data, ~0,
+		   (u_int *) &objp->n_key_data, GSSRPC_MAX_ARRAY_COUNT,
 		   sizeof(krb5_key_data), (xdrproc_t)xdr_krb5_key_data))
 	return (FALSE);
     return (TRUE);
@@ -90,7 +90,8 @@ xdr_osa_princ_ent_rec(XDR *xdrs, osa_princ_ent_t objp)
     if (!xdr_krb5_kvno(xdrs, &objp->admin_history_kvno))
 	return (FALSE);
     if (!xdr_array(xdrs, (caddr_t *) &objp->old_keys,
-		   (unsigned int *) &objp->old_key_len, ~0,
+		   (unsigned int *) &objp->old_key_len,
+		   GSSRPC_MAX_ARRAY_COUNT,
 		   sizeof(osa_pw_hist_ent), (xdrproc_t)xdr_osa_pw_hist_ent))
 	return (FALSE);
     return (TRUE);
diff --git a/src/lib/rpc/auth_gssapi_misc.c b/src/lib/rpc/auth_gssapi_misc.c
index 57fc1fb39..72ae57aa0 100644
--- a/src/lib/rpc/auth_gssapi_misc.c
+++ b/src/lib/rpc/auth_gssapi_misc.c
@@ -35,20 +35,14 @@ bool_t xdr_gss_buf(
      XDR *xdrs,
      gss_buffer_t buf)
 {
-     /*
-      * On decode, xdr_bytes will only allocate buf->value if the
-      * length read in is < maxsize (last arg).  This is dumb, because
-      * the whole point of allocating memory is so that I don't *have*
-      * to know the maximum length.  -1 effectively disables this
-      * braindamage.
-      */
      bool_t result;
      /* Fix type mismatches between APIs.  */
      unsigned int length = buf->length;
      char *cp = buf->value;
      result = xdr_bytes(xdrs, &cp, &length,
 			(xdrs->x_op == XDR_DECODE && buf->value == NULL)
-			? (unsigned int) -1 : (unsigned int) buf->length);
+			? GSSRPC_MAX_MESSAGE_SIZE
+			: (unsigned int) buf->length);
      buf->value = cp;
      buf->length = length;
      return result;
@@ -285,10 +279,10 @@ bool_t auth_gssapi_unwrap_data(
      in_buf.value = NULL;
      out_buf.value = NULL;
      cp = in_buf.value;
-     if (! xdr_bytes(in_xdrs, &cp, &length, (unsigned int) -1)) {
+     if (! xdr_bytes(in_xdrs, &cp, &length, GSSRPC_MAX_MESSAGE_SIZE)) {
 	 PRINTF(("gssapi_unwrap_data: deserializing encrypted data failed\n"));
 	 temp_xdrs.x_op = XDR_FREE;
-	 (void)xdr_bytes(&temp_xdrs, &cp, &length, (unsigned int) -1);
+	 (void)xdr_bytes(&temp_xdrs, &cp, &length, GSSRPC_MAX_MESSAGE_SIZE);
 	 in_buf.value = NULL;
 	 return FALSE;
      }
diff --git a/src/lib/rpc/authgss_prot.c b/src/lib/rpc/authgss_prot.c
index 9a48277b3..5edace5eb 100644
--- a/src/lib/rpc/authgss_prot.c
+++ b/src/lib/rpc/authgss_prot.c
@@ -150,8 +150,11 @@ xdr_rpc_gss_wrap_data(XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr,
 	databuf.value = xdralloc_getdata(&tmpxdrs);
 
 	if (svc == RPCSEC_GSS_SVC_INTEGRITY) {
-		if (!xdr_rpc_gss_buf(xdrs, &databuf, (unsigned int)-1))
+		u_int mem_budget = GSSRPC_MAX_MESSAGE_SIZE;
+
+		if (!xdr_rpc_gss_buf(xdrs, &databuf, mem_budget))
 			goto errout;
+		mem_budget -= databuf.length;
 
 		/* Checksum rpc_gss_data_t. */
 		maj_stat = gss_get_mic(&min_stat, ctx, qop,
@@ -161,7 +164,7 @@ xdr_rpc_gss_wrap_data(XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr,
 			goto errout;
 		}
 		/* Marshal checksum. */
-		xdr_stat = xdr_rpc_gss_buf(xdrs, &wrapbuf, (unsigned int)-1);
+		xdr_stat = xdr_rpc_gss_buf(xdrs, &wrapbuf, mem_budget);
 		gss_release_buffer(&min_stat, &wrapbuf);
 	}
 	else if (svc == RPCSEC_GSS_SVC_PRIVACY) {
@@ -173,7 +176,8 @@ xdr_rpc_gss_wrap_data(XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr,
 			goto errout;
 		}
 		/* Marshal databody_priv. */
-		xdr_stat = xdr_rpc_gss_buf(xdrs, &wrapbuf, (unsigned int)-1);
+		xdr_stat = xdr_rpc_gss_buf(xdrs, &wrapbuf,
+					   GSSRPC_MAX_MESSAGE_SIZE);
 		gss_release_buffer(&min_stat, &wrapbuf);
 	}
 errout:
@@ -201,13 +205,16 @@ xdr_rpc_gss_unwrap_data(XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr,
 	memset(&wrapbuf, 0, sizeof(wrapbuf));
 
 	if (svc == RPCSEC_GSS_SVC_INTEGRITY) {
+		u_int mem_budget = GSSRPC_MAX_MESSAGE_SIZE;
+
 		/* Decode databody_integ. */
-		if (!xdr_rpc_gss_buf(xdrs, &databuf, (unsigned int)-1)) {
+		if (!xdr_rpc_gss_buf(xdrs, &databuf, mem_budget)) {
 			log_debug("xdr decode databody_integ failed");
 			return (FALSE);
 		}
+		mem_budget -= databuf.length;
 		/* Decode checksum. */
-		if (!xdr_rpc_gss_buf(xdrs, &wrapbuf, (unsigned int)-1)) {
+		if (!xdr_rpc_gss_buf(xdrs, &wrapbuf, mem_budget)) {
 			gss_release_buffer(&min_stat, &databuf);
 			log_debug("xdr decode checksum failed");
 			return (FALSE);
@@ -225,7 +232,7 @@ xdr_rpc_gss_unwrap_data(XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr,
 	}
 	else if (svc == RPCSEC_GSS_SVC_PRIVACY) {
 		/* Decode databody_priv. */
-		if (!xdr_rpc_gss_buf(xdrs, &wrapbuf, (unsigned int)-1)) {
+		if (!xdr_rpc_gss_buf(xdrs, &wrapbuf, GSSRPC_MAX_MESSAGE_SIZE)) {
 			log_debug("xdr decode databody_priv failed");
 			return (FALSE);
 		}
diff --git a/src/lib/rpc/xdr.c b/src/lib/rpc/xdr.c
index 49c31b3d1..59b2b3fff 100644
--- a/src/lib/rpc/xdr.c
+++ b/src/lib/rpc/xdr.c
@@ -670,7 +670,7 @@ xdr_string(XDR *xdrs, char **cpp, u_int maxsize)
 bool_t
 xdr_wrapstring(XDR *xdrs, char **cpp)
 {
-	if (xdr_string(xdrs, cpp, LASTUNSIGNED)) {
+	if (xdr_string(xdrs, cpp, GSSRPC_MAX_ELEMENT_SIZE)) {
 		return (TRUE);
 	}
 	return (FALSE);
_______________________________________________
cvs-krb5 mailing list
cvs-krb5@mit.edu
https://mailman.mit.edu/mailman/listinfo/cvs-krb5

home help back first fref pref prev next nref lref last post