Skip to content

Commit 43884dc

Browse files
hartkoppgregkh
authored andcommitted
can: isotp: fix use-after-free race with concurrent NETDEV_UNREGISTER
commit 20bab8b upstream. 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> Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net> Signed-off-by: Sasha Levin <sashal@kernel.org>
1 parent 3ff8c24 commit 43884dc

1 file changed

Lines changed: 59 additions & 29 deletions

File tree

net/can/isotp.c

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ struct isotp_sock {
151151
struct sock sk;
152152
int bound;
153153
int ifindex;
154+
struct net_device *dev;
154155
canid_t txid;
155156
canid_t rxid;
156157
ktime_t tx_gap;
@@ -964,6 +965,14 @@ static int isotp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
964965
goto err_event_drop;
965966
}
966967

968+
/* so->bound is only checked once above - a wakeup may have
969+
* unbound/rebound the socket meanwhile, so re-validate it
970+
*/
971+
if (!so->bound) {
972+
err = -EADDRNOTAVAIL;
973+
goto err_out_drop;
974+
}
975+
967976
/* PDU size > default => try max_pdu_size */
968977
if (size > so->tx.buflen && so->tx.buflen < max_pdu_size) {
969978
u8 *newbuf = kmalloc(max_pdu_size, GFP_KERNEL);
@@ -1199,28 +1208,30 @@ static int isotp_release(struct socket *sock)
11991208
list_del(&so->notifier);
12001209
spin_unlock(&isotp_notifier_lock);
12011210

1211+
rtnl_lock();
12021212
lock_sock(sk);
12031213

1204-
/* remove current filters & unregister */
1205-
if (so->bound) {
1206-
if (so->ifindex) {
1207-
struct net_device *dev;
1208-
1209-
dev = dev_get_by_index(net, so->ifindex);
1210-
if (dev) {
1211-
if (isotp_register_rxid(so))
1212-
can_rx_unregister(net, dev, so->rxid,
1213-
SINGLE_MASK(so->rxid),
1214-
isotp_rcv, sk);
1215-
1216-
can_rx_unregister(net, dev, so->txid,
1217-
SINGLE_MASK(so->txid),
1218-
isotp_rcv_echo, sk);
1219-
dev_put(dev);
1220-
}
1221-
}
1214+
/* remove current filters & unregister
1215+
* tracked reference so->dev is taken at bind() time with rtnl_lock
1216+
*/
1217+
if (so->bound && so->dev) {
1218+
if (isotp_register_rxid(so))
1219+
can_rx_unregister(net, so->dev, so->rxid,
1220+
SINGLE_MASK(so->rxid),
1221+
isotp_rcv, sk);
1222+
1223+
can_rx_unregister(net, so->dev, so->txid,
1224+
SINGLE_MASK(so->txid),
1225+
isotp_rcv_echo, sk);
1226+
dev_put(so->dev);
12221227
}
12231228

1229+
so->ifindex = 0;
1230+
so->bound = 0;
1231+
so->dev = NULL;
1232+
1233+
rtnl_unlock();
1234+
12241235
/* Always wait for a grace period before touching the timers below.
12251236
* A concurrent NETDEV_UNREGISTER may have already unregistered our
12261237
* filters and cleared so->bound in isotp_notify() without waiting
@@ -1233,9 +1244,6 @@ static int isotp_release(struct socket *sock)
12331244
hrtimer_cancel(&so->txtimer);
12341245
hrtimer_cancel(&so->rxtimer);
12351246

1236-
so->ifindex = 0;
1237-
so->bound = 0;
1238-
12391247
sock_orphan(sk);
12401248
sock->sk = NULL;
12411249

@@ -1289,13 +1297,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len)
12891297
if (!addr->can_ifindex)
12901298
return -ENODEV;
12911299

1300+
rtnl_lock();
12921301
lock_sock(sk);
12931302

12941303
if (so->bound) {
12951304
err = -EINVAL;
12961305
goto out;
12971306
}
12981307

1308+
/* A transmission or reception that outlived a previous binding
1309+
* (unbound by NETDEV_UNREGISTER) may still be draining; the FC/echo
1310+
* and RX watchdog timers bound how long this takes. Checked together
1311+
* with so->bound in the same lock_sock() section above, so there is
1312+
* no window in which a concurrent isotp_notify() could be missed.
1313+
*/
1314+
if (so->tx.state != ISOTP_IDLE || so->rx.state != ISOTP_IDLE) {
1315+
err = -EAGAIN;
1316+
goto out;
1317+
}
1318+
12991319
/* ensure different CAN IDs when the rx_id is to be registered */
13001320
if (isotp_register_rxid(so) && rx_id == tx_id) {
13011321
err = -EADDRNOTAVAIL;
@@ -1308,14 +1328,12 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len)
13081328
goto out;
13091329
}
13101330
if (dev->type != ARPHRD_CAN) {
1311-
dev_put(dev);
13121331
err = -ENODEV;
1313-
goto out;
1332+
goto out_put_dev;
13141333
}
1315-
if (dev->mtu < so->ll.mtu) {
1316-
dev_put(dev);
1334+
if (READ_ONCE(dev->mtu) < so->ll.mtu) {
13171335
err = -EINVAL;
1318-
goto out;
1336+
goto out_put_dev;
13191337
}
13201338
if (!(dev->flags & IFF_UP))
13211339
notify_enetdown = 1;
@@ -1333,16 +1351,25 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len)
13331351
can_rx_register(net, dev, tx_id, SINGLE_MASK(tx_id),
13341352
isotp_rcv_echo, sk, "isotpe", sk);
13351353

1336-
dev_put(dev);
1337-
13381354
/* switch to new settings */
13391355
so->ifindex = ifindex;
13401356
so->rxid = rx_id;
13411357
so->txid = tx_id;
13421358
so->bound = 1;
13431359

1360+
/* bind() ok -> hold a reference for so->dev so that isotp_release()
1361+
* can safely reach the device later, even if a concurrent
1362+
* NETDEV_UNREGISTER has already unlisted it by ifindex.
1363+
*/
1364+
so->dev = dev;
1365+
dev_hold(so->dev);
1366+
1367+
out_put_dev:
1368+
/* remove potential reference from dev_get_by_index() */
1369+
dev_put(dev);
13441370
out:
13451371
release_sock(sk);
1372+
rtnl_unlock();
13461373

13471374
if (notify_enetdown) {
13481375
sk->sk_err = ENETDOWN;
@@ -1545,7 +1572,7 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,
15451572
if (!net_eq(dev_net(dev), sock_net(sk)))
15461573
return;
15471574

1548-
if (so->ifindex != dev->ifindex)
1575+
if (so->dev != dev)
15491576
return;
15501577

15511578
switch (msg) {
@@ -1561,10 +1588,12 @@ static void isotp_notify(struct isotp_sock *so, unsigned long msg,
15611588
can_rx_unregister(dev_net(dev), dev, so->txid,
15621589
SINGLE_MASK(so->txid),
15631590
isotp_rcv_echo, sk);
1591+
dev_put(so->dev);
15641592
}
15651593

15661594
so->ifindex = 0;
15671595
so->bound = 0;
1596+
so->dev = NULL;
15681597
release_sock(sk);
15691598

15701599
sk->sk_err = ENODEV;
@@ -1624,6 +1653,7 @@ static int isotp_init(struct sock *sk)
16241653

16251654
so->ifindex = 0;
16261655
so->bound = 0;
1656+
so->dev = NULL;
16271657

16281658
so->opt.flags = CAN_ISOTP_DEFAULT_FLAGS;
16291659
so->opt.ext_address = CAN_ISOTP_DEFAULT_EXT_ADDRESS;

0 commit comments

Comments
 (0)