aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2015-08-03 13:09:10 +0100
committerPeter Maydell <peter.maydell@linaro.org>2015-08-03 13:09:10 +0100
commit2a3612ccc1fa9cea77bd193afbfe21c77e7e91ef (patch)
tree49b4eda59c814c477f8dcb12b70babc8d4ebce0f
parentbd80b5963f58c601f31d3186b89887bf8e182fb5 (diff)
parent8357946b15f0a31f73dd691b7da95f29318ed310 (diff)
Merge remote-tracking branch 'remotes/stefanha/tags/rtl8139-cplus-tx-input-validation-pull-request' into staging
Pull request # gpg: Signature made Mon Aug 3 13:08:25 2015 BST using RSA key ID 81AB73C8 # gpg: Good signature from "Stefan Hajnoczi <stefanha@redhat.com>" # gpg: aka "Stefan Hajnoczi <stefanha@gmail.com>" * remotes/stefanha/tags/rtl8139-cplus-tx-input-validation-pull-request: rtl8139: check TCP Data Offset field (CVE-2015-5165) rtl8139: skip offload on short TCP header (CVE-2015-5165) rtl8139: check IP Total Length field (CVE-2015-5165) rtl8139: check IP Header Length field (CVE-2015-5165) rtl8139: skip offload on short Ethernet/IP header (CVE-2015-5165) rtl8139: drop tautologous if (ip) {...} statement (CVE-2015-5165) rtl8139: avoid nested ifs in IP header parsing (CVE-2015-5165) Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r--hw/net/rtl8139.c363
1 files changed, 190 insertions, 173 deletions
diff --git a/hw/net/rtl8139.c b/hw/net/rtl8139.c
index e0db4727ae..edbb61ccf3 100644
--- a/hw/net/rtl8139.c
+++ b/hw/net/rtl8139.c
@@ -2150,6 +2150,11 @@ static int rtl8139_cplus_transmit_one(RTL8139State *s)
{
DPRINTF("+++ C+ mode offloaded task checksum\n");
+ /* Large enough for Ethernet and IP headers? */
+ if (saved_size < ETH_HLEN + sizeof(ip_header)) {
+ goto skip_offload;
+ }
+
/* ip packet header */
ip_header *ip = NULL;
int hlen = 0;
@@ -2160,223 +2165,235 @@ static int rtl8139_cplus_transmit_one(RTL8139State *s)
size_t eth_payload_len = 0;
int proto = be16_to_cpu(*(uint16_t *)(saved_buffer + 12));
- if (proto == ETH_P_IP)
+ if (proto != ETH_P_IP)
{
- DPRINTF("+++ C+ mode has IP packet\n");
-
- /* not aligned */
- eth_payload_data = saved_buffer + ETH_HLEN;
- eth_payload_len = saved_size - ETH_HLEN;
-
- ip = (ip_header*)eth_payload_data;
-
- if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) {
- DPRINTF("+++ C+ mode packet has bad IP version %d "
- "expected %d\n", IP_HEADER_VERSION(ip),
- IP_HEADER_VERSION_4);
- ip = NULL;
- } else {
- hlen = IP_HEADER_LENGTH(ip);
- ip_protocol = ip->ip_p;
- ip_data_len = be16_to_cpu(ip->ip_len) - hlen;
- }
+ goto skip_offload;
+ }
+
+ DPRINTF("+++ C+ mode has IP packet\n");
+
+ /* not aligned */
+ eth_payload_data = saved_buffer + ETH_HLEN;
+ eth_payload_len = saved_size - ETH_HLEN;
+
+ ip = (ip_header*)eth_payload_data;
+
+ if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) {
+ DPRINTF("+++ C+ mode packet has bad IP version %d "
+ "expected %d\n", IP_HEADER_VERSION(ip),
+ IP_HEADER_VERSION_4);
+ goto skip_offload;
}
- if (ip)
+ hlen = IP_HEADER_LENGTH(ip);
+ if (hlen < sizeof(ip_header) || hlen > eth_payload_len) {
+ goto skip_offload;
+ }
+
+ ip_protocol = ip->ip_p;
+
+ ip_data_len = be16_to_cpu(ip->ip_len);
+ if (ip_data_len < hlen || ip_data_len > eth_payload_len) {
+ goto skip_offload;
+ }
+ ip_data_len -= hlen;
+
+ if (txdw0 & CP_TX_IPCS)
{
- if (txdw0 & CP_TX_IPCS)
- {
- DPRINTF("+++ C+ mode need IP checksum\n");
+ DPRINTF("+++ C+ mode need IP checksum\n");
- if (hlen<sizeof(ip_header) || hlen>eth_payload_len) {/* min header length */
- /* bad packet header len */
- /* or packet too short */
- }
- else
- {
- ip->ip_sum = 0;
- ip->ip_sum = ip_checksum(ip, hlen);
- DPRINTF("+++ C+ mode IP header len=%d checksum=%04x\n",
- hlen, ip->ip_sum);
- }
+ ip->ip_sum = 0;
+ ip->ip_sum = ip_checksum(ip, hlen);
+ DPRINTF("+++ C+ mode IP header len=%d checksum=%04x\n",
+ hlen, ip->ip_sum);
+ }
+
+ if ((txdw0 & CP_TX_LGSEN) && ip_protocol == IP_PROTO_TCP)
+ {
+ /* Large enough for the TCP header? */
+ if (ip_data_len < sizeof(tcp_header)) {
+ goto skip_offload;
}
- if ((txdw0 & CP_TX_LGSEN) && ip_protocol == IP_PROTO_TCP)
- {
- int large_send_mss = (txdw0 >> 16) & CP_TC_LGSEN_MSS_MASK;
+ int large_send_mss = (txdw0 >> 16) & CP_TC_LGSEN_MSS_MASK;
- DPRINTF("+++ C+ mode offloaded task TSO MTU=%d IP data %d "
- "frame data %d specified MSS=%d\n", ETH_MTU,
- ip_data_len, saved_size - ETH_HLEN, large_send_mss);
+ DPRINTF("+++ C+ mode offloaded task TSO MTU=%d IP data %d "
+ "frame data %d specified MSS=%d\n", ETH_MTU,
+ ip_data_len, saved_size - ETH_HLEN, large_send_mss);
- int tcp_send_offset = 0;
- int send_count = 0;
+ int tcp_send_offset = 0;
+ int send_count = 0;
- /* maximum IP header length is 60 bytes */
- uint8_t saved_ip_header[60];
+ /* maximum IP header length is 60 bytes */
+ uint8_t saved_ip_header[60];
- /* save IP header template; data area is used in tcp checksum calculation */
- memcpy(saved_ip_header, eth_payload_data, hlen);
+ /* save IP header template; data area is used in tcp checksum calculation */
+ memcpy(saved_ip_header, eth_payload_data, hlen);
- /* a placeholder for checksum calculation routine in tcp case */
- uint8_t *data_to_checksum = eth_payload_data + hlen - 12;
- // size_t data_to_checksum_len = eth_payload_len - hlen + 12;
+ /* a placeholder for checksum calculation routine in tcp case */
+ uint8_t *data_to_checksum = eth_payload_data + hlen - 12;
+ // size_t data_to_checksum_len = eth_payload_len - hlen + 12;
- /* pointer to TCP header */
- tcp_header *p_tcp_hdr = (tcp_header*)(eth_payload_data + hlen);
+ /* pointer to TCP header */
+ tcp_header *p_tcp_hdr = (tcp_header*)(eth_payload_data + hlen);
- int tcp_hlen = TCP_HEADER_DATA_OFFSET(p_tcp_hdr);
+ int tcp_hlen = TCP_HEADER_DATA_OFFSET(p_tcp_hdr);
- /* ETH_MTU = ip header len + tcp header len + payload */
- int tcp_data_len = ip_data_len - tcp_hlen;
- int tcp_chunk_size = ETH_MTU - hlen - tcp_hlen;
+ /* Invalid TCP data offset? */
+ if (tcp_hlen < sizeof(tcp_header) || tcp_hlen > ip_data_len) {
+ goto skip_offload;
+ }
- DPRINTF("+++ C+ mode TSO IP data len %d TCP hlen %d TCP "
- "data len %d TCP chunk size %d\n", ip_data_len,
- tcp_hlen, tcp_data_len, tcp_chunk_size);
+ /* ETH_MTU = ip header len + tcp header len + payload */
+ int tcp_data_len = ip_data_len - tcp_hlen;
+ int tcp_chunk_size = ETH_MTU - hlen - tcp_hlen;
- /* note the cycle below overwrites IP header data,
- but restores it from saved_ip_header before sending packet */
+ DPRINTF("+++ C+ mode TSO IP data len %d TCP hlen %d TCP "
+ "data len %d TCP chunk size %d\n", ip_data_len,
+ tcp_hlen, tcp_data_len, tcp_chunk_size);
- int is_last_frame = 0;
+ /* note the cycle below overwrites IP header data,
+ but restores it from saved_ip_header before sending packet */
- for (tcp_send_offset = 0; tcp_send_offset < tcp_data_len; tcp_send_offset += tcp_chunk_size)
- {
- uint16_t chunk_size = tcp_chunk_size;
-
- /* check if this is the last frame */
- if (tcp_send_offset + tcp_chunk_size >= tcp_data_len)
- {
- is_last_frame = 1;
- chunk_size = tcp_data_len - tcp_send_offset;
- }
-
- DPRINTF("+++ C+ mode TSO TCP seqno %08x\n",
- be32_to_cpu(p_tcp_hdr->th_seq));
-
- /* add 4 TCP pseudoheader fields */
- /* copy IP source and destination fields */
- memcpy(data_to_checksum, saved_ip_header + 12, 8);
-
- DPRINTF("+++ C+ mode TSO calculating TCP checksum for "
- "packet with %d bytes data\n", tcp_hlen +
- chunk_size);
-
- if (tcp_send_offset)
- {
- memcpy((uint8_t*)p_tcp_hdr + tcp_hlen, (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset, chunk_size);
- }
-
- /* keep PUSH and FIN flags only for the last frame */
- if (!is_last_frame)
- {
- TCP_HEADER_CLEAR_FLAGS(p_tcp_hdr, TCP_FLAG_PUSH|TCP_FLAG_FIN);
- }
-
- /* recalculate TCP checksum */
- ip_pseudo_header *p_tcpip_hdr = (ip_pseudo_header *)data_to_checksum;
- p_tcpip_hdr->zeros = 0;
- p_tcpip_hdr->ip_proto = IP_PROTO_TCP;
- p_tcpip_hdr->ip_payload = cpu_to_be16(tcp_hlen + chunk_size);
-
- p_tcp_hdr->th_sum = 0;
-
- int tcp_checksum = ip_checksum(data_to_checksum, tcp_hlen + chunk_size + 12);
- DPRINTF("+++ C+ mode TSO TCP checksum %04x\n",
- tcp_checksum);
-
- p_tcp_hdr->th_sum = tcp_checksum;
-
- /* restore IP header */
- memcpy(eth_payload_data, saved_ip_header, hlen);
-
- /* set IP data length and recalculate IP checksum */
- ip->ip_len = cpu_to_be16(hlen + tcp_hlen + chunk_size);
-
- /* increment IP id for subsequent frames */
- ip->ip_id = cpu_to_be16(tcp_send_offset/tcp_chunk_size + be16_to_cpu(ip->ip_id));
-
- ip->ip_sum = 0;
- ip->ip_sum = ip_checksum(eth_payload_data, hlen);
- DPRINTF("+++ C+ mode TSO IP header len=%d "
- "checksum=%04x\n", hlen, ip->ip_sum);
-
- int tso_send_size = ETH_HLEN + hlen + tcp_hlen + chunk_size;
- DPRINTF("+++ C+ mode TSO transferring packet size "
- "%d\n", tso_send_size);
- rtl8139_transfer_frame(s, saved_buffer, tso_send_size,
- 0, (uint8_t *) dot1q_buffer);
-
- /* add transferred count to TCP sequence number */
- p_tcp_hdr->th_seq = cpu_to_be32(chunk_size + be32_to_cpu(p_tcp_hdr->th_seq));
- ++send_count;
- }
+ int is_last_frame = 0;
- /* Stop sending this frame */
- saved_size = 0;
- }
- else if (txdw0 & (CP_TX_TCPCS|CP_TX_UDPCS))
+ for (tcp_send_offset = 0; tcp_send_offset < tcp_data_len; tcp_send_offset += tcp_chunk_size)
{
- DPRINTF("+++ C+ mode need TCP or UDP checksum\n");
+ uint16_t chunk_size = tcp_chunk_size;
- /* maximum IP header length is 60 bytes */
- uint8_t saved_ip_header[60];
- memcpy(saved_ip_header, eth_payload_data, hlen);
+ /* check if this is the last frame */
+ if (tcp_send_offset + tcp_chunk_size >= tcp_data_len)
+ {
+ is_last_frame = 1;
+ chunk_size = tcp_data_len - tcp_send_offset;
+ }
- uint8_t *data_to_checksum = eth_payload_data + hlen - 12;
- // size_t data_to_checksum_len = eth_payload_len - hlen + 12;
+ DPRINTF("+++ C+ mode TSO TCP seqno %08x\n",
+ be32_to_cpu(p_tcp_hdr->th_seq));
/* add 4 TCP pseudoheader fields */
/* copy IP source and destination fields */
memcpy(data_to_checksum, saved_ip_header + 12, 8);
- if ((txdw0 & CP_TX_TCPCS) && ip_protocol == IP_PROTO_TCP)
+ DPRINTF("+++ C+ mode TSO calculating TCP checksum for "
+ "packet with %d bytes data\n", tcp_hlen +
+ chunk_size);
+
+ if (tcp_send_offset)
+ {
+ memcpy((uint8_t*)p_tcp_hdr + tcp_hlen, (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset, chunk_size);
+ }
+
+ /* keep PUSH and FIN flags only for the last frame */
+ if (!is_last_frame)
{
- DPRINTF("+++ C+ mode calculating TCP checksum for "
- "packet with %d bytes data\n", ip_data_len);
+ TCP_HEADER_CLEAR_FLAGS(p_tcp_hdr, TCP_FLAG_PUSH|TCP_FLAG_FIN);
+ }
- ip_pseudo_header *p_tcpip_hdr = (ip_pseudo_header *)data_to_checksum;
- p_tcpip_hdr->zeros = 0;
- p_tcpip_hdr->ip_proto = IP_PROTO_TCP;
- p_tcpip_hdr->ip_payload = cpu_to_be16(ip_data_len);
+ /* recalculate TCP checksum */
+ ip_pseudo_header *p_tcpip_hdr = (ip_pseudo_header *)data_to_checksum;
+ p_tcpip_hdr->zeros = 0;
+ p_tcpip_hdr->ip_proto = IP_PROTO_TCP;
+ p_tcpip_hdr->ip_payload = cpu_to_be16(tcp_hlen + chunk_size);
- tcp_header* p_tcp_hdr = (tcp_header *) (data_to_checksum+12);
+ p_tcp_hdr->th_sum = 0;
- p_tcp_hdr->th_sum = 0;
+ int tcp_checksum = ip_checksum(data_to_checksum, tcp_hlen + chunk_size + 12);
+ DPRINTF("+++ C+ mode TSO TCP checksum %04x\n",
+ tcp_checksum);
- int tcp_checksum = ip_checksum(data_to_checksum, ip_data_len + 12);
- DPRINTF("+++ C+ mode TCP checksum %04x\n",
- tcp_checksum);
+ p_tcp_hdr->th_sum = tcp_checksum;
- p_tcp_hdr->th_sum = tcp_checksum;
- }
- else if ((txdw0 & CP_TX_UDPCS) && ip_protocol == IP_PROTO_UDP)
- {
- DPRINTF("+++ C+ mode calculating UDP checksum for "
- "packet with %d bytes data\n", ip_data_len);
+ /* restore IP header */
+ memcpy(eth_payload_data, saved_ip_header, hlen);
- ip_pseudo_header *p_udpip_hdr = (ip_pseudo_header *)data_to_checksum;
- p_udpip_hdr->zeros = 0;
- p_udpip_hdr->ip_proto = IP_PROTO_UDP;
- p_udpip_hdr->ip_payload = cpu_to_be16(ip_data_len);
+ /* set IP data length and recalculate IP checksum */
+ ip->ip_len = cpu_to_be16(hlen + tcp_hlen + chunk_size);
- udp_header *p_udp_hdr = (udp_header *) (data_to_checksum+12);
+ /* increment IP id for subsequent frames */
+ ip->ip_id = cpu_to_be16(tcp_send_offset/tcp_chunk_size + be16_to_cpu(ip->ip_id));
- p_udp_hdr->uh_sum = 0;
+ ip->ip_sum = 0;
+ ip->ip_sum = ip_checksum(eth_payload_data, hlen);
+ DPRINTF("+++ C+ mode TSO IP header len=%d "
+ "checksum=%04x\n", hlen, ip->ip_sum);
- int udp_checksum = ip_checksum(data_to_checksum, ip_data_len + 12);
- DPRINTF("+++ C+ mode UDP checksum %04x\n",
- udp_checksum);
+ int tso_send_size = ETH_HLEN + hlen + tcp_hlen + chunk_size;
+ DPRINTF("+++ C+ mode TSO transferring packet size "
+ "%d\n", tso_send_size);
+ rtl8139_transfer_frame(s, saved_buffer, tso_send_size,
+ 0, (uint8_t *) dot1q_buffer);
- p_udp_hdr->uh_sum = udp_checksum;
- }
+ /* add transferred count to TCP sequence number */
+ p_tcp_hdr->th_seq = cpu_to_be32(chunk_size + be32_to_cpu(p_tcp_hdr->th_seq));
+ ++send_count;
+ }
- /* restore IP header */
- memcpy(eth_payload_data, saved_ip_header, hlen);
+ /* Stop sending this frame */
+ saved_size = 0;
+ }
+ else if (txdw0 & (CP_TX_TCPCS|CP_TX_UDPCS))
+ {
+ DPRINTF("+++ C+ mode need TCP or UDP checksum\n");
+
+ /* maximum IP header length is 60 bytes */
+ uint8_t saved_ip_header[60];
+ memcpy(saved_ip_header, eth_payload_data, hlen);
+
+ uint8_t *data_to_checksum = eth_payload_data + hlen - 12;
+ // size_t data_to_checksum_len = eth_payload_len - hlen + 12;
+
+ /* add 4 TCP pseudoheader fields */
+ /* copy IP source and destination fields */
+ memcpy(data_to_checksum, saved_ip_header + 12, 8);
+
+ if ((txdw0 & CP_TX_TCPCS) && ip_protocol == IP_PROTO_TCP)
+ {
+ DPRINTF("+++ C+ mode calculating TCP checksum for "
+ "packet with %d bytes data\n", ip_data_len);
+
+ ip_pseudo_header *p_tcpip_hdr = (ip_pseudo_header *)data_to_checksum;
+ p_tcpip_hdr->zeros = 0;
+ p_tcpip_hdr->ip_proto = IP_PROTO_TCP;
+ p_tcpip_hdr->ip_payload = cpu_to_be16(ip_data_len);
+
+ tcp_header* p_tcp_hdr = (tcp_header *) (data_to_checksum+12);
+
+ p_tcp_hdr->th_sum = 0;
+
+ int tcp_checksum = ip_checksum(data_to_checksum, ip_data_len + 12);
+ DPRINTF("+++ C+ mode TCP checksum %04x\n",
+ tcp_checksum);
+
+ p_tcp_hdr->th_sum = tcp_checksum;
}
+ else if ((txdw0 & CP_TX_UDPCS) && ip_protocol == IP_PROTO_UDP)
+ {
+ DPRINTF("+++ C+ mode calculating UDP checksum for "
+ "packet with %d bytes data\n", ip_data_len);
+
+ ip_pseudo_header *p_udpip_hdr = (ip_pseudo_header *)data_to_checksum;
+ p_udpip_hdr->zeros = 0;
+ p_udpip_hdr->ip_proto = IP_PROTO_UDP;
+ p_udpip_hdr->ip_payload = cpu_to_be16(ip_data_len);
+
+ udp_header *p_udp_hdr = (udp_header *) (data_to_checksum+12);
+
+ p_udp_hdr->uh_sum = 0;
+
+ int udp_checksum = ip_checksum(data_to_checksum, ip_data_len + 12);
+ DPRINTF("+++ C+ mode UDP checksum %04x\n",
+ udp_checksum);
+
+ p_udp_hdr->uh_sum = udp_checksum;
+ }
+
+ /* restore IP header */
+ memcpy(eth_payload_data, saved_ip_header, hlen);
}
}
+skip_offload:
/* update tally counter */
++s->tally_counters.TxOk;