Commit c8ac3774 authored by Herbert Xu's avatar Herbert Xu Committed by David S. Miller

[TCP]: Fix bug #5070: kernel BUG at net/ipv4/tcp_output.c:864

1) We send out a normal sized packet with TSO on to start off.
2) ICMP is received indicating a smaller MTU.
3) We send the current sk_send_head which needs to be fragmented
since it was created before the ICMP event.  The first fragment
is then sent out.

At this point the remaining fragment is allocated by tcp_fragment.
However, its size is padded to fit the L1 cache-line size therefore
creating tail-room up to 124 bytes long.

This fragment will also be sitting at sk_send_head.

4) tcp_sendmsg is called again and it stores data in the tail-room of
of the fragment.
5) tcp_push_one is called by tcp_sendmsg which then calls tso_fragment
since the packet as a whole exceeds the MTU.

At this point we have a packet that has data in the head area being
fed to tso_fragment which bombs out.

My take on this is that we shouldn't ever call tcp_fragment on a TSO
socket for a packet that is yet to be transmitted since this creates
a packet on sk_send_head that cannot be extended.

So here is a patch to change it so that tso_fragment is always used
in this case.
Signed-off-by: default avatarHerbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 793245ee
...@@ -861,7 +861,8 @@ static int tso_fragment(struct sock *sk, struct sk_buff *skb, unsigned int len, ...@@ -861,7 +861,8 @@ static int tso_fragment(struct sock *sk, struct sk_buff *skb, unsigned int len,
u16 flags; u16 flags;
/* All of a TSO frame must be composed of paged data. */ /* All of a TSO frame must be composed of paged data. */
BUG_ON(skb->len != skb->data_len); if (skb->len != skb->data_len)
return tcp_fragment(sk, skb, len, mss_now);
buff = sk_stream_alloc_pskb(sk, 0, 0, GFP_ATOMIC); buff = sk_stream_alloc_pskb(sk, 0, 0, GFP_ATOMIC);
if (unlikely(buff == NULL)) if (unlikely(buff == NULL))
...@@ -974,6 +975,8 @@ static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle) ...@@ -974,6 +975,8 @@ static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle)
sent_pkts = 0; sent_pkts = 0;
while ((skb = sk->sk_send_head)) { while ((skb = sk->sk_send_head)) {
unsigned int limit;
tso_segs = tcp_init_tso_segs(sk, skb, mss_now); tso_segs = tcp_init_tso_segs(sk, skb, mss_now);
BUG_ON(!tso_segs); BUG_ON(!tso_segs);
...@@ -994,8 +997,9 @@ static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle) ...@@ -994,8 +997,9 @@ static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle)
break; break;
} }
limit = mss_now;
if (tso_segs > 1) { if (tso_segs > 1) {
u32 limit = tcp_window_allows(tp, skb, limit = tcp_window_allows(tp, skb,
mss_now, cwnd_quota); mss_now, cwnd_quota);
if (skb->len < limit) { if (skb->len < limit) {
...@@ -1004,14 +1008,11 @@ static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle) ...@@ -1004,14 +1008,11 @@ static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle)
if (trim) if (trim)
limit = skb->len - trim; limit = skb->len - trim;
} }
if (skb->len > limit) {
if (tso_fragment(sk, skb, limit, mss_now))
break;
} }
} else if (unlikely(skb->len > mss_now)) {
if (unlikely(tcp_fragment(sk, skb, mss_now, mss_now))) if (skb->len > limit &&
unlikely(tso_fragment(sk, skb, limit, mss_now)))
break; break;
}
TCP_SKB_CB(skb)->when = tcp_time_stamp; TCP_SKB_CB(skb)->when = tcp_time_stamp;
...@@ -1064,10 +1065,13 @@ void tcp_push_one(struct sock *sk, unsigned int mss_now) ...@@ -1064,10 +1065,13 @@ void tcp_push_one(struct sock *sk, unsigned int mss_now)
cwnd_quota = tcp_snd_test(sk, skb, mss_now, TCP_NAGLE_PUSH); cwnd_quota = tcp_snd_test(sk, skb, mss_now, TCP_NAGLE_PUSH);
if (likely(cwnd_quota)) { if (likely(cwnd_quota)) {
unsigned int limit;
BUG_ON(!tso_segs); BUG_ON(!tso_segs);
limit = mss_now;
if (tso_segs > 1) { if (tso_segs > 1) {
u32 limit = tcp_window_allows(tp, skb, limit = tcp_window_allows(tp, skb,
mss_now, cwnd_quota); mss_now, cwnd_quota);
if (skb->len < limit) { if (skb->len < limit) {
...@@ -1076,14 +1080,11 @@ void tcp_push_one(struct sock *sk, unsigned int mss_now) ...@@ -1076,14 +1080,11 @@ void tcp_push_one(struct sock *sk, unsigned int mss_now)
if (trim) if (trim)
limit = skb->len - trim; limit = skb->len - trim;
} }
if (skb->len > limit) {
if (unlikely(tso_fragment(sk, skb, limit, mss_now)))
return;
} }
} else if (unlikely(skb->len > mss_now)) {
if (unlikely(tcp_fragment(sk, skb, mss_now, mss_now))) if (skb->len > limit &&
unlikely(tso_fragment(sk, skb, limit, mss_now)))
return; return;
}
/* Send it out now. */ /* Send it out now. */
TCP_SKB_CB(skb)->when = tcp_time_stamp; TCP_SKB_CB(skb)->when = tcp_time_stamp;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment