mirror of https://github.com/ipxe/ipxe.git
[tls] Avoid potential out-of-bound reads in length fields
Many TLS records contain variable-length fields. We currently validate the overall record length, but do so only after reading the length of the variable-length field. If the record is too short to even contain the length field, then we may read uninitialised data from beyond the end of the record. This is harmless in practice (since the subsequent overall record length check would fail regardless of the value read from the uninitialised length field), but causes warnings from some analysis tools. Fix by validating that the overall record length is sufficient to contain the length field before reading from the length field. Signed-off-by: Michael Brown <mcb30@ipxe.org>pull/44/head
parent
e303a6b387
commit
05dcb07cb2
109
src/net/tls.c
109
src/net/tls.c
|
@ -1271,10 +1271,9 @@ static int tls_new_alert ( struct tls_session *tls, const void *data,
|
||||||
uint8_t description;
|
uint8_t description;
|
||||||
char next[0];
|
char next[0];
|
||||||
} __attribute__ (( packed )) *alert = data;
|
} __attribute__ (( packed )) *alert = data;
|
||||||
const void *end = alert->next;
|
|
||||||
|
|
||||||
/* Sanity check */
|
/* Sanity check */
|
||||||
if ( end != ( data + len ) ) {
|
if ( sizeof ( *alert ) != len ) {
|
||||||
DBGC ( tls, "TLS %p received overlength Alert\n", tls );
|
DBGC ( tls, "TLS %p received overlength Alert\n", tls );
|
||||||
DBGC_HD ( tls, data, len );
|
DBGC_HD ( tls, data, len );
|
||||||
return -EINVAL_ALERT;
|
return -EINVAL_ALERT;
|
||||||
|
@ -1310,24 +1309,28 @@ static int tls_new_server_hello ( struct tls_session *tls,
|
||||||
uint16_t version;
|
uint16_t version;
|
||||||
uint8_t random[32];
|
uint8_t random[32];
|
||||||
uint8_t session_id_len;
|
uint8_t session_id_len;
|
||||||
char next[0];
|
uint8_t session_id[0];
|
||||||
} __attribute__ (( packed )) *hello_a = data;
|
} __attribute__ (( packed )) *hello_a = data;
|
||||||
|
const uint8_t *session_id;
|
||||||
const struct {
|
const struct {
|
||||||
uint8_t session_id[hello_a->session_id_len];
|
|
||||||
uint16_t cipher_suite;
|
uint16_t cipher_suite;
|
||||||
uint8_t compression_method;
|
uint8_t compression_method;
|
||||||
char next[0];
|
char next[0];
|
||||||
} __attribute__ (( packed )) *hello_b = ( void * ) &hello_a->next;
|
} __attribute__ (( packed )) *hello_b;
|
||||||
const void *end = hello_b->next;
|
|
||||||
uint16_t version;
|
uint16_t version;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
/* Sanity check */
|
/* Parse header */
|
||||||
if ( end > ( data + len ) ) {
|
if ( ( sizeof ( *hello_a ) > len ) ||
|
||||||
|
( hello_a->session_id_len > ( len - sizeof ( *hello_a ) ) ) ||
|
||||||
|
( sizeof ( *hello_b ) > ( len - sizeof ( *hello_a ) -
|
||||||
|
hello_a->session_id_len ) ) ) {
|
||||||
DBGC ( tls, "TLS %p received underlength Server Hello\n", tls );
|
DBGC ( tls, "TLS %p received underlength Server Hello\n", tls );
|
||||||
DBGC_HD ( tls, data, len );
|
DBGC_HD ( tls, data, len );
|
||||||
return -EINVAL_HELLO;
|
return -EINVAL_HELLO;
|
||||||
}
|
}
|
||||||
|
session_id = hello_a->session_id;
|
||||||
|
hello_b = ( ( void * ) ( session_id + hello_a->session_id_len ) );
|
||||||
|
|
||||||
/* Check and store protocol version */
|
/* Check and store protocol version */
|
||||||
version = ntohs ( hello_a->version );
|
version = ntohs ( hello_a->version );
|
||||||
|
@ -1380,14 +1383,7 @@ static int tls_new_server_hello ( struct tls_session *tls,
|
||||||
*/
|
*/
|
||||||
static int tls_parse_chain ( struct tls_session *tls,
|
static int tls_parse_chain ( struct tls_session *tls,
|
||||||
const void *data, size_t len ) {
|
const void *data, size_t len ) {
|
||||||
const void *end = ( data + len );
|
size_t remaining = len;
|
||||||
const struct {
|
|
||||||
tls24_t length;
|
|
||||||
uint8_t data[0];
|
|
||||||
} __attribute__ (( packed )) *certificate;
|
|
||||||
size_t certificate_len;
|
|
||||||
struct x509_certificate *cert;
|
|
||||||
const void *next;
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
/* Free any existing certificate chain */
|
/* Free any existing certificate chain */
|
||||||
|
@ -1402,25 +1398,37 @@ static int tls_parse_chain ( struct tls_session *tls,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add certificates to chain */
|
/* Add certificates to chain */
|
||||||
while ( data < end ) {
|
while ( remaining ) {
|
||||||
|
const struct {
|
||||||
|
tls24_t length;
|
||||||
|
uint8_t data[0];
|
||||||
|
} __attribute__ (( packed )) *certificate = data;
|
||||||
|
size_t certificate_len;
|
||||||
|
size_t record_len;
|
||||||
|
struct x509_certificate *cert;
|
||||||
|
|
||||||
/* Extract raw certificate data */
|
/* Parse header */
|
||||||
certificate = data;
|
if ( sizeof ( *certificate ) > remaining ) {
|
||||||
|
DBGC ( tls, "TLS %p underlength certificate:\n", tls );
|
||||||
|
DBGC_HDA ( tls, 0, data, remaining );
|
||||||
|
rc = -EINVAL_CERTIFICATE;
|
||||||
|
goto err_underlength;
|
||||||
|
}
|
||||||
certificate_len = tls_uint24 ( &certificate->length );
|
certificate_len = tls_uint24 ( &certificate->length );
|
||||||
next = ( certificate->data + certificate_len );
|
if ( certificate_len > ( remaining - sizeof ( *certificate ) )){
|
||||||
if ( next > end ) {
|
|
||||||
DBGC ( tls, "TLS %p overlength certificate:\n", tls );
|
DBGC ( tls, "TLS %p overlength certificate:\n", tls );
|
||||||
DBGC_HDA ( tls, 0, data, ( end - data ) );
|
DBGC_HDA ( tls, 0, data, remaining );
|
||||||
rc = -EINVAL_CERTIFICATE;
|
rc = -EINVAL_CERTIFICATE;
|
||||||
goto err_overlength;
|
goto err_overlength;
|
||||||
}
|
}
|
||||||
|
record_len = ( sizeof ( *certificate ) + certificate_len );
|
||||||
|
|
||||||
/* Add certificate to chain */
|
/* Add certificate to chain */
|
||||||
if ( ( rc = x509_append_raw ( tls->chain, certificate->data,
|
if ( ( rc = x509_append_raw ( tls->chain, certificate->data,
|
||||||
certificate_len ) ) != 0 ) {
|
certificate_len ) ) != 0 ) {
|
||||||
DBGC ( tls, "TLS %p could not append certificate: %s\n",
|
DBGC ( tls, "TLS %p could not append certificate: %s\n",
|
||||||
tls, strerror ( rc ) );
|
tls, strerror ( rc ) );
|
||||||
DBGC_HDA ( tls, 0, data, ( end - data ) );
|
DBGC_HDA ( tls, 0, data, remaining );
|
||||||
goto err_parse;
|
goto err_parse;
|
||||||
}
|
}
|
||||||
cert = x509_last ( tls->chain );
|
cert = x509_last ( tls->chain );
|
||||||
|
@ -1428,13 +1436,15 @@ static int tls_parse_chain ( struct tls_session *tls,
|
||||||
tls, x509_name ( cert ) );
|
tls, x509_name ( cert ) );
|
||||||
|
|
||||||
/* Move to next certificate in list */
|
/* Move to next certificate in list */
|
||||||
data = next;
|
data += record_len;
|
||||||
|
remaining -= record_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_parse:
|
err_parse:
|
||||||
err_overlength:
|
err_overlength:
|
||||||
|
err_underlength:
|
||||||
x509_chain_put ( tls->chain );
|
x509_chain_put ( tls->chain );
|
||||||
tls->chain = NULL;
|
tls->chain = NULL;
|
||||||
err_alloc_chain:
|
err_alloc_chain:
|
||||||
|
@ -1455,12 +1465,18 @@ static int tls_new_certificate ( struct tls_session *tls,
|
||||||
tls24_t length;
|
tls24_t length;
|
||||||
uint8_t certificates[0];
|
uint8_t certificates[0];
|
||||||
} __attribute__ (( packed )) *certificate = data;
|
} __attribute__ (( packed )) *certificate = data;
|
||||||
size_t certificates_len = tls_uint24 ( &certificate->length );
|
size_t certificates_len;
|
||||||
const void *end = ( certificate->certificates + certificates_len );
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
/* Sanity check */
|
/* Parse header */
|
||||||
if ( end != ( data + len ) ) {
|
if ( sizeof ( *certificate ) > len ) {
|
||||||
|
DBGC ( tls, "TLS %p received underlength Server Certificate\n",
|
||||||
|
tls );
|
||||||
|
DBGC_HD ( tls, data, len );
|
||||||
|
return -EINVAL_CERTIFICATES;
|
||||||
|
}
|
||||||
|
certificates_len = tls_uint24 ( &certificate->length );
|
||||||
|
if ( certificates_len > ( len - sizeof ( *certificate ) ) ) {
|
||||||
DBGC ( tls, "TLS %p received overlength Server Certificate\n",
|
DBGC ( tls, "TLS %p received overlength Server Certificate\n",
|
||||||
tls );
|
tls );
|
||||||
DBGC_HD ( tls, data, len );
|
DBGC_HD ( tls, data, len );
|
||||||
|
@ -1521,11 +1537,10 @@ static int tls_new_server_hello_done ( struct tls_session *tls,
|
||||||
const struct {
|
const struct {
|
||||||
char next[0];
|
char next[0];
|
||||||
} __attribute__ (( packed )) *hello_done = data;
|
} __attribute__ (( packed )) *hello_done = data;
|
||||||
const void *end = hello_done->next;
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
/* Sanity check */
|
/* Sanity check */
|
||||||
if ( end != ( data + len ) ) {
|
if ( sizeof ( *hello_done ) != len ) {
|
||||||
DBGC ( tls, "TLS %p received overlength Server Hello Done\n",
|
DBGC ( tls, "TLS %p received overlength Server Hello Done\n",
|
||||||
tls );
|
tls );
|
||||||
DBGC_HD ( tls, data, len );
|
DBGC_HD ( tls, data, len );
|
||||||
|
@ -1557,12 +1572,11 @@ static int tls_new_finished ( struct tls_session *tls,
|
||||||
uint8_t verify_data[12];
|
uint8_t verify_data[12];
|
||||||
char next[0];
|
char next[0];
|
||||||
} __attribute__ (( packed )) *finished = data;
|
} __attribute__ (( packed )) *finished = data;
|
||||||
const void *end = finished->next;
|
|
||||||
uint8_t digest_out[ digest->digestsize ];
|
uint8_t digest_out[ digest->digestsize ];
|
||||||
uint8_t verify_data[ sizeof ( finished->verify_data ) ];
|
uint8_t verify_data[ sizeof ( finished->verify_data ) ];
|
||||||
|
|
||||||
/* Sanity check */
|
/* Sanity check */
|
||||||
if ( end != ( data + len ) ) {
|
if ( sizeof ( *finished ) != len ) {
|
||||||
DBGC ( tls, "TLS %p received overlength Finished\n", tls );
|
DBGC ( tls, "TLS %p received overlength Finished\n", tls );
|
||||||
DBGC_HD ( tls, data, len );
|
DBGC_HD ( tls, data, len );
|
||||||
return -EINVAL_FINISHED;
|
return -EINVAL_FINISHED;
|
||||||
|
@ -1598,27 +1612,37 @@ static int tls_new_finished ( struct tls_session *tls,
|
||||||
*/
|
*/
|
||||||
static int tls_new_handshake ( struct tls_session *tls,
|
static int tls_new_handshake ( struct tls_session *tls,
|
||||||
const void *data, size_t len ) {
|
const void *data, size_t len ) {
|
||||||
const void *end = ( data + len );
|
size_t remaining = len;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
while ( data != end ) {
|
while ( remaining ) {
|
||||||
const struct {
|
const struct {
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
tls24_t length;
|
tls24_t length;
|
||||||
uint8_t payload[0];
|
uint8_t payload[0];
|
||||||
} __attribute__ (( packed )) *handshake = data;
|
} __attribute__ (( packed )) *handshake = data;
|
||||||
const void *payload = &handshake->payload;
|
const void *payload;
|
||||||
size_t payload_len = tls_uint24 ( &handshake->length );
|
size_t payload_len;
|
||||||
const void *next = ( payload + payload_len );
|
size_t record_len;
|
||||||
|
|
||||||
/* Sanity check */
|
/* Parse header */
|
||||||
if ( next > end ) {
|
if ( sizeof ( *handshake ) > remaining ) {
|
||||||
|
DBGC ( tls, "TLS %p received underlength Handshake\n",
|
||||||
|
tls );
|
||||||
|
DBGC_HD ( tls, data, remaining );
|
||||||
|
return -EINVAL_HANDSHAKE;
|
||||||
|
}
|
||||||
|
payload_len = tls_uint24 ( &handshake->length );
|
||||||
|
if ( payload_len > ( remaining - sizeof ( *handshake ) ) ) {
|
||||||
DBGC ( tls, "TLS %p received overlength Handshake\n",
|
DBGC ( tls, "TLS %p received overlength Handshake\n",
|
||||||
tls );
|
tls );
|
||||||
DBGC_HD ( tls, data, len );
|
DBGC_HD ( tls, data, len );
|
||||||
return -EINVAL_HANDSHAKE;
|
return -EINVAL_HANDSHAKE;
|
||||||
}
|
}
|
||||||
|
payload = &handshake->payload;
|
||||||
|
record_len = ( sizeof ( *handshake ) + payload_len );
|
||||||
|
|
||||||
|
/* Handle payload */
|
||||||
switch ( handshake->type ) {
|
switch ( handshake->type ) {
|
||||||
case TLS_SERVER_HELLO:
|
case TLS_SERVER_HELLO:
|
||||||
rc = tls_new_server_hello ( tls, payload, payload_len );
|
rc = tls_new_server_hello ( tls, payload, payload_len );
|
||||||
|
@ -1648,16 +1672,15 @@ static int tls_new_handshake ( struct tls_session *tls,
|
||||||
* which are explicitly excluded).
|
* which are explicitly excluded).
|
||||||
*/
|
*/
|
||||||
if ( handshake->type != TLS_HELLO_REQUEST )
|
if ( handshake->type != TLS_HELLO_REQUEST )
|
||||||
tls_add_handshake ( tls, data,
|
tls_add_handshake ( tls, data, record_len );
|
||||||
sizeof ( *handshake ) +
|
|
||||||
payload_len );
|
|
||||||
|
|
||||||
/* Abort on failure */
|
/* Abort on failure */
|
||||||
if ( rc != 0 )
|
if ( rc != 0 )
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
/* Move to next handshake record */
|
/* Move to next handshake record */
|
||||||
data = next;
|
data += record_len;
|
||||||
|
remaining -= record_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
Loading…
Reference in New Issue