Re: [PATCH net] net/sched: sch_teql: restore skb->dev on the slave failure path

Jamal Hadi Salim <[email protected]>
Newsgroups org.kernel.vger.netdev
Message-ID <CAM0EoMmXhqmuSkLCS1hXczFK4GZtQR2Gy1YZ5Vx2wfqCD87PDA@mail.gmail.com>
On Mon, Aug 10, 2026 at 8:11 PM Jakub Kicinski <[email protected]> wrote:
>
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> ---
> net/sched: sch_teql: restore skb->dev on the slave failure path
>
> teql_master_xmit() used to leave skb->dev pointing at a slave after that
> slave's ndo_start_xmit() failed, so the skb could end up queued on a later
> slave's neighbour arp_queue while referring to the earlier slave.  The
> patch restores skb->dev to the master device at the end of each slave
> iteration.
>

Let's please apply this patch. The patch correctly fixes the reported
UAF (namespace-reachable, slave deleted while skb queued on another
slave's neighbour).

What sashikos are claiming is partly followup material (the rmmod
issue requires root where this UAF requires CAP_NET_ADMIN) and
partially hallucinations. So, let's address those issues in follow-up
patches.

cheers,
jamal

> > diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c
> > index 5c42a29a981c..9e52afc2d980 100644
> > --- a/net/sched/sch_teql.c
> > +++ b/net/sched/sch_teql.c
> > @@ -377,6 +377,7 @@ static netdev_tx_t teql_master_xmit(struct sk_buff *skb, struct net_device *dev)
> >                       nores = 1;
> >                       break;
> >               }
> > +             skb->dev = dev;
>
> [Severity: High]
> This isn't a bug introduced by this patch, but does it leave a smaller
> version of the same dangling skb->dev behind, with the master netdev in
> place of the previous slave?
>
> After the restore, skb->dev is the teql master, and the skb can still be
> parked on the arp_queue of a neighbour that belongs to a slave device.  On
> the second pass (skb_res == skb after the goto restart), teql_resolve() ->
> __teql_resolve() creates the neighbour on the slave because dst->dev is the
> master:
>
> net/sched/sch_teql.c:__teql_resolve() {
>         ...
>         if (dst->dev != dev) {
>                 struct neighbour *mn;
>
>                 mn = __neigh_lookup_errno(n->tbl, n->primary_key, dev);
>         ...
>         if (neigh_event_send(n, skb_res) == 0) {
>         ...
>         } else {
>                 err = (skb_res == NULL) ? -EAGAIN : 1;
>         }
> }
>
> __neigh_event_send() then keeps the skb:
>
> net/core/neighbour.c:__neigh_event_send() {
>         ...
>                 skb_dst_force(skb);
>                 __skb_queue_tail(&neigh->arp_queue, skb);
>         ...
> }
>
> teql_master_xmit() returns NETDEV_TX_OK for the err == 1 case and loses
> ownership.  At that point the queued skb's skb->dev (master) is not the
> neighbour's device (slave), so the arp_queue flushing done by
> arp_ifdown()/neigh_ifdown() for the master's device never sees this skb.
> skb->dev also takes no netdev reference.
>
> Is the held dst enough to pin the master here?  On NETDEV_UNREGISTER,
> rt_flush_dev() and dst_dev_put() swap dst->dev to blackhole_netdev via
> netdev_ref_replace(), which drops the master's refcount, so
> netdev_wait_allrefs() does not hold off free_netdev().  The master has no
> rtnl_link_ops, so the free comes from teql_exit():
>
> net/sched/sch_teql.c:teql_exit() {
>         ...
>                 unregister_qdisc(&master->qops);
>                 unregister_netdev(master->dev);
>                 free_netdev(master->dev);
>         ...
> }
>
> which means rmmod sch_teql after the last teql qdisc is deleted, racing a
> pending neighbour resolution (the retransmit window is multiple seconds by
> default).  The next use of the queued skb dereferences the freed master
> through the same path as the KASAN report quoted in the commit message,
> neigh_invalidate() -> arp_error_report() -> dst_link_failure() ->
> ipv4_link_failure() -> __icmp_send():
>
> net/ipv4/icmp.c:__icmp_send() {
>         ...
>         if (!(skb_in->dev && (skb_in->dev->flags&IFF_LOOPBACK)) &&
>               !icmpv4_global_allow(net, type, code, &apply_ratelimit))
>         ...
> }
>
> and on the successful resolution path,
> neigh_update_process_arp_queue() -> n1->output() -> dev_queue_xmit() uses
> the freed device's netdev_ops and _tx.
>
> Would setting skb->dev to the device of the neighbour that actually takes
> the skb (the slave used by __teql_resolve()), or declining to hand the skb
> to a neighbour whose device differs from skb->dev, close the remaining
> window?
>
> > \t\t__skb_pull(skb, skb_network_offset(skb));
> >       } while ((q = rcu_dereference(NEXT_SLAVE(q))) != start);
> >
> --
> pw-bot: cr
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.