Subject: [PATCH AUTOSEL 7.0-6.6] ipv6: move IFA_F_PERMANENT percpu allocation in process scope

From: Sasha Levin <[email-masked]>
Date: Mon, 20 Apr 2026 09:17:24 -0400

From: Paolo Abeni <[email-masked]>

[ Upstream commit 8e6405f8218b3f412d36b772318e94d589513eba ]

Observed at boot time:

 CPU: 43 UID: 0 PID: 3595 Comm: (t-daemon) Not tainted 6.12.0 #1
 Call Trace:
  <TASK>
  dump_stack_lvl+0x4e/0x70
  pcpu_alloc_noprof.cold+0x1f/0x4b
  fib_nh_common_init+0x4c/0x110
  fib6_nh_init+0x387/0x740
  ip6_route_info_create+0x46d/0x640
  addrconf_f6i_alloc+0x13b/0x180
  addrconf_permanent_addr+0xd0/0x220
  addrconf_notify+0x93/0x540
  notifier_call_chain+0x5a/0xd0
  __dev_notify_flags+0x5c/0xf0
  dev_change_flags+0x54/0x70
  do_setlink+0x36c/0xce0
  rtnl_setlink+0x11f/0x1d0
  rtnetlink_rcv_msg+0x142/0x3f0
  netlink_rcv_skb+0x50/0x100
  netlink_unicast+0x242/0x390
  netlink_sendmsg+0x21b/0x470
  __sys_sendto+0x1dc/0x1f0
  __x64_sys_sendto+0x24/0x30
  do_syscall_64+0x7d/0x160
  entry_SYSCALL_64_after_hwframe+0x76/0x7e
 RIP: 0033:0x7f5c3852f127
 Code: 0c 00 f7 d8 64 89 02 48 c7 c0 ff ff ff ff eb b8 0f 1f 00 f3 0f 1e fa 80 3d 85 ef 0c 00 00 41 89 ca 74 10 b8 2c 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 71 c3 55 48 83 ec 30 44 89 4c 24 2c 4c 89 44
 RSP: 002b:00007ffe86caf4c8 EFLAGS: 00000202 ORIG_RAX: 000000000000002c
 RAX: ffffffffffffffda RBX: 0000556c5cd93210 RCX: 00007f5c3852f127
 RDX: 0000000000000020 RSI: 0000556c5cd938b0 RDI: 0000000000000003
 RBP: 00007ffe86caf5a0 R08: 00007ffe86caf4e0 R09: 0000000000000080
 R10: 0000000000000000 R11: 0000000000000202 R12: 0000556c5cd932d0
 R13: 00000000021d05d1 R14: 00000000021d05d1 R15: 0000000000000001

IFA_F_PERMANENT addresses require the allocation of a bunch of percpu
pointers, currently in atomic scope.

