From 99a6ec6bd6a1c73753710b33743206e9e05dc172 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Tue, 25 Oct 2022 16:58:55 -0500
Subject: [PATCH] krb5: CVE-2022-42898 PAC parse integer overflows

Catch overflows that result from adding PAC_INFO_BUFFER_SIZE.

Samba BUG: https://bugzilla.samba.org/show_bug.cgi?id=15203

Signed-off-by: Nico Williams <nico@twosigma.com>
---
 lib/krb5/pac.c | 178 +++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 150 insertions(+), 28 deletions(-)

diff --git a/lib/krb5/pac.c b/lib/krb5/pac.c
index c26201be9cd7..cf051cd1f1f4 100644
--- a/lib/krb5/pac.c
+++ b/lib/krb5/pac.c
@@ -112,6 +112,56 @@ HMAC_MD5_any_checksum(krb5_context context,
 }
 
 
+static krb5_error_code pac_header_size(krb5_context context,
+				       uint32_t num_buffers,
+				       uint32_t *result)
+{
+    krb5_error_code ret;
+    uint32_t header_size;
+
+    /* Guard against integer overflow on 32-bit systems. */
+    if (num_buffers > 1000) {
+	ret = EINVAL;
+	krb5_set_error_message(context, ret, "PAC has too many buffers");
+	return ret;
+    }
+    header_size = PAC_INFO_BUFFER_SIZE * num_buffers;
+
+    /* Guard against integer overflow on 32-bit systems. */
+    if (header_size > UINT32_MAX - PACTYPE_SIZE) {
+	ret = EINVAL;
+	krb5_set_error_message(context, ret, "PAC has too many buffers");
+	return ret;
+    }
+    header_size += PACTYPE_SIZE;
+
+    *result = header_size;
+
+    return 0;
+}
+
+static krb5_error_code pac_aligned_size(krb5_context context,
+					uint32_t size,
+					uint32_t *aligned_size)
+{
+    krb5_error_code ret;
+
+    /* Guard against integer overflow on 32-bit systems. */
+    if (size > UINT32_MAX - (PAC_ALIGNMENT - 1)) {
+	ret = EINVAL;
+	krb5_set_error_message(context, ret, "integer overrun");
+	return ret;
+    }
+    size += PAC_ALIGNMENT - 1;
+
+    /* align to PAC_ALIGNMENT */
+    size = (size / PAC_ALIGNMENT) * PAC_ALIGNMENT;
+
+    *aligned_size = size;
+
+    return 0;
+}
+
 /*
  *
  */
@@ -153,8 +203,12 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len,
 	goto out;
     }
 
-    p->pac = calloc(1,
-		    sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * (tmp - 1)));
+    ret = pac_header_size(context, tmp, &header_end);
+    if (ret) {
+	return ret;
+    }
+
+    p->pac = calloc(1, header_end);
     if (p->pac == NULL) {
 	ret = krb5_enomem(context);
 	goto out;
@@ -163,7 +217,6 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len,
     p->pac->numbuffers = tmp;
     p->pac->version = tmp2;
 
-    header_end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
     if (header_end > len) {
 	ret = EINVAL;
 	goto out;
@@ -292,37 +345,65 @@ krb5_pac_add_buffer(krb5_context context, krb5_pac p,
 {
     krb5_error_code ret;
     void *ptr;
-    size_t len, offset, header_end, old_end;
+    uint32_t unaligned_len, num_buffers, len, offset, header_end, old_end;
     uint32_t i;
 
-    len = p->pac->numbuffers;
+    if (data->length > UINT32_MAX) {
+	ret = EINVAL;
+	krb5_set_error_message(context, ret, "integer overrun");
+	return ret;
+    }
+
+    num_buffers = p->pac->numbuffers;
+
+    if (num_buffers >= UINT32_MAX) {
+	ret = EINVAL;
+	krb5_set_error_message(context, ret, "integer overrun");
+	return ret;
+    }
+    ret = pac_header_size(context, num_buffers + 1, &header_end);
+    if (ret) {
+	return ret;
+    }
 
-    ptr = realloc(p->pac,
-		  sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * len));
+    ptr = realloc(p->pac, header_end);
     if (ptr == NULL)
 	return krb5_enomem(context);
 
     p->pac = ptr;
 
-    for (i = 0; i < len; i++)
+    for (i = 0; i < num_buffers; i++) {
+	if (p->pac->buffers[i].offset_lo > UINT32_MAX - PAC_INFO_BUFFER_SIZE) {
+	    ret = EINVAL;
+	    krb5_set_error_message(context, ret, "integer overrun");
+	    return ret;
+	}
+
 	p->pac->buffers[i].offset_lo += PAC_INFO_BUFFER_SIZE;
+    }
 
+    if (p->data.length > UINT32_MAX - PAC_INFO_BUFFER_SIZE) {
+	ret = EINVAL;
+	krb5_set_error_message(context, ret, "integer overrun");
+	return ret;
+    }
     offset = p->data.length + PAC_INFO_BUFFER_SIZE;
 
-    p->pac->buffers[len].type = type;
-    p->pac->buffers[len].buffersize = data->length;
-    p->pac->buffers[len].offset_lo = offset;
-    p->pac->buffers[len].offset_hi = 0;
+    p->pac->buffers[num_buffers].type = type;
+    p->pac->buffers[num_buffers].buffersize = data->length;
+    p->pac->buffers[num_buffers].offset_lo = offset;
+    p->pac->buffers[num_buffers].offset_hi = 0;
 
     old_end = p->data.length;
-    len = p->data.length + data->length + PAC_INFO_BUFFER_SIZE;
-    if (len < p->data.length) {
+    if (offset > UINT32_MAX - data->length) {
 	krb5_set_error_message(context, EINVAL, "integer overrun");
 	return EINVAL;
     }
+    unaligned_len = offset + data->length;
 
-    /* align to PAC_ALIGNMENT */
-    len = ((len + PAC_ALIGNMENT - 1) / PAC_ALIGNMENT) * PAC_ALIGNMENT;
+    ret = pac_aligned_size(context, unaligned_len, &len);
+    if (ret)
+	return ret;
 
     ret = krb5_data_realloc(&p->data, len);
     if (ret) {
@@ -333,7 +414,7 @@ krb5_pac_add_buffer(krb5_context context, krb5_pac p,
     /*
      * make place for new PAC INFO BUFFER header
      */
-    header_end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
+    header_end -= PAC_INFO_BUFFER_SIZE;
     memmove((unsigned char *)p->data.data + header_end + PAC_INFO_BUFFER_SIZE,
 	    (unsigned char *)p->data.data + header_end ,
 	    old_end - header_end);
@@ -346,7 +427,7 @@ krb5_pac_add_buffer(krb5_context context, krb5_pac p,
     memcpy((unsigned char *)p->data.data + offset,
 	   data->data, data->length);
     memset((unsigned char *)p->data.data + offset + data->length,
-	   0, p->data.length - offset - data->length);
+ 	   0, p->data.length - unaligned_len);
 
     p->pac->numbuffers += 1;
 
@@ -375,8 +456,8 @@ krb5_pac_get_buffer(krb5_context context, krb5_pac p,
     uint32_t i;
 
     for (i = 0; i < p->pac->numbuffers; i++) {
-	const size_t len = p->pac->buffers[i].buffersize;
-	const size_t offset = p->pac->buffers[i].offset_lo;
+	const uint32_t len = p->pac->buffers[i].buffersize;
+	const uint32_t offset = p->pac->buffers[i].offset_lo;
 
 	if (p->pac->buffers[i].type != type)
 	    continue;
@@ -981,8 +1062,8 @@ _krb5_pac_sign(krb5_context context,
     size_t server_size, priv_size;
     uint32_t server_offset = 0, priv_offset = 0;
     uint32_t server_cksumtype = 0, priv_cksumtype = 0;
-    int num = 0;
-    size_t i;
+    uint32_t num = 0;
+    uint32_t i;
     krb5_data logon, d;
 
     krb5_data_zero(&logon);
@@ -1030,8 +1111,18 @@ _krb5_pac_sign(krb5_context context,
 
     if (num) {
 	void *ptr;
-
-	ptr = realloc(p->pac, sizeof(*p->pac) + (sizeof(p->pac->buffers[0]) * (p->pac->numbuffers + num - 1)));
+        uint32_t len;
+
+ 	if (p->pac->numbuffers > UINT32_MAX - num) {
+ 	    ret = EINVAL;
+ 	    krb5_set_error_message(context, ret, "integer overrun");
+ 	    goto out;
+ 	}
+ 	ret = pac_header_size(context, p->pac->numbuffers + num, &len);
+ 	if (ret)
+ 	    goto out;
+ 
+ 	ptr = realloc(p->pac, len);
 	if (ptr == NULL)
 	    return krb5_enomem(context);
 
@@ -1084,7 +1175,9 @@ _krb5_pac_sign(krb5_context context,
     CHECK(ret, krb5_store_uint32(sp, p->pac->numbuffers), out);
     CHECK(ret, krb5_store_uint32(sp, p->pac->version), out);
 
-    end = PACTYPE_SIZE + (PAC_INFO_BUFFER_SIZE * p->pac->numbuffers);
+    ret = pac_header_size(context, p->pac->numbuffers, &end);
+    if (ret)
+        goto out;
 
     for (i = 0; i < p->pac->numbuffers; i++) {
 	uint32_t len;
@@ -1094,11 +1187,31 @@ _krb5_pac_sign(krb5_context context,
 	/* store data */
 
 	if (p->pac->buffers[i].type == PAC_SERVER_CHECKSUM) {
+	    if (server_size > UINT32_MAX - 4) {
+		ret = EINVAL;
+		krb5_set_error_message(context, ret, "integer overrun");
+		goto out;
+	    }
+	    if (end > UINT32_MAX - 4) {
+		ret = EINVAL;
+		krb5_set_error_message(context, ret, "integer overrun");
+		goto out;
+	    }
 	    len = server_size + 4;
 	    server_offset = end + 4;
 	    CHECK(ret, krb5_store_uint32(spdata, server_cksumtype), out);
 	    CHECK(ret, fill_zeros(context, spdata, server_size), out);
 	} else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) {
+	    if (priv_size > UINT32_MAX - 4) {
+		ret = EINVAL;
+		krb5_set_error_message(context, ret, "integer overrun");
+		goto out;
+	    }
+	    if (end > UINT32_MAX - 4) {
+		ret = EINVAL;
+		krb5_set_error_message(context, ret, "integer overrun");
+		goto out;
+	    }
 	    len = priv_size + 4;
 	    priv_offset = end + 4;
 	    CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out);
@@ -1129,11 +1242,20 @@ _krb5_pac_sign(krb5_context context,
 
 	/* advance data endpointer and align */
 	{
-	    int32_t e;
+	    uint32_t e;
 
+	    if (end > UINT32_MAX - len) {
+		ret = EINVAL;
+		krb5_set_error_message(context, ret, "integer overrun");
+		goto out;
+	    }
 	    end += len;
-	    e = ((end + PAC_ALIGNMENT - 1) / PAC_ALIGNMENT) * PAC_ALIGNMENT;
-	    if ((int32_t)end != e) {
+
+	    ret = pac_aligned_size(context, end, &e);
+	    if (ret)
+		goto out;
+
+	    if (end != e) {
 		CHECK(ret, fill_zeros(context, spdata, e - end), out);
 	    }
 	    end = e;
-- 
2.38.1

