[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH net-next v4 12/15] net: test the skb layouts skb_segment() refuses



The GSO KUnit test only checks layouts skb_segment() can segment.  Add
the ones it has to refuse:

 - The non-uniform frag_list with SG on, which used to hit the
   BUG_ON(i >= nfrags) the existing test steps around.
 - A frag_list member that has its own frag_list.
 - An skb whose len counts bytes it doesn't hold.  The data runs out on
   a segment boundary, in the middle of a segment, after the last
   frag_list member, and with GSO_BY_FRAGS.

Run the last two groups without SG as well.  There the nested frag_list
is segmented.  A len that is too big fails with -EFAULT from
skb_copy_bits(), except with GSO_BY_FRAGS, which still gets -EINVAL.

They warn on CONFIG_DEBUG_NET kernels, so run them with the warning
backtrace suppressed.  Also update the comment on the non-uniform case,
which still says SG would hit that BUG_ON().

Reviewed-by: Willem de Bruijn <willemb@xxxxxxxxxx>
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
 net/core/net_test.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 154 insertions(+), 4 deletions(-)

diff --git a/net/core/net_test.c b/net/core/net_test.c
index 2b5080c22663..03203454cd19 100644
--- a/net/core/net_test.c
+++ b/net/core/net_test.c
@@ -41,6 +41,17 @@ enum gso_test_nr {
        GSO_TEST_RESEGMENT_MULTI,
        GSO_TEST_RESEGMENT_TAIL,
        GSO_TEST_RESEGMENT_ONE_MSS,
+       GSO_TEST_FRAG_LIST_NON_UNIFORM_SG,
+       GSO_TEST_FRAG_LIST_NESTED,
+       GSO_TEST_SHORT_LINEAR,
+       GSO_TEST_SHORT_LINEAR_STRADDLE,
+       GSO_TEST_SHORT_FRAG_LIST,
+       GSO_TEST_SHORT_GSO_BY_FRAGS,
+       GSO_TEST_FRAG_LIST_NESTED_NO_SG,
+       GSO_TEST_SHORT_LINEAR_NO_SG,
+       GSO_TEST_SHORT_LINEAR_STRADDLE_NO_SG,
+       GSO_TEST_SHORT_FRAG_LIST_NO_SG,
+       GSO_TEST_SHORT_GSO_BY_FRAGS_NO_SG,
 };
 
 struct gso_test_case {
@@ -58,11 +69,18 @@ struct gso_test_case {
        unsigned int gso_type;
        /* skbs from alloc_skb() instead of build_skb(), head_frag clear */
        bool no_head_frag;
+       /* bytes that skb->len counts but the skb doesn't hold */
+       unsigned int missing_len;
+       /* segment without NETIF_F_SG */
+       bool no_sg;
 
        /* output as expected */
        unsigned int nr_segs;
        const unsigned int *segs;
        const unsigned int *gso_segs;
+
+       /* skb_segment() is expected to fail with this error */
+       int err;
 };
 
 static struct gso_test_case cases[] = {
@@ -241,6 +259,110 @@ static struct gso_test_case cases[] = {
                },
                .gso_segs = (const unsigned int[]) { 0, 0, 0, 0 },
        },
+       {
+               /* frag_list members that don't end on gso_size boundaries
+                * can't be segmented with SG
+                */
+               .id = GSO_TEST_FRAG_LIST_NON_UNIFORM_SG,
+               .name = "frag_list_non_uniform_sg",
+               .linear_len = GSO_TEST_SIZE,
+               .nr_frag_skbs = 4,
+               .frag_skbs = (const unsigned int[]) { GSO_TEST_SIZE, 1, 
GSO_TEST_SIZE, 2 },
+               .err = -EINVAL,
+       },
+       {
+               /* the second frag_list member hangs off the first one's
+                * frag_list instead of off the head's
+                */
+               .id = GSO_TEST_FRAG_LIST_NESTED,
+               .name = "frag_list_nested",
+               .linear_len = GSO_TEST_SIZE,
+               .nr_frag_skbs = 2,
+               .frag_skbs = (const unsigned int[]) { GSO_TEST_SIZE, 
GSO_TEST_SIZE },
+               .err = -EINVAL,
+       },
+       {
+               /* the data runs out on a segment boundary */
+               .id = GSO_TEST_SHORT_LINEAR,
+               .name = "short_linear",
+               .linear_len = GSO_TEST_SIZE,
+               .missing_len = 50,
+               .err = -EINVAL,
+       },
+       {
+               /* the data runs out in the middle of a segment */
+               .id = GSO_TEST_SHORT_LINEAR_STRADDLE,
+               .name = "short_linear_straddle",
+               .linear_len = GSO_TEST_SIZE + 500,
+               .missing_len = 700,
+               .err = -EINVAL,
+       },
+       {
+               .id = GSO_TEST_SHORT_FRAG_LIST,
+               .name = "short_frag_list",
+               .linear_len = GSO_TEST_SIZE,
+               .nr_frag_skbs = 1,
+               .frag_skbs = (const unsigned int[]) { GSO_TEST_SIZE },
+               .missing_len = 50,
+               .err = -EINVAL,
+       },
+       {
+               .id = GSO_TEST_SHORT_GSO_BY_FRAGS,
+               .name = "short_gso_by_frags",
+               .nr_frag_skbs = 2,
+               .frag_skbs = (const unsigned int[]) { 100, 200 },
+               .missing_len = 50,
+               .err = -EINVAL,
+       },
+       {
+               /* without SG the nested data is copied by skb_copy_bits(),
+                * which does look inside a member's own frag_list
+                */
+               .id = GSO_TEST_FRAG_LIST_NESTED_NO_SG,
+               .name = "frag_list_nested_no_sg",
+               .linear_len = GSO_TEST_SIZE,
+               .nr_frag_skbs = 2,
+               .frag_skbs = (const unsigned int[]) { GSO_TEST_SIZE, 
GSO_TEST_SIZE },
+               .no_sg = true,
+               .nr_segs = 3,
+               .segs = (const unsigned int[]) { GSO_TEST_SIZE, GSO_TEST_SIZE, 
GSO_TEST_SIZE },
+       },
+       {
+               /* without SG it is skb_copy_bits() that runs out of data */
+               .id = GSO_TEST_SHORT_LINEAR_NO_SG,
+               .name = "short_linear_no_sg",
+               .linear_len = GSO_TEST_SIZE,
+               .missing_len = 50,
+               .no_sg = true,
+               .err = -EFAULT,
+       },
+       {
+               .id = GSO_TEST_SHORT_LINEAR_STRADDLE_NO_SG,
+               .name = "short_linear_straddle_no_sg",
+               .linear_len = GSO_TEST_SIZE + 500,
+               .missing_len = 700,
+               .no_sg = true,
+               .err = -EFAULT,
+       },
+       {
+               .id = GSO_TEST_SHORT_FRAG_LIST_NO_SG,
+               .name = "short_frag_list_no_sg",
+               .linear_len = GSO_TEST_SIZE,
+               .nr_frag_skbs = 1,
+               .frag_skbs = (const unsigned int[]) { GSO_TEST_SIZE },
+               .missing_len = 50,
+               .no_sg = true,
+               .err = -EFAULT,
+       },
+       {
+               .id = GSO_TEST_SHORT_GSO_BY_FRAGS_NO_SG,
+               .name = "short_gso_by_frags_no_sg",
+               .nr_frag_skbs = 2,
+               .frag_skbs = (const unsigned int[]) { 100, 200 },
+               .missing_len = 50,
+               .no_sg = true,
+               .err = -EINVAL,
+       },
 };
 
 static void gso_test_case_to_desc(struct gso_test_case *t, char *desc)
