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

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

krb5 commit [krb5-1.22]: Prevent KDC read overrun in ASN.1 decoder

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

From: ghudson@mit.edu
To: cvs-krb5@mit.edu
Message-Id: <20261109170246.ADDA8105839@krbdev.mit.edu>
Date: Mon,  9 Nov 2026 12:02:46 -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/4e941239157dd30969998f33db0708b8d297bd41
commit 4e941239157dd30969998f33db0708b8d297bd41
Author: Greg Hudson <ghudson@mit.edu>
Date:   Tue Oct 6 20:52:05 2026 -0400

    Prevent KDC read overrun in ASN.1 decoder
    
    The KDC decodes requests using the libkrb5 ASN.1 DER decoder, but
    obtains pointers to subsets of the encoding (such as the KDC-REQ-BODY)
    via its own fetch_asn1_field().  This utility function does not
    consider the input length, assumes the tag identifier is a single
    byte, and supports indefinite length encoding.  The libkrb5 decoder
    accepts multi-byte tag identifiers and does not enforce minimality of
    tag identifier encoding.  A request packet crafted using a non-minimal
    tag identifier can pass through the libkrb5 decoder, but fool
    fetch_asn1_field() into indefinite length processing, which may send
    it reading far beyond the bounds of the packet.  Discovered by Claude
    and Ada Logics.
    
    In fetch_asn1_field(), error out if we see a tag identifier byte
    indicating a multi-byte encoding.  Also remove indefinite length
    processing; the libkrb5 decoder stopped supporting it in commit
    d9443a58cd364349b7d764f4e997f3af7d979a87.
    
    CVE-2026-107364:
    
    In all released versions of MIT krb5, an unauthenticated attacker can
    cause a KDC process to perform a large read overrun, likely causing
    the process to terminate.
    
    (cherry picked from commit cf44b0658832871f739d5074e7d27288b2718bfb)
    
    ticket: 9251
    version_fixed: 1.22.3

 src/kdc/kdc_util.c             | 33 ++++++-------------------------
 src/tests/Makefile.in          |  1 +
 src/tests/t_cve-2026-107364.py | 45 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 52 insertions(+), 27 deletions(-)

diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c
index 33b803fa9..20a13b448 100644
--- a/src/kdc/kdc_util.c
+++ b/src/kdc/kdc_util.c
@@ -917,9 +917,7 @@ static int
 asn1length(unsigned char **astream)
 {
     int length;         /* resulting length */
-    int sublen;         /* sublengths */
     int blen;           /* bytes of length */
-    unsigned char *p;   /* substring searching */
 
     if (**astream & 0x80) {
         blen = **astream & 0x7f;
@@ -929,23 +927,9 @@ asn1length(unsigned char **astream)
         for (++*astream, length = 0; blen; ++*astream, blen--) {
             length = (length << 8) | **astream;
         }
+        /* Reject indefinite length form. */
         if (length == 0) {
-            /* indefinite length, figure out by hand */
-            p = *astream;
-            p++;
-            while (1) {
-                /* compute value length. */
-                if ((sublen = asn1length(&p)) < 0) {
-                    return(-1);
-                }
-                p += sublen;
-                /* check for termination */
-                if ((!*p++) && (!*p)) {
-                    p++;
-                    break;
-                }
-            }
-            length = p - *astream;
+            return(-1);
         }
     } else {
         length = **astream;
@@ -974,7 +958,6 @@ fetch_asn1_field(unsigned char *astream, unsigned int level,
     int lastlevel = 1000;       /* last level seen */
     int length;                 /* various lengths */
     int tag;                    /* tag number */
-    unsigned char savelen;      /* saved length of our field */
 
     classes = -1;
     /* we assume that the first identifier/length will tell us
@@ -989,6 +972,10 @@ fetch_asn1_field(unsigned char *astream, unsigned int level,
        until we hit the "level" we want, and then process that level for our
        subfield, always making sure we don't go off the end of the stream.  */
     while (astream < estream) {
+        /* The libkrb5 decoder supports multi-byte tag numbers, but we do not.
+         * Error out if we see one to prevent desynchronization. */
+        if (asn1_id_tag(*astream) == ASN1_ID_TAG)
+            return(-1);
         if (!asn1_id_constructed(*astream)) {
             return(-1);
         }
@@ -1003,18 +990,10 @@ fetch_asn1_field(unsigned char *astream, unsigned int level,
                 if (tag == (int)field) {
                     /* return length and data */
                     astream++;
-                    savelen = *astream;
                     if ((length = asn1length(&astream)) < 0) {
                         return(-1);
                     }
                     data->length = length;
-                    /* if the field length is indefinite, we will have to subtract two
-                       (terminating octets) from the length returned since we don't want
-                       to pass any info from the "wrapper" back.  asn1length will always return
-                       the *total* length of the field, not just what's contained in it */
-                    if ((savelen & 0xff) == 0x80) {
-                        data->length -=2 ;
-                    }
                     data->data = (char *)astream;
                     return(0);
                 } else if (tag <= classes) {
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
index 41ac0d3b2..961c5e82b 100644
--- a/src/tests/Makefile.in
+++ b/src/tests/Makefile.in
@@ -170,6 +170,7 @@ check-pytests: responder s2p s4u2proxy unlockiter s4u2self
 	$(RUNPYTEST) $(srcdir)/t_cve-2013-1416.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_cve-2013-1417.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_cve-2021-36222.py $(PYTESTFLAGS)
+	$(RUNPYTEST) $(srcdir)/t_cve-2026-107364.py $(PYTESTFLAGS)
 	$(RM) au.log
 	$(RUNPYTEST) $(srcdir)/t_audit.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/jsonwalker.py -d $(srcdir)/au_dict.json \
diff --git a/src/tests/t_cve-2026-107364.py b/src/tests/t_cve-2026-107364.py
new file mode 100644
index 000000000..216a6d95f
--- /dev/null
+++ b/src/tests/t_cve-2026-107364.py
@@ -0,0 +1,45 @@
+import socket
+from k5test import *
+
+realm = K5Realm()
+
+# CVE-2026-107364 KDC out-of-bounds read with a specific ASN.1
+# encoding
+
+s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+a = (hostname, realm.portbase)
+
+# This AS-REQ packet fools the affected version of the KDC's
+# fetch_asn_field() into performing an indefinite length calculation,
+# reading far beyond the bounds of the packet.
+m = ('6A8185'                   # [APPLICATION 10]
+     '3F8010' '8180'            # SEQUENCE (with multi-byte tag identifier)
+     'A103' '0201' '05'         #  [1] pvno = 5
+     'A203' '0201' '0A'         #  [2] msg-type = 10
+     'A474' '3072'              #  [4] req_body = SEQUENCE
+     'A007' '0305' '0000000010' #   [0] kdc-options = 0
+     'A111' '300F'              #   [1] cname = SEQUENCE
+     'A003' '0201' '01'         #    [0] name-type = NT-PRINCIPAL
+     'A108' '3006'              #    [1] name-string = SEQUENCE-OF
+     '1B04' '75736572'          #     user
+     'A20D' '1B0B' '4B5242544553542E434F4D'
+                                #    [2] realm = KRBTEST.COM
+     'A320' '301E'              #    [3] sname = SEQUENCE
+     'A003' '0201' '02'         #     [0] name-type = NT-SRV-INST
+     'A117' '3015'              #      [1] name-string = SEQUENCE-OF
+     '1B06' '6B7262746774'      #       krbtgt
+     '1B0B' '4B5242544553542E434F4D'
+                                #       KRBTEST.COM
+     'A511' '180F' '32303337303130313030303030305A'
+                                #   [5] till = 20370101000000Z
+     'A706' '0204' '00BC614E'   #   [7] nonce = 12345678
+     'A808' '3006'              #   [8] etype = SEQUENCE OF
+     '020112' '020111'          #    aes256-cts aes128-cts
+     '0183FFFFFF')              # DER value of length 2^24-1
+
+s.sendto(bytes.fromhex(m), a)
+
+# Make sure kinit still works.
+realm.kinit(realm.user_princ, password('user'))
+
+success('CVE-2026-107364 regression test')
_______________________________________________
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