Forwarded: test: bpf lru nmi deadlock fix

syzbot
2026-03-15T15:52:44Z

For archival purposes, forwarding an incoming command email to
linux-kernel@vger.kernel.org, syzkaller-bugs@googlegroups.com.

***

Subject: test: bpf lru nmi deadlock fix
Author: nooraineqbal@gmail.com

#syz test: git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf.git master

From b1add52f63c8309c3a91bcc294139134ce1c0fa5 Mon Sep 17 00:00:00 2001
From: Noorain Eqbal <nooraineqbal@gmail.com>
Date: Sun, 15 Mar 2026 21:02:42 +0530
Subject: [PATCH] bpf: Fix NMI deadlocks in LRU map operations

LRU maps can deadlock when accessed from NMI context (e.g., when
attached to perf events with hardware breakpoints). The issue occurs
because raw_spin_lock_irqsave() cannot prevent NMI interruption on
the same CPU.

This is the same issue that was fixed for queue and stack maps in
commit a34a9f1a19af ("bpf: Avoid deadlock when using queue and stack
maps from NMI"). The queue/stack map fix was later updated to use
resilient spinlocks which provide built in NMI deadlock detection.

Apply the same fix to LRU maps by replacing raw_spin_lock_irqsave()
with raw_res_spin_lock_irqsave() in all LRU pop/push operations:
- bpf_common_lru_pop_free()
- bpf_percpu_lru_pop_free()
- bpf_common_lru_push_free()
- bpf_percpu_lru_push_free()

The resilient spinlock will return -EDEADLK when it detects a deadlock
scenario (NMI trying to acquire a lock already held), allowing the
operation to fail safely instead of deadlocking.

Reproducer and lockdep splat:
https://syzkaller.appspot.com/bug?id=c4d6f5f7d392471722983a2e85ae391360ca7ae8

Related discussion:
https://lore.kernel.org/bpf/CAPPBnEYO4R+m+SpVc2gNj_x31R6fo1uJvj2bK2YS1P09GWT6kQ@mail.gmail.com/

Fixes: 3a08c2fd7634 ("bpf: LRU List")
Reported-by: syzbot+c69a0a2c816716f1e0d5@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=c69a0a2c816716f1e0d5

Signed-off-by: Noorain Eqbal <nooraineqbal@gmail.com>
---
kernel/bpf/bpf_lru_list.c | 46 +++++++++++++++++++++++----------------
kernel/bpf/bpf_lru_list.h | 5 +++--
2 files changed, 30 insertions(+), 21 deletions(-)

diff --git a/kernel/bpf/bpf_lru_list.c b/kernel/bpf/bpf_lru_list.c
index e7a2fc60523f..0cd1d5c511fa 100644
--- a/kernel/bpf/bpf_lru_list.c
+++ b/kernel/bpf/bpf_lru_list.c
@@ -4,6 +4,7 @@
#include <linux/cpumask.h>
#include <linux/spinlock.h>
#include <linux/percpu.h>
+#include <asm/rqspinlock.h>

#include "bpf_lru_list.h"

@@ -307,9 +308,10 @@ static void bpf_lru_list_push_free(struct bpf_lru_list *l,
if (WARN_ON_ONCE(IS_LOCAL_LIST_TYPE(node->type)))
return;

- raw_spin_lock_irqsave(&l->lock, flags);
+ if (raw_res_spin_lock_irqsave(&l->lock, flags))
+ return;
__bpf_lru_node_move(l, node, BPF_LRU_LIST_T_FREE);
- raw_spin_unlock_irqrestore(&l->lock, flags);
+ raw_res_spin_unlock_irqrestore(&l->lock, flags);
}

static void bpf_lru_list_pop_free_to_local(struct bpf_lru *lru,
@@ -319,7 +321,7 @@ static void bpf_lru_list_pop_free_to_local(struct bpf_lru *lru,
struct bpf_lru_node *node, *tmp_node;
unsigned int nfree = 0;

- raw_spin_lock(&l->lock);
+ raw_res_spin_lock(&l->lock);

__local_list_flush(l, loc_l);

@@ -338,7 +340,7 @@ static void bpf_lru_list_pop_free_to_local(struct bpf_lru *lru,
local_free_list(loc_l),
BPF_LRU_LOCAL_LIST_T_FREE);

- raw_spin_unlock(&l->lock);
+ raw_res_spin_unlock(&l->lock);
}

static void __local_list_add_pending(struct bpf_lru *lru,
@@ -404,7 +406,8 @@ static struct bpf_lru_node *bpf_percpu_lru_pop_free(struct bpf_lru *lru,

l = per_cpu_ptr(lru->percpu_lru, cpu);

- raw_spin_lock_irqsave(&l->lock, flags);
+ if (raw_res_spin_lock_irqsave(&l->lock, flags))
+ return NULL;

__bpf_lru_list_rotate(lru, l);

@@ -420,7 +423,7 @@ static struct bpf_lru_node *bpf_percpu_lru_pop_free(struct bpf_lru *lru,
__bpf_lru_node_move(l, node, BPF_LRU_LIST_T_INACTIVE);
}

- raw_spin_unlock_irqrestore(&l->lock, flags);
+ raw_res_spin_unlock_irqrestore(&l->lock, flags);

return node;
}
@@ -437,7 +440,8 @@ static struct bpf_lru_node *bpf_common_lru_pop_free(struct bpf_lru *lru,

loc_l = per_cpu_ptr(clru->local_list, cpu);

- raw_spin_lock_irqsave(&loc_l->lock, flags);
+ if (raw_res_spin_lock_irqsave(&loc_l->lock, flags))
+ return NULL;

node = __local_list_pop_free(loc_l);
if (!node) {
@@ -448,7 +452,7 @@ static struct bpf_lru_node *bpf_common_lru_pop_free(struct bpf_lru *lru,
if (node)
__local_list_add_pending(lru, loc_l, cpu, node, hash);

- raw_spin_unlock_irqrestore(&loc_l->lock, flags);
+ raw_res_spin_unlock_irqrestore(&loc_l->lock, flags);

if (node)
return node;
@@ -466,13 +470,14 @@ static struct bpf_lru_node *bpf_common_lru_pop_free(struct bpf_lru *lru,
do {
steal_loc_l = per_cpu_ptr(clru->local_list, steal);

- raw_spin_lock_irqsave(&steal_loc_l->lock, flags);
+ if (raw_res_spin_lock_irqsave(&steal_loc_l->lock, flags))
+ return NULL;

node = __local_list_pop_free(steal_loc_l);
if (!node)
node = __local_list_pop_pending(lru, steal_loc_l);

- raw_spin_unlock_irqrestore(&steal_loc_l->lock, flags);
+ raw_res_spin_unlock_irqrestore(&steal_loc_l->lock, flags);

steal = cpumask_next_wrap(steal, cpu_possible_mask);
} while (!node && steal != first_steal);
@@ -480,9 +485,10 @@ static struct bpf_lru_node *bpf_common_lru_pop_free(struct bpf_lru *lru,
loc_l->next_steal = steal;

if (node) {
- raw_spin_lock_irqsave(&loc_l->lock, flags);
+ if (raw_res_spin_lock_irqsave(&loc_l->lock, flags))
+ return NULL;
__local_list_add_pending(lru, loc_l, cpu, node, hash);
- raw_spin_unlock_irqrestore(&loc_l->lock, flags);
+ raw_res_spin_unlock_irqrestore(&loc_l->lock, flags);
}

return node;
@@ -511,10 +517,11 @@ static void bpf_common_lru_push_free(struct bpf_lru *lru,

loc_l = per_cpu_ptr(lru->common_lru.local_list, node->cpu);

- raw_spin_lock_irqsave(&loc_l->lock, flags);
+ if (raw_res_spin_lock_irqsave(&loc_l->lock, flags))
+ return;

if (unlikely(node->type != BPF_LRU_LOCAL_LIST_T_PENDING)) {
- raw_spin_unlock_irqrestore(&loc_l->lock, flags);
+ raw_res_spin_unlock_irqrestore(&loc_l->lock, flags);
goto check_lru_list;
}

@@ -522,7 +529,7 @@ static void bpf_common_lru_push_free(struct bpf_lru *lru,
bpf_lru_node_clear_ref(node);
list_move(&node->list, local_free_list(loc_l));

- raw_spin_unlock_irqrestore(&loc_l->lock, flags);
+ raw_res_spin_unlock_irqrestore(&loc_l->lock, flags);
return;
}

@@ -538,11 +545,12 @@ static void bpf_percpu_lru_push_free(struct bpf_lru *lru,

l = per_cpu_ptr(lru->percpu_lru, node->cpu);

- raw_spin_lock_irqsave(&l->lock, flags);
+ if (raw_res_spin_lock_irqsave(&l->lock, flags))
+ return;

__bpf_lru_node_move(l, node, BPF_LRU_LIST_T_FREE);

- raw_spin_unlock_irqrestore(&l->lock, flags);
+ raw_res_spin_unlock_irqrestore(&l->lock, flags);
}

void bpf_lru_push_free(struct bpf_lru *lru, struct bpf_lru_node *node)
@@ -625,7 +633,7 @@ static void bpf_lru_locallist_init(struct bpf_lru_locallist *loc_l, int cpu)

loc_l->next_steal = cpu;

- raw_spin_lock_init(&loc_l->lock);
+ raw_res_spin_lock_init(&loc_l->lock);
}

static void bpf_lru_list_init(struct bpf_lru_list *l)
@@ -640,7 +648,7 @@ static void bpf_lru_list_init(struct bpf_lru_list *l)

l->next_inactive_rotation = &l->lists[BPF_LRU_LIST_T_INACTIVE];

- raw_spin_lock_init(&l->lock);
+ raw_res_spin_lock_init(&l->lock);
}

int bpf_lru_init(struct bpf_lru *lru, bool percpu, u32 hash_offset,
diff --git a/kernel/bpf/bpf_lru_list.h b/kernel/bpf/bpf_lru_list.h
index fe2661a58ea9..ecd93c77a7ff 100644
--- a/kernel/bpf/bpf_lru_list.h
+++ b/kernel/bpf/bpf_lru_list.h
@@ -7,6 +7,7 @@
#include <linux/cache.h>
#include <linux/list.h>
#include <linux/spinlock_types.h>
+#include <asm/rqspinlock.h>

#define NR_BPF_LRU_LIST_T (3)
#define NR_BPF_LRU_LIST_COUNT (2)
@@ -34,13 +35,13 @@ struct bpf_lru_list {
/* The next inactive list rotation starts from here */
struct list_head *next_inactive_rotation;

- raw_spinlock_t lock ____cacheline_aligned_in_smp;
+ rqspinlock_t lock ____cacheline_aligned_in_smp;
};

struct bpf_lru_locallist {
struct list_head lists[NR_BPF_LRU_LOCAL_LIST_T];
u16 next_steal;
- raw_spinlock_t lock;
+ rqspinlock_t lock;
};

struct bpf_common_lru {
--
2.53.0