[tcp] Guard against malformed TCP options

Signed-off-by: Michael Brown <mcb30@ipxe.org>
pull/45/head
Michael Brown 2016-01-27 23:06:50 +00:00
parent 6366fa7af6
commit fef8e34b6f
2 changed files with 53 additions and 13 deletions

View File

@ -140,8 +140,6 @@ struct tcp_timestamp_padded_option {
/** Parsed TCP options */ /** Parsed TCP options */
struct tcp_options { struct tcp_options {
/** MSS option, if present */
const struct tcp_mss_option *mssopt;
/** Window scale option, if present */ /** Window scale option, if present */
const struct tcp_window_scale_option *wsopt; const struct tcp_window_scale_option *wsopt;
/** SACK permitted option, if present */ /** SACK permitted option, if present */

View File

@ -904,50 +904,86 @@ static struct tcp_connection * tcp_demux ( unsigned int local_port ) {
/** /**
* Parse TCP received options * Parse TCP received options
* *
* @v tcp TCP connection * @v tcp TCP connection (may be NULL)
* @v data Raw options data * @v tcphdr TCP header
* @v len Raw options length * @v hlen TCP header length
* @v options Options structure to fill in * @v options Options structure to fill in
* @ret rc Return status code
*/ */
static void tcp_rx_opts ( struct tcp_connection *tcp, const void *data, static int tcp_rx_opts ( struct tcp_connection *tcp,
size_t len, struct tcp_options *options ) { const struct tcp_header *tcphdr, size_t hlen,
const void *end = ( data + len ); struct tcp_options *options ) {
const void *data = ( ( ( void * ) tcphdr ) + sizeof ( *tcphdr ) );
const void *end = ( ( ( void * ) tcphdr ) + hlen );
const struct tcp_option *option; const struct tcp_option *option;
unsigned int kind; unsigned int kind;
size_t remaining;
size_t min;
/* Sanity check */
assert ( hlen >= sizeof ( *tcphdr ) );
/* Parse options */
memset ( options, 0, sizeof ( *options ) ); memset ( options, 0, sizeof ( *options ) );
while ( data < end ) { while ( ( remaining = ( end - data ) ) ) {
/* Extract option code */
option = data; option = data;
kind = option->kind; kind = option->kind;
/* Handle single-byte options */
if ( kind == TCP_OPTION_END ) if ( kind == TCP_OPTION_END )
return; break;
if ( kind == TCP_OPTION_NOP ) { if ( kind == TCP_OPTION_NOP ) {
data++; data++;
continue; continue;
} }
/* Handle multi-byte options */
min = sizeof ( *option );
switch ( kind ) { switch ( kind ) {
case TCP_OPTION_MSS: case TCP_OPTION_MSS:
options->mssopt = data; /* Ignore received MSS */
break; break;
case TCP_OPTION_WS: case TCP_OPTION_WS:
options->wsopt = data; options->wsopt = data;
min = sizeof ( *options->wsopt );
break; break;
case TCP_OPTION_SACK_PERMITTED: case TCP_OPTION_SACK_PERMITTED:
options->spopt = data; options->spopt = data;
min = sizeof ( *options->spopt );
break; break;
case TCP_OPTION_SACK: case TCP_OPTION_SACK:
/* Ignore received SACKs */ /* Ignore received SACKs */
break; break;
case TCP_OPTION_TS: case TCP_OPTION_TS:
options->tsopt = data; options->tsopt = data;
min = sizeof ( *options->tsopt );
break; break;
default: default:
DBGC ( tcp, "TCP %p received unknown option %d\n", DBGC ( tcp, "TCP %p received unknown option %d\n",
tcp, kind ); tcp, kind );
break; break;
} }
if ( remaining < min ) {
DBGC ( tcp, "TCP %p received truncated option %d\n",
tcp, kind );
return -EINVAL;
}
if ( option->length < min ) {
DBGC ( tcp, "TCP %p received underlength option %d\n",
tcp, kind );
return -EINVAL;
}
if ( option->length > remaining ) {
DBGC ( tcp, "TCP %p received overlength option %d\n",
tcp, kind );
return -EINVAL;
}
data += option->length; data += option->length;
} }
return 0;
} }
/** /**
@ -1011,6 +1047,12 @@ static int tcp_rx_syn ( struct tcp_connection *tcp, uint32_t seq,
tcp->snd_win_scale = options->wsopt->scale; tcp->snd_win_scale = options->wsopt->scale;
tcp->rcv_win_scale = TCP_RX_WINDOW_SCALE; tcp->rcv_win_scale = TCP_RX_WINDOW_SCALE;
} }
DBGC ( tcp, "TCP %p using %stimestamps, %sSACK, TX window "
"x%d, RX window x%d\n", tcp,
( ( tcp->flags & TCP_TS_ENABLED ) ? "" : "no " ),
( ( tcp->flags & TCP_SACK_ENABLED ) ? "" : "no " ),
( 1 << tcp->snd_win_scale ),
( 1 << tcp->rcv_win_scale ) );
} }
/* Ignore duplicate SYN */ /* Ignore duplicate SYN */
@ -1369,8 +1411,8 @@ static int tcp_rx ( struct io_buffer *iobuf,
ack = ntohl ( tcphdr->ack ); ack = ntohl ( tcphdr->ack );
raw_win = ntohs ( tcphdr->win ); raw_win = ntohs ( tcphdr->win );
flags = tcphdr->flags; flags = tcphdr->flags;
tcp_rx_opts ( tcp, ( ( ( void * ) tcphdr ) + sizeof ( *tcphdr ) ), if ( ( rc = tcp_rx_opts ( tcp, tcphdr, hlen, &options ) ) != 0 )
( hlen - sizeof ( *tcphdr ) ), &options ); goto discard;
if ( tcp && options.tsopt ) if ( tcp && options.tsopt )
tcp->ts_val = ntohl ( options.tsopt->tsval ); tcp->ts_val = ntohl ( options.tsopt->tsval );
iob_pull ( iobuf, hlen ); iob_pull ( iobuf, hlen );