diff --git a/core/rmnet_descriptor.c b/core/rmnet_descriptor.c index c71d44c63d2e..f7818d90a20c 100644 --- a/core/rmnet_descriptor.c +++ b/core/rmnet_descriptor.c @@ -849,6 +849,16 @@ skip_frags: th->seq = frag_desc->tcp_seq; } + if (frag_desc->tcp_flags_set) { + struct tcphdr *th; + __be16 *flags; + + th = (struct tcphdr *) + (rmnet_map_data_ptr(head_skb) + frag_desc->ip_len); + flags = (__be16 *)&tcp_flag_word(th); + *flags = frag_desc->tcp_flags; + } + /* Handle csum offloading */ if (frag_desc->csum_valid && frag_desc->hdrs_valid) { /* Set the partial checksum information */ @@ -984,6 +994,24 @@ static void __rmnet_frag_segment_data(struct rmnet_frag_descriptor *coal_desc, new_desc->tcp_seq_set = 1; new_desc->tcp_seq = htonl(ntohl(th->seq) + coal_desc->data_offset); + + /* Don't allow any dangerous flags to appear in any segments + * other than the last. + */ + if (th->fin || th->psh) { + if (offset + dlen < coal_desc->len) { + __be32 flag_word = tcp_flag_word(th); + + /* Clear the FIN and PSH flags from this + * segment. + */ + flag_word &= ~TCP_FLAG_FIN; + flag_word &= ~TCP_FLAG_PSH; + + new_desc->tcp_flags_set = 1; + new_desc->tcp_flags = *((__be16 *)&flag_word); + } + } } else if (coal_desc->trans_proto == IPPROTO_UDP) { struct udphdr *uh, __uh; diff --git a/core/rmnet_descriptor.h b/core/rmnet_descriptor.h index 142a14a5be1d..f7bfa55bffe7 100644 --- a/core/rmnet_descriptor.h +++ b/core/rmnet_descriptor.h @@ -40,6 +40,7 @@ struct rmnet_frag_descriptor { u32 hash; __be32 tcp_seq; __be16 ip_id; + __be16 tcp_flags; u16 data_offset; u16 gso_size; u16 gso_segs; @@ -53,7 +54,8 @@ struct rmnet_frag_descriptor { ip_id_set:1, tcp_seq_set:1, flush_shs:1, - reserved:3; + tcp_flags_set:1, + reserved:2; }; /* Descriptor management */ diff --git a/core/rmnet_map_data.c b/core/rmnet_map_data.c index f5a9c357ba63..8388ce31c446 100644 --- a/core/rmnet_map_data.c +++ b/core/rmnet_map_data.c @@ -744,22 +744,23 @@ __rmnet_map_segment_coal_skb(struct sk_buff *coal_skb, struct rmnet_priv *priv = netdev_priv(coal_skb->dev); __sum16 *check = NULL; u32 alloc_len; + u32 dlen = coal_meta->data_len * coal_meta->pkt_count; + u32 hlen = coal_meta->ip_len + coal_meta->trans_len; bool zero_csum = false; /* We can avoid copying the data if the SKB we got from the lower-level * drivers was nonlinear. */ if (skb_is_nonlinear(coal_skb)) - alloc_len = coal_meta->ip_len + coal_meta->trans_len; + alloc_len = hlen; else - alloc_len = coal_meta->ip_len + coal_meta->trans_len + - (coal_meta->data_len * coal_meta->pkt_count); + alloc_len = hlen + dlen; skbn = alloc_skb(alloc_len, GFP_ATOMIC); if (!skbn) return; - skb_reserve(skbn, coal_meta->ip_len + coal_meta->trans_len); + skb_reserve(skbn, hlen); rmnet_map_nonlinear_copy(coal_skb, coal_meta, skbn); /* Push transport header and update necessary fields */ @@ -771,6 +772,17 @@ __rmnet_map_segment_coal_skb(struct sk_buff *coal_skb, th->seq = htonl(ntohl(th->seq) + coal_meta->data_offset); check = &th->check; + + /* Don't allow dangerous flags to be set in any segment but the + * last one. + */ + if (th->fin || th->psh) { + if (hlen + coal_meta->data_offset + dlen < + coal_skb->len) { + th->fin = 0; + th->psh = 0; + } + } } else if (coal_meta->trans_proto == IPPROTO_UDP) { struct udphdr *uh = udp_hdr(skbn); @@ -846,7 +858,7 @@ __rmnet_map_segment_coal_skb(struct sk_buff *coal_skb, __skb_queue_tail(list, skbn); /* Update meta information to move past the data we just segmented */ - coal_meta->data_offset += coal_meta->data_len * coal_meta->pkt_count; + coal_meta->data_offset += dlen; coal_meta->pkt_id = pkt_id + 1; coal_meta->pkt_count = 0; }