[31683] in CVS-changelog-for-Kerberos-V5
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