krb5 commit: Fix null deref in KDC when decoding invalid NDR

[email protected]
Newsgroups gmane.comp.encryption.kerberos.cvs
Message-ID <[email protected]>
https://github.com/krb5/krb5/commit/fa62bd33a0c0889c083999c0289ffa81a5d51e7b
commit fa62bd33a0c0889c083999c0289ffa81a5d51e7b
Author: Greg Hudson <[email protected]>
Date:   Wed Oct 12 00:46:52 2022 -0400

    Fix null deref in KDC when decoding invalid NDR
    
    In ndr_dec_delegation_info(), keep di->transited_services_length valid
    by incrementing it as we add entries.  Otherwise
    ndr_free_delegation_info() could dereference a null
    di->transited_services field.  Also bound nservices using data->length
    to prevent inordinately large memory allocations.  Credit to OSS-Fuzz
    for discovering the issues.
    
    ticket: 9073 (new)
    tags: pullup
    target_version: 1.20-next

 src/kdc/ndr.c   | 14 +++++++-------
 src/kdc/t_ndr.c | 32 +++++++++++++++++++++++++++++---
 2 files changed, 36 insertions(+), 10 deletions(-)

diff --git a/src/kdc/ndr.c b/src/kdc/ndr.c
index 2d16e6a5b..48395abe5 100644
--- a/src/kdc/ndr.c
+++ b/src/kdc/ndr.c
@@ -142,7 +142,7 @@ ndr_dec_delegation_info(krb5_data *data, struct pac_s4u_delegation_info **out)
     krb5_error_code ret;
     struct pac_s4u_delegation_info *di = NULL;
     struct k5input in;
-    uint32_t i, object_buffer_length;
+    uint32_t i, object_buffer_length, nservices;
     uint8_t version, endianness, common_header_length;
 
     *out = NULL;
@@ -193,26 +193,26 @@ ndr_dec_delegation_info(krb5_data *data, struct pac_s4u_delegation_info **out)
     ret = dec_wchar_pointer(&in, &di->proxy_target);
     if (ret)
         goto error;
-    di->transited_services_length = k5_input_get_uint32_le(&in);
+    nservices = k5_input_get_uint32_le(&in);
 
     /* Here, we have encoded 2 bytes of length, 2 bytes of (length + 2), and 4
      * bytes of pointer, for each element (deferred pointers). */
-    if (di->transited_services_length > UINT32_MAX / 8) {
+    if (nservices > data->length / 8) {
         ret = ERANGE;
         goto error;
     }
-    (void)k5_input_get_bytes(&in, 8 * di->transited_services_length);
+    (void)k5_input_get_bytes(&in, 8 * nservices);
 
     /* Since we're likely to add another entry, leave a blank at the end. */
-    di->transited_services = k5calloc(di->transited_services_length + 1,
-                                      sizeof(char *), &ret);
+    di->transited_services = k5calloc(nservices + 1, sizeof(char *), &ret);
     if (di->transited_services == NULL)
         goto error;
 
-    for (i = 0; i < di->transited_services_length; i++) {
+    for (i = 0; i < nservices; i++) {
         ret = dec_wchar_pointer(&in, &di->transited_services[i]);
         if (ret)
             goto error;
+        di->transited_services_length++;
     }
 
     ret = in.status;
diff --git a/src/kdc/t_ndr.c b/src/kdc/t_ndr.c
index 6f1173f47..378066044 100644
--- a/src/kdc/t_ndr.c
+++ b/src/kdc/t_ndr.c
@@ -107,8 +107,24 @@ static uint8_t s4u_di_double[] = {
     0x45, 0x00, 0x53, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 };
 
+static uint8_t fuzz1[] = {
+    0x01, 0x10, 0x08, 0x20, 0x20, 0x20, 0x20, 0x20, 0x24, 0x00, 0x00, 0x00,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xff, 0xff, 0xff,
+    0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00,
+    0x20, 0x20, 0x20, 0x20
+};
+
+static uint8_t fuzz2[] = {
+    0x01, 0x10, 0x08, 0x00, 0x00, 0xff, 0xff, 0xff, 0x24, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x1e, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x16, 0x00, 0x00,
+    0x1e, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x1e
+};
+
 static void
-test_dec_enc(uint8_t *blob, size_t len, char *name)
+test_dec_enc(uint8_t *blob, size_t len, char *name, int fail)
 {
     krb5_data data_in, data_out;
     struct pac_s4u_delegation_info *di = NULL;
@@ -120,7 +136,14 @@ test_dec_enc(uint8_t *blob, size_t len, char *name)
 
     data_in = make_data(blob, len);
     ret = ndr_dec_delegation_info(&data_in, &di);
-    if (ret) {
+    if (fail) {
+        if (!ret) {
+            printf("%s: unexpected decode success\n", name);
+            exit(1);
+        }
+        printf("%s: failed as expected\n", name);
+        return;
+    } else if (ret) {
         printf("%s: bad decode (%d): %s\n", name, ret, strerror(ret));
         exit(1);
     }
@@ -146,7 +169,8 @@ test_dec_enc(uint8_t *blob, size_t len, char *name)
     printf("%s matched\n\n", name);
 }
 
-#define RUN_TEST(blob) test_dec_enc(blob, sizeof(blob), #blob)
+#define RUN_TEST(blob) test_dec_enc(blob, sizeof(blob), #blob, 0)
+#define RUN_TEST_FAIL(blob) test_dec_enc(blob, sizeof(blob), #blob, 1)
 
 int
 main()
@@ -156,6 +180,8 @@ main()
     RUN_TEST(s4u_di_short);
     RUN_TEST(s4u_di_long);
     RUN_TEST(s4u_di_double);
+    RUN_TEST_FAIL(fuzz1);
+    RUN_TEST_FAIL(fuzz2);
 
     printf("Passed NDR tests\n");
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.