net: preserve IP control block during GSO segmentation
authorKonstantin Khlebnikov <koct9i@gmail.com>
Fri, 8 Jan 2016 12:21:46 +0000 (15:21 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 31 Jan 2016 19:29:00 +0000 (11:29 -0800)
[ Upstream commit 9207f9d45b0ad071baa128e846d7e7ed85016df3 ]

Skb_gso_segment() uses skb control block during segmentation.
This patch adds 32-bytes room for previous control block which
will be copied into all resulting segments.

This patch fixes kernel crash during fragmenting forwarded packets.
Fragmentation requires valid IP CB in skb for clearing ip options.
Also patch removes custom save/restore in ovs code, now it's redundant.

Signed-off-by: Konstantin Khlebnikov <koct9i@gmail.com>
Link: http://lkml.kernel.org/r/CALYGNiP-0MZ-FExV2HutTvE9U-QQtkKSoE--KN=JQE5STYsjAA@mail.gmail.com
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
include/linux/skbuff.h
net/core/dev.c
net/ipv4/ip_output.c
net/openvswitch/datapath.c
net/xfrm/xfrm_output.c

index 4355129fff91b6f188136af2a499d6100f2e5bfd..9147f9f34cbe460eec9f782cb226095006f07b11 100644 (file)
@@ -3446,7 +3446,8 @@ struct skb_gso_cb {
        int     encap_level;
        __u16   csum_start;
 };
-#define SKB_GSO_CB(skb) ((struct skb_gso_cb *)(skb)->cb)
+#define SKB_SGO_CB_OFFSET      32
+#define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)->cb + SKB_SGO_CB_OFFSET))
 
 static inline int skb_tnl_header_len(const struct sk_buff *inner_skb)
 {
index ae00b894e67555257b1ba81dd57cecec72eed161..7f00f24397700647d9e2be6ddd90482d65388971 100644 (file)
@@ -2542,6 +2542,8 @@ static inline bool skb_needs_check(struct sk_buff *skb, bool tx_path)
  *
  *     It may return NULL if the skb requires no segmentation.  This is
  *     only possible when GSO is used for verifying header integrity.
+ *
+ *     Segmentation preserves SKB_SGO_CB_OFFSET bytes of previous skb cb.
  */
 struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
                                  netdev_features_t features, bool tx_path)
@@ -2556,6 +2558,9 @@ struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
                        return ERR_PTR(err);
        }
 
+       BUILD_BUG_ON(SKB_SGO_CB_OFFSET +
+                    sizeof(*SKB_GSO_CB(skb)) > sizeof(skb->cb));
+
        SKB_GSO_CB(skb)->mac_offset = skb_headroom(skb);
        SKB_GSO_CB(skb)->encap_level = 0;
 
index 36ac9f3a64518baa686c0a8a99379eb390d0f07a..49f02856304df550f91bfa0ff79f234e64d31928 100644 (file)
@@ -240,6 +240,7 @@ static int ip_finish_output_gso(struct net *net, struct sock *sk,
         * from host network stack.
         */
        features = netif_skb_features(skb);
+       BUILD_BUG_ON(sizeof(*IPCB(skb)) > SKB_SGO_CB_OFFSET);
        segs = skb_gso_segment(skb, features & ~NETIF_F_GSO_MASK);
        if (IS_ERR_OR_NULL(segs)) {
                kfree_skb(skb);
index 91a8b004dc510272e6ab09c22ccea821cb62ac49..deadfdab1bc384c858642e176d690bcf62a103c0 100644 (file)
@@ -336,12 +336,10 @@ static int queue_gso_packets(struct datapath *dp, struct sk_buff *skb,
        unsigned short gso_type = skb_shinfo(skb)->gso_type;
        struct sw_flow_key later_key;
        struct sk_buff *segs, *nskb;
-       struct ovs_skb_cb ovs_cb;
        int err;
 
-       ovs_cb = *OVS_CB(skb);
+       BUILD_BUG_ON(sizeof(*OVS_CB(skb)) > SKB_SGO_CB_OFFSET);
        segs = __skb_gso_segment(skb, NETIF_F_SG, false);
-       *OVS_CB(skb) = ovs_cb;
        if (IS_ERR(segs))
                return PTR_ERR(segs);
        if (segs == NULL)
@@ -359,7 +357,6 @@ static int queue_gso_packets(struct datapath *dp, struct sk_buff *skb,
        /* Queue all of the segments. */
        skb = segs;
        do {
-               *OVS_CB(skb) = ovs_cb;
                if (gso_type & SKB_GSO_UDP && skb != segs)
                        key = &later_key;
 
index cc3676eb6239707909e41c34841abbd4ea8958dd..ff4a91fcab9fd291b09eb48487690301c4388ae2 100644 (file)
@@ -167,6 +167,8 @@ static int xfrm_output_gso(struct net *net, struct sock *sk, struct sk_buff *skb
 {
        struct sk_buff *segs;
 
+       BUILD_BUG_ON(sizeof(*IPCB(skb)) > SKB_SGO_CB_OFFSET);
+       BUILD_BUG_ON(sizeof(*IP6CB(skb)) > SKB_SGO_CB_OFFSET);
        segs = skb_gso_segment(skb, 0);
        kfree_skb(skb);
        if (IS_ERR(segs))