Similar to commit 51454ea42c1a ("ipv6: fix locking issues with loops
over idev->addr_list"), move fixup_permanent_addr() outside the
&idev->lock scope, and do the allocations with GFP_KERNEL. With such
change fixup_permanent_addr() is invoked with the BH enabled, and the
ifp lock acquired there needs the BH variant.

Note that we don't need to acquire a reference to the permanent
addresses before releasing the mentioned write lock, because
addrconf_permanent_addr() runs under RTNL and ifa removal always happens
under RTNL, too.

Also the PERMANENT flag is constant in the relevant scope, as it can be
cleared only by inet6_addr_modify() under the RTNL lock.

Reviewed-by: David Ahern <[email-masked]>
Signed-off-by: Paolo Abeni <[email-masked]>
Link: https://patch.msgid.link/[email-masked]
Signed-off-by: Jakub Kicinski <[email-masked]>
Signed-off-by: Sasha Levin <[email-masked]>
---

LLM Generated explanations, may be completely bogus:

Now I have all the information needed. Let me compile my analysis.

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: PARSE THE SUBJECT LINE
- **Subsystem**: ipv6 (networking)
- **Action verb**: "move" - restructuring allocation scope
- **Summary**: Move IFA_F_PERMANENT percpu allocation from atomic to
  process (GFP_KERNEL) scope
- Record: [ipv6] [move] [Change percpu allocation from GFP_ATOMIC to
  GFP_KERNEL by restructuring lock scope]

### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS
- **Reviewed-by**: David Ahern <[email-masked]> - former networking
  subsystem maintainer, very authoritative
- **Signed-off-by**: Paolo Abeni <[email-masked]> - current net-next
  maintainer, author
- **Link**: https://patch.msgid.link/46a7a030727e236af2dc7752994cd4f04f4
  [email-masked]
- **Signed-off-by**: Jakub Kicinski <[email-masked]> - committer,
  net/net-next maintainer
- No Fixes: tag (expected for candidates)
- No Cc: stable (expected)
- Record: Reviewed by David Ahern, authored by Paolo Abeni (net-next co-
  maintainer), committed by Jakub Kicinski. Applied to net-next (not
  net).

### Step 1.3: ANALYZE THE COMMIT BODY TEXT
- **Bug described**: At boot time, `pcpu_alloc_noprof.cold` is triggered
  during IPv6 permanent address route setup. This is the cold
  (warning/failure) path of per-cpu allocation.
- **Symptom**: GFP_ATOMIC percpu allocation failure when setting up
  permanent IPv6 addresses during NETDEV_UP handling. The call trace
  shows: `addrconf_permanent_addr -> fixup_permanent_addr ->
  addrconf_f6i_alloc -> ip6_route_info_create -> fib6_nh_init ->
  fib_nh_common_init -> pcpu_alloc_noprof.cold`
- **Root cause**: `addrconf_permanent_addr()` holds `idev->lock` (write
  spinlock with BH disabled) while calling `fixup_permanent_addr()`,
  forcing GFP_ATOMIC for all allocations inside. Per-cpu allocations
  with GFP_ATOMIC are unreliable, especially on systems with many CPUs.
- **Kernel version**: Observed on 6.12.0 with 43+ CPUs
- Record: Real boot-time allocation failure. IPv6 permanent address
  setup fails when percpu allocation with GFP_ATOMIC fails, causing the
  address to be dropped.

### Step 1.4: DETECT HIDDEN BUG FIXES
This IS a bug fix despite being described as "move". When GFP_ATOMIC
percpu allocation fails, `fixup_permanent_addr()` returns an error, and
`addrconf_permanent_addr()` then DROPS the IPv6 address
(`ipv6_del_addr`). Users lose permanent IPv6 addresses at boot.
- Record: Yes, this is a real bug fix. The "move" language hides the
  fact that GFP_ATOMIC failures cause IPv6 addresses to be lost.

## PHASE 2: DIFF ANALYSIS

### Step 2.1: INVENTORY THE CHANGES
- **File**: `net/ipv6/addrconf.c` - 19 lines added, 12 removed (net +7)
- **Functions modified**: `fixup_permanent_addr()` and
  `addrconf_permanent_addr()`
- **Scope**: Single-file, well-contained change in two related functions
- Record: Single file, ~31 lines total change, two functions in same
  call chain.

### Step 2.2: UNDERSTAND THE CODE FLOW CHANGE
**Hunk 1** (`fixup_permanent_addr`):
- Before: GFP_ATOMIC for route allocation, plain spin_lock/unlock for
  ifp->lock
- After: GFP_KERNEL for route allocation, spin_lock_bh/unlock_bh (needed
  because BH is now enabled)
- GFP_ATOMIC -> GFP_KERNEL in both `addrconf_f6i_alloc()` and
  `addrconf_prefix_route()` calls

**Hunk 2** (`addrconf_permanent_addr`):
- Before: Holds `idev->lock` throughout iteration and calls
  `fixup_permanent_addr()` inside the lock
- After: Builds temporary list of PERMANENT addresses while holding
  lock, releases lock, then iterates temporary list calling
  `fixup_permanent_addr()` without lock held
- Uses existing `if_list_aux` infrastructure (same pattern as commit
  51454ea42c1a)
- Adds ASSERT_RTNL() for safety

### Step 2.3: IDENTIFY THE BUG MECHANISM
**Category**: Allocation failure in atomic context / resource setup
failure
- The bug is that percpu allocations (via `alloc_percpu_gfp()` in
  `fib_nh_common_init()`) with GFP_ATOMIC can fail, especially on high-
  CPU-count systems
- When the allocation fails, the permanent IPv6 address is dropped
- The fix moves the work outside the spinlock so GFP_KERNEL can be used
- Record: Allocation failure bug. GFP_ATOMIC percpu allocation in
  fib_nh_common_init fails -> route creation fails -> permanent IPv6
  address dropped.

### Step 2.4: ASSESS THE FIX QUALITY
- **Obviously correct**: Yes - the if_list_aux pattern is proven
  (already used in `addrconf_ifdown` and `dev_forward_change`)
- **Minimal/surgical**: Yes - single file, two functions, well-contained
- **Regression risk**: Low - the lock restructuring is safe per RTNL
  protection. The spin_lock -> spin_lock_bh change is correct because BH
  is now enabled.
- **Red flags**: None. The locking argument is well-explained in the
  commit message (RTNL protects against concurrent ifa removal).
- Record: High quality fix. Proven pattern, correct BH handling, well-
  documented safety argument.

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: BLAME THE CHANGED LINES
- `fixup_permanent_addr()` introduced by f1705ec197e7 (Feb 2016, "net:
  ipv6: Make address flushing on ifdown optional") in v4.5
- The buggy GFP_ATOMIC has been present since this code was created
- `addrconf_permanent_addr()` also from the same commit
- Record: Buggy code introduced in v4.5 (f1705ec197e7, 2016). Present in
  ALL stable trees.

### Step 3.2: FOLLOW THE FIXES TAG
No Fixes: tag present (expected).

### Step 3.3: CHECK FILE HISTORY
- fd63f185979b0 ("ipv6: prevent possible UaF in
  addrconf_permanent_addr()") is a prerequisite - already in v7.0
- 51454ea42c1a ("ipv6: fix locking issues with loops over
  idev->addr_list") introduced the if_list_aux pattern - in v5.19+
- Record: Two prerequisites identified, both present in v7.0.

### Step 3.4: CHECK THE AUTHOR
- Paolo Abeni is the net-next co-maintainer - maximum authority for
  networking code
- David Ahern reviewed it - he's the original author of much of this
  code
- Record: Author is subsystem co-maintainer. Reviewer is the original
  code author.

### Step 3.5: CHECK FOR DEPENDENCIES
- Requires `if_list_aux` field in `inet6_ifaddr` (from commit
  51454ea42c1a, v5.19+) - present in v7.0
- Requires fd63f185979b0 UaF fix (already in v7.0)
- Requires `d465bd07d16e3` gfp_flags passdown through
  `ip6_route_info_create_nh()` - present in v7.0
- The diff applies cleanly against v7.0 (verified)
- Record: All dependencies satisfied in v7.0. Clean apply confirmed.

## PHASE 4: MAILING LIST RESEARCH

### Step 4.1: ORIGINAL PATCH DISCUSSION
- Found via b4 am: Applied to netdev/net-next.git (main) as commit
  8e6405f8218b
- This is v2 of the patch (v1 was the initial UaF fix that became
  fd63f185979b0)
- Applied by Jakub Kicinski
- Submitted to net-next, not net
- Record: v2 patch, applied to net-next. Upstream commit is
  8e6405f8218b.

### Step 4.2: REVIEWERS
- Paolo Abeni (author, net-next co-maintainer)
- David Ahern (reviewer, original code author)
- Jakub Kicinski (committer, net maintainer)
- All key networking maintainers involved
- Record: Maximum authority review chain.

### Step 4.3: BUG REPORT
- The stack trace in the commit is from a real system (6.12.0, 43+ CPUs)
- `pcpu_alloc_noprof.cold` is the failure/warning path for percpu
  allocations
- Record: Real-world observation on production system.

### Step 4.4: SERIES CONTEXT
- This is standalone (v2 of a single patch), not part of a multi-patch
  series
- Record: Standalone fix.

### Step 4.5: STABLE DISCUSSION
- No specific stable discussion found
- Note: applied to net-next, not net, suggesting author didn't consider
  it urgent

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1-5.4: FUNCTION ANALYSIS
- `addrconf_permanent_addr()` is called from `addrconf_notify()` on
  `NETDEV_UP` events
- This is the boot-time path for restoring permanent IPv6 addresses when
  interfaces come up
- Call chain: `addrconf_notify() -> addrconf_permanent_addr() ->
  fixup_permanent_addr() -> addrconf_f6i_alloc() -> ... ->
  fib_nh_common_init() -> alloc_percpu_gfp()`
- The allocation in `fib_nh_common_init()` is `alloc_percpu_gfp(struct
  rtable __rcu *, gfp_flags)` - this allocates per-CPU pointers
- On high-CPU systems, percpu allocations are larger and more likely to
  fail with GFP_ATOMIC
- This path runs on every NETDEV_UP event for every interface
- Record: Code is in a common boot path. Allocation failure causes
  permanent address loss.

## PHASE 6: STABLE TREE ANALYSIS

### Step 6.1: BUGGY CODE IN STABLE TREES
- The buggy GFP_ATOMIC code exists since v4.5 (f1705ec197e7)
- Present in ALL active stable trees
- Record: Bug present in all stable trees from v4.5 onward.

### Step 6.2: BACKPORT COMPLICATIONS
- For 7.0: Clean apply (verified via `git diff v7.0 8e6405f8218b`)
- For 6.12 and older: Would need checking for gfp_flags passdown chain
- Record: Clean apply for 7.0.y. May need adjustment for older trees.

### Step 6.3: RELATED FIXES IN STABLE
- None found for this specific GFP_ATOMIC issue
- Record: No related fix already in stable.

## PHASE 7: SUBSYSTEM CONTEXT

### Step 7.1: SUBSYSTEM CRITICALITY
- **Subsystem**: net/ipv6 (networking, IPv6 address configuration)
- **Criticality**: IMPORTANT - IPv6 connectivity affects many users,
  especially on servers
- Record: IMPORTANT subsystem. IPv6 permanent address loss at boot
  affects server connectivity.

### Step 7.2: SUBSYSTEM ACTIVITY
- `net/ipv6/addrconf.c` has 106+ commits between v6.6 and v7.0
- Actively maintained area
- Record: Very active subsystem.

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: WHO IS AFFECTED
- Systems with many CPUs (43+ shown in trace) using IPv6 permanent
  addresses
- More likely on servers/enterprise systems
- Record: Affects multi-CPU systems with IPv6, primarily servers.

### Step 8.2: TRIGGER CONDITIONS
- Triggered at boot time during interface bring-up (NETDEV_UP)
- Also triggered whenever `rtnl_setlink` brings an interface up
- More likely under memory pressure or on high-CPU-count systems
- Record: Triggered at boot/interface-up. More common on high-CPU
  systems.

### Step 8.3: FAILURE MODE SEVERITY
- When triggered: permanent IPv6 address is DROPPED from the interface
- This means IPv6 connectivity loss for that address
- Not a crash, but an operational failure (lost connectivity)
- Record: Severity HIGH - IPv6 address loss leads to connectivity
  failure.

### Step 8.4: RISK-BENEFIT RATIO
- **Benefit**: Prevents IPv6 address loss on multi-CPU systems at boot
- **Risk**: Low - proven pattern (if_list_aux), well-reviewed, single
  file
- Record: Benefit HIGH / Risk LOW = favorable ratio.

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: EVIDENCE COMPILATION

**FOR backporting:**
- Fixes real boot-time IPv6 address loss on multi-CPU systems
- Stack trace from a real 6.12.0 deployment
- Written by net-next co-maintainer, reviewed by original code author
- Uses proven if_list_aux pattern already in the same file
- Single file, ~31 lines, well-contained
- Bug present since v4.5 - affects all stable trees
- Clean apply against v7.0

**AGAINST backporting:**
- Applied to net-next, not net (author didn't consider it critical)
- No Fixes: tag or Cc: stable from author
- Structural change (lock restructuring), not a one-line fix
- Not a crash - "just" drops IPv6 addresses when allocation fails

**UNRESOLVED:**
- Exact failure rate on real systems unknown (depends on CPU count and
  memory state)
- Could not access lore.kernel.org for full review discussion (Anubis
  protection)

### Step 9.2: STABLE RULES CHECKLIST
1. Obviously correct and tested? **YES** - proven pattern, reviewed by
   original code author and subsystem maintainer
2. Fixes a real bug? **YES** - GFP_ATOMIC percpu allocation failure
   causes IPv6 address loss
3. Important issue? **YES** - IPv6 connectivity loss at boot on multi-
   CPU systems
4. Small and contained? **YES** - single file, ~31 lines, two functions
   in same call chain
5. No new features or APIs? **YES** - pure internal restructuring
6. Can apply to stable? **YES** - clean apply to v7.0 verified

### Step 9.3: EXCEPTION CATEGORIES
Not an exception category - this is a standard bug fix.

### Step 9.4: DECISION
The fix addresses a real operational issue (IPv6 permanent address loss
at boot due to GFP_ATOMIC percpu allocation failure). While it was
routed to net-next rather than net, the bug has real-world impact on
multi-CPU systems. The fix is well-reviewed by the most authoritative
people for this code, uses a proven pattern, and applies cleanly to
v7.0.

## Verification

- [Phase 1] Parsed tags: Reviewed-by David Ahern, Signed-off-by Paolo
  Abeni and Jakub Kicinski. No Fixes/Cc-stable (expected).
- [Phase 2] Diff analysis: GFP_ATOMIC -> GFP_KERNEL in
  fixup_permanent_addr(), lock restructuring in
  addrconf_permanent_addr() using proven if_list_aux pattern.
- [Phase 3] git blame: Code introduced by f1705ec197e7 (v4.5, 2016). Bug
  present since v4.5.
- [Phase 3] git merge-base: 51454ea42c1a (if_list_aux) in v5.19+,
  fd63f185979b0 (UaF fix) in v7.0, d465bd07d16e3 (gfp passdown) in v7.0.
- [Phase 3] git diff v7.0 8e6405f8218b: Clean apply confirmed.
- [Phase 4] b4 am: Found upstream commit 8e6405f8218b, applied to net-
  next by Jakub Kicinski. v2 patch.
- [Phase 4] Mailing list thread: Confirmed patchwork-bot shows clean
  application to netdev/net-next.git.
- [Phase 4] b4 dig -w on related commit: Confirmed all key networking
  maintainers were involved.
- [Phase 5] Call chain: addrconf_notify -> addrconf_permanent_addr ->
  fixup_permanent_addr -> addrconf_f6i_alloc -> ... ->
  fib_nh_common_init -> alloc_percpu_gfp with GFP_ATOMIC fails.
- [Phase 5] fib_nh_common_init: Verified it does alloc_percpu_gfp() at
  line 619-620 of fib_semantics.c.
- [Phase 6] v7.0: All dependencies present, clean apply verified.
- [Phase 8] Failure mode: IPv6 permanent address dropped (ipv6_del_addr
  called) when allocation fails - HIGH severity.
- UNVERIFIED: Could not access lore.kernel.org review comments due to
  Anubis bot protection. The full reviewer feedback on v1->v2 evolution
  could not be examined.

**YES**

 net/ipv6/addrconf.c | 31 +++++++++++++++++++------------
 1 file changed, 19 insertions(+), 12 deletions(-)

diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index dd0b4d80e0f84..77c77e843c96c 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -3585,15 +3585,15 @@ static int fixup_permanent_addr(struct net *net,
 		struct fib6_info *f6i, *prev;
 
 		f6i = addrconf_f6i_alloc(net, idev, &ifp->addr, false,
-					 GFP_ATOMIC, NULL);
+					 GFP_KERNEL, NULL);
 		if (IS_ERR(f6i))
 			return PTR_ERR(f6i);
 
 		/* ifp->rt can be accessed outside of rtnl */
-		spin_lock(&ifp->lock);
+		spin_lock_bh(&ifp->lock);
 		prev = ifp->rt;
 		ifp->rt = f6i;
-		spin_unlock(&ifp->lock);
+		spin_unlock_bh(&ifp->lock);
 
 		fib6_info_release(prev);
 	}
@@ -3601,7 +3601,7 @@ static int fixup_permanent_addr(struct net *net,
 	if (!(ifp->flags & IFA_F_NOPREFIXROUTE)) {
 		addrconf_prefix_route(&ifp->addr, ifp->prefix_len,
 				      ifp->rt_priority, idev->dev, 0, 0,
-				      GFP_ATOMIC);
+				      GFP_KERNEL);
 	}
 
 	if (ifp->state == INET6_IFADDR_STATE_PREDAD)
@@ -3612,29 +3612,36 @@ static int fixup_permanent_addr(struct net *net,
 
 static void addrconf_permanent_addr(struct net *net, struct net_device *dev)
 {
-	struct inet6_ifaddr *ifp, *tmp;
+	struct inet6_ifaddr *ifp;
+	LIST_HEAD(tmp_addr_list);
 	struct inet6_dev *idev;
 
+	/* Mutual exclusion with other if_list_aux users. */
+	ASSERT_RTNL();
+
 	idev = __in6_dev_get(dev);
 	if (!idev)
 		return;
 
 	write_lock_bh(&idev->lock);
+	list_for_each_entry(ifp, &idev->addr_list, if_list) {
+		if (ifp->flags & IFA_F_PERMANENT)
+			list_add_tail(&ifp->if_list_aux, &tmp_addr_list);
+	}
+	write_unlock_bh(&idev->lock);
 
-	list_for_each_entry_safe(ifp, tmp, &idev->addr_list, if_list) {
-		if ((ifp->flags & IFA_F_PERMANENT) &&
-		    fixup_permanent_addr(net, idev, ifp) < 0) {
-			write_unlock_bh(&idev->lock);
+	while (!list_empty(&tmp_addr_list)) {
+		ifp = list_first_entry(&tmp_addr_list,
+				       struct inet6_ifaddr, if_list_aux);
+		list_del(&ifp->if_list_aux);
 
+		if (fixup_permanent_addr(net, idev, ifp) < 0) {
 			net_info_ratelimited("%s: Failed to add prefix route for address %pI6c; dropping\n",
 					     idev->dev->name, &ifp->addr);
 			in6_ifa_hold(ifp);
 			ipv6_del_addr(ifp);
-			write_lock_bh(&idev->lock);
 		}
 	}
-
-	write_unlock_bh(&idev->lock);
 }
 
 static int addrconf_notify(struct notifier_block *this, unsigned long event,
-- 
2.53.0