mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
tipc: serialize udp bearer replicast list updates
tipc_udp_rcast_add() and cleanup_bearer() both update ub->rcast.list with
list_add_rcu() / list_del_rcu(), but nothing serializes them. The add runs
from the encap receive softirq (via tipc_udp_rcast_disc()) without
rtnl_lock(), so it can race the cleanup delete and corrupt the list:
list_del corruption. prev->next should be ffff8880298d7ab8,
but was ffff88802449ad38. (prev=ffff888027e3ec98)
kernel BUG at lib/list_debug.c:62!
RIP: __list_del_entry_valid_or_report+0x17a/0x200
Workqueue: events cleanup_bearer
Call Trace:
cleanup_bearer (net/tipc/udp_media.c:811)
process_one_work (kernel/workqueue.c:3302)
worker_thread (kernel/workqueue.c:3466)
The bearer can be enabled from an unprivileged user namespace, as the
TIPCv2 generic-netlink ops carry no GENL_ADMIN_PERM.
Add a spinlock to struct udp_bearer and take it around the list_add_rcu()
in tipc_udp_rcast_add() and the list_del_rcu() loop in cleanup_bearer() so
the two writers can no longer corrupt the list.
Reject a duplicate peer under the same lock before allocating, and remove
tipc_udp_is_known_peer(). The old lockless pre-check in
tipc_udp_rcast_disc() was racy: two softirqs discovering the same peer
could both find it absent and add it twice.
cleanup_bearer() runs from a workqueue after tipc_udp_disable() clears the
bearer's up bit, so an encap softirq can still reach tipc_udp_rcast_add()
and add a peer after cleanup_bearer() has already emptied the list, leaking
that entry when the bearer is freed. Mark the bearer disabled under
rcast_lock once the list is emptied and refuse further additions.
Fixes: ef20cd4dd1 ("tipc: introduce UDP replicast")
Reported-by: Xiang Mei <xmei5@asu.edu>
Suggested-by: Tung Nguyen <tung.quang.nguyen@est.tech>
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
Reviewed-by: Tung Nguyen <tung.quang.nguyen@est.tech>
Link: https://patch.msgid.link/20260716025203.9332-2-bestswngs@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
1c50efa1fa
commit
350e592ff4
|
|
@ -94,6 +94,8 @@ struct udp_replicast {
|
|||
* @ifindex: local address scope
|
||||
* @work: used to schedule deferred work on a bearer
|
||||
* @rcast: associated udp_replicast container
|
||||
* @rcast_lock: serialize updates to @rcast.list against concurrent updaters
|
||||
* @disabled: bearer is being torn down; reject further @rcast.list additions
|
||||
*/
|
||||
struct udp_bearer {
|
||||
struct tipc_bearer __rcu *bearer;
|
||||
|
|
@ -101,6 +103,8 @@ struct udp_bearer {
|
|||
u32 ifindex;
|
||||
struct work_struct work;
|
||||
struct udp_replicast rcast;
|
||||
spinlock_t rcast_lock;
|
||||
bool disabled;
|
||||
};
|
||||
|
||||
static int tipc_udp_is_mcast_addr(struct udp_media_addr *addr)
|
||||
|
|
@ -278,26 +282,6 @@ static int tipc_udp_send_msg(struct net *net, struct sk_buff *skb,
|
|||
return err;
|
||||
}
|
||||
|
||||
static bool tipc_udp_is_known_peer(struct tipc_bearer *b,
|
||||
struct udp_media_addr *addr)
|
||||
{
|
||||
struct udp_replicast *rcast, *tmp;
|
||||
struct udp_bearer *ub;
|
||||
|
||||
ub = rcu_dereference_rtnl(b->media_ptr);
|
||||
if (!ub) {
|
||||
pr_err_ratelimited("UDP bearer instance not found\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
list_for_each_entry_safe(rcast, tmp, &ub->rcast.list, list) {
|
||||
if (!memcmp(&rcast->addr, addr, sizeof(struct udp_media_addr)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int tipc_udp_rcast_add(struct tipc_bearer *b,
|
||||
struct udp_media_addr *addr)
|
||||
{
|
||||
|
|
@ -308,16 +292,34 @@ static int tipc_udp_rcast_add(struct tipc_bearer *b,
|
|||
if (!ub)
|
||||
return -ENODEV;
|
||||
|
||||
spin_lock_bh(&ub->rcast_lock);
|
||||
if (ub->disabled) {
|
||||
spin_unlock_bh(&ub->rcast_lock);
|
||||
return 0;
|
||||
}
|
||||
list_for_each_entry(rcast, &ub->rcast.list, list) {
|
||||
if (!memcmp(&rcast->addr, addr, sizeof(*addr))) {
|
||||
spin_unlock_bh(&ub->rcast_lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
rcast = kmalloc_obj(*rcast, GFP_ATOMIC);
|
||||
if (!rcast)
|
||||
if (!rcast) {
|
||||
spin_unlock_bh(&ub->rcast_lock);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (dst_cache_init(&rcast->dst_cache, GFP_ATOMIC)) {
|
||||
spin_unlock_bh(&ub->rcast_lock);
|
||||
kfree(rcast);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memcpy(&rcast->addr, addr, sizeof(struct udp_media_addr));
|
||||
list_add_rcu(&rcast->list, &ub->rcast.list);
|
||||
b->bcast_addr.broadcast = TIPC_REPLICAST_SUPPORT;
|
||||
spin_unlock_bh(&ub->rcast_lock);
|
||||
|
||||
if (ntohs(addr->proto) == ETH_P_IP)
|
||||
pr_info("New replicast peer: %pI4\n", &rcast->addr.ipv4);
|
||||
|
|
@ -325,8 +327,6 @@ static int tipc_udp_rcast_add(struct tipc_bearer *b,
|
|||
else if (ntohs(addr->proto) == ETH_P_IPV6)
|
||||
pr_info("New replicast peer: %pI6\n", &rcast->addr.ipv6);
|
||||
#endif
|
||||
b->bcast_addr.broadcast = TIPC_REPLICAST_SUPPORT;
|
||||
list_add_rcu(&rcast->list, &ub->rcast.list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -361,9 +361,6 @@ static int tipc_udp_rcast_disc(struct tipc_bearer *b, struct sk_buff *skb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (likely(tipc_udp_is_known_peer(b, &src)))
|
||||
return 0;
|
||||
|
||||
return tipc_udp_rcast_add(b, &src);
|
||||
}
|
||||
|
||||
|
|
@ -644,9 +641,6 @@ int tipc_udp_nl_bearer_add(struct tipc_bearer *b, struct nlattr *attr)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (tipc_udp_is_known_peer(b, &addr))
|
||||
return 0;
|
||||
|
||||
return tipc_udp_rcast_add(b, &addr);
|
||||
}
|
||||
|
||||
|
|
@ -679,6 +673,7 @@ static int tipc_udp_enable(struct net *net, struct tipc_bearer *b,
|
|||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&ub->rcast.list);
|
||||
spin_lock_init(&ub->rcast_lock);
|
||||
|
||||
if (!attrs[TIPC_NLA_BEARER_UDP_OPTS])
|
||||
goto err;
|
||||
|
|
@ -819,10 +814,13 @@ static void cleanup_bearer(struct work_struct *work)
|
|||
struct udp_replicast *rcast, *tmp;
|
||||
struct tipc_net *tn;
|
||||
|
||||
spin_lock_bh(&ub->rcast_lock);
|
||||
list_for_each_entry_safe(rcast, tmp, &ub->rcast.list, list) {
|
||||
list_del_rcu(&rcast->list);
|
||||
call_rcu_hurry(&rcast->rcu, rcast_free_rcu);
|
||||
}
|
||||
ub->disabled = true;
|
||||
spin_unlock_bh(&ub->rcast_lock);
|
||||
|
||||
tn = tipc_net(sock_net(ub->sk));
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user