From: Hangbin Liu <hangbin.liu@linux.dev>
To: Jakub Kicinski <kuba@kernel.org>, jv@jvosburgh.net, razor@blackwall.org
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
pabeni@redhat.com, daniel@iogearbox.net, joamaki@gmail.com,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
liuhangbin@kylinos.cn
Subject: Re: [PATCH net v4] bonding: fix slave_cnt leak on XDP error paths
Date: Wed, 16 Sep 2026 13:07:27 +0800 [thread overview]
Message-ID: <aqokD2avF7B7Towi@fedora> (raw)
In-Reply-To: <20260916012721.1653880-2-kuba@kernel.org>
Hi Jakub,
Thanks for the notify.
Jay, Nikolay, please correct me if you disagree with my comments.
On Tue, Sep 15, 2026 at 06:27:22PM -0700, Jakub Kicinski wrote:
> Clashiko classified the broader issues as independent but I'm not sure.
> If there are more problems with pre-publishing the count we need
> a different fix? Or am I not reading closely enough?
>
> AI review:
> ---
> bonding: fix slave_cnt leak on XDP error paths
>
> The three XDP failure exits in bond_enslave() used to jump straight to
> err_sysfs_del, which skipped the slave_cnt decrement and let the counter
> drift upward on every failed enslave. A new err_slave_cnt label is added
> ahead of err_sysfs_del to undo the increment.
>
> > diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c
> > index a9bff7663eeca..577d85c838040 100644
> > --- a/drivers/net/bonding/bond_main.c
> > +++ b/drivers/net/bonding/bond_main.c
> > @@ -2305,7 +2305,7 @@ int bond_enslave(struct net_device *bond_dev, struct net_device *slave_dev,
> > SLAVE_NL_ERR(bond_dev, slave_dev, extack,
> > "Slave does not support XDP");
> > res = -EOPNOTSUPP;
> > - goto err_sysfs_del;
> > + goto err_slave_cnt;
> > }
> > } else if (bond->xdp_prog) {
> > struct netdev_bpf xdp = {
>
> [Severity: High]
>
> This is a pre-existing ordering issue that dates back to 9e2ee5c7e7c3 and
> is not introduced by this patch, but since the new err_slave_cnt stage sits
> right here it seems worth asking about.
>
> By the time bond_enslave() reaches this ndo_bpf / ndo_xdp_xmit test, the
> slave has already been published to the lockless transmit path:
>
> res = bond_master_upper_dev_link(bond, new_slave, extack);
> ...
> WRITE_ONCE(bond->slave_cnt, bond->slave_cnt + 1);
> ...
> if (bond_uses_primary(bond)) {
> block_netpoll_tx();
> bond_select_active_slave(bond);
> unblock_netpoll_tx();
> }
>
> if (!slave_dev->netdev_ops->ndo_bpf ||
> !slave_dev->netdev_ops->ndo_xdp_xmit) {
>
> For the trunk modes the switch default arm already ran
> bond_set_active_slave(new_slave) with new_slave->link == BOND_LINK_UP, so
> bond_slave_can_tx() is true for it.
Not since the bond_uses_primary() if checking, the new_slave could be set to
active even more early during enslave. e.g. in bond_main.c:2174
switch (BOND_MODE(bond)) {
...
case BOND_MODE_TLB:
case BOND_MODE_ALB:
bond_set_active_slave(new_slave);
default:
bond_set_active_slave(new_slave);
I think the bond assume the new slave is in trunk modes in this situation,
which could pass any traffic.
>
> Can a concurrent bond_xdp_xmit(), which only takes rcu_read_lock() and does
> not hold RTNL, pick that not-yet-validated slave in this window?
>
> bond_xdp_xmit()
> bond_xdp_xmit_roundrobin_slave_get()
> slave_cnt = READ_ONCE(bond->slave_cnt);
> if (likely(slave_cnt)) {
> slave_id = bond_rr_gen_slave_id(bond) % slave_cnt;
> return bond_get_slave_by_id(bond, slave_id);
bond_get_slave_by_id() loop each slaves, even the slave_cnt not updated, the
new slave still could be chosen.
>
> bond_get_slave_by_id() only checks bond_slave_can_tx(), never XDP
> capability, and bond_xdp_xmit() then does:
>
> err = slave_dev->netdev_ops->ndo_xdp_xmit(slave_dev, 1, frames1, flags);
>
> with no NULL test, so would this be an indirect call through a NULL
> ndo_xdp_xmit for a slave that lacks it?
>
> The devmap gate does not appear to help here either: __xdp_enqueue() and
> is_valid_dst() only look at the bond device's xdp_features, which still
> advertise NETDEV_XDP_ACT_NDO_XMIT during this window because
> bond_xdp_set_features() only runs after the XDP block. The same exposure
> looks reachable through bond_xmit_activebackup_slave_get() for
> bond_uses_primary() modes, where bond_select_active_slave() may publish the
> new slave as curr_active_slave before the capability check.
>
> Would it make sense to move the ndo_bpf / ndo_xdp_xmit validation earlier,
> before the slave is linked and counted?
I saw Nikolay has done a similar fix e9acda52fd2e ("bonding: fix use-after-free
due to enslave fail after slave array update"). But the risk is till there.
So how up we should move the xdp ops?
Maybe add a checking as a workaround before we do a safe re-order for
bond_enslave?
if (likely(slave_dev->netdev_ops->ndo_xdp_xmit))
err = slave_dev->netdev_ops->ndo_xdp_xmit(slave_dev, 1, frames1, flags);
Nikolay, what do you think?
>
> [ ... ]
>
> > @@ -2348,6 +2348,9 @@ int bond_enslave(struct net_device *bond_dev, struct net_device *slave_dev,
> > return 0;
> >
> > /* Undo stages on error */
> > +err_slave_cnt:
> > + WRITE_ONCE(bond->slave_cnt, bond->slave_cnt - 1);
> > +
> > err_sysfs_del:
> > bond_sysfs_slave_del(new_slave);
> >
>
> [Severity: Medium]
>
> These leaks are pre-existing, the three XDP exits already fell into this
> same unwind chain before this patch, so they are not introduced here. Since
> this patch adds the stage where the symmetric undo would belong, is it worth
> handling the rest of the state applied just above the XDP block?
>
> Reaching the XDP checks means this group has already run, with no
> intervening label:
>
> WRITE_ONCE(bond->slave_cnt, bond->slave_cnt + 1);
> netdev_compute_master_upper_features(bond->dev, true);
> bond_set_carrier(bond);
>
> bond_slave_ns_maddrs_add(bond, new_slave);
>
> err_slave_cnt reverses only the counter and falls through to err_sysfs_del,
> so does the bond keep a feature mask recomputed from a device that never
> became a slave, and for a first slave with an up link keep reporting
> LOWER_UP with zero slaves, since nothing on the unwind path re-runs
> netdev_compute_master_upper_features() or bond_set_carrier()?
Yes, I think we could do it, in a separate patch.
>
> Does bond_slave_ns_maddrs_add() also leak solicited-node multicast
> addresses on the rejected netdev? bond_options.c takes real references:
>
> void bond_slave_ns_maddrs_add(struct bonding *bond, struct slave *slave)
> {
> if (!bond->params.arp_validate)
> return;
> slave_set_ns_maddrs(bond, slave, true);
> }
>
> and no label between err_slave_cnt and err_undo_flags calls
> bond_slave_ns_maddrs_del(). err_detach only re-runs
> bond_change_active_slave() / bond_select_active_slave() when the new slave
> had become curr_active_slave, so with mode=active-backup plus arp_validate
> plus ns_ip6_target and the new slave staying backup, would these accumulate
> on each retried enslave?
Yes, we could.
>
> For round-robin and 802.3ad, which are both XDP-capable and not
> bond_uses_primary(), the block above also ran:
>
> if (bond_dev->flags & IFF_PROMISC) {
> res = dev_set_promiscuity(slave_dev, 1);
> ...
> if (bond_dev->flags & IFF_ALLMULTI) {
> res = dev_set_allmulti(slave_dev, 1);
> ...
> dev_mc_sync_multiple(slave_dev, bond_dev);
> dev_uc_sync_multiple(slave_dev, bond_dev);
> if (BOND_MODE(bond) == BOND_MODE_8023AD)
> dev_mc_add(slave_dev, lacpdu_mcast_addr);
>
> Does the released device stay in promiscuous and allmulti mode with the
> multicast and unicast sync entries still programmed after the XDP failure?
Yes
>
> __bond_release_one() performs exactly the mirror operations, which suggests
> these are the required undo steps:
>
> bond_slave_ns_maddrs_del(bond, slave);
> ...
> WRITE_ONCE(bond->slave_cnt, bond->slave_cnt - 1);
> ...
> netdev_compute_master_upper_features(bond->dev, true);
> ...
> if (old_flags & IFF_PROMISC)
> dev_set_promiscuity(slave_dev, -1);
> ...
> if (old_flags & IFF_ALLMULTI)
> dev_set_allmulti(slave_dev, -1);
> ...
> if (old_flags & IFF_UP)
> bond_hw_addr_flush(bond_dev, slave_dev);
>
> and that path is not reachable here because the enslave failed.
Yes, I think we can do it in another patch.
Thanks
Hangbin
prev parent reply other threads:[~2026-09-16 5:07 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-14 3:09 Hangbin Liu
2026-09-15 9:07 ` Nikolay Aleksandrov
2026-09-16 1:27 ` Jakub Kicinski
2026-09-16 5:07 ` Hangbin Liu [this message]
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=aqokD2avF7B7Towi@fedora \
--to=hangbin.liu@linux.dev \
--cc=andrew+netdev@lunn.ch \
--cc=daniel@iogearbox.net \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=joamaki@gmail.com \
--cc=jv@jvosburgh.net \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=liuhangbin@kylinos.cn \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=razor@blackwall.org \
/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
all inboxes | Powered by JetHome®