mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: netdev-bot+sashiko@kernel.org
To: sinyavin@gmail.com
Cc: stephan@gerhold.net, stephan.gerhold@linaro.org,
	loic.poulain@oss.qualcomm.com, ryazanov.s.a@gmail.com,
	johannes@sipsolutions.net, andrew+netdev@lunn.ch,
	davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
	pabeni@redhat.com, linux-arm-msm@vger.kernel.org,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH net v3] net: wwan: qcom_bam_dmux: account network packets
Date: Sun, 06 Sep 2026 15:00:17 +0000	[thread overview]
Message-ID: <178870681701.219967.5248698671156408999@kernel.org> (raw)
In-Reply-To: <20260902124506.363174-1-sinyavin@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:

Critical: 0 · High: 1 (1) · Medium: 1 (1) · Low: 0

- [High] Use-after-free write of freed per-CPU statistics memory from the
  TX DMA completion callback.
- [Medium] Asymmetric / incomplete drop accounting introduced with the
  new statistics plumbing.

Pre-existing issues:
- [High] Disclosure of uninitialized/stale kernel memory driven by the
  modem-supplied length field.
- [Medium] struct net_device leak on the driver removal path:
  bam_dmux_register_netdev_work() allocates each interface with…

--

net: wwan: qcom_bam_dmux: account network packets

The patch adds per-CPU software statistics to the BAM-DMUX data path by
setting NETDEV_PCPU_STAT_TSTATS, wiring up dev_get_tstats64() and
accounting TX packets from the DMA completion callback and RX packets after
the BAM-DMUX header is removed. It also counts packets discarded by the
transmit error path.

A few questions on the statistics plumbing below.

> diff --git a/drivers/net/wwan/qcom_bam_dmux.c b/drivers/net/wwan/qcom_bam_dmux.c
> index cc6ace8d6437..ef3356200b3e 100644
> --- a/drivers/net/wwan/qcom_bam_dmux.c
> +++ b/drivers/net/wwan/qcom_bam_dmux.c
> @@ -177,8 +177,12 @@ static void bam_dmux_tx_callback(void *data)
>  {
>  	struct bam_dmux_skb_dma *skb_dma = data;
>  	struct sk_buff *skb = skb_dma->skb;
> +	struct net_device *netdev = skb->dev;
>  
>  	bam_dmux_tx_done(skb_dma);
> +	if (netdev)
> +		dev_sw_netstats_tx_add(netdev, 1,
> +				       ((struct bam_dmux_hdr *)skb->data)->len);
>  	dev_consume_skb_any(skb);
>  }

[Severity: High]
Can this write through a freed per-CPU pointer when a TX descriptor
completes while the device is being removed?

dev_sw_netstats_tx_add() dereferences dev->tstats with no validity check:

