Netdev List
 help / color / mirror / Atom feed
From: Jakub Kicinski <kuba@kernel.org>
To: Jamal Hadi Salim <jhs@mojatatu.com>
Cc: netdev@vger.kernel.org, Victor Nogueira <victor@mojatatu.com>,
	Jiri Pirko <jiri@resnulli.us>,
	"David S . Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@kernel.org>,
	Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>,
	stable@vger.kernel.org, sashiko-bot@kernel.org
Subject: Re: [PATCH net v3 2/2] net/sched: sch_teql: keep skb->dev consistent on the arp-queue path
Date: Tue, 10 Nov 2026 10:19:08 -0800	[thread overview]
Message-ID: <20261110101908.2e64e8e9@kernel.org> (raw)
In-Reply-To: <QDISC-6HS9.v3.r0.20261103062649-2@mojatatu.com>

On Tue,  3 Nov 2026 07:39:16 -0500 Jamal Hadi Salim wrote:
> This is a follow-up to commit dc4b95b8fee9 ("net/sched: sch_teql: restore
> skb->dev on the slave failure path") by Victor Nogueira, which covered the
> slave side of the same mismatch.
> 
> The issue:
> teql_master_xmit() sets skb->dev = slave only right before the slave's
> ndo_start_xmit(). If the slave's neighbour is unresolved, teql_resolve()
> parks the skb on the slave's arp_queue with skb->dev still naming the teql
> master. teql_exit() frees the masters on module unload and a slave can
> outlive them, so a queued skb can be reinjected through freed memory.
> 
> The Fix:
> Set skb->dev = slave before teql_resolve() so the parked skb names the
> device whose neighbour owns it, and restore skb->dev = n1->dev at the
> neigh_update_process_arp_queue() reinjection point so the skb goes back
> through teql_master_xmit() instead of leaving headerless.
> 
> The parked skb has a second consumer: when the neighbour fails instead of
> resolving, neigh_invalidate() hands it to error_report() and
> dst_link_failure(), which read skb->dev to report the failure, so it names
> the slave on that path too.
> 
> The restore is limited to a top-level neighbour whose device has no
> header_ops, which is the shaper/eql/sch_teql class: those devices get
> neigh_direct_output() from arp_constructor() and ndisc_constructor(), an
> output that transmits on skb->dev and builds no link header, so the skb
> has to be moved back.
> 
> A forward that steers the skb to another device, nft fwd ... neigh or
> bpf_redirect_neigh() with explicit nexthop parameters, parks it with the
> dst of the original route still attached. The drain looks the neighbour
> up from that dst and not from the forwarding device, so when the original
> route is itself a headerless device this gate matches there and the skb
> goes back out the original device, running its egress hook a second time.
> Those forwards supply their own device and nexthop and do not consult the
> retained dst, so dropping it at those two sites is where that is fixed;
> that is a separate change to the producers, not part of this series.
> 
> Some context:
> Only a module unload frees a master. teql_master_setup() installs no
> rtnl_link_ops and default_device_exit_net() moves the device to init_net
> when its namespace exits, so the window is a parked skb plus CAP_SYS_MODULE
> on an unloaded sch_teql.
> 
> Conditions to recreate the bug:
> CONFIG_NET_SCH_TEQL=m and CONFIG_VETH=y, run as root. Create a veth pair,
> make one end a teql slave, give the master an address and a route, flush
> the slave's neighbour entry and send one packet:
> 
>   ip link add v0 type veth peer name v0p
>   ip link set v0 up;  ip link set v0p up
>   tc qdisc add dev v0 root teql0
>   ip link set teql0 up
>   ip addr add 10.0.0.1/24 dev teql0
>   ip route add 10.0.0.99/32 dev teql0
>   ip neigh flush dev v0
>   ping -I teql0 -c 1 10.0.0.99 &
>   sleep 1
> 
> The skb waits on v0's arp_queue while the solicitation is in flight.
> Remove the qdisc, which drops the module reference it holds, and unload
> the module while the skb still waits:
> 
>   tc qdisc del dev v0 root
>   rmmod sch_teql
> 
> teql_exit() frees teql0 there. When the solicitation then fails,
> neigh_invalidate() drains the queue through arp_error_report() and
> ipv4_link_failure(), whose __icmp_send() reads skb_in->dev; that is the
> freed master before this patch and v0 after it. A resolved neighbour
> drains the same queue through neigh_update_process_arp_queue().
> 
> On the unfixed tree the skb is parked on v0's arp_queue while skb->dev
> still names teql0, so the reinjection can reach a master that teql_exit()
> has freed. Moving skb->dev to the slave without restoring it at the
> reinjection point leaves the packet going out v0 without a link header.
> 
> The park and the headerless delivery reproduce without the unload.
> Freeing the master while the skb waits needs sch_teql built as a module,
> as above.
> 
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Reported-by: Sashiko (nipa) <sashiko-bot@kernel.org>
> Closes: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260824115928.4099988-1-victor@mojatatu.com
> Link: https://lore.kernel.org/netdev/20260824115928.4099988-1-victor@mojatatu.com/
> Link: https://lore.kernel.org/netdev/20261026160747.283702-1-horms@kernel.org/
> Link: https://lore.kernel.org/netdev/20261031165925.780930-1-horms@kernel.org/
> Link: https://lore.kernel.org/netdev/179363700308.3131.11778179538290251434@kernel.org/
> Cc: stable@vger.kernel.org
> Reviewed-by: Victor Nogueira <victor@mojatatu.com>
> Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
> 
> ---
> 
> v2 -> v3:
> - Fix the conditions to recreate the bug: reaching a freed master needs
>   sch_teql built as a module, the slave qdisc removed and the module
>   unloaded while the skb still waits, and say which part of it reproduces
>   without the unload.  Considered comments from Sashiko (nipa).
> - Name the neighbour failure path that drains the same parked skb.
> 
> v1 -> v2:
> - Limit the skb->dev restore in neigh_update_process_arp_queue() to a
>   top-level neighbour whose device has no header_ops: shaper, eql and
>   sch_teql output straight on skb->dev without building a link header, so
>   the skb has to be moved back for them.
> - Say how a master is freed and add the conditions to recreate the bug.
>   Considered comments from Sashiko (nipa) and Sashiko (gemini).
> ---
>  net/core/neighbour.c | 3 +++
>  net/sched/sch_teql.c | 2 +-
>  2 files changed, 4 insertions(+), 1 deletion(-)
> 
> diff --git a/net/core/neighbour.c b/net/core/neighbour.c
> index d6e9c7c59a89..541cf699abae 100644
> --- a/net/core/neighbour.c
> +++ b/net/core/neighbour.c
> @@ -1339,6 +1339,9 @@ static void neigh_update_process_arp_queue(struct neighbour *neigh)
>  			if (n2)
>  				n1 = n2;
>  		}
> +		/* headerless outputs (teql, shaper) transmit on skb->dev */
> +		if (!n1->dev->header_ops)
> +			skb->dev = n1->dev;
>  		READ_ONCE(n1->output)(n1, skb);
>  		if (n2)
>  			neigh_release(n2);

