From 253b60e402039ce8b67715b40328a92087e1eeef Mon Sep 17 00:00:00 2001 From: Sean Tranchetti Date: Thu, 29 Oct 2020 10:59:47 -0700 Subject: [PATCH] rmnet_core: check tcp flags when segmenting packets Now that the hardware will coalesce additional flags into the RSC frame, we must check the TCP flags explicitly and remove ones that make no sense when segmenting the RSC frame for any reason. In particular, we must check for TCP_FIN, and clear this flag from any segmetents of the RSSC frame except the last to avoid breaking the TCP stream integrity. Change-Id: I7f80437991821ab29cd190a9d5bd45e9cdb4c333 Signed-off-by: Sean Tranchetti --- core/rmnet_descriptor.c | 28 ++++++++++++++++++++++++++++ core/rmnet_descriptor.h | 4 +++- core/rmnet_map_data.c | 22 +++++++++++++++++----- 3 files changed, 48 insertions(+), 6 deletions(-) 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; }