KASAN reported a slab-use-after-free in tty_write_room() reachable from caif_serial's TX path. The TX handler dereferences ser->tty while ldisc_close() can drop the driver's tty reference. Since ser->tty was not cleared and accesses were not synchronized, the TX path could race with tty teardown and dereference a stale ser->tty pointer. Fix it by serializing accesses to ser->tty with a dedicated lock. The TX path grabs a tty kref under the lock and drops it after the TX attempt, while ldisc_close() clears ser->tty under the same lock before putting the old tty reference. This prevents the TX path from observing a freed tty object via ser->tty. Reported-by: Shuangpeng Bai Closes: https://groups.google.com/g/syzkaller/c/usNe0oKtoXw/m/x8qUc3yUAQAJ Signed-off-by: Shuangpeng Bai --- drivers/net/caif/caif_serial.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/net/caif/caif_serial.c b/drivers/net/caif/caif_serial.c index c398ac42eae9..fd1213685a89 100644 --- a/drivers/net/caif/caif_serial.c +++ b/drivers/net/caif/caif_serial.c @@ -68,6 +68,7 @@ struct ser_device { struct net_device *dev; struct sk_buff_head head; struct tty_struct *tty; + spinlock_t tty_lock; /* protects ser->tty */ bool tx_started; unsigned long state; #ifdef CONFIG_DEBUG_FS @@ -197,12 +198,21 @@ static int handle_tx(struct ser_device *ser) struct sk_buff *skb; int tty_wr, len, room; + spin_lock(&ser->tty_lock); tty = ser->tty; + tty_kref_get(tty); + spin_unlock(&ser->tty_lock); + + if (!tty) + return 0; + ser->tx_started = true; /* Enter critical section */ - if (test_and_set_bit(CAIF_SENDING, &ser->state)) + if (test_and_set_bit(CAIF_SENDING, &ser->state)) { + tty_kref_put(tty); return 0; + } /* skb_peek is safe because handle_tx is called after skb_queue_tail */ while ((skb = skb_peek(&ser->head)) != NULL) { @@ -245,9 +255,11 @@ static int handle_tx(struct ser_device *ser) ser->common.flowctrl != NULL) ser->common.flowctrl(ser->dev, ON); clear_bit(CAIF_SENDING, &ser->state); + tty_kref_put(tty); return 0; error: clear_bit(CAIF_SENDING, &ser->state); + tty_kref_put(tty); return tty_wr; } @@ -327,6 +339,7 @@ static int ldisc_open(struct tty_struct *tty) return -ENOMEM; ser = netdev_priv(dev); + spin_lock_init(&ser->tty_lock); ser->tty = tty_kref_get(tty); ser->dev = dev; debugfs_init(ser, tty); @@ -354,8 +367,13 @@ static int ldisc_open(struct tty_struct *tty) static void ldisc_close(struct tty_struct *tty) { struct ser_device *ser = tty->disc_data; + struct tty_struct *old; - tty_kref_put(ser->tty); + spin_lock(&ser->tty_lock); + old = ser->tty; + ser->tty = NULL; + spin_unlock(&ser->tty_lock); + tty_kref_put(old); spin_lock(&ser_lock); list_move(&ser->node, &ser_release_list); -- 2.34.1