Skip to content

Commit 20bab8b

Browse files
hartkoppmarckleinebudde
authored andcommitted
can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER
isotp_release() looked up the bound network device via dev_get_by_index() using the stored ifindex. During device unregistration the device is unlisted from the ifindex hash before the NETDEV_UNREGISTER notifier chain runs, so a concurrent isotp_release() could find no device, skip can_rx_unregister() entirely, and still proceed to free the socket. Since isotp_release() had already removed itself from the isotp notifier list at that point, isotp_notify() would never get a chance to clean up either, leaving a stale CAN filter that keeps pointing at the freed socket. Fix this the same way raw.c already does: hold a tracked reference to the bound net_device in the socket (so->dev/so->dev_tracker) from bind() onward instead of re-resolving it from the ifindex, and serialize bind()/release() with rtnl_lock() so that so->dev is always consistent with what the NETDEV_UNREGISTER notifier sees. so->dev stays valid regardless of ifindex-hash unlisting, and is only ever cleared by whichever of isotp_release()/isotp_notify() gets there first, so the filter is always removed exactly once. isotp_bind() now rejects a (re)bind with -EAGAIN while so->[tx|rx].state isn't ISOTP_IDLE yet, so a timer left running by a prior NETDEV_UNREGISTER can't act on a newly bound so->ifindex. Both checks share the same lock_sock() section, so there is no window in which a concurrent isotp_notify() clearing so->bound could be missed. Fixes: e057dd3 ("can: add ISO 15765-2:2016 transport protocol") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-can/20260707101420.47F261F000E9@smtp.kernel.org/ Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net> Link: https://patch.msgid.link/20260712-isotp-fixes-v10-2-793a1b1ce17f@hartkopp.net Cc: stable@kernel.org Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
1 parent 9b1a02e commit 20bab8b

1 file changed

Lines changed: 59 additions & 28 deletions

File tree

net/can/isotp.c

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ struct isotp_sock {
152152
struct sock sk;
153153
int bound;
154154
int ifindex;
155+
struct net_device *dev;
156+
netdevice_tracker dev_tracker;
155157
canid_t txid;
156158
canid_t rxid;
157159
ktime_t tx_gap;
@@ -978,6 +980,14 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
978980
goto err_event_drop;
979981
}
980982

