Coordinated Disclosure Timeline
- 2023-02-06: Issues reported to maintainer
- 2023-02-08: Report acknowledged and patch proposed
- 2023-02-13: Fixes merged
Summary
Multiple vulnerabilities in the MIT Kerberos V5 (krb5) library can trigger an out-of-bounds read when parsing and verifying SPNEGO tokens.
Product
MIT Kerberos V5 (krb5)
Tested Version
Details
Issue 1: out-of-bounds read in SPNEGO get_mech_set (GHSL-2023-016
)
get_mech_set
parses a list of mechanism OIDs from the token passed to gss_accept_sec_context
. The OIDs are formatted as a sequence of DER-encoded objects:
static gss_OID_set
get_mech_set(OM_uint32 *minor_status, unsigned char **buff_in,
unsigned int buff_length)
{
gss_OID_set returned_mechSet;
OM_uint32 major_status;
int length;
unsigned int bytes;
OM_uint32 set_length;
unsigned char *start;
int i; // 1
...
major_status = gss_create_empty_oid_set(minor_status,
&returned_mechSet);
if (major_status != GSS_S_COMPLETE)
return (NULL);
for (set_length = 0, i = 0; set_length < (unsigned int)length; i++) { // 5
gss_OID_desc *temp = get_mech_oid(minor_status, buff_in,
buff_length - (*buff_in - start));
if (temp == NULL)
break;
major_status = gss_add_oid_set_member(minor_status, // 2
temp, &returned_mechSet);
if (major_status == GSS_S_COMPLETE) // 3
set_length += returned_mechSet->elements[i].length +2; // 4
generic_gss_release_oid(minor_status, &temp);
}
return (returned_mechSet);
}
At (1), i
represents the number of OIDs contained in returned_mechSet
and is used in the main processing loop.
At (2) the function attempts to insert temp
into returned_mechSet
. This operation can possibly fail. The return code is checked at (3) and the logic at (4) is skipped if gss_add_oid_set_member
failed. An attacker can cause gss_add_oid_set_member
to fail by adding a zero-length OID to the list of mechanisms.
However, on the next loop iteration, (5), i
is incremented even though we did not add an OID to returned_mechSet
. If the next loop iteration successfully inserts an OID into returned_mechSet
then the returned_mechSet->elements[i]
logic at (4) is triggered. This causes an out-of-bounds read because i
is greater than the number of elements in returned_mechSet
.
By including multiple zero-length OIDs, we can increment i
arbitrarily and control the out-of-bounds read.
Impact
This out of bounds read can lead to a denial of service if the accessed memory is not mapped.
Issue 2: out-of-bounds read in SPNEGO g_verify_neg_token_init (GHSL-2023-017
)
g_verify_neg_token_init
validates a SPNEGO token provided via gss_accept_sec_context
:
static int
g_verify_neg_token_init(unsigned char **buf_in, unsigned int cur_size)
{
unsigned char *buf = *buf_in;
unsigned char *endptr = buf + cur_size;
int seqsize;
int ret = 0;
unsigned int bytes;
/*
* Verify this is a NegotiationToken type token
* - check for a0(context specific identifier)
* - get length and verify that enoughd ata exists
*/
if (g_get_tag_and_length(&buf, CONTEXT, cur_size, &bytes) < 0) // 1
return (G_BAD_TOK_HEADER);
cur_size = bytes; /* should indicate bytes remaining */
/*
* Verify the next piece, it should identify this as
* a strucure of type NegTokenInit.
*/
if (*buf++ == SEQUENCE) { // 2
if ((seqsize = gssint_get_der_length(&buf, cur_size, &bytes)) < 0) // 3
return (G_BAD_TOK_HEADER);
/*
* Make sure we have the entire buffer as described
*/
if (seqsize > endptr - buf)
return (G_BAD_TOK_HEADER);
} else {
return (G_BAD_TOK_HEADER);
}
cur_size = seqsize; /* should indicate bytes remaining */
/*
* Verify that the first blob is a sequence of mechTypes
*/
if (*buf++ == CONTEXT) { // 4
if ((seqsize = gssint_get_der_length(&buf, cur_size, &bytes)) < 0)
return (G_BAD_TOK_HEADER);
...
At (1), we use g_get_tag_and_length
to check for a CONTEXT
tag and decode the following length bytes. g_get_tag_and_length
returns a negative value on error or zero on success. On success it places the decoded length in bytes
. Zero length sequences are valid in ASN.1 so g_get_tag_and_length
does not validate that bytes
is non-zero. At (2), buf
is dereferenced without validating that cur_size
/bytes
is non-zero which can trigger an out-of-bounds read.
A similar vulnerability exists at (3) and (4). The gssint_get_der_length
call at (3) will return a negative value on error or the length of the ASN.1 object on success. There is no validation that the sequence length (cur_size
/seqsize
) is non-zero which causes an out-of-bounds read at (4).
Impact
These out of bounds reads could lead to a denial of service if the accessed memory is not mapped.
Issue 3: invalid length calculation in SPNEGO g_verify_neg_token_init (GHSL-2023-018
)
In the same function, g_verify_neg_token_init
, the cur_size
variable does not take into account the tag byte of the ASN.1 object when calculating the number of bytes in an ASN.1 object:
static int
g_verify_neg_token_init(unsigned char **buf_in, unsigned int cur_size)
{
unsigned char *buf = *buf_in;
unsigned char *endptr = buf + cur_size;
int seqsize;
int ret = 0;
unsigned int bytes;
/*
* Verify this is a NegotiationToken type token
* - check for a0(context specific identifier)
* - get length and verify that enoughd ata exists
*/
if (g_get_tag_and_length(&buf, CONTEXT, cur_size, &bytes) < 0) // 1
return (G_BAD_TOK_HEADER);
cur_size = bytes; /* should indicate bytes remaining */ // 2
/*
* Verify the next piece, it should identify this as
* a strucure of type NegTokenInit.
*/
if (*buf++ == SEQUENCE) { // 3
if ((seqsize = gssint_get_der_length(&buf, cur_size, &bytes)) < 0) // 4
return (G_BAD_TOK_HEADER);
/*
* Make sure we have the entire buffer as described
*/
if (seqsize > endptr - buf)
return (G_BAD_TOK_HEADER);
} else {
return (G_BAD_TOK_HEADER);
}
cur_size = seqsize; /* should indicate bytes remaining */ // 5
/*
* Verify that the first blob is a sequence of mechTypes
*/
if (*buf++ == CONTEXT) { // 6
if ((seqsize = gssint_get_der_length(&buf, cur_size, &bytes)) < 0) // 7
return (G_BAD_TOK_HEADER);
...
At (1), we read the size of the object into bytes
, which is then assigned to cur_size
at (2). This represents the number of bytes in the object including the tag, length bytes and value. At (3), we inspect the tag byte of the object, *buf
, to verify that it is a SEQUENCE
object. At the same time we increment buf
but do not decrement cur_size
. We then call gssint_get_der_length
with cur_size
at (4). This can then trigger an out-of-bounds read in gssint_get_der_length
.
A similar vulnerability exists starting at (4). The result of the gssint_get_der_length
call is the size of the SEQUENCE
object including the tag, length bytes and value. This is assigned to cur_size
at (5), which is not is not decremented when reading the tag byte at (6). This can trigger a similar out-of-bounds read in the gssint_get_der_length
call at (7).
Impact
These out of bounds reads could lead to a denial of service if the accessed memory is not mapped.
Credit
These issues were discovered and reported by GHSL team member @philipturnbull (Phil Turnbull).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2023-016
, GHSL-2023-017
, or GHSL-2023-018
in any communication regarding these issues.