Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

atm: lec: fix use-after-free in sock_def_readable()

A race condition exists between lec_atm_close() setting priv->lecd
to NULL and concurrent access to priv->lecd in send_to_lecd(),
lec_handle_bridge(), and lec_atm_send(). When the socket is freed
via RCU while another thread is still using it, a use-after-free
occurs in sock_def_readable() when accessing the socket's wait queue.

The root cause is that lec_atm_close() clears priv->lecd without
any synchronization, while callers dereference priv->lecd without
any protection against concurrent teardown.

Fix this by converting priv->lecd to an RCU-protected pointer:
- Mark priv->lecd as __rcu in lec.h
- Use rcu_assign_pointer() in lec_atm_close() and lecd_attach()
for safe pointer assignment
- Use rcu_access_pointer() for NULL checks that do not dereference
the pointer in lec_start_xmit(), lec_push(), send_to_lecd() and
lecd_attach()
- Use rcu_read_lock/rcu_dereference/rcu_read_unlock in send_to_lecd(),
lec_handle_bridge() and lec_atm_send() to safely access lecd
- Use rcu_assign_pointer() followed by synchronize_rcu() in
lec_atm_close() to ensure all readers have completed before
proceeding. This is safe since lec_atm_close() is called from
vcc_release() which holds lock_sock(), a sleeping lock.
- Remove the manual sk_receive_queue drain from lec_atm_close()
since vcc_destroy_socket() already drains it after lec_atm_close()
returns.

v2: Switch from spinlock + sock_hold/put approach to RCU to properly
fix the race. The v1 spinlock approach had two issues pointed out
by Eric Dumazet:
1. priv->lecd was still accessed directly after releasing the
lock instead of using a local copy.
2. The spinlock did not prevent packets being queued after
lec_atm_close() drains sk_receive_queue since timer and
workqueue paths bypass netif_stop_queue().

Note: Syzbot patch testing was attempted but the test VM terminated
unexpectedly with "Connection to localhost closed by remote host",
likely due to a QEMU AHCI emulation issue unrelated to this fix.
Compile testing with "make W=1 net/atm/lec.o" passes cleanly.

Reported-by: syzbot+f50072212ab792c86925@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=f50072212ab792c86925
Link: https://lore.kernel.org/all/20260309093614.502094-1-kartikey406@gmail.com/T/ [v1]
Signed-off-by: Deepanshu Kartikey <kartikey406@gmail.com>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Link: https://patch.msgid.link/20260309155908.508768-1-kartikey406@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Deepanshu Kartikey and committed by
Jakub Kicinski
92281487 99600f79

