From: Eric Dumazet <edumazet@google.com> mainline inclusion from mainline-v7.2-rc3 commit 9b26518b6896a16b809b1e42986f4ebac7bccc1e category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/17356 CVE: CVE-2026-72322 Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?i... -------------------------------- A race condition exists between device teardown and incoming MLD query processing, leading to a Use-After-Free in the MLD delayed work. During device destruction, the primary reference to inet6_dev is dropped, which can drop its refcount to 0. The actual freeing of inet6_dev memory is deferred via RCU. Concurrently, the packet receive path runs under RCU read lock and obtains the inet6_dev pointer. Because the memory is RCU-protected, CPU-0 can safely dereference inet6_dev even if its refcount has hit 0. However, if CPU-0 calls igmp6_event_query() and schedules delayed work, it attempts to acquire a reference using in6_dev_hold(). This increments the refcount from 0 to 1, triggering a "refcount_t: addition on 0" warning. Since the inet6_dev memory is still scheduled to be freed after the RCU grace period, the device is freed while the work is still scheduled. When the work runs, it accesses the freed memory, causing a kernel panic. Fix this by using refcount_inc_not_zero() (via a new helper in6_dev_hold_safe()) to prevent acquiring a reference if the device is already being destroyed. If the refcount is 0, we do not schedule the work. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Eric Dumazet <edumazet@google.com> Reviewed-by: Ido Schimmel <idosch@nvidia.com> Link: https://patch.msgid.link/20260705181756.963063-3-edumazet@google.com Signed-off-by: Paolo Abeni <pabeni@redhat.com> Conflicts: include/net/addrconf.h net/ipv6/mcast.c [addrconf.h: the upstream hunk context after in6_dev_hold() references ip6_ignore_linkdown(), which does not exist in 4.19, so only the new in6_dev_hold_safe() helper is added and the ip6_ignore_linkdown() context is dropped. mcast.c: 4.19 use timers (mld_gq_start_timer/mld_ifc_start_timer/ mld_dad_start_timer + mod_timer) instead of workqueue (mld_*_start_work + mod_delayed_work); igmp6_event_query/report have no workqueue queues, so those hunks are not applicable. The refcount fix is applied to the three _start_timer functions instead, preserving the upstream semantics.In 4.19, the `igmp6_event_query/report` hunks are dropped entirely (sync implementation, no workqueue queues); the target's existing refcount sites are already covered by the three `_start_timer` fixes.] Signed-off-by: JiangJieHua <jiangjiehua1@huawei.com> --- include/net/addrconf.h | 5 +++++ net/ipv6/mcast.c | 18 ++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/include/net/addrconf.h b/include/net/addrconf.h index 10d270f004f0d..26f0a5bc234be 100644 --- a/include/net/addrconf.h +++ b/include/net/addrconf.h @@ -425,6 +425,11 @@ static inline void in6_dev_hold(struct inet6_dev *idev) refcount_inc(&idev->refcnt); } +static inline bool in6_dev_hold_safe(struct inet6_dev *idev) +{ + return refcount_inc_not_zero(&idev->refcnt); +} + void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp); static inline void in6_ifa_put(struct inet6_ifaddr *ifp) diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index bb484fe27e137..787800af097a5 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -1037,8 +1037,10 @@ static void mld_gq_start_timer(struct inet6_dev *idev) unsigned long tv = prandom_u32() % idev->mc_maxdelay; idev->mc_gq_running = 1; - if (!mod_timer(&idev->mc_gq_timer, jiffies+tv+2)) - in6_dev_hold(idev); + if (in6_dev_hold_safe(idev)) { + if (mod_timer(&idev->mc_gq_timer, jiffies+tv+2)) + in6_dev_put(idev); + } } static void mld_gq_stop_timer(struct inet6_dev *idev) @@ -1052,8 +1054,10 @@ static void mld_ifc_start_timer(struct inet6_dev *idev, unsigned long delay) { unsigned long tv = prandom_u32() % delay; - if (!mod_timer(&idev->mc_ifc_timer, jiffies+tv+2)) - in6_dev_hold(idev); + if (in6_dev_hold_safe(idev)) { + if (mod_timer(&idev->mc_ifc_timer, jiffies+tv+2)) + in6_dev_put(idev); + } } static void mld_ifc_stop_timer(struct inet6_dev *idev) @@ -1067,8 +1071,10 @@ static void mld_dad_start_timer(struct inet6_dev *idev, unsigned long delay) { unsigned long tv = prandom_u32() % delay; - if (!mod_timer(&idev->mc_dad_timer, jiffies+tv+2)) - in6_dev_hold(idev); + if (in6_dev_hold_safe(idev)) { + if (mod_timer(&idev->mc_dad_timer, jiffies+tv+2)) + in6_dev_put(idev); + } } static void mld_dad_stop_timer(struct inet6_dev *idev) -- 2.33.8