[PATCH net v4 1/3] net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances

From: Selvamani Rajagopal via B4 Relay

Date: Mon Jul 20 2026 - 22:27:20 EST


From: Selvamani Rajagopal <Selvamani.Rajagopal@xxxxxxxxxx>

Threaded IRQ uses waiting_tx_skb. Transmit path also uses
this pointer without any mutual exclusion protection. As a
result, it might leak skb buffer, particularly threaded IRQ
runs in the middle of tranmsmit path, near skb_linearize.

Fixes: b542d13fab0f ("net: ethernet: oa_tc6: Interrupt is active low, level triggered.")
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@xxxxxxxxxx>

---
changes in v4
- No change
changes in v3
- Added the missed out spin lock protection for
waiting_tx_skb and disable_traffic flag
changes in v2
- added the missing prefix to the title
---
drivers/net/ethernet/oa_tc6.c | 100 +++++++++++++++++++++++++++++-------------
1 file changed, 70 insertions(+), 30 deletions(-)

diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 0727d53345a3..5b24cce4f9b5 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -652,6 +652,26 @@ static int oa_tc6_enable_data_transfer(struct oa_tc6 *tc6)
return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, value);
}

+/* Called when a frame that is meant to be transmitted, is dropped. */
+static void oa_tc6_drop_tx_skb(struct oa_tc6 *tc6, struct sk_buff *skb)
+{
+ if (skb) {
+ tc6->netdev->stats.tx_dropped++;
+ dev_kfree_skb_any(skb);
+ }
+}
+
+static struct sk_buff *oa_tc6_detach_waiting_tx_skb(struct oa_tc6 *tc6)
+{
+ struct sk_buff *skb;
+
+ lockdep_assert_held(&tc6->tx_skb_lock);
+ skb = tc6->waiting_tx_skb;
+ tc6->waiting_tx_skb = NULL;
+
+ return skb;
+}
+
static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6)
{
if (tc6->rx_skb) {
@@ -663,26 +683,30 @@ static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6)

static void oa_tc6_cleanup_ongoing_tx_skb(struct oa_tc6 *tc6)
{
- if (tc6->ongoing_tx_skb) {
- tc6->netdev->stats.tx_dropped++;
- kfree_skb(tc6->ongoing_tx_skb);
- tc6->ongoing_tx_skb = NULL;
- }
+ oa_tc6_drop_tx_skb(tc6, tc6->ongoing_tx_skb);
+ tc6->ongoing_tx_skb = NULL;
}

static void oa_tc6_cleanup_waiting_tx_skb(struct oa_tc6 *tc6)
{
- if (tc6->waiting_tx_skb) {
- tc6->netdev->stats.tx_dropped++;
- kfree_skb(tc6->waiting_tx_skb);
- tc6->waiting_tx_skb = NULL;
- }
+ struct sk_buff *skb;
+
+ spin_lock_bh(&tc6->tx_skb_lock);
+ skb = oa_tc6_detach_waiting_tx_skb(tc6);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+
+ oa_tc6_drop_tx_skb(tc6, skb);
}

-static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
+static void oa_tc6_free_ongoing_skbs(struct oa_tc6 *tc6)
{
oa_tc6_cleanup_ongoing_tx_skb(tc6);
oa_tc6_cleanup_ongoing_rx_skb(tc6);
+}
+
+static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
+{
+ oa_tc6_free_ongoing_skbs(tc6);
oa_tc6_cleanup_waiting_tx_skb(tc6);
}

@@ -693,9 +717,15 @@ static void oa_tc6_free_pending_skbs(struct oa_tc6 *tc6)
static void oa_tc6_disable_traffic(struct oa_tc6 *tc6)
{
u32 regval = INT_MASK0_ALL_INTERRUPTS;
+ struct sk_buff *skb;

+ spin_lock_bh(&tc6->tx_skb_lock);
tc6->disable_traffic = true;
- oa_tc6_free_pending_skbs(tc6);
+ skb = oa_tc6_detach_waiting_tx_skb(tc6);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+
+ oa_tc6_drop_tx_skb(tc6, skb);
+ oa_tc6_free_ongoing_skbs(tc6);
oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, regval);
oa_tc6_read_register(tc6, OA_TC6_REG_STATUS0, &regval);
oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, regval);
@@ -1136,8 +1166,7 @@ static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6)
if (ret == -EAGAIN)
continue;

- oa_tc6_cleanup_ongoing_tx_skb(tc6);
- oa_tc6_cleanup_ongoing_rx_skb(tc6);
+ oa_tc6_free_ongoing_skbs(tc6);
netdev_err(tc6->netdev, "Device error: %d\n", ret);
return ret;
}
@@ -1159,15 +1188,20 @@ static irqreturn_t oa_tc6_macphy_threaded_irq(int irq, void *data)
* no need to attempt spi transfer, once it fails. Pending skbs
* are already freed.
*/
- if (!tc6->disable_traffic) {
- while (tc6->int_flag ||
- (tc6->waiting_tx_skb && tc6->tx_credits)) {
- ret = oa_tc6_try_spi_transfer(tc6);
- if (ret) {
- disable_irq_nosync(tc6->spi->irq);
- oa_tc6_disable_traffic(tc6);
- break;
- }
+ spin_lock_bh(&tc6->tx_skb_lock);
+ if (tc6->disable_traffic) {
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ return IRQ_HANDLED;
+ }
+ spin_unlock_bh(&tc6->tx_skb_lock);
+
+ while (tc6->int_flag ||
+ (tc6->waiting_tx_skb && tc6->tx_credits)) {
+ ret = oa_tc6_try_spi_transfer(tc6);
+ if (ret) {
+ disable_irq_nosync(tc6->spi->irq);
+ oa_tc6_disable_traffic(tc6);
+ break;
}
}

@@ -1250,18 +1284,22 @@ EXPORT_SYMBOL_GPL(oa_tc6_zero_align_receive_frame_enable);
*/
netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb)
{
- if (tc6->disable_traffic || tc6->waiting_tx_skb) {
- netif_stop_queue(tc6->netdev);
- return NETDEV_TX_BUSY;
- }
-
if (skb_linearize(skb)) {
- dev_kfree_skb_any(skb);
- tc6->netdev->stats.tx_dropped++;
+ oa_tc6_drop_tx_skb(tc6, skb);
return NETDEV_TX_OK;
}

spin_lock_bh(&tc6->tx_skb_lock);
+ if (tc6->waiting_tx_skb) {
+ netif_stop_queue(tc6->netdev);
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ return NETDEV_TX_BUSY;
+ }
+ if (tc6->disable_traffic) {
+ spin_unlock_bh(&tc6->tx_skb_lock);
+ oa_tc6_drop_tx_skb(tc6, skb);
+ return NETDEV_TX_OK;
+ }
tc6->waiting_tx_skb = skb;
spin_unlock_bh(&tc6->tx_skb_lock);

@@ -1393,7 +1431,9 @@ EXPORT_SYMBOL_GPL(oa_tc6_init);
*/
void oa_tc6_exit(struct oa_tc6 *tc6)
{
+ spin_lock_bh(&tc6->tx_skb_lock);
tc6->disable_traffic = true;
+ spin_unlock_bh(&tc6->tx_skb_lock);
disable_irq(tc6->spi->irq);
oa_tc6_phy_exit(tc6);
oa_tc6_free_pending_skbs(tc6);

--
2.43.0