From: Jiayuan Chen <jiayuan.chen@linux.dev>
To: Dudu Lu <phx0fer@gmail.com>,
davem@davemloft.net, edumazet@kernel.org, kuba@kernel.org,
pabeni@redhat.com
Cc: netdev@vger.kernel.org
Subject: Re: [PATCH net] net: reject flow_limit_table_len values below 2
Date: Fri, 23 Oct 2026 17:52:30 +0800 [thread overview]
Message-ID: <cbf14506-d689-469e-8aed-509ad5fce16a@linux.dev> (raw)
In-Reply-To: <20261021155505.67208-1-phx0fer@gmail.com>
On 10/21/26 11:55 PM, Dudu Lu wrote:
> flow_limit_table_len_sysctl() rejects non-powers-of-two but accepts 1,
> which is 2^0. A length of 1 then gives log_buckets = ilog2(1) = 0, and
> skb_flow_limit() computes hash_32(hash, 0):
>
> include/linux/hash.h:68
> return __hash_32(val) >> (32 - bits); /* bits == 0 -> shift by 32 */
>
> A shift by 32 is undefined and, on arm64/x86, the "hash" becomes the full
> 32-bit product val * GOLDEN_RATIO_32 -- an unbounded index into a
> buckets[] flex array that is one byte long inside a 289-byte allocation:
>
> UBSAN: shift-out-of-bounds in ./include/linux/hash.h:68:24
> shift exponent 32 is too large for 32-bit type 'unsigned int'
> __ubsan_handle_shift_out_of_bounds+0x11c/0x208
> enqueue_to_backlog+0xbe4/0xc44
> netif_rx_internal+0x130/0x4d4
>
> The subsequent read-modify-write at dev.c:5443-5446 is an out-of-bounds
> write into the allocation (also observed as use-after-free). Require at
> least two buckets so log_buckets is never 0.
>
> Fixes: 99bbc7074190 ("rps: selective flow shedding during softnet overflow")
> Assisted-by: LLM
> Signed-off-by: Dudu Lu <phx0fer@gmail.com>
> ---
> Tested base
> -----------
> Detected on Linux 7.3-rc4 (commit 93f51579e7df), arm64 QEMU guest.
> Re-checked against mainline f47d223627f8 (v7.3-341): the affected code
> is unchanged between the two, so the bug is still present there.
> Checked against linux-next next-20261021 (commit 9a482184d910): the
> affected code is unchanged there as well, so the bug is still present in
> linux-next too.
>
> Reproducer status
> -----------------
> A reproducer was written and executed on an arm64 QEMU guest (KASAN + UBSAN);
> UBSAN fired at the claimed sink. The reproducer source is not posted here per
> Documentation/process/security-bugs.rst; it is available on request.
>
> Triggering conditions
> ---------------------
> root (flow_limit_table_len and netdev_max_backlog live only in the init-net
> net_core_table, absent from the netns table).
>
> Notes
> -----
> Built and checkpatch-clean: this patch was applied on top of 7.3-rc4
> (commit 93f51579e7df) and a full arm64 kernel (defconfig) was built with
> it -- no new warnings or errors. scripts/checkpatch.pl reports 0 errors,
> 0 warnings.
>
> net/core/sysctl_net_core.c | 7 ++++++-
> 1 file changed, 6 insertions(+), 1 deletion(-)
>
> diff --git a/net/core/sysctl_net_core.c b/net/core/sysctl_net_core.c
> index eb35da3556f4..bb0fa69f94f5 100644
> --- a/net/core/sysctl_net_core.c
> +++ b/net/core/sysctl_net_core.c
> @@ -283,7 +283,12 @@ static int flow_limit_table_len_sysctl(const struct ctl_table *table, int write,
> ptr = table->data;
> old = *ptr;
> ret = proc_dointvec(table, write, buffer, lenp, ppos);
> - if (!ret && write && !is_power_of_2(*ptr)) {
> + /* A length of 1 is a power of two but gives log_buckets == 0,
> + * which makes skb_flow_limit() compute hash_32(hash, 0) -- a
> + * shift by 32 -- and index the one-byte buckets[] array with the
> + * full 32-bit hash. Require at least two buckets.
> + */
> + if (!ret && write && (*ptr < 2 || !is_power_of_2(*ptr))) {
> *ptr = old;
> ret = -EINVAL;
> }
1. Need more thought about flow_limit_table_len=1.
A single bucket still has well-defined semantics: once the backlog
is half full, every packet hitting skb_flow_limit() is dropped. The
same effect can be achieved by reducing netdev_max_backlog, so
rejecting 1 may be acceptable.
2. ptr is unsigned, so -2147483648 (0x80000000) still passes your check,
as it is >= 2 and a power of two.
3. sd_flow_limit::history is u16, and the bucket index is saved into it:
skb_flow_limit():
/* bucket index, 0 .. flow_limit_table_len - 1 */
new_flow = hash_32(skb_get_hash(skb), fl->log_buckets);
/* saved into a u16 */
fl->history[fl->history_head] = new_flow;
So with flow_limit_table_len > 65536 the bucket index gets truncated
when stored, and 128 packets later buckets[old_flow]-- decrements the
wrong bucket. It is not an out-of-bounds access, but the accounting
becomes wrong. An upper bound of 65536 seems needed as well.
next prev parent reply other threads:[~2026-10-23 9:52 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-21 15:55 [PATCH net] net: reject flow_limit_table_len values below 2 Dudu Lu
2026-10-21 16:00 ` netdev-bot+sinfo
2026-10-23 9:52 ` Jiayuan Chen [this message]
2026-10-25 17:06 ` netdev-bot+sashiko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cbf14506-d689-469e-8aed-509ad5fce16a@linux.dev \
--to=jiayuan.chen@linux.dev \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=phx0fer@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox