123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- From 55fbf435edbe2e92dd8101669b1ce7144bc96fef Mon Sep 17 00:00:00 2001
- From: Greg Hudson <ghudson@mit.edu>
- Date: Fri, 14 Jun 2024 10:56:12 -0400
- Subject: [PATCH] Fix vulnerabilities in GSS message token handling
- 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 b0a2f8a5365f2eec3e27d78907de9f9d2c80505a)
- ticket: 9128
- version_fixed: 1.21.3
- ---
- src/lib/gssapi/krb5/k5sealv3.c | 5 +
- src/lib/gssapi/krb5/k5sealv3iov.c | 3 +-
- src/lib/gssapi/krb5/k5unsealiov.c | 80 +++++++++-
- src/tests/gssapi/t_invalid.c | 233 +++++++++++++++++++++++++-----
- 4 files changed, 275 insertions(+), 46 deletions(-)
- diff --git a/src/lib/gssapi/krb5/k5sealv3.c b/src/lib/gssapi/krb5/k5sealv3.c
- index 3b4f8cb837b..1fcbdfbb870 100644
- --- a/src/lib/gssapi/krb5/k5sealv3.c
- +++ b/src/lib/gssapi/krb5/k5sealv3.c
- @@ -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;
- diff --git a/src/lib/gssapi/krb5/k5sealv3iov.c b/src/lib/gssapi/krb5/k5sealv3iov.c
- index 333ee124ddf..f8e90c35b44 100644
- --- a/src/lib/gssapi/krb5/k5sealv3iov.c
- +++ b/src/lib/gssapi/krb5/k5sealv3iov.c
- @@ -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 */
- diff --git a/src/lib/gssapi/krb5/k5unsealiov.c b/src/lib/gssapi/krb5/k5unsealiov.c
- index 85a9574f365..21b501731e6 100644
- --- a/src/lib/gssapi/krb5/k5unsealiov.c
- +++ b/src/lib/gssapi/krb5/k5unsealiov.c
- @@ -25,6 +25,7 @@
- */
-
- #include "k5-int.h"
- +#include "k5-der.h"
- #include "gssapiP_krb5.h"
-
- static OM_uint32
- @@ -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
- @@ -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);
- @@ -316,15 +383,14 @@ 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;
- }
- @@ -332,7 +398,7 @@ kg_unseal_iov_token(OM_uint32 *minor_status,
- toktype2 = load_16_be(ptr);
-
- ptr += 2;
- - bodysize -= 2;
- + hlen -= 2;
-
- switch (toktype2) {
- case KG2_TOK_MIC_MSG:
- diff --git a/src/tests/gssapi/t_invalid.c b/src/tests/gssapi/t_invalid.c
- index 9876a11e673..882e163634d 100644
- --- a/src/tests/gssapi/t_invalid.c
- +++ b/src/tests/gssapi/t_invalid.c
- @@ -36,31 +36,41 @@
- *
- * 1. A pre-CFX wrap or MIC token processed with a CFX-only context causes a
- * null pointer dereference. (The token must use SEAL_ALG_NONE or it will
- - * be rejected.)
- + * be rejected.) This vulnerability also applies to IOV unwrap.
- *
- - * 2. A pre-CFX wrap or MIC token with fewer than 24 bytes after the ASN.1
- + * 2. A CFX wrap token with a different value of EC between the plaintext and
- + * encrypted copies will be erroneously accepted, which allows a message
- + * truncation attack. This vulnerability also applies to IOV unwrap.
- + *
- + * 3. A CFX wrap token with a plaintext length fewer than 16 bytes causes an
- + * access before the beginning of the input buffer, possibly leading to a
- + * crash.
- + *
- + * 4. A CFX wrap token with a plaintext EC value greater than the plaintext
- + * length - 16 causes an integer underflow when computing the result length,
- + * likely causing a crash.
- + *
- + * 5. An IOV unwrap operation will overrun the header buffer if an ASN.1
- + * wrapper longer than the header buffer is present.
- + *
- + * 6. A pre-CFX wrap or MIC token with fewer than 24 bytes after the ASN.1
- * header causes an input buffer overrun, usually leading to either a segv
- * or a GSS_S_DEFECTIVE_TOKEN error due to garbage algorithm, filler, or
- - * sequence number values.
- + * sequence number values. This vulnerability also applies to IOV unwrap.
- *
- - * 3. A pre-CFX wrap token with fewer than 16 + cksumlen bytes after the ASN.1
- + * 7. A pre-CFX wrap token with fewer than 16 + cksumlen bytes after the ASN.1
- * header causes an integer underflow when computing the ciphertext length,
- * leading to an allocation error on 32-bit platforms or a segv on 64-bit
- * platforms. A pre-CFX MIC token of this size causes an input buffer
- * overrun when comparing the checksum, perhaps leading to a segv.
- *
- - * 4. A pre-CFX wrap token with fewer than conflen + padlen bytes in the
- + * 8. A pre-CFX wrap token with fewer than conflen + padlen bytes in the
- * ciphertext (where padlen is the last byte of the decrypted ciphertext)
- * causes an integer underflow when computing the original message length,
- * leading to an allocation error.
- *
- - * 5. In the mechglue, truncated encapsulation in the initial context token can
- + * 9. In the mechglue, truncated encapsulation in the initial context token can
- * cause input buffer overruns in gss_accept_sec_context().
- - *
- - * Vulnerabilities #1 and #2 also apply to IOV unwrap, although tokens with
- - * fewer than 16 bytes after the ASN.1 header will be rejected.
- - * Vulnerabilities #2 and #5 can only be robustly detected using a
- - * memory-checking environment such as valgrind.
- */
-
- #include "k5-int.h"
- @@ -109,17 +119,25 @@ struct test {
- }
- };
-
- -/* Fake up enough of a CFX GSS context for gss_unwrap, using an AES key. */
- +static void *
- +ealloc(size_t len)
- +{
- + void *ptr = calloc(len, 1);
- +
- + if (ptr == NULL)
- + abort();
- + return ptr;
- +}
- +
- +/* Fake up enough of a CFX GSS context for gss_unwrap, using an AES key.
- + * The context takes ownership of subkey. */
- static gss_ctx_id_t
- -make_fake_cfx_context()
- +make_fake_cfx_context(krb5_key subkey)
- {
- gss_union_ctx_id_t uctx;
- krb5_gss_ctx_id_t kgctx;
- - krb5_keyblock kb;
-
- - kgctx = calloc(1, sizeof(*kgctx));
- - if (kgctx == NULL)
- - abort();
- + kgctx = ealloc(sizeof(*kgctx));
- kgctx->established = 1;
- kgctx->proto = 1;
- if (g_seqstate_init(&kgctx->seqstate, 0, 0, 0, 0) != 0)
- @@ -128,15 +146,10 @@ make_fake_cfx_context()
- kgctx->sealalg = -1;
- kgctx->signalg = -1;
-
- - kb.enctype = ENCTYPE_AES128_CTS_HMAC_SHA1_96;
- - kb.length = 16;
- - kb.contents = (unsigned char *)"1234567887654321";
- - if (krb5_k_create_key(NULL, &kb, &kgctx->subkey) != 0)
- - abort();
- + kgctx->subkey = subkey;
- + kgctx->cksumtype = CKSUMTYPE_HMAC_SHA1_96_AES128;
-
- - uctx = calloc(1, sizeof(*uctx));
- - if (uctx == NULL)
- - abort();
- + uctx = ealloc(sizeof(*uctx));
- uctx->mech_type = &mech_krb5;
- uctx->internal_ctx_id = (gss_ctx_id_t)kgctx;
- return (gss_ctx_id_t)uctx;
- @@ -150,9 +163,7 @@ make_fake_context(const struct test *test)
- krb5_gss_ctx_id_t kgctx;
- krb5_keyblock kb;
-
- - kgctx = calloc(1, sizeof(*kgctx));
- - if (kgctx == NULL)
- - abort();
- + kgctx = ealloc(sizeof(*kgctx));
- kgctx->established = 1;
- if (g_seqstate_init(&kgctx->seqstate, 0, 0, 0, 0) != 0)
- abort();
- @@ -174,9 +185,7 @@ make_fake_context(const struct test *test)
- if (krb5_k_create_key(NULL, &kb, &kgctx->enc) != 0)
- abort();
-
- - uctx = calloc(1, sizeof(*uctx));
- - if (uctx == NULL)
- - abort();
- + uctx = ealloc(sizeof(*uctx));
- uctx->mech_type = &mech_krb5;
- uctx->internal_ctx_id = (gss_ctx_id_t)kgctx;
- return (gss_ctx_id_t)uctx;
- @@ -206,9 +215,7 @@ make_token(unsigned char *token, size_t len, gss_buffer_t out)
-
- assert(mech_krb5.length == 9);
- assert(len + 11 < 128);
- - wrapped = malloc(len + 13);
- - if (wrapped == NULL)
- - abort();
- + wrapped = ealloc(len + 13);
- wrapped[0] = 0x60;
- wrapped[1] = len + 11;
- wrapped[2] = 0x06;
- @@ -219,6 +226,18 @@ make_token(unsigned char *token, size_t len, gss_buffer_t out)
- out->value = wrapped;
- }
-
- +/* Create a 16-byte header for a CFX confidential wrap token to be processed by
- + * the fake CFX context. */
- +static void
- +write_cfx_header(uint16_t ec, uint8_t *out)
- +{
- + memset(out, 0, 16);
- + store_16_be(KG2_TOK_WRAP_MSG, out);
- + out[2] = FLAG_WRAP_CONFIDENTIAL;
- + out[3] = 0xFF;
- + store_16_be(ec, out + 4);
- +}
- +
- /* Unwrap a superficially valid RFC 1964 token with a CFX-only context, with
- * regular and IOV unwrap. */
- static void
- @@ -250,6 +269,134 @@ test_bogus_1964_token(gss_ctx_id_t ctx)
- free(in.value);
- }
-
- +static void
- +test_cfx_altered_ec(gss_ctx_id_t ctx, krb5_key subkey)
- +{
- + OM_uint32 major, minor;
- + uint8_t tokbuf[128], plainbuf[24];
- + krb5_data plain;
- + krb5_enc_data cipher;
- + gss_buffer_desc in, out;
- + gss_iov_buffer_desc iov[2];
- +
- + /* Construct a header with a plaintext EC value of 3. */
- + write_cfx_header(3, tokbuf);
- +
- + /* Encrypt a plaintext and a copy of the header with the EC value 0. */
- + memcpy(plainbuf, "truncate", 8);
- + memcpy(plainbuf + 8, tokbuf, 16);
- + store_16_be(0, plainbuf + 12);
- + plain = make_data(plainbuf, 24);
- + cipher.ciphertext.data = (char *)tokbuf + 16;
- + cipher.ciphertext.length = sizeof(tokbuf) - 16;
- + cipher.enctype = subkey->keyblock.enctype;
- + if (krb5_k_encrypt(NULL, subkey, KG_USAGE_INITIATOR_SEAL, NULL,
- + &plain, &cipher) != 0)
- + abort();
- +
- + /* Verify that the token is rejected by gss_unwrap(). */
- + in.value = tokbuf;
- + in.length = 16 + cipher.ciphertext.length;
- + major = gss_unwrap(&minor, ctx, &in, &out, NULL, NULL);
- + if (major != GSS_S_DEFECTIVE_TOKEN)
- + abort();
- + (void)gss_release_buffer(&minor, &out);
- +
- + /* Verify that the token is rejected by gss_unwrap_iov(). */
- + iov[0].type = GSS_IOV_BUFFER_TYPE_STREAM;
- + iov[0].buffer = in;
- + iov[1].type = GSS_IOV_BUFFER_TYPE_DATA;
- + major = gss_unwrap_iov(&minor, ctx, NULL, NULL, iov, 2);
- + if (major != GSS_S_DEFECTIVE_TOKEN)
- + abort();
- +}
- +
- +static void
- +test_cfx_short_plaintext(gss_ctx_id_t ctx, krb5_key subkey)
- +{
- + OM_uint32 major, minor;
- + uint8_t tokbuf[128], zerobyte = 0;
- + krb5_data plain;
- + krb5_enc_data cipher;
- + gss_buffer_desc in, out;
- +
- + write_cfx_header(0, tokbuf);
- +
- + /* Encrypt a single byte, with no copy of the header. */
- + plain = make_data(&zerobyte, 1);
- + cipher.ciphertext.data = (char *)tokbuf + 16;
- + cipher.ciphertext.length = sizeof(tokbuf) - 16;
- + cipher.enctype = subkey->keyblock.enctype;
- + if (krb5_k_encrypt(NULL, subkey, KG_USAGE_INITIATOR_SEAL, NULL,
- + &plain, &cipher) != 0)
- + abort();
- +
- + /* Verify that the token is rejected by gss_unwrap(). */
- + in.value = tokbuf;
- + in.length = 16 + cipher.ciphertext.length;
- + major = gss_unwrap(&minor, ctx, &in, &out, NULL, NULL);
- + if (major != GSS_S_DEFECTIVE_TOKEN)
- + abort();
- + (void)gss_release_buffer(&minor, &out);
- +}
- +
- +static void
- +test_cfx_large_ec(gss_ctx_id_t ctx, krb5_key subkey)
- +{
- + OM_uint32 major, minor;
- + uint8_t tokbuf[128] = { 0 }, plainbuf[20];
- + krb5_data plain;
- + krb5_enc_data cipher;
- + gss_buffer_desc in, out;
- +
- + /* Construct a header with an EC value of 5. */
- + write_cfx_header(5, tokbuf);
- +
- + /* Encrypt a 4-byte plaintext plus the header. */
- + memcpy(plainbuf, "abcd", 4);
- + memcpy(plainbuf + 4, tokbuf, 16);
- + plain = make_data(plainbuf, 20);
- + cipher.ciphertext.data = (char *)tokbuf + 16;
- + cipher.ciphertext.length = sizeof(tokbuf) - 16;
- + cipher.enctype = subkey->keyblock.enctype;
- + if (krb5_k_encrypt(NULL, subkey, KG_USAGE_INITIATOR_SEAL, NULL,
- + &plain, &cipher) != 0)
- + abort();
- +
- + /* Verify that the token is rejected by gss_unwrap(). */
- + in.value = tokbuf;
- + in.length = 16 + cipher.ciphertext.length;
- + major = gss_unwrap(&minor, ctx, &in, &out, NULL, NULL);
- + if (major != GSS_S_DEFECTIVE_TOKEN)
- + abort();
- + (void)gss_release_buffer(&minor, &out);
- +}
- +
- +static void
- +test_iov_large_asn1_wrapper(gss_ctx_id_t ctx)
- +{
- + OM_uint32 minor, major;
- + uint8_t databuf[10] = { 0 };
- + gss_iov_buffer_desc iov[2];
- +
- + /*
- + * In this IOV array, the header contains a DER tag with a dangling eight
- + * bytes of length field. The data IOV indicates a total token length
- + * sufficient to contain the length bytes.
- + */
- + iov[0].type = GSS_IOV_BUFFER_TYPE_HEADER;
- + iov[0].buffer.value = ealloc(2);
- + iov[0].buffer.length = 2;
- + memcpy(iov[0].buffer.value, "\x60\x88", 2);
- + iov[1].type = GSS_IOV_BUFFER_TYPE_DATA;
- + iov[1].buffer.value = databuf;
- + iov[1].buffer.length = 10;
- + major = gss_unwrap_iov(&minor, ctx, NULL, NULL, iov, 2);
- + if (major != GSS_S_DEFECTIVE_TOKEN)
- + abort();
- + free(iov[0].buffer.value);
- +}
- +
- /* Process wrap and MIC tokens with incomplete headers. */
- static void
- test_short_header(gss_ctx_id_t ctx)
- @@ -399,9 +546,7 @@ try_accept(void *value, size_t len)
- gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
-
- /* Copy the provided value to make input overruns more obvious. */
- - in.value = malloc(len);
- - if (in.value == NULL)
- - abort();
- + in.value = ealloc(len);
- memcpy(in.value, value, len);
- in.length = len;
- (void)gss_accept_sec_context(&minor, &ctx, GSS_C_NO_CREDENTIAL, &in,
- @@ -436,11 +581,23 @@ test_short_encapsulation()
- int
- main(int argc, char **argv)
- {
- + krb5_keyblock kb;
- + krb5_key cfx_subkey;
- gss_ctx_id_t ctx;
- size_t i;
-
- - ctx = make_fake_cfx_context();
- + kb.enctype = ENCTYPE_AES128_CTS_HMAC_SHA1_96;
- + kb.length = 16;
- + kb.contents = (unsigned char *)"1234567887654321";
- + if (krb5_k_create_key(NULL, &kb, &cfx_subkey) != 0)
- + abort();
- +
- + ctx = make_fake_cfx_context(cfx_subkey);
- test_bogus_1964_token(ctx);
- + test_cfx_altered_ec(ctx, cfx_subkey);
- + test_cfx_short_plaintext(ctx, cfx_subkey);
- + test_cfx_large_ec(ctx, cfx_subkey);
- + test_iov_large_asn1_wrapper(ctx);
- free_fake_context(ctx);
-
- for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) {
|