mirror of https://github.com/ipxe/ipxe.git
[tcp] Guard against malformed TCP options
Signed-off-by: Michael Brown <mcb30@ipxe.org>pull/45/head
parent
6366fa7af6
commit
fef8e34b6f
|
@ -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 */
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
Loading…
Reference in New Issue