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 --- 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