include/linux/netdevice.h:
    static inline void dev_sw_netstats_tx_add(struct net_device *dev,
                                              unsigned int packets,
                                              unsigned int len)
    {
            struct pcpu_sw_netstats *tstats = this_cpu_ptr(dev->tstats);
            ...

For NETDEV_PCPU_STAT_TSTATS that pointer is released by
netdev_run_todo() -> netdev_do_free_pcpu_stats():

net/core/dev.c:netdev_do_free_pcpu_stats() {
        case NETDEV_PCPU_STAT_TSTATS:
                free_percpu(dev->tstats);
                break;
}

and dev->tstats is not cleared afterwards.

In bam_dmux_remove() the todo list runs inside rtnl_unlock(), while the TX
DMA channel is only stopped much later:

    unregister_netdevice_many(&list);
    rtnl_unlock();                      /* netdev_run_todo(): tstats freed */
    cancel_work_sync(&dmux->tx_wakeup_work);
    ...
    if (!wait_event_timeout(dmux->pc_wait, !dmux->rx, BAM_DMUX_REMOTE_TIMEOUT))
            dev_err(dev, "Timed out waiting for remote side to suspend\n");
    disable_irq(dmux->pc_irq);
    bam_dmux_power_off(dmux);            /* dmaengine_terminate_sync(dmux->tx) */

Does anything prevent a descriptor still owned by the BAM hardware from
completing inside that window (which can include a BAM_DMUX_REMOTE_TIMEOUT
wait of up to two seconds) and running dev_sw_netstats_tx_add() on the
freed percpu chunk?

The deferred path looks similar: a packet queued through the "active <= 0"
branch of bam_dmux_netdev_start_xmit() is submitted by
bam_dmux_tx_wakeup_work(), which is only cancelled after rtnl_unlock(), and
cancel_work_sync() waits for the work function, not for the DMA it just
issued.

Can the "if (netdev)" test catch any of this?  skb->dev holds no reference,
bam_dmux_netdev_setup() never sets dev->needs_free_netdev and no
priv_destructor is installed, so the struct net_device body is still
readable and the pointer test passes even though tstats is gone.

The same exposure exists on receive: dmux->netdevs[] is never cleared in
bam_dmux_remove(), and bam_dmux_cmd_data() tests
"!netdev || !netif_running(netdev)" on entry but writes dev->tstats further
down, in tasklet context, while unregistration proceeds in process context.

> @@ -368,6 +372,7 @@ static netdev_tx_t bam_dmux_netdev_start_xmit(struct sk_buff *skb,
>  
>  drop:
>  	bam_dmux_tx_done(skb_dma);
> +	dev_core_stats_tx_dropped_inc(netdev);
>  	dev_kfree_skb_any(skb);
>  	return NETDEV_TX_OK;
>  }

[Severity: Medium]
With this increment the driver starts reporting the dropped fields of
rtnl_link_stats64, but two sibling discard sites are left uncounted.  Was
that intentional?

bam_dmux_tx_wakeup_work() finishes the deferred branch for skbs already
accepted with NETDEV_TX_OK, and neither of its failure paths reaches
bam_dmux_tx_callback():

    ret = pm_runtime_resume_and_get(dmux->dev);
    if (ret < 0) {
            dev_err(dmux->dev, "Failed to resume: %d\n", ret);
            return;
    }
    ...
    for_each_set_bit(i, &pending, BAM_DMUX_NUM_SKB) {
            bam_dmux_skb_dma_submit_tx(&dmux->tx_skbs[i]);
    }

The submit return value is discarded, and it can fail:

    if (!desc) {
            dev_err(dmux->dev, "Failed to prepare TX DMA buffer\n");
            return false;
    }

so such a packet moves neither tx_packets nor tx_dropped.  (The skb is also
leaked and its ring slot wedged there, which predates this patch, but it is
the reason no counter can move.)

On the receive side, bam_dmux_cmd_data() returns early for
"!netdev || !netif_running(netdev)" and for "hdr->len > BAM_DMUX_MAX_DATA_SIZE",
and bam_dmux_rx_callback() drops frames with a bad magic, all before the new
dev_sw_netstats_rx_add().  None of them touches rx_dropped or rx_errors, so
userspace now sees TX discards while RX discards stay at zero.  Could
dev_core_stats_rx_dropped_inc() be added on those paths, where a valid
netdev is already in hand?

[ ... ]

> @@ -421,6 +427,7 @@ static void bam_dmux_netdev_setup(struct net_device *dev)
>  	dev->needed_headroom = sizeof(struct bam_dmux_hdr);
>  	dev->needed_tailroom = sizeof(u32); /* word-aligned */
>  	dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN;
> +	dev->pcpu_stat_type = NETDEV_PCPU_STAT_TSTATS;

[Severity: Medium]
This isn't a bug introduced by this patch, but does the driver leak every
registered struct net_device on unbind?  bam_dmux_netdev_setup() sets no
dev->needs_free_netdev here (and no priv_destructor is installed anywhere in
the file), and bam_dmux_remove() only unregisters:

    for (i = 0; i < BAM_DMUX_NUM_CH; ++i)
            if (dmux->netdevs[i])
                    unregister_netdevice_queue(dmux->netdevs[i], &list);
    unregister_netdevice_many(&list);
    rtnl_unlock();

with no matching free_netdev().  free_netdev() appears only on the
register_netdev() failure path in bam_dmux_register_netdev_work().
netdev_run_todo() frees the per-CPU stats unconditionally but only frees the
device when needs_free_netdev is set:

net/core/dev.c:netdev_run_todo() {
        netdev_do_free_pcpu_stats(dev);
        if (dev->priv_destructor)
                dev->priv_destructor(dev);
        if (dev->needs_free_netdev)
                free_netdev(dev);
}

so each wwan%d device and its private area stay allocated, and
dmux->netdevs[] keeps pointing at them.  This is also why the freed-tstats
write above does not fault and can pass casual testing.

> @@ -533,6 +540,7 @@ static void bam_dmux_cmd_data(struct bam_dmux_skb_dma *skb_dma)
>  		break;
>  	}
>  
> +	dev_sw_netstats_rx_add(netdev, skb->len);
>  	netif_receive_skb(skb);
>  }

[Severity: High]
This is a pre-existing issue, but does skb->len here describe bytes the modem
actually transferred?

bam_dmux_skb_dma_queue_rx() allocates a non-zeroed buffer and puts the whole
thing:

    skb_dma->skb = __netdev_alloc_skb(NULL, BAM_DMUX_BUFFER_SIZE, gfp);
    if (!skb_dma->skb)
            return false;
    skb_put(skb_dma->skb, BAM_DMUX_BUFFER_SIZE);

so skb->len is always BAM_DMUX_BUFFER_SIZE regardless of the transfer size,
and nothing in the driver queries the DMA residue (no dma_tx_status() call
anywhere).  bam_dmux_cmd_data() then only bounds the peer-supplied length:

    if (hdr->len > BAM_DMUX_MAX_DATA_SIZE) { ... return; }
    ...
    skb_pull(skb, sizeof(*hdr));
    skb_trim(skb, hdr->len);

Since skb_trim() can only shrink, a modem that writes a short frame but
declares hdr->len = BAM_DMUX_MAX_DATA_SIZE gets the stale contents of the
recycled page frag delivered to netif_receive_skb() as payload, and the new
dev_sw_netstats_rx_add(netdev, skb->len) counts those bytes as received
traffic.  Should hdr->len be clamped to the actually transferred length, or
the RX buffer zeroed?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260902124506.363174-1-sinyavin%40gmail.com

      parent reply	other threads:[~2026-09-06 15:00 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-02 12:45 Dmitry Sinyavin
2026-09-03  8:27 ` Dmitry Sinyavin
2026-09-06 15:00 ` netdev-bot+sashiko [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=178870681701.219967.5248698671156408999@kernel.org \
    --to=netdev-bot+sashiko@kernel.org \
    --cc=andrew+netdev@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=johannes@sipsolutions.net \
    --cc=kuba@kernel.org \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=loic.poulain@oss.qualcomm.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=ryazanov.s.a@gmail.com \
    --cc=sinyavin@gmail.com \
    --cc=stephan.gerhold@linaro.org \
    --cc=stephan@gerhold.net \
    /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®