@@ -337,21 +459,49 @@ static void gso_test_func(struct kunit *test)
                skb->data_len += total_size;
                skb->truesize += total_true_size;
 
-               if (tcase->id == GSO_TEST_GSO_BY_FRAGS)
+               if (tcase->id == GSO_TEST_GSO_BY_FRAGS ||
+                   tcase->id == GSO_TEST_SHORT_GSO_BY_FRAGS ||
+                   tcase->id == GSO_TEST_SHORT_GSO_BY_FRAGS_NO_SG)
                        skb_shinfo(skb)->gso_size = GSO_BY_FRAGS;
+
+               if (tcase->id == GSO_TEST_FRAG_LIST_NESTED ||
+                   tcase->id == GSO_TEST_FRAG_LIST_NESTED_NO_SG) {
+                       struct sk_buff *first = skb_shinfo(skb)->frag_list;
+
+                       skb_shinfo(first)->frag_list = first->next;
+                       first->next = NULL;
+                       first->len += tcase->frag_skbs[1];
+                       first->data_len += tcase->frag_skbs[1];
+               }
        }
 
+       skb->len += tcase->missing_len;
+       skb->data_len += tcase->missing_len;
+
        features = NETIF_F_SG | NETIF_F_HW_CSUM;
        if (tcase->id == GSO_TEST_GSO_PARTIAL)
                features |= NETIF_F_GSO_PARTIAL;
 
-       /* TODO: this should also work with SG,
-        * rather than hit BUG_ON(i >= nfrags)
+       /* TODO: this should also work with SG, rather than fail the
+        * i >= nfrags check in skb_segment()
         */
-       if (tcase->id == GSO_TEST_FRAG_LIST_NON_UNIFORM)
+       if (tcase->id == GSO_TEST_FRAG_LIST_NON_UNIFORM || tcase->no_sg)
                features &= ~NETIF_F_SG;
 
        SKB_GSO_CB(skb)->max_segs = tcase->max_segs;
+
+       if (tcase->err) {
+               /* the suppression block doesn't run if it can't be set up */
+               segs = NULL;
+               kunit_warning_suppress(test) {
+                       segs = skb_segment(skb, features);
+               }
+               KUNIT_EXPECT_EQ(test, PTR_ERR_OR_ZERO(segs), tcase->err);
+               if (!IS_ERR_OR_NULL(segs))
+                       kfree_skb_list(segs);
+               goto free_gso_skb;
+       }
+
        segs = skb_segment(skb, features);
        if (IS_ERR(segs)) {
                KUNIT_FAIL(test, "segs error %pe", segs);

-- 
2.55.0




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.