+48 -26
+47 -25
net/atm/lec.c
··· 154 154 /* 0x01 is topology change */ 155 155 156 156 priv = netdev_priv(dev); 157 - atm_force_charge(priv->lecd, skb2->truesize); 158 - sk = sk_atm(priv->lecd); 159 - skb_queue_tail(&sk->sk_receive_queue, skb2); 160 - sk->sk_data_ready(sk); 157 + struct atm_vcc *vcc; 158 + 159 + rcu_read_lock(); 160 + vcc = rcu_dereference(priv->lecd); 161 + if (vcc) { 162 + atm_force_charge(vcc, skb2->truesize); 163 + sk = sk_atm(vcc); 164 + skb_queue_tail(&sk->sk_receive_queue, skb2); 165 + sk->sk_data_ready(sk); 166 + } else { 167 + dev_kfree_skb(skb2); 168 + } 169 + rcu_read_unlock(); 161 170 } 162 171 } 163 172 #endif /* IS_ENABLED(CONFIG_BRIDGE) */ ··· 225 216 int is_rdesc; 226 217 227 218 pr_debug("called\n"); 228 - if (!priv->lecd) { 219 + if (!rcu_access_pointer(priv->lecd)) { 229 220 pr_info("%s:No lecd attached\n", dev->name); 230 221 dev->stats.tx_errors++; 231 222 netif_stop_queue(dev); ··· 458 449 break; 459 450 skb2->len = sizeof(struct atmlec_msg); 460 451 skb_copy_to_linear_data(skb2, mesg, sizeof(*mesg)); 461 - atm_force_charge(priv->lecd, skb2->truesize); 462 - sk = sk_atm(priv->lecd); 463 - skb_queue_tail(&sk->sk_receive_queue, skb2); 464 - sk->sk_data_ready(sk); 452 + struct atm_vcc *vcc; 453 + 454 + rcu_read_lock(); 455 + vcc = rcu_dereference(priv->lecd); 456 + if (vcc) { 457 + atm_force_charge(vcc, skb2->truesize); 458 + sk = sk_atm(vcc); 459 + skb_queue_tail(&sk->sk_receive_queue, skb2); 460 + sk->sk_data_ready(sk); 461 + } else { 462 + dev_kfree_skb(skb2); 463 + } 464 + rcu_read_unlock(); 465 465 } 466 466 } 467 467 #endif /* IS_ENABLED(CONFIG_BRIDGE) */ ··· 486 468 487 469 static void lec_atm_close(struct atm_vcc *vcc) 488 470 { 489 - struct sk_buff *skb; 490 471 struct net_device *dev = (struct net_device *)vcc->proto_data; 491 472 struct lec_priv *priv = netdev_priv(dev); 492 473 493 - priv->lecd = NULL; 474 + rcu_assign_pointer(priv->lecd, NULL); 475 + synchronize_rcu(); 494 476 /* Do something needful? */ 495 477 496 478 netif_stop_queue(dev); 497 479 lec_arp_destroy(priv); 498 - 499 - if (skb_peek(&sk_atm(vcc)->sk_receive_queue)) 500 - pr_info("%s closing with messages pending\n", dev->name); 501 - while ((skb = skb_dequeue(&sk_atm(vcc)->sk_receive_queue))) { 502 - atm_return(vcc, skb->truesize); 503 - dev_kfree_skb(skb); 504 - } 505 480 506 481 pr_info("%s: Shut down!\n", dev->name); 507 482 module_put(THIS_MODULE); ··· 521 510 const unsigned char *mac_addr, const unsigned char *atm_addr, 522 511 struct sk_buff *data) 523 512 { 513 + struct atm_vcc *vcc; 524 514 struct sock *sk; 525 515 struct sk_buff *skb; 526 516 struct atmlec_msg *mesg; 527 517 528 - if (!priv || !priv->lecd) 518 + if (!priv || !rcu_access_pointer(priv->lecd)) 529 519 return -1; 520 + 530 521 skb = alloc_skb(sizeof(struct atmlec_msg), GFP_ATOMIC); 531 522 if (!skb) 532 523 return -1; ··· 545 532 if (atm_addr) 546 533 memcpy(&mesg->content.normal.atm_addr, atm_addr, ATM_ESA_LEN); 547 534 548 - atm_force_charge(priv->lecd, skb->truesize); 549 - sk = sk_atm(priv->lecd); 535 + rcu_read_lock(); 536 + vcc = rcu_dereference(priv->lecd); 537 + if (!vcc) { 538 + rcu_read_unlock(); 539 + kfree_skb(skb); 540 + return -1; 541 + } 542 + 543 + atm_force_charge(vcc, skb->truesize); 544 + sk = sk_atm(vcc); 550 545 skb_queue_tail(&sk->sk_receive_queue, skb); 551 546 sk->sk_data_ready(sk); 552 547 553 548 if (data != NULL) { 554 549 pr_debug("about to send %d bytes of data\n", data->len); 555 - atm_force_charge(priv->lecd, data->truesize); 550 + atm_force_charge(vcc, data->truesize); 556 551 skb_queue_tail(&sk->sk_receive_queue, data); 557 552 sk->sk_data_ready(sk); 558 553 } 559 554 555 + rcu_read_unlock(); 560 556 return 0; 561 557 } 562 558 ··· 640 618 641 619 atm_return(vcc, skb->truesize); 642 620 if (*(__be16 *) skb->data == htons(priv->lecid) || 643 - !priv->lecd || !(dev->flags & IFF_UP)) { 621 + !rcu_access_pointer(priv->lecd) || !(dev->flags & IFF_UP)) { 644 622 /* 645 623 * Probably looping back, or if lecd is missing, 646 624 * lecd has gone down ··· 775 753 priv = netdev_priv(dev_lec[i]); 776 754 } else { 777 755 priv = netdev_priv(dev_lec[i]); 778 - if (priv->lecd) 756 + if (rcu_access_pointer(priv->lecd)) 779 757 return -EADDRINUSE; 780 758 } 781 759 lec_arp_init(priv); 782 760 priv->itfnum = i; /* LANE2 addition */ 783 - priv->lecd = vcc; 761 + rcu_assign_pointer(priv->lecd, vcc); 784 762 vcc->dev = &lecatm_dev; 785 763 vcc_insert_socket(sk_atm(vcc)); 786 764
+1 -1
net/atm/lec.h
··· 91 91 */ 92 92 spinlock_t lec_arp_lock; 93 93 struct atm_vcc *mcast_vcc; /* Default Multicast Send VCC */ 94 - struct atm_vcc *lecd; 94 + struct atm_vcc __rcu *lecd; 95 95 struct delayed_work lec_arp_work; /* C10 */ 96 96 unsigned int maximum_unknown_frame_count; 97 97 /*