983+
/* so->bound is only checked once above - a wakeup may have
984+
* unbound/rebound the socket meanwhile, so re-validate it
985+
*/
986+
if (!so->bound) {
987+
err = -EADDRNOTAVAIL;
988+
goto err_out_drop;
989+
}
990+
981991
/* PDU size > default => try max_pdu_size */
982992
if (size > so->tx.buflen && so->tx.buflen < max_pdu_size) {
983993
u8 *newbuf = kmalloc(max_pdu_size, GFP_KERNEL);
@@ -1219,28 +1229,30 @@ static int isotp_release(struct socket *sock)
12191229
list_del(&so->notifier);
12201230
spin_unlock(&isotp_notifier_lock);
12211231

1232+
rtnl_lock();
12221233
lock_sock(sk);
12231234

1224-
/* remove current filters & unregister */
1225-
if (so->bound) {
1226-
if (so->ifindex) {
1227-
struct net_device *dev;
1228-
1229-
dev = dev_get_by_index(net, so->ifindex);
1230-
if (dev) {
1231-
if (isotp_register_rxid(so))
1232-
can_rx_unregister(net, dev, so->rxid,
1233-
SINGLE_MASK(so->rxid),
1234-
isotp_rcv, sk);
1235-
1236-
can_rx_unregister(net, dev, so->txid,
1237-
SINGLE_MASK(so->txid),
1238-
isotp_rcv_echo, sk);
1239-
dev_put(dev);
1240-
}
1241-
}
1235+
/* remove current filters & unregister
1236+
* tracked reference so->dev is taken at bind() time with rtnl_lock
1237+
*/
1238+
if (so->bound && so->dev) {
1239+
if (isotp_register_rxid(so))
1240+
can_rx_unregister(net, so->dev, so->rxid,
1241+
SINGLE_MASK(so->rxid),
1242+
isotp_rcv, sk);
1243+
1244+
can_rx_unregister(net, so->dev, so->txid,
1245+
SINGLE_MASK(so->txid),
1246+
isotp_rcv_echo, sk);
1247+
netdev_put(so->dev, &so->dev_tracker);
12421248
}
12431249

1250+
so->ifindex = 0;
1251+
so->bound = 0;
1252+
so->dev = NULL;
1253+
1254+
rtnl_unlock();
1255+
12441256
/* Always wait for a grace period before touching the timers below.
12451257
* A concurrent NETDEV_UNREGISTER may have already unregistered our
12461258
* filters and cleared so->bound in isotp_notify() without waiting
@@ -1253,9 +1265,6 @@ static int isotp_release(struct socket *sock)
12531265
hrtimer_cancel(&so->txtimer);
12541266
hrtimer_cancel(&so->rxtimer);
12551267

1256-
so->ifindex = 0;
1257-
so->bound = 0;
1258-
12591268
sock_orphan(sk);
12601269
sock->sk = NULL;
12611270

@@ -1310,13 +1319,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
13101319
if (!addr->can_ifindex)
13111320
return -ENODEV;
13121321

1322+
rtnl_lock();
13131323
lock_sock(sk);
13141324

13151325
if (so->bound) {
13161326
err = -EINVAL;
13171327
goto out;
13181328
}
13191329

1330+
/* A transmission or reception that outlived a previous binding
1331+
* (unbound by NETDEV_UNREGISTER) may still be draining; the FC/echo
1332+
* and RX watchdog timers bound how long this takes. Checked together
1333+
* with so->bound in the same lock_sock() section above, so there is
1334+
* no window in which a concurrent isotp_notify() could be missed.
1335+
*/
1336+
if (so->tx.state != ISOTP_IDLE || so->rx.state != ISOTP_IDLE) {
1337+
err = -EAGAIN;
1338+
goto out;
1339+
}
1340+
13201341
/* ensure different CAN IDs when the rx_id is to be registered */
13211342
if (isotp_register_rxid(so) && rx_id == tx_id) {
13221343
err = -EADDRNOTAVAIL;
@@ -1329,14 +1350,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
13291350
goto out;
13301351
}
13311352
if (dev->type != ARPHRD_CAN) {
1332-
dev_put(dev);
13331353
err = -ENODEV;
1334-
goto out;
1354+
goto out_put_dev;
13351355
}
13361356
if (READ_ONCE(dev->mtu) < so->ll.mtu) {
1337-
dev_put(dev);
13381357
err = -EINVAL;
1339-
goto out;
1358+
goto out_put_dev;
13401359
}
13411360
if (!(dev->flags & IFF_UP))
13421361
notify_enetdown = 1;
@@ -1354,16 +1373,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int l
13541373
can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id),
13551374
isotp_rcv_echo, sk, "isotpe", sk);
13561375

1357-
dev_put(dev);
1358-
13591376
/* switch to new settings */
13601377
so->ifindex = ifindex;
13611378
so->rxid = rx_id;
13621379
so->txid = tx_id;
13631380
so->bound = 1;
13641381

1382+
/* bind() ok -> hold a reference for so->dev so that isotp_release()
1383+
* can safely reach the device later, even if a concurrent
1384+
* NETDEV_UNREGISTER has already unlisted it by ifindex.
1385+
*/
1386+
so->dev = dev;
1387+
netdev_hold(so->dev, &so->dev_tracker, GFP_KERNEL);
1388+
1389+
out_put_dev:
1390+
/* remove potential reference from dev_get_by_index() */
1391+
dev_put(dev);
13651392
out:
13661393
release_sock(sk);
1394+
rtnl_unlock();
13671395

13681396
if (notify_enetdown) {
13691397
sk->sk_err = ENETDOWN;
@@ -1566,7 +1594,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,
15661594
if (!net_eq(dev_net(dev), sock_net(sk)))
15671595
return;
15681596

1569-
if (so->ifindex != dev->ifindex)
1597+
if (so->dev != dev)
15701598
return;
15711599

15721600
switch (msg) {
@@ -1582,10 +1610,12 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,
15821610
can_rx_unregister(dev_net(dev), dev, so->txid,
15831611
SINGLE_MASK(so->txid),
15841612
isotp_rcv_echo, sk);
1613+
netdev_put(so->dev, &so->dev_tracker);
15851614
}
15861615

15871616
so->ifindex = 0;
15881617
so->bound = 0;
1618+
so->dev = NULL;
15891619
release_sock(sk);
15901620

15911621
sk->sk_err = ENODEV;
@@ -1645,6 +1675,7 @@ static int isotp_init(struct sock *sk)
16451675

16461676
so->ifindex = 0;
16471677
so->bound = 0;
1678+
so->dev = NULL;
16481679

16491680
so->opt.flags = CAN_ISOTP_DEFAULT_FLAGS;
16501681
so->opt.ext_address = CAN_ISOTP_DEFAULT_EXT_ADDRESS;

0 commit comments

Comments
 (0)