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

[PATCH net-next v2 02/15] net: skbuff: add skb_checksum_full() that reports failure



skb_checksum() can't fail.  When the range runs into unreadable frags
or past the end of the skb it returns 0, which is also a valid
checksum, so its callers can't tell the sum from a failure.  For the
receive-side validation helpers that means a packet whose payload was
never checksummed can pass, and commit 20ddd0fd2fcd ("net: skbuff:
don't BUG() on leftover length in skb_checksum() and friends") made the
past-the-end case one of those instead of a BUG().

Add skb_checksum_full(), which adds the range's sum to *@csump and
returns 0, or returns -EFAULT for unreadable frags or a range past the
end of the skb, and -EINVAL for a negative length, leaving *@csump
alone.  It's __must_check so a caller can't ignore the failure, and it
recurses into frag_list members through itself so a failure there
isn't lost either.

skb_checksum() becomes a wrapper around it that behaves as before for
the callers that haven't been converted: it returns 0 on failure and
warns once if that was because of unreadable frags.  The following
patches convert the core users, and the rest will follow per subsystem.

Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
---
 include/linux/skbuff.h |  2 ++
 net/core/skbuff.c      | 56 +++++++++++++++++++++++++++++++++++++++++---------
 2 files changed, 48 insertions(+), 10 deletions(-)

diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 09db32f70b53..969b1c3269ec 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -4279,6 +4279,8 @@ int skb_copy_bits(const struct sk_buff *skb, int offset, 
void *to, int len);
 int skb_store_bits(struct sk_buff *skb, int offset, const void *from, int len);
 int __must_check skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
                                        u8 *to, int len, __wsum *csum);
+int __must_check skb_checksum_full(const struct sk_buff *skb, int offset,
+                                  int len, __wsum *csump);
 int skb_splice_bits(struct sk_buff *skb, struct sock *sk, unsigned int offset,
                    struct pipe_inode_info *pipe, unsigned int len,
                    unsigned int flags);
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index a2b52a5a61a9..061c61422a38 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -3545,12 +3545,31 @@ int skb_store_bits(struct sk_buff *skb, int offset, 
const void *from, int len)
 EXPORT_SYMBOL(skb_store_bits);
 
 /* Checksum skb data. */
-__wsum skb_checksum(const struct sk_buff *skb, int offset, int len, __wsum 
csum)
+/**
+ *     skb_checksum_full - checksum a range of an skb
+ *     @skb: buffer to checksum
+ *     @offset: offset in @skb to start at
+ *     @len: number of bytes to checksum
+ *     @csump: the sum to add to; on success the range's sum is added to it
+ *
+ *     Return: 0 on success, -EINVAL if @len is negative, or -EFAULT if @skb
+ *     has unreadable frags in the range or runs out before @len does.  On
+ *     failure *@csump is not changed.
+ */
+int skb_checksum_full(const struct sk_buff *skb, int offset, int len,
+                     __wsum *csump)
 {
        int start = skb_headlen(skb);
        int i, copy = start - offset;
        struct sk_buff *frag_iter;
+       __wsum csum = *csump;
        int pos = 0;
+       int err;
+
+       if (unlikely(len < 0)) {
+               DEBUG_NET_WARN_ON_ONCE(1);
+               return -EINVAL;
+       }
 
        /* Checksum header. */
        if (copy > 0) {
@@ -3558,13 +3577,13 @@ __wsum skb_checksum(const struct sk_buff *skb, int 
offset, int len, __wsum csum)
                        copy = len;
                csum = csum_partial(skb->data + offset, copy, csum);
                if ((len -= copy) == 0)
-                       return csum;
+                       goto out;
                offset += copy;
                pos     = copy;
        }
 
-       if (WARN_ON_ONCE(!skb_frags_readable(skb)))
-               return 0;
+       if (!skb_frags_readable(skb))
+               return -EFAULT;
 
        for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
                int end;
@@ -3593,7 +3612,7 @@ __wsum skb_checksum(const struct sk_buff *skb, int 
offset, int len, __wsum csum)
                        }
 
                        if (!(len -= copy))
-                               return csum;
+                               goto out;
                        offset += copy;
                }
                start = end;
@@ -3606,14 +3625,17 @@ __wsum skb_checksum(const struct sk_buff *skb, int 
offset, int len, __wsum csum)
 
                end = start + frag_iter->len;
                if ((copy = end - offset) > 0) {
-                       __wsum csum2;
+                       __wsum csum2 = 0;
+
                        if (copy > len)
                                copy = len;
-                       csum2 = skb_checksum(frag_iter, offset - start, copy,
-                                            0);
+                       err = skb_checksum_full(frag_iter, offset - start, copy,
+                                               &csum2);
+                       if (err)
+                               return err;
                        csum = csum_block_add(csum, csum2, pos);
                        if ((len -= copy) == 0)
-                               return csum;
+                               goto out;
                        offset += copy;
                        pos    += copy;
                }
@@ -3621,9 +3643,23 @@ __wsum skb_checksum(const struct sk_buff *skb, int 
offset, int len, __wsum csum)
        }
        if (unlikely(len)) {
                DEBUG_NET_WARN_ON_ONCE(1);
-               return 0;
+               return -EFAULT;
        }
+out:
+       *csump = csum;
+       return 0;
+}
+EXPORT_SYMBOL(skb_checksum_full);
 
+/* skb_checksum() has no way to report failure, so it returns 0, which is a
+ * valid checksum.  Use skb_checksum_full() in new code.
+ */
+__wsum skb_checksum(const struct sk_buff *skb, int offset, int len, __wsum 
csum)
+{
+       if (unlikely(skb_checksum_full(skb, offset, len, &csum))) {
+               WARN_ON_ONCE(!skb_frags_readable(skb));
+               return 0;
+       }
        return csum;
 }
 EXPORT_SYMBOL(skb_checksum);

-- 
2.55.0




 


Rackspace

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