netfilter: ipset: fix refcount race between list:set GC and swap
authorXiang Mei (Microsoft) <xmei5@asu.edu>
Wed, 22 Jul 2026 22:38:32 +0000 (22:38 +0000)
committerPablo Neira Ayuso <pablo@netfilter.org>
Mon, 10 Aug 2026 18:10:31 +0000 (20:10 +0200)
__ip_set_put_byindex() resolved the index to a set pointer under RCU,
then took ip_set_ref_lock in __ip_set_put() to decrement set->ref.
ip_set_swap() holds that same lock while swapping both the ip_set_list
slots and the two sets' ref counters, so it can interleave between the
dereference and the lock acquisition, leaving the caller to decrement a
set whose reference already moved to the other index and hit
BUG_ON(set->ref == 0). list_set_gc() reaches this from timer softirq,
which the nfnl mutex does not serialize against swap: an expiring
list:set member calls list_set_del() -> ip_set_put_byindex() while
IPSET_CMD_SWAP runs on the referenced sets.

Resolve the index and decrement under ip_set_ref_lock, as ip_set_swap()
already does, keeping the refcount tied to the index rather than to a
stale set pointer.

  kernel BUG at net/netfilter/ipset/ip_set_core.c:685!
  Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
  RIP: 0010:ip_set_put_byindex (net/netfilter/ipset/ip_set_core.c:870)
  Call Trace:
   <IRQ>
   list_set_del (net/netfilter/ipset/ip_set_list_set.c:159)
   set_cleanup_entries (net/netfilter/ipset/ip_set_list_set.c:181)
   list_set_gc (net/netfilter/ipset/ip_set_list_set.c:578)
   call_timer_fn (kernel/time/timer.c:1748)
   __run_timers (kernel/time/timer.c:1799 kernel/time/timer.c:2374)
   run_timer_softirq (kernel/time/timer.c:2405)
   </IRQ>
  Kernel panic - not syncing: Fatal exception in interrupt

Fixes: 9076aea76538 ("netfilter: ipset: Increase the number of maximal sets automatically")
Reported-by: AutonomousCodeSecurity@microsoft.com
Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
Acked-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
net/netfilter/ipset/ip_set_core.c

index 543851a..0a86a17 100644 (file)
@@ -680,11 +680,18 @@ __ip_set_get(struct ip_set *set)
 }
 
 static void
-__ip_set_put(struct ip_set *set)
+__ip_set_put_locked(struct ip_set *set)
 {
-       write_lock_bh(&ip_set_ref_lock);
+       lockdep_assert_held(&ip_set_ref_lock);
        BUG_ON(set->ref == 0);
        set->ref--;
+}
+
+static void
+__ip_set_put(struct ip_set *set)
+{
+       write_lock_bh(&ip_set_ref_lock);
+       __ip_set_put_locked(set);
        write_unlock_bh(&ip_set_ref_lock);
 }
 
@@ -855,11 +862,11 @@ __ip_set_put_byindex(struct ip_set_net *inst, ip_set_id_t index)
 {
        struct ip_set *set;
 
-       rcu_read_lock();
-       set = rcu_dereference(inst->ip_set_list)[index];
+       write_lock_bh(&ip_set_ref_lock);
+       set = ip_set(inst, index);
        if (set)
-               __ip_set_put(set);
-       rcu_read_unlock();
+               __ip_set_put_locked(set);
+       write_unlock_bh(&ip_set_ref_lock);
 }
 
 void