On Sun, 1 Sep 2019 16:05:48 -0400
Willem de Bruijn <willemdebruijn.ker...@gmail.com> wrote:

> One quick fix is to disable sg and thus revert to copying in this
> case. Not ideal, but better than a kernel splat:
> 
> @@ -3714,6 +3714,9 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
>         sg = !!(features & NETIF_F_SG);
>         csum = !!can_checksum_protocol(features, proto);
> 
> +       if (list_skb && skb_headlen(list_skb) && !list_skb->head_frag)
> +               sg = false;
> +

Thanks Willem.

I followed this approach, and further refined it based on the conditions
that lead to this BUG_ON:

 - existance of frag_list
 - mangled gso_size (using SKB_GSO_DODGY as a hint)
 - some frag in the frag_list has a linear part that is NOT head_frag,
   or length not equal to the requested gso_size

BTW, doing so allowed me to refactor a loop that tests for similar
conditions in the !(features & NETIF_F_GSO_PARTIAL) case, where an
attempt to execute partial splitting at the frag_list pointer (see
07b26c9454a2 and 43170c4e0ba7).

I've tested this using the reproducer, with various linear skbs in
the frag_list and different gso_size mangling. All resulting 'segs'
looked correct. Did not test on a live system yet.

Comments are welcome.

specifically, I would like to know whether we can
 - better refine the condition where this "sg=false fallback" needs
   to be applied
 - consolidate my new 'list_skb && (type & SKB_GSO_DODGY)' case with
   the existing '!(features & NETIF_F_GSO_PARTIAL)' case

see below:


@@ -3470,6 +3470,27 @@ static inline skb_frag_t 
skb_head_frag_to_page_desc(struct sk_buff *frag_skb)
        return head_frag;
 }
 
+static inline bool skb_is_nonlinear_equal_frags(struct sk_buff *skb,
+                                               unsigned int total_len,
+                                               unsigned int frag_len,
+                                               unsigned int *remain)
+{
+       struct sk_buff *iter;
+
+       skb_walk_frags(skb, iter) {
+               if (iter->len != frag_len && iter->next)
+                       return false;
+               if (skb_headlen(iter) && !iter->head_frag)
+                       return false;
+
+               total_len -= iter->len;
+       }
+
+       if (remain)
+               *remain = total_len;
+       return total_len == frag_len;
+}
+
 /**
  *     skb_segment - Perform protocol segmentation on skb.
  *     @head_skb: buffer to segment
@@ -3486,6 +3507,7 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
        struct sk_buff *tail = NULL;
        struct sk_buff *list_skb = skb_shinfo(head_skb)->frag_list;
        skb_frag_t *frag = skb_shinfo(head_skb)->frags;
+       unsigned int type = skb_shinfo(head_skb)->gso_type;
        unsigned int mss = skb_shinfo(head_skb)->gso_size;
        unsigned int doffset = head_skb->data - skb_mac_header(head_skb);
        struct sk_buff *frag_skb = head_skb;
@@ -3510,13 +3532,29 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
        sg = !!(features & NETIF_F_SG);
        csum = !!can_checksum_protocol(features, proto);
 
-       if (sg && csum && (mss != GSO_BY_FRAGS))  {
+       if (sg && (mss != GSO_BY_FRAGS))  {
+               if (list_skb && (type & SKB_GSO_DODGY)) {
+                       /* gso_size is untrusted.
+                        * if head_skb has a frag_list that contains a frag
+                        * with a linear (non head_frag) part, or a frag whose
+                        * length doesn't fit requested mss, fallback to skb
+                        * copying by disabling sg.
+                        */
+                       if (!skb_is_nonlinear_equal_frags(head_skb, len, mss,
+                                                         NULL)) {
+                               sg = false;
+                               goto normal;
+                       }
+               }
+
+               if (!csum)
+                       goto normal;
+
                if (!(features & NETIF_F_GSO_PARTIAL)) {
-                       struct sk_buff *iter;
                        unsigned int frag_len;
 
                        if (!list_skb ||
-                           !net_gso_ok(features, 
skb_shinfo(head_skb)->gso_type))
+                           !net_gso_ok(features, type))
                                goto normal;
 
                        /* If we get here then all the required
@@ -3528,17 +3566,10 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
                         * the last are of the same length.
                         */
                        frag_len = list_skb->len;
-                       skb_walk_frags(head_skb, iter) {
-                               if (frag_len != iter->len && iter->next)
-                                       goto normal;
-                               if (skb_headlen(iter) && !iter->head_frag)
-                                       goto normal;
-
-                               len -= iter->len;
-                       }
-
-                       if (len != frag_len)
+                       if (!skb_is_nonlinear_equal_frags(head_skb, len,
+                                                         frag_len, &len)) {
                                goto normal;
+                       }
                }
 
                /* GSO partial only requires that we trim off any excess that

Reply via email to