Looks odd, please split this out to its own fix, and make sure you CC
neigh maintainers.

> diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c
> index ed8faa848f18..4eac1f9c8ddf 100644
> --- a/net/sched/sch_teql.c
> +++ b/net/sched/sch_teql.c
> @@ -368,13 +368,13 @@ static netdev_tx_t teql_master_xmit(struct sk_buff *skb, struct net_device *dev)
>  			continue;
>  		}
>  
> +		skb->dev = slave;
>  		switch (teql_resolve(skb, skb_res, slave, slave_txq, &hlen)) {
>  		case 0:
>  			if (__netif_tx_trylock(slave_txq)) {
>  				unsigned int length = qdisc_pkt_len(skb);
>  				int rc = NETDEV_TX_BUSY;
>  
> -				skb->dev = slave;
>  				if (!netif_xmit_frozen_or_stopped(slave_txq))
>  					rc = netdev_start_xmit(skb, slave, slave_txq, false);
>  				__netif_tx_unlock(slave_txq);
-- 
pw-bot: cr

      parent reply	other threads:[~2026-11-10 18:19 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03 12:39 [PATCH net v3 0/2] net/sched: sch_teql: fix header-cloned write and retry strip Jamal Hadi Salim
2026-11-03 12:39 ` [PATCH net v3 1/2] net/sched: sch_teql: unshare the header before writing and fix " Jamal Hadi Salim
2026-11-09 17:15   ` Simon Horman
2026-11-03 12:39 ` [PATCH net v3 2/2] net/sched: sch_teql: keep skb->dev consistent on the arp-queue path Jamal Hadi Salim
2026-11-07 15:16   ` netdev-bot+sashiko
2026-11-09 11:44     ` Jamal Hadi Salim
2026-11-09 17:15       ` Simon Horman
2026-11-10 18:19       ` Jakub Kicinski
2026-11-10 19:35         ` Jamal Hadi Salim
2026-11-10 18:19   ` Jakub Kicinski [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=20261110101908.2e64e8e9@kernel.org \
    --to=kuba@kernel.org \
    --cc=davem@davemloft.net \
    --cc=edumazet@kernel.org \
    --cc=horms@kernel.org \
    --cc=jhs@mojatatu.com \
    --cc=jiri@resnulli.us \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=sashiko-bot@kernel.org \
    --cc=stable@vger.kernel.org \
    --cc=victor@mojatatu.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