* [PATCH net-next v3 0/4] net/sched: sch_cake: fixes for accounting and reconfiguration
@ 2026-10-20 11:00 Jamal Hadi Salim
2026-10-20 11:00 ` [PATCH net-next v3 1/4] net/sched/sch_cake: serialize reconfiguration with the datapath Jamal Hadi Salim
0 siblings, 1 reply; 7+ messages in thread
From: Jamal Hadi Salim @ 2026-10-20 11:00 UTC (permalink / raw)
To: netdev
Cc: Jamal Hadi Salim, Toke Høiland-Jørgensen, Jiri Pirko,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, cake, Victor Nogueira, Sashiko
Four independent fixes in the CAKE qdisc. Three are follow-ups to the
autorate reconfiguration work around commit 7cbfb180945c ("net/sched:
sch_cake: fix autorate reconfiguration throttling") and the 32-bit
admission-counter wraps fixed in gred/bfifo/plug by commit 4c660ee8c809
("net: sched: fix 32-bit backlog wrap in gred, bfifo and plug enqueue");
the fourth is a dead-code cleanup found while auditing the dump path.
Patch 1 serializes a 'tc qdisc change' with the datapath's autorate
update under the qdisc lock, so the netlink writer and the autorate
writer can no longer lose the configured rate. rate_bps needs no atomic
(it is only loaded and stored), so it stays a plain u64 with
READ_ONCE()/WRITE_ONCE() annotations.
Patch 2 propagates the backlog reduction of the reconfigure-time tin
purge to classful parents, so a CAKE leaf under HTB no longer leaves the
purged packets in the parent's qlen/backlog forever.
Patch 3 widens buffer_used and buffer_max_used to u64. The u32
accumulator wraps once the resident truesize passes 2^32, bypassing the
over-limit drop loop while the configured limit is a u32 attribute; the
u32 MEMORY_USED export is clamped so widening the counter does not
truncate the reported value.
Patch 4 drops an unreachable 'nla_nest_end() < 0' error check: the
helper never returns a negative value.
All four are hardening for net-next (no Fixes:, no Cc: stable): the
counter wrap, the missing parent-backlog propagation and the
reconfiguration race all predate any contract we could anchor a
regression on. Patches 1-2 were reported by Sashiko (gemini) and patch
3 comes from the gred 32-bit-wrap family; patch 4 is a static cleanup.
v2 -> v3:
- Patch 1/4: reuse the rate snapshot in cake_dequeue() and
cake_reconfigure() instead of a second READ_ONCE() per use, order the
declarations longest-first (Björn Töpel), and scope the annotation
claim: READ_ONCE()/WRITE_ONCE() do not make a 64-bit access
single-copy atomic on 32-bit, so the lockless cake_config_dump() and
cake_mq readers remain best-effort (Sashiko nipa).
- Patch 2/4: no change; carries Toke's v1 Acked-by.
- Patch 3/4: publish buffer_max_used as u64_stats_t under the qdisc lock
and snapshot it with u64_stats_fetch_begin()/retry(), so the lockless
stat read is coherent on 32-bit (Sashiko gemini + nipa).
- Patch 4/4: no change; carries its Reviewed-by and Acked-by tags.
v1 -> v2:
- Patch 1/4: drop atomic64_t; rate_bps stays a plain u64 with
READ_ONCE()/WRITE_ONCE() annotations (per Toke's review: the accesses
are plain loads/stores, so no atomic is needed).
- Patch 2/4: no change; carries Toke's v1 Acked-by.
- Patch 3/4: clamp the MEMORY_USED export to U32_MAX, so widening the
counter does not truncate the u32 stat (Sashiko nipa Medium).
- Patch 4/4: no change; carries Toke's v1 Acked-by.
Jamal Hadi Salim (4):
net/sched: sch_cake: serialize reconfiguration with the datapath
net/sched: sch_cake: reduce parent backlog when clearing a tin
net/sched: sch_cake: widen buffer_used to u64
net/sched: sch_cake: drop dead nla_nest_end() error check
net/sched/sch_cake.c | 69 ++++++++++++++++++++++++++++++++++++--------------
1 file changed, 50 insertions(+), 19 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH net-next v3 1/4] net/sched/sch_cake: serialize reconfiguration with the datapath 2026-10-20 11:00 [PATCH net-next v3 0/4] net/sched: sch_cake: fixes for accounting and reconfiguration Jamal Hadi Salim @ 2026-10-20 11:00 ` Jamal Hadi Salim 2026-10-20 11:00 ` [PATCH net-next v3 2/4] net/sched/sch_cake: reduce parent backlog when clearing a tin Jamal Hadi Salim 2026-10-20 12:49 ` [PATCH net-next v3 1/4] net/sched/sch_cake: serialize reconfiguration with the datapath Toke Høiland-Jørgensen 0 siblings, 2 replies; 7+ messages in thread From: Jamal Hadi Salim @ 2026-10-20 11:00 UTC (permalink / raw) To: netdev Cc: Jamal Hadi Salim, Toke Høiland-Jørgensen, Björn Töpel, Jiri Pirko, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, cake, Victor Nogueira, Sashiko This is a follow-up to commit 7cbfb180945c ("net/sched: sch_cake: fix autorate reconfiguration throttling"). That change only stored last_reconfig_time; it did not address the concurrency between the autorate rate update in cake_enqueue() and the netlink reconfiguration path, which the original review flagged. The issue: cake_change() -> cake_config_change() updates the fields of the shared struct cake_sched_config under RTNL but not under the qdisc lock: it only took sch_tree_lock() later, around cake_reconfigure(). The datapath in turn serializes cake_enqueue() with itself under the qdisc lock, and its autorate-ingress path stores the estimated rate into q->config->rate_bps. Two writers therefore update the same rate with no common lock: CPU0: cake_enqueue() sees CAKE_FLAG_AUTORATE_INGRESS, computes an estimate, and is about to store it. CPU1: 'tc qdisc change ... bandwidth X' clears autorate in its local rate_flags, writes rate_bps = X, publishes the cleared flag, and waits in sch_tree_lock(). CPU0: stores the estimate, calls cake_reconfigure(), and unlocks. CPU1: reconfigures from the estimate and returns. The qdisc is left with autorate disabled but with the estimate installed instead of X. The fix: Commit the parsed configuration and reconfigure while holding the same qdisc lock that serializes the datapath, so the netlink writer and the autorate writer can no longer interleave. rate_bps needs no atomic: it is only ever loaded and stored, so plain READ_ONCE()/WRITE_ONCE() annotations are enough for the accesses that run outside the lock. The lockless paths read the rate once into a local and reuse that value, so a single coherent snapshot is used per operation. The annotations do not make a 64-bit access single-copy atomic on 32-bit: READ_ONCE()/WRITE_ONCE() are still two 32-bit accesses there. The lockless readers -- cake_config_dump() and the cake_mq children, which observe the shared config under a different child's lock -- can therefore still see a torn rate on 32-bit. That rate is an advisory shaper value and is accepted as best-effort; the state that must be consistent (the configured rate and the autorate flag) is written under the qdisc lock. Conditions to recreate the bug: The lost update (a) needs no sanitizer. Put cake in autorate-ingress on a device, drive bursty traffic with gaps so the 250 ms autorate reconfiguration fires, and concurrently loop 'tc qdisc change ... cake bandwidth <N>kbit' without autorate-ingress (specifying a bandwidth clears the flag). If the autorate store lands after the change wrote its rate but before the change reconfigures, the qdisc ends up with autorate disabled and the estimate installed instead of <N>; reading the rate back then shows the estimate, which an unfixed kernel can do and a fixed one cannot. (b) The unsynchronized access is reported by KCSAN: build with CONFIG_KCSAN=y and run the same trigger with the change loop keeping autorate-ingress set; KCSAN reports a data race in cake_config_change / cake_enqueue. The non-atomic 64-bit store only shows up as tearing on a 32-bit machine; on 64-bit the store is atomic and KCSAN flags the access discipline instead. Reported-by: Sashiko (gemini) <sashiko-bot@kernel.org> Link: https://sashiko.dev/#/patchset/20260816012109.2865223-1-ooonea@gmail.com Link: https://lore.kernel.org/netdev/20260816012109.2865223-1-ooonea@gmail.com/ Link: https://lore.kernel.org/netdev/875wzctsky.fsf@toke.dk/ Link: https://lore.kernel.org/netdev/87ld7t3kl3.fsf@all.your.base.are.belong.to.us/ Reviewed-by: Victor Nogueira <victor@mojatatu.com> Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com> --- v2 -> v3: - Reuse the rate snapshot in cake_dequeue() and cake_reconfigure() instead of a second READ_ONCE() per use, and order the declarations longest-first (Björn Töpel). - Commit log: scope the annotation claim. READ_ONCE()/WRITE_ONCE() do not make a 64-bit access single-copy atomic on 32-bit, so the lockless cake_config_dump()/cake_mq readers remain best-effort (Sashiko nipa). - Conditions: make recipe (a) distinguish fixed from unfixed (the change must clear autorate) and separate the KCSAN-only effect (b). v1 -> v2: - Drop atomic64_t for rate_bps. Per Toke's v1 review, rate_bps takes only non-RMW ops, so per atomic_t.txt SEMANTICS a plain u64 with READ_ONCE()/ WRITE_ONCE() annotations is the right shape (no atomic needed). Reverts the struct field to u64 and every atomic64_* back to READ_ONCE()/ WRITE_ONCE(). The cake_change() commit-and-reconfigure-under-sch_tree_lock fix (the real race fix) is unchanged. net/sched/sch_cake.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index dc93267029e7..d71617aa56d9 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -1906,7 +1906,8 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, if (ktime_after(now, ktime_add_ms(q->last_reconfig_time, 250))) { - q->config->rate_bps = (q->avg_peak_bandwidth * 15) >> 4; + WRITE_ONCE(q->config->rate_bps, + (q->avg_peak_bandwidth * 15) >> 4); q->last_reconfig_time = now; cake_reconfigure(sch); } @@ -2019,9 +2020,9 @@ static struct sk_buff *cake_dequeue(struct Qdisc *sch) if (q->config->is_shared && q->rate_ns && now - q->last_checked_active >= q->config->sync_time) { + u64 new_rate = READ_ONCE(q->config->rate_bps); struct net_device *dev = qdisc_dev(sch); struct cake_sched_data *other_priv; - u64 new_rate = q->config->rate_bps; u64 other_qlen, other_last_active; struct Qdisc *other_sch; u32 num_active_qs = 1; @@ -2042,7 +2043,7 @@ static struct sk_buff *cake_dequeue(struct Qdisc *sch) } if (num_active_qs > 1) - new_rate = div64_u64(q->config->rate_bps, num_active_qs); + new_rate = div64_u64(new_rate, num_active_qs); cake_configure_rates(sch, new_rate, true); q->last_checked_active = now; @@ -2625,13 +2626,15 @@ static void cake_reconfigure(struct Qdisc *sch) struct cake_sched_data *qd = qdisc_priv(sch); struct cake_sched_config *q = qd->config; u32 buffer_limit; + u64 rate_bps; - cake_configure_rates(sch, qd->config->rate_bps, false); + rate_bps = READ_ONCE(qd->config->rate_bps); + cake_configure_rates(sch, rate_bps, false); if (q->buffer_config_limit) { buffer_limit = q->buffer_config_limit; - } else if (q->rate_bps) { - u64 t = q->rate_bps * q->interval; + } else if (rate_bps) { + u64 t = rate_bps * q->interval; do_div(t, USEC_PER_SEC / 4); buffer_limit = max_t(u32, t, 4U << 20); @@ -2784,9 +2787,16 @@ static int cake_change(struct Qdisc *sch, struct nlattr *opt, return -EOPNOTSUPP; } + /* Once the qdisc is live, commit and reconfigure under the lock that + * serializes the datapath, so a concurrent 'tc qdisc change' and the + * autorate update cannot lose the configured rate. + */ + if (qd->tins) + sch_tree_lock(sch); + ret = cake_config_change(q, opt, extack, &overhead_changed); if (ret) - return ret; + goto unlock; if (overhead_changed) { WRITE_ONCE(qd->max_netlen, 0); @@ -2795,13 +2805,13 @@ static int cake_change(struct Qdisc *sch, struct nlattr *opt, WRITE_ONCE(qd->min_adjlen, ~0); } - if (qd->tins) { - sch_tree_lock(sch); + if (qd->tins) cake_reconfigure(sch); +unlock: + if (qd->tins) sch_tree_unlock(sch); - } - return 0; + return ret; } static void cake_destroy(struct Qdisc *sch) -- 2.43.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH net-next v3 2/4] net/sched/sch_cake: reduce parent backlog when clearing a tin 2026-10-20 11:00 ` [PATCH net-next v3 1/4] net/sched/sch_cake: serialize reconfiguration with the datapath Jamal Hadi Salim @ 2026-10-20 11:00 ` Jamal Hadi Salim 2026-10-20 11:00 ` [PATCH net-next v3 3/4] net/sched/sch_cake: widen buffer_used to u64 Jamal Hadi Salim 2026-10-20 12:49 ` [PATCH net-next v3 1/4] net/sched/sch_cake: serialize reconfiguration with the datapath Toke Høiland-Jørgensen 1 sibling, 1 reply; 7+ messages in thread From: Jamal Hadi Salim @ 2026-10-20 11:00 UTC (permalink / raw) To: netdev Cc: Jamal Hadi Salim, Toke Høiland-Jørgensen, Jiri Pirko, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, cake, Victor Nogueira, Sashiko The issue: CAKE has purged unused tins on DiffServ reconfiguration since commit 83f8fd69af4f ("sch_cake: Add DiffServ handling") without propagating the resulting backlog reduction to classful parents. The issue was noticed while reviewing 7cbfb180945c, but predates that change. cake_clear_tin() drops every packet from a tin with kfree_skb_reason() but never calls qdisc_tree_reduce_backlog(), so a CAKE qdisc under a classful parent (e.g. HTB) leaves the dropped packets in the parent qdisc's and class's qlen/backlog accounting forever. The normal dequeue path does reduce the parent; only the live reconfigure purge misses it. The fix: Tins are cleared from cake_reset() and, on reconfigure, from cake_configure_rates(). The qdisc core already owns the parent delta for the reset path: qdisc_purge_queue() snapshots qlen/backlog, runs qdisc_reset(), then reduces the tree, so propagating the reduction from cake_clear_tin() itself would subtract the same backlog twice. Snapshot CAKE's qlen/backlog around only the reconfiguration purge in cake_configure_rates() and reduce the parent once afterwards, matching the delta pattern already used by cake_enqueue(). Conditions to recreate the bug: put CAKE (diffserv8) under an HTB class, flood DSCP-classified traffic into a high tin so it backlogs, then change CAKE to diffserv3. The HTB parent keeps the purged packets in its qlen and backlog; a kernel with the fix shows the parent at the leaf's backlog (426b/5p) instead of retaining the purged packets. Reported-by: Sashiko (gemini) <sashiko-bot@kernel.org> Link: https://sashiko.dev/#/patchset/20260816012109.2865223-1-ooonea@gmail.com Link: https://lore.kernel.org/netdev/20260816012109.2865223-1-ooonea@gmail.com/ Link: https://lore.kernel.org/netdev/8733ugtry8.fsf@toke.dk/ Reviewed-by: Victor Nogueira <victor@mojatatu.com> Acked-by: Toke Høiland-Jørgensen <toke@toke.dk> Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com> --- v2 -> v3: - No changes to this patch. Resent as part of the series v3; keeps its v1 Reviewed-by and Acked-by tags. v1 -> v2: - No changes to this patch. Resent as part of the series v2. It is unchanged and carries Toke Høiland-Jørgensen's Acked-by from the v1 posting. net/sched/sch_cake.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index 5223efae5a28..f97240b1ee9c 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -2704,10 +2704,17 @@ static void cake_configure_rates(struct Qdisc *sch, u64 rate, bool rate_adjust) } if (!rate_adjust) { + u32 backlog = sch->qstats.backlog, qlen = sch->q.qlen; + for (c = qd->tin_cnt; c < CAKE_MAX_TINS; c++) { cake_clear_tin(sch, c); qd->tins[c].cparams.mtu_time = qd->tins[ft].cparams.mtu_time; } + + backlog -= sch->qstats.backlog; + qlen -= sch->q.qlen; + if (qlen) + qdisc_tree_reduce_backlog(sch, qlen, backlog); } qd->rate_ns = qd->tins[ft].tin_rate_ns; -- 2.43.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH net-next v3 3/4] net/sched/sch_cake: widen buffer_used to u64 2026-10-20 11:00 ` [PATCH net-next v3 2/4] net/sched/sch_cake: reduce parent backlog when clearing a tin Jamal Hadi Salim @ 2026-10-20 11:00 ` Jamal Hadi Salim 2026-10-20 11:00 ` [PATCH net-next v3 4/4] net/sched/sch_cake: drop dead nla_nest_end() error check Jamal Hadi Salim 2026-10-20 12:55 ` [PATCH net-next v3 3/4] net/sched/sch_cake: widen buffer_used to u64 Toke Høiland-Jørgensen 0 siblings, 2 replies; 7+ messages in thread From: Jamal Hadi Salim @ 2026-10-20 11:00 UTC (permalink / raw) To: netdev Cc: Jamal Hadi Salim, Toke Høiland-Jørgensen, Jiri Pirko, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, cake, Victor Nogueira, Sashiko This is a follow-up to commit 4c660ee8c809 ("net: sched: fix 32-bit backlog wrap in gred, bfifo and plug enqueue"), which promoted the backlog + length admission sums in gred, bfifo and plug to u64. CAKE's own memory accounting has the same 32-bit wrap. The issue: cake_sched_data.buffer_used accumulates skb->truesize and is compared against memory_limit: q->buffer_used += skb->truesize; if (q->buffer_used <= q->buffer_limit) return NET_XMIT_SUCCESS; while (q->buffer_used > q->buffer_limit) drop... buffer_used is u32, so once the resident truesize passes 2^32 it wraps to a small value, the <= test passes and the drop loop is skipped even though far more than the configured limit is queued. memory_limit is a u32 attribute (TCA_CAKE_MEMORY), so buffer_limit can be as large as U32_MAX and never caps buffer_used below the wrap point; with a large memory_limit the queue then grows until the machine is out of memory. The fix: Widen buffer_used and buffer_max_used to u64, the same shape as the gred/bfifo/plug fix. buffer_limit stays u32; the comparison promotes it to u64, so the bounded queue stops at the configured limit below 2^32 and the counter can no longer wrap. buffer_used is subtracted on dequeue and drop, so the widened type flows through unchanged. The ACK-filter replacement applied the net delta as one expression, q->buffer_used += skb->truesize - ack->truesize. Both operands are unsigned int, so the subtraction is evaluated at u32 and a larger removed ACK wraps the delta; the old u32 accumulator folded that back to the correct net value, but the widened u64 accumulator would persist it as a roughly 4 GiB over-count. Apply the replacement as two exact operations on the widened counter instead; the queue already accounts ack->truesize, so the subtraction cannot underflow. Widening the counters alone is incomplete because cake_dump_stats() still exports buffer_max_used through PUT_STAT_U32(MEMORY_USED, ...), and tc.yaml declares memory-used as u32. buffer_max_used only ever rises, so once the widened counter passes 2^32 the export reports peak - 2^32 for the rest of the qdisc's life. Clamp the exported value to U32_MAX; the counter itself is unaffected. The dump path runs without the qdisc lock: tc_fill_qdisc() passes NULL to gnet_stats_start_copy_compat(), so the reader races with the enqueue-side update. READ_ONCE() is not an atomic 64-bit load on 32-bit CPUs, so a reader can observe a torn high-water mark and report a bogus MEMORY_USED. Publish the counter as u64_stats_t under the qdisc lock, which the enqueue path already holds, and take a seqcount-protected snapshot in the dump. The counter stays u64; only the exported value is bounded. Conditions to recreate the bug: CAP_NET_ADMIN (root or a user namespace, the qdisc attach is gated by netlink_net_capable(CAP_NET_ADMIN)). A TUN device is needed so the root qdisc can park packets instead of dropping them. Open /dev/net/tun, request TUNSETIFF with IFF_TUN | IFF_NO_PI | IFF_BACKPRESSURE and keep the fd open without reading it (run it in the background and stop it at teardown): with IFF_BACKPRESSURE the full tx ring calls netif_tx_stop_queue() rather than dropping, so the queue behind the qdisc can grow past 4 GiB of resident truesize. A plain 'ip tuntap add' attaches no fd and does not request IFF_BACKPRESSURE, so it cannot park the queue this way. ip link set tun0 txqueuelen 32 up ip addr add 10.99.0.1/24 dev tun0 tc qdisc add dev tun0 root handle 1: cake memlimit 4294967295 # Then push more than 4 GiB of resident truesize through the qdisc: six # UDP senders using large SO_SNDBUFFORCE buffers send 1400-byte # datagrams (the reported OOM witness used 6 workers for 100 s on a # 12 GiB guest). On the unfixed kernel buffer_used wraps, the admission # test passes, the drop loop is bypassed and the guest runs out of # memory; with the fix the counter passes the limit without wrapping and # the excess is dropped. # The ACK-filter over-count is a separate corner: it needs ack-filter # enabled and a queued pure ACK whose truesize is larger than the # replacement ACK. Use a fresh interface (or 'tc qdisc change') rather # than a second root 'tc qdisc add' on an interface that already has a # qdisc: tc qdisc change dev tun0 root cake ack-filter CONFIG_NET_SCH_CAKE=y and CONFIG_TUN=y are required, and the memory-residency recipe needs a root-run sender envelope with enough guest RAM to hold the queued data. Reported-by: Sashiko (gemini) <sashiko-bot@kernel.org> Closes: https://sashiko.dev/#/patchset/20260818095927.15901-1-jhs@mojatatu.com Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260818095927.15901-1-jhs@mojatatu.com Link: https://lore.kernel.org/netdev/20260818095927.15901-1-jhs@mojatatu.com/ Reviewed-by: Victor Nogueira <victor@mojatatu.com> Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com> --- v2 -> v3: - Make the widened buffer_max_used reader coherent on 32-bit: publish the high-water mark as u64_stats_t under the qdisc lock (the enqueue path already holds it) and snapshot it in cake_dump_stats() with u64_stats_fetch_begin()/u64_stats_fetch_retry(). cake_dump_stats() runs without the qdisc lock: tc_fill_qdisc() passes NULL to gnet_stats_start_copy_compat(), so the former lockless READ_ONCE() of the u64 raced with the enqueue-side update. READ_ONCE() is not an atomic 64-bit load on 32-bit CPUs, so MEMORY_USED could report a torn high-water mark (Sashiko gemini + nipa v2 review, Low). No behavioural change on 64-bit; the U32_MAX clamp of the exported stat is retained. v1 -> v2: - Clamp the MEMORY_USED export to U32_MAX: cake_dump_stats() still exported the widened buffer_max_used through PUT_STAT_U32(), so once the monotonic counter passed 2^32 the u32 stat truncated to peak - 2^32 (Sashiko nipa Medium "buffer_max_used export truncation"). The counter itself is unaffected; only the reported value is bounded. net/sched/sch_cake.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index dc93267029e7..290bbc77df16 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -63,6 +63,7 @@ #include <linux/slab.h> #include <linux/vmalloc.h> #include <linux/reciprocal_div.h> +#include <linux/u64_stats_sync.h> #include <net/netlink.h> #include <linux/if_vlan.h> #include <net/gso.h> @@ -234,8 +235,10 @@ struct cake_sched_data { u16 tin_cnt; /* resource tracking */ - u32 buffer_used; - u32 buffer_max_used; + + struct u64_stats_sync buffer_max_used_sync; + u64_stats_t buffer_max_used; + u64 buffer_used; u32 buffer_limit; /* indices for dequeue */ @@ -1850,7 +1853,8 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, qdisc_qstats_drop(sch); ack_pkt_len = qdisc_pkt_len(ack); WRITE_ONCE(b->bytes, b->bytes + ack_pkt_len); - q->buffer_used += skb->truesize - ack->truesize; + q->buffer_used += skb->truesize; + q->buffer_used -= ack->truesize; if (q->config->rate_flags & CAKE_FLAG_INGRESS) cake_advance_shaper(q, b, ack, now, true); @@ -1940,8 +1944,11 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, cake_inc_dsthost_bulk_flow_count(b, flow, q->config->flow_mode); } - if (q->buffer_used > q->buffer_max_used) - WRITE_ONCE(q->buffer_max_used, q->buffer_used); + if (q->buffer_used > u64_stats_read(&q->buffer_max_used)) { + u64_stats_update_begin(&q->buffer_max_used_sync); + u64_stats_set(&q->buffer_max_used, q->buffer_used); + u64_stats_update_end(&q->buffer_max_used_sync); + } if (q->buffer_used <= q->buffer_limit) return NET_XMIT_SUCCESS; @@ -2845,6 +2852,7 @@ static int cake_init(struct Qdisc *sch, struct nlattr *opt, qd->cur_flow = 0; qd->config = q; + u64_stats_init(&qd->buffer_max_used_sync); qdisc_watchdog_init(&qd->watchdog, sch); if (opt) { @@ -2995,6 +3003,8 @@ static int cake_dump_stats(struct Qdisc *sch, struct gnet_dump *d) struct nlattr *stats = nla_nest_start_noflag(d->skb, TCA_STATS_APP); struct cake_sched_data *q = qdisc_priv(sch); struct nlattr *tstats, *ts; + u64 buffer_max_used; + unsigned int start; int i; if (!stats) @@ -3010,9 +3020,14 @@ static int cake_dump_stats(struct Qdisc *sch, struct gnet_dump *d) goto nla_put_failure; \ } while (0) + do { + start = u64_stats_fetch_begin(&q->buffer_max_used_sync); + buffer_max_used = u64_stats_read(&q->buffer_max_used); + } while (u64_stats_fetch_retry(&q->buffer_max_used_sync, start)); + PUT_STAT_U64(CAPACITY_ESTIMATE64, READ_ONCE(q->avg_peak_bandwidth)); PUT_STAT_U32(MEMORY_LIMIT, READ_ONCE(q->buffer_limit)); - PUT_STAT_U32(MEMORY_USED, READ_ONCE(q->buffer_max_used)); + PUT_STAT_U32(MEMORY_USED, min_t(u64, buffer_max_used, U32_MAX)); PUT_STAT_U32(AVG_NETOFF, ((READ_ONCE(q->avg_netoff) + 0x8000) >> 16)); PUT_STAT_U32(MAX_NETLEN, READ_ONCE(q->max_netlen)); PUT_STAT_U32(MAX_ADJLEN, READ_ONCE(q->max_adjlen)); -- 2.43.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH net-next v3 4/4] net/sched/sch_cake: drop dead nla_nest_end() error check 2026-10-20 11:00 ` [PATCH net-next v3 3/4] net/sched/sch_cake: widen buffer_used to u64 Jamal Hadi Salim @ 2026-10-20 11:00 ` Jamal Hadi Salim 2026-10-20 12:55 ` [PATCH net-next v3 3/4] net/sched/sch_cake: widen buffer_used to u64 Toke Høiland-Jørgensen 1 sibling, 0 replies; 7+ messages in thread From: Jamal Hadi Salim @ 2026-10-20 11:00 UTC (permalink / raw) To: netdev Cc: Jamal Hadi Salim, Toke Høiland-Jørgensen, Jiri Pirko, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, Victor Nogueira, cake cake_dump_class_stats() closes the TCA_STATS_APP nest with if (nla_nest_end(d->skb, stats) < 0) return -1; nla_nest_end() never returns a negative value: it has a single `return skb->len;` and no error path (that is why the overflow-aware nla_nest_end_safe() helper was later added). The '< 0' test can never be true, so the 'return -1' is unreachable dead code. Drop the check and close the nest with the bare statement, matching the sibling cake nests in the same file. This issue was discovered while fixing a bug on the gate action, see: https://lore.kernel.org/netdev/QDISC-H19Z.v1.20261001053234@mojatatu.com/ Conditions to recreate the bug: - not runtime observable: the issue is found by inspection. Link: https://lore.kernel.org/netdev/87v77csd5q.fsf@toke.dk/ Reviewed-by: Victor Nogueira <victor@mojatatu.com> Acked-by: Toke Høiland-Jørgensen <toke@toke.dk> Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com> --- v2 -> v3: - No changes to this patch. Re-sent as part of the series v3; keeps its Reviewed-by and Acked-by tags. v1 -> v2: - No changes to this patch. Re-sent as part of the series v2; it keeps Toke's v1 Acked-by, carried forward. net/sched/sch_cake.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index 4caf9718c9bd..124f552023f8 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -3211,8 +3211,7 @@ static int cake_dump_class_stats(struct Qdisc *sch, unsigned long cl, READ_ONCE(flow->cvars.drop_next)))); } - if (nla_nest_end(d->skb, stats) < 0) - return -1; + nla_nest_end(d->skb, stats); } return 0; -- 2.43.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH net-next v3 3/4] net/sched/sch_cake: widen buffer_used to u64 2026-10-20 11:00 ` [PATCH net-next v3 3/4] net/sched/sch_cake: widen buffer_used to u64 Jamal Hadi Salim 2026-10-20 11:00 ` [PATCH net-next v3 4/4] net/sched/sch_cake: drop dead nla_nest_end() error check Jamal Hadi Salim @ 2026-10-20 12:55 ` Toke Høiland-Jørgensen 1 sibling, 0 replies; 7+ messages in thread From: Toke Høiland-Jørgensen @ 2026-10-20 12:55 UTC (permalink / raw) To: Jamal Hadi Salim, netdev Cc: Jamal Hadi Salim, Jiri Pirko, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, cake, Victor Nogueira, Sashiko Jamal Hadi Salim <jhs@mojatatu.com> writes: > This is a follow-up to commit 4c660ee8c809 ("net: sched: fix 32-bit > backlog wrap in gred, bfifo and plug enqueue"), which promoted the > backlog + length admission sums in gred, bfifo and plug to u64. CAKE's > own memory accounting has the same 32-bit wrap. > > The issue: > cake_sched_data.buffer_used accumulates skb->truesize and is compared > against memory_limit: > > q->buffer_used += skb->truesize; > if (q->buffer_used <= q->buffer_limit) > return NET_XMIT_SUCCESS; > while (q->buffer_used > q->buffer_limit) > drop... > > buffer_used is u32, so once the resident truesize passes 2^32 it wraps to > a small value, the <= test passes and the drop loop is skipped even > though far more than the configured limit is queued. memory_limit is a > u32 attribute (TCA_CAKE_MEMORY), so buffer_limit can be as large as > U32_MAX and never caps buffer_used below the wrap point; with a large > memory_limit the queue then grows until the machine is out of memory. > > The fix: > Widen buffer_used and buffer_max_used to u64, the same shape as the > gred/bfifo/plug fix. buffer_limit stays u32; the comparison promotes it > to u64, so the bounded queue stops at the configured limit below 2^32 and > the counter can no longer wrap. buffer_used is subtracted on dequeue and > drop, so the widened type flows through unchanged. The ACK-filter > replacement applied the net delta as one expression, > q->buffer_used += skb->truesize - ack->truesize. Both operands are > unsigned int, so the subtraction is evaluated at u32 and a larger removed > ACK wraps the delta; the old u32 accumulator folded that back to the > correct net value, but the widened u64 accumulator would persist it as a > roughly 4 GiB over-count. Apply the replacement as two exact operations > on the widened counter instead; the queue already accounts ack->truesize, > so the subtraction cannot underflow. > > Widening the counters alone is incomplete because cake_dump_stats() still > exports buffer_max_used through PUT_STAT_U32(MEMORY_USED, ...), and > tc.yaml declares memory-used as u32. buffer_max_used only ever rises, so > once the widened counter passes 2^32 the export reports peak - 2^32 for > the rest of the qdisc's life. Clamp the exported value to U32_MAX; the > counter itself is unaffected. > > The dump path runs without the qdisc lock: tc_fill_qdisc() passes NULL to > gnet_stats_start_copy_compat(), so the reader races with the enqueue-side > update. READ_ONCE() is not an atomic 64-bit load on 32-bit CPUs, so a > reader can observe a torn high-water mark and report a bogus MEMORY_USED. > Publish the counter as u64_stats_t under the qdisc lock, which the enqueue > path already holds, and take a seqcount-protected snapshot in the dump. > The counter stays u64; only the exported value is bounded. > > Conditions to recreate the bug: > CAP_NET_ADMIN (root or a user namespace, the qdisc attach is gated by > netlink_net_capable(CAP_NET_ADMIN)). > > A TUN device is needed so the root qdisc can park packets instead of > dropping them. Open /dev/net/tun, request TUNSETIFF with > IFF_TUN | IFF_NO_PI | IFF_BACKPRESSURE and keep the fd open without > reading it (run it in the background and stop it at teardown): with > IFF_BACKPRESSURE the full tx ring calls netif_tx_stop_queue() rather than > dropping, so the queue behind the qdisc can grow past 4 GiB of resident > truesize. A plain 'ip tuntap add' attaches no fd and does not request > IFF_BACKPRESSURE, so it cannot park the queue this way. > > ip link set tun0 txqueuelen 32 up > ip addr add 10.99.0.1/24 dev tun0 > tc qdisc add dev tun0 root handle 1: cake memlimit 4294967295 > > # Then push more than 4 GiB of resident truesize through the qdisc: six > # UDP senders using large SO_SNDBUFFORCE buffers send 1400-byte > # datagrams (the reported OOM witness used 6 workers for 100 s on a > # 12 GiB guest). On the unfixed kernel buffer_used wraps, the admission > # test passes, the drop loop is bypassed and the guest runs out of > # memory; with the fix the counter passes the limit without wrapping and > # the excess is dropped. > > # The ACK-filter over-count is a separate corner: it needs ack-filter > # enabled and a queued pure ACK whose truesize is larger than the > # replacement ACK. Use a fresh interface (or 'tc qdisc change') rather > # than a second root 'tc qdisc add' on an interface that already has a > # qdisc: > tc qdisc change dev tun0 root cake ack-filter > > CONFIG_NET_SCH_CAKE=y and CONFIG_TUN=y are required, and the > memory-residency recipe needs a root-run sender envelope with enough > guest RAM to hold the queued data. > > Reported-by: Sashiko (gemini) <sashiko-bot@kernel.org> > Closes: https://sashiko.dev/#/patchset/20260818095927.15901-1-jhs@mojatatu.com > Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260818095927.15901-1-jhs@mojatatu.com > Link: https://lore.kernel.org/netdev/20260818095927.15901-1-jhs@mojatatu.com/ > Reviewed-by: Victor Nogueira <victor@mojatatu.com> > Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com> Acked-by: Toke Høiland-Jørgensen <toke@toke.dk> ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net-next v3 1/4] net/sched/sch_cake: serialize reconfiguration with the datapath 2026-10-20 11:00 ` [PATCH net-next v3 1/4] net/sched/sch_cake: serialize reconfiguration with the datapath Jamal Hadi Salim 2026-10-20 11:00 ` [PATCH net-next v3 2/4] net/sched/sch_cake: reduce parent backlog when clearing a tin Jamal Hadi Salim @ 2026-10-20 12:49 ` Toke Høiland-Jørgensen 1 sibling, 0 replies; 7+ messages in thread From: Toke Høiland-Jørgensen @ 2026-10-20 12:49 UTC (permalink / raw) To: Jamal Hadi Salim, netdev Cc: Jamal Hadi Salim, Björn Töpel, Jiri Pirko, David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, cake, Victor Nogueira, Sashiko Jamal Hadi Salim <jhs@mojatatu.com> writes: > This is a follow-up to commit 7cbfb180945c ("net/sched: sch_cake: fix > autorate reconfiguration throttling"). That change only stored > last_reconfig_time; it did not address the concurrency between the > autorate rate update in cake_enqueue() and the netlink reconfiguration > path, which the original review flagged. > > The issue: > cake_change() -> cake_config_change() updates the fields of the shared > struct cake_sched_config under RTNL but not under the qdisc lock: it only > took sch_tree_lock() later, around cake_reconfigure(). The datapath in > turn serializes cake_enqueue() with itself under the qdisc lock, and its > autorate-ingress path stores the estimated rate into > q->config->rate_bps. Two writers therefore update the same rate with no > common lock: > > CPU0: cake_enqueue() sees CAKE_FLAG_AUTORATE_INGRESS, computes an > estimate, and is about to store it. > CPU1: 'tc qdisc change ... bandwidth X' clears autorate in its local > rate_flags, writes rate_bps = X, publishes the cleared flag, and > waits in sch_tree_lock(). > CPU0: stores the estimate, calls cake_reconfigure(), and unlocks. > CPU1: reconfigures from the estimate and returns. > > The qdisc is left with autorate disabled but with the estimate installed > instead of X. > > The fix: > Commit the parsed configuration and reconfigure while holding the same > qdisc lock that serializes the datapath, so the netlink writer and the > autorate writer can no longer interleave. rate_bps needs no atomic: it is > only ever loaded and stored, so plain READ_ONCE()/WRITE_ONCE() annotations > are enough for the accesses that run outside the lock. The lockless > paths read the rate once into a local and reuse that value, so a single > coherent snapshot is used per operation. > > The annotations do not make a 64-bit access single-copy atomic on 32-bit: > READ_ONCE()/WRITE_ONCE() are still two 32-bit accesses there. The lockless > readers -- cake_config_dump() and the cake_mq children, which observe the > shared config under a different child's lock -- can therefore still see a > torn rate on 32-bit. That rate is an advisory shaper value and is accepted > as best-effort; the state that must be consistent (the configured rate and > the autorate flag) is written under the qdisc lock. > > Conditions to recreate the bug: > The lost update (a) needs no sanitizer. Put cake in autorate-ingress on a > device, drive bursty traffic with gaps so the 250 ms autorate reconfiguration > fires, and concurrently loop 'tc qdisc change ... cake bandwidth <N>kbit' > without autorate-ingress (specifying a bandwidth clears the flag). > If the autorate store lands after the change wrote its rate but before the > change reconfigures, the qdisc ends up with autorate disabled and the estimate > installed instead of <N>; reading the rate back then shows the estimate, which > an unfixed kernel can do and a fixed one cannot. > (b) The unsynchronized access is reported by KCSAN: build with CONFIG_KCSAN=y > and run the same trigger with the change loop keeping autorate-ingress set; > KCSAN reports a data race in cake_config_change / cake_enqueue. > The non-atomic 64-bit store only shows up as tearing on a 32-bit machine; > on 64-bit the store is atomic and KCSAN flags the access discipline instead. > > Reported-by: Sashiko (gemini) <sashiko-bot@kernel.org> > Link: https://sashiko.dev/#/patchset/20260816012109.2865223-1-ooonea@gmail.com > Link: https://lore.kernel.org/netdev/20260816012109.2865223-1-ooonea@gmail.com/ > Link: https://lore.kernel.org/netdev/875wzctsky.fsf@toke.dk/ > Link: https://lore.kernel.org/netdev/87ld7t3kl3.fsf@all.your.base.are.belong.to.us/ > Reviewed-by: Victor Nogueira <victor@mojatatu.com> > Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com> Acked-by: Toke Høiland-Jørgensen <toke@toke.dk> ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-20 12:55 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-20 11:00 [PATCH net-next v3 0/4] net/sched: sch_cake: fixes for accounting and reconfiguration Jamal Hadi Salim 2026-10-20 11:00 ` [PATCH net-next v3 1/4] net/sched/sch_cake: serialize reconfiguration with the datapath Jamal Hadi Salim 2026-10-20 11:00 ` [PATCH net-next v3 2/4] net/sched/sch_cake: reduce parent backlog when clearing a tin Jamal Hadi Salim 2026-10-20 11:00 ` [PATCH net-next v3 3/4] net/sched/sch_cake: widen buffer_used to u64 Jamal Hadi Salim 2026-10-20 11:00 ` [PATCH net-next v3 4/4] net/sched/sch_cake: drop dead nla_nest_end() error check Jamal Hadi Salim 2026-10-20 12:55 ` [PATCH net-next v3 3/4] net/sched/sch_cake: widen buffer_used to u64 Toke Høiland-Jørgensen 2026-10-20 12:49 ` [PATCH net-next v3 1/4] net/sched/sch_cake: serialize reconfiguration with the datapath Toke Høiland-Jørgensen
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.