Skip to content

Commit

Permalink
Fix vulnerabilities in GSS message token handling
Browse files Browse the repository at this point in the history
In gss_krb5int_unseal_token_v3() and gss_krb5int_unseal_v3_iov(),
verify the Extra Count field of CFX wrap tokens against the encrypted
header.  Reported by Jacob Champion.

In gss_krb5int_unseal_token_v3(), check for a decrypted plaintext
length too short to contain the encrypted header and extra count
bytes.  Reported by Jacob Champion.

In kg_unseal_iov_token(), separately track the header IOV length and
complete token length when parsing the token's ASN.1 wrapper.  This
fix contains modified versions of functions from k5-der.h and
util_token.c; this duplication will be cleaned up in a future commit.

CVE-2024-37370:

In MIT krb5 release 1.3 and later, an attacker can modify the
plaintext Extra Count field of a confidential GSS krb5 wrap token,
causing the unwrapped token to appear truncated to the application.

CVE-2024-37371:

In MIT krb5 release 1.3 and later, an attacker can cause invalid
memory reads by sending message tokens with invalid length fields.

(cherry picked from commit b0a2f8a)

ticket: 9128
version_fixed: 1.21.3
  • Loading branch information
greghudson committed Jun 26, 2024
1 parent ddf1efd commit 55fbf43
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 46 deletions.
5 changes: 5 additions & 0 deletions src/lib/gssapi/krb5/k5sealv3.c
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,15 @@ gss_krb5int_unseal_token_v3(krb5_context *contextptr,
/* Don't use bodysize here! Use the fact that
cipher.ciphertext.length has been adjusted to the
correct length. */
if (plain.length < 16 + ec) {
free(plain.data);
goto defective;
}
althdr = (unsigned char *)plain.data + plain.length - 16;
if (load_16_be(althdr) != KG2_TOK_WRAP_MSG
|| althdr[2] != ptr[2]
|| althdr[3] != ptr[3]
|| load_16_be(althdr+4) != ec
|| memcmp(althdr+8, ptr+8, 8)) {
free(plain.data);
goto defective;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/gssapi/krb5/k5sealv3iov.c
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,10 @@ gss_krb5int_unseal_v3_iov(krb5_context context,
if (load_16_be(althdr) != KG2_TOK_WRAP_MSG
|| althdr[2] != ptr[2]
|| althdr[3] != ptr[3]
|| load_16_be(althdr + 4) != ec
|| memcmp(althdr + 8, ptr + 8, 8) != 0) {
*minor_status = 0;
return GSS_S_BAD_SIG;
return GSS_S_DEFECTIVE_TOKEN;
}
} else {
/* Verify checksum: note EC is checksum size here, not padding */
Expand Down
80 changes: 73 additions & 7 deletions src/lib/gssapi/krb5/k5unsealiov.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/

#include "k5-int.h"
#include "k5-der.h"
#include "gssapiP_krb5.h"

static OM_uint32
Expand Down Expand Up @@ -265,6 +266,73 @@ kg_unseal_v1_iov(krb5_context context,
return retval;
}

/* Similar to k5_der_get_value(), but output an unchecked content length
* instead of a k5input containing the contents. */
static inline bool
get_der_tag(struct k5input *in, uint8_t idbyte, size_t *len_out)
{
uint8_t lenbyte, i;
size_t len;

/* Do nothing if in is empty or the next byte doesn't match idbyte. */
if (in->status || in->len == 0 || *in->ptr != idbyte)
return false;

/* Advance past the identifier byte and decode the length. */
(void)k5_input_get_byte(in);
lenbyte = k5_input_get_byte(in);
if (lenbyte < 128) {
len = lenbyte;
} else {
len = 0;
for (i = 0; i < (lenbyte & 0x7F); i++) {
if (len > (SIZE_MAX >> 8)) {
k5_input_set_status(in, EOVERFLOW);
return false;
}
len = (len << 8) | k5_input_get_byte(in);
}
}

if (in->status)
return false;

*len_out = len;
return true;
}

/*
* Similar to g_verify_token_header() without toktype or flags, but do not read
* more than *header_len bytes of ASN.1 wrapper, and on output set *header_len
* to the remaining number of header bytes. Verify the outer DER tag's length
* against token_len, which may be larger (but not smaller) than *header_len.
*/
static gss_int32
verify_detached_wrapper(const gss_OID_desc *mech, size_t *header_len,
uint8_t **header_in, size_t token_len)
{
struct k5input in, mech_der;
gss_OID_desc toid;
size_t len;

k5_input_init(&in, *header_in, *header_len);

if (get_der_tag(&in, 0x60, &len)) {
if (len != token_len - (in.ptr - *header_in))
return G_BAD_TOK_HEADER;
if (!k5_der_get_value(&in, 0x06, &mech_der))
return G_BAD_TOK_HEADER;
toid.elements = (uint8_t *)mech_der.ptr;
toid.length = mech_der.len;
if (!g_OID_equal(&toid, mech))
return G_WRONG_MECH;
}

*header_in = (uint8_t *)in.ptr;
*header_len = in.len;
return 0;
}

/*
* Caller must provide TOKEN | DATA | PADDING | TRAILER, except
* for DCE in which case it can just provide TOKEN | DATA (must
Expand All @@ -285,8 +353,7 @@ kg_unseal_iov_token(OM_uint32 *minor_status,
gss_iov_buffer_t header;
gss_iov_buffer_t padding;
gss_iov_buffer_t trailer;
size_t input_length;
unsigned int bodysize;
size_t input_length, hlen;
int toktype2;

header = kg_locate_header_iov(iov, iov_count, toktype);
Expand Down Expand Up @@ -316,23 +383,22 @@ kg_unseal_iov_token(OM_uint32 *minor_status,
input_length += trailer->buffer.length;
}

code = g_verify_token_header(ctx->mech_used,
&bodysize, &ptr, -1,
input_length, 0);
hlen = header->buffer.length;
code = verify_detached_wrapper(ctx->mech_used, &hlen, &ptr, input_length);
if (code != 0) {
*minor_status = code;
return GSS_S_DEFECTIVE_TOKEN;
}

if (bodysize < 2) {
if (hlen < 2) {
*minor_status = (OM_uint32)G_BAD_TOK_HEADER;
return GSS_S_DEFECTIVE_TOKEN;
}

toktype2 = load_16_be(ptr);

ptr += 2;
bodysize -= 2;
hlen -= 2;

switch (toktype2) {
case KG2_TOK_MIC_MSG:
Expand Down
Loading

0 comments on commit 55fbf43

Please sign in to comment.