mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-21 23:57:36 -04:00
tipc: fix use-after-free of the discoverer in tipc_disc_rcv()
bearer_disable() frees b->disc with tipc_disc_delete()'s plain kfree(),
but tipc_disc_rcv() still dereferences b->disc in RX softirq under
rcu_read_lock() (tipc_udp_recv -> tipc_rcv -> tipc_disc_rcv).
L2 bearers are safe thanks to the synchronize_net() in
tipc_disable_l2_media(), but the UDP bearer defers that call to the
cleanup_bearer() workqueue, so the discoverer is freed with no grace
period:
BUG: KASAN: slab-use-after-free in tipc_disc_rcv (net/tipc/discover.c:149)
Read of size 8 at addr ffff88802348b728 by task poc_tipc/184
<IRQ>
tipc_disc_rcv (net/tipc/discover.c:149)
tipc_rcv (net/tipc/node.c:2126)
tipc_udp_recv (net/tipc/udp_media.c:391)
udp_rcv (net/ipv4/udp.c:2643)
ip_local_deliver_finish (net/ipv4/ip_input.c:241)
</IRQ>
Freed by task 181:
kfree (mm/slub.c:6565)
bearer_disable (net/tipc/bearer.c:418)
tipc_nl_bearer_disable (net/tipc/bearer.c:1001)
The bearer is freed with kfree_rcu(); free the discoverer the same way.
Add an rcu_head to struct tipc_discoverer and free it and its skb from an
RCU callback.
Because the RCU callback (tipc_disc_free_rcu) lives in module text, a
call_rcu() that is still pending when the tipc module is unloaded would
invoke a freed function. Add an rcu_barrier() to tipc_exit() after the
bearer subsystem has been torn down, so all pending discoverer callbacks
have run before the module text goes away.
Reachable from an unprivileged user namespace: the TIPCv2 genl family is
netnsok and its bearer commands have no GENL_ADMIN_PERM. Needs CONFIG_TIPC
and CONFIG_TIPC_MEDIA_UDP.
Fixes: 25b0b9c4e8 ("tipc: handle collisions of 32-bit node address hash values")
Reported-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
Reviewed-by: Tung Nguyen <tung.quang.nguyen@est.tech>
Link: https://patch.msgid.link/20260617135744.3383175-3-bestswngs@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
committed by
Jakub Kicinski
parent
62b01f72d9
commit
1579342d71
@@ -218,6 +218,11 @@ static void __exit tipc_exit(void)
|
||||
unregister_pernet_device(&tipc_net_ops);
|
||||
tipc_unregister_sysctl();
|
||||
|
||||
/* TODO: Wait for all timers that called call_rcu() to finish before
|
||||
* calling rcu_barrier().
|
||||
*/
|
||||
rcu_barrier();
|
||||
|
||||
pr_info("Deactivated\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
* @skb: request message to be (repeatedly) sent
|
||||
* @timer: timer governing period between requests
|
||||
* @timer_intv: current interval between requests (in ms)
|
||||
* @rcu: RCU head for deferred freeing
|
||||
*/
|
||||
struct tipc_discoverer {
|
||||
u32 bearer_id;
|
||||
@@ -69,6 +70,7 @@ struct tipc_discoverer {
|
||||
struct sk_buff *skb;
|
||||
struct timer_list timer;
|
||||
unsigned long timer_intv;
|
||||
struct rcu_head rcu;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -382,6 +384,15 @@ int tipc_disc_create(struct net *net, struct tipc_bearer *b,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tipc_disc_free_rcu(struct rcu_head *rp)
|
||||
{
|
||||
struct tipc_discoverer *d = container_of(rp, struct tipc_discoverer,
|
||||
rcu);
|
||||
|
||||
kfree_skb(d->skb);
|
||||
kfree(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* tipc_disc_delete - destroy object sending periodic link setup requests
|
||||
* @d: ptr to link dest structure
|
||||
@@ -389,8 +400,7 @@ int tipc_disc_create(struct net *net, struct tipc_bearer *b,
|
||||
void tipc_disc_delete(struct tipc_discoverer *d)
|
||||
{
|
||||
timer_shutdown_sync(&d->timer);
|
||||
kfree_skb(d->skb);
|
||||
kfree(d);
|
||||
call_rcu(&d->rcu, tipc_disc_free_rcu);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user