syzbot reported a list_del corruption in flow_block_cb_setup_simple(). [0] flow_block_cb_setup_simple() accesses the driver_block_list (e.g., netdevsim's nsim_block_cb_list) without any synchronization. The nftables offload path calls into this function via ndo_setup_tc while holding the per-netns commit_mutex, but this mutex does not prevent concurrent access from tasks in different network namespaces that share the same driver_block_list, leading to list corruption: - Task A (FLOW_BLOCK_BIND) calls list_add_tail() to insert a new flow_block_cb into driver_block_list. - Task B (FLOW_BLOCK_UNBIND) concurrently calls list_del() on another flow_block_cb from the same list. - The concurrent modifications corrupt the list pointers. Fix this by adding a static mutex (flow_block_cb_list_lock) that protects all driver_block_list operations within flow_block_cb_setup_simple(). Also add a flow_block_cb_remove_driver() helper for external callers that need to remove a block_cb from the driver list under the same lock, and convert nft_indr_block_cleanup() to use it. [0]: list_del corruption. prev->next should be ffff888028878200, but was ffffffff8e940fc0. (prev=ffffffff8e940fc0) ------------[ cut here ]------------ kernel BUG at lib/list_debug.c:64! Oops: invalid opcode: 0000 [#1] SMP KASAN PTI CPU: 1 UID: 0 PID: 6308 Comm: syz.3.231 Not tainted syzkaller #0 PREEMPT(full) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 08/18/2025 RIP: 0010:__list_del_entry_valid_or_report+0x15a/0x190 lib/list_debug.c:62 [...] Call Trace: __list_del_entry_valid include/linux/list.h:124 [inline] __list_del_entry include/linux/list.h:215 [inline] list_del include/linux/list.h:229 [inline] flow_block_cb_setup_simple+0x62d/0x740 net/core/flow_offload.c:369 nft_block_offload_cmd net/netfilter/nf_tables_offload.c:397 [inline] nft_chain_offload_cmd+0x293/0x660 net/netfilter/nf_tables_offload.c:451 nft_flow_block_chain net/netfilter/nf_tables_offload.c:471 [inline] nft_flow_offload_chain net/netfilter/nf_tables_offload.c:513 [inline] nft_flow_rule_offload_commit+0x40d/0x1b60 net/netfilter/nf_tables_offload.c:592 nf_tables_commit+0x675/0x8710 net/netfilter/nf_tables_api.c:10925 nfnetlink_rcv_batch net/netfilter/nfnetlink.c:576 [inline] nfnetlink_rcv_skb_batch net/netfilter/nfnetlink.c:649 [inline] nfnetlink_rcv+0x1ac9/0x2590 net/netfilter/nfnetlink.c:667 netlink_unicast_kernel net/netlink/af_netlink.c:1320 [inline] netlink_unicast+0x82c/0x9e0 net/netlink/af_netlink.c:1346 netlink_sendmsg+0x805/0xb30 net/netlink/af_netlink.c:1896 sock_sendmsg_nosec net/socket.c:727 [inline] __sock_sendmsg+0x219/0x270 net/socket.c:742 ____sys_sendmsg+0x505/0x830 net/socket.c:2630 ___sys_sendmsg+0x21f/0x2a0 net/socket.c:2684 __sys_sendmsg net/socket.c:2716 [inline] __do_sys_sendmsg net/socket.c:2721 [inline] __se_sys_sendmsg net/socket.c:2719 [inline] __x64_sys_sendmsg+0x19b/0x260 net/socket.c:2719 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xfa/0x3b0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fixes: 955bcb6ea0df ("drivers: net: use flow block API") Reported-by: syzbot+5a66db916cdde0dbcc1c@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=5a66db916cdde0dbcc1c Tested-by: syzbot+5a66db916cdde0dbcc1c@syzkaller.appspotmail.com Signed-off-by: Shigeru Yoshida --- include/net/flow_offload.h | 2 ++ net/core/flow_offload.c | 41 ++++++++++++++++++++++++------- net/netfilter/nf_tables_offload.c | 2 +- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/include/net/flow_offload.h b/include/net/flow_offload.h index 596ab9791e4d..ff6d2bcb2cca 100644 --- a/include/net/flow_offload.h +++ b/include/net/flow_offload.h @@ -673,6 +673,8 @@ int flow_block_cb_setup_simple(struct flow_block_offload *f, flow_setup_cb_t *cb, void *cb_ident, void *cb_priv, bool ingress_only); +void flow_block_cb_remove_driver(struct flow_block_cb *block_cb); + enum flow_cls_command { FLOW_CLS_REPLACE, FLOW_CLS_DESTROY, diff --git a/net/core/flow_offload.c b/net/core/flow_offload.c index bc5169482710..137a44af5e1c 100644 --- a/net/core/flow_offload.c +++ b/net/core/flow_offload.c @@ -334,6 +334,8 @@ bool flow_block_cb_is_busy(flow_setup_cb_t *cb, void *cb_ident, } EXPORT_SYMBOL(flow_block_cb_is_busy); +static DEFINE_MUTEX(flow_block_cb_list_lock); + int flow_block_cb_setup_simple(struct flow_block_offload *f, struct list_head *driver_block_list, flow_setup_cb_t *cb, @@ -341,6 +343,7 @@ int flow_block_cb_setup_simple(struct flow_block_offload *f, bool ingress_only) { struct flow_block_cb *block_cb; + int err = 0; if (ingress_only && f->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS) @@ -348,32 +351,52 @@ int flow_block_cb_setup_simple(struct flow_block_offload *f, f->driver_block_list = driver_block_list; + mutex_lock(&flow_block_cb_list_lock); + switch (f->command) { case FLOW_BLOCK_BIND: - if (flow_block_cb_is_busy(cb, cb_ident, driver_block_list)) - return -EBUSY; + if (flow_block_cb_is_busy(cb, cb_ident, driver_block_list)) { + err = -EBUSY; + break; + } block_cb = flow_block_cb_alloc(cb, cb_ident, cb_priv, NULL); - if (IS_ERR(block_cb)) - return PTR_ERR(block_cb); + if (IS_ERR(block_cb)) { + err = PTR_ERR(block_cb); + break; + } flow_block_cb_add(block_cb, f); list_add_tail(&block_cb->driver_list, driver_block_list); - return 0; + break; case FLOW_BLOCK_UNBIND: block_cb = flow_block_cb_lookup(f->block, cb, cb_ident); - if (!block_cb) - return -ENOENT; + if (!block_cb) { + err = -ENOENT; + break; + } flow_block_cb_remove(block_cb, f); list_del(&block_cb->driver_list); - return 0; + break; default: - return -EOPNOTSUPP; + err = -EOPNOTSUPP; + break; } + + mutex_unlock(&flow_block_cb_list_lock); + return err; } EXPORT_SYMBOL(flow_block_cb_setup_simple); +void flow_block_cb_remove_driver(struct flow_block_cb *block_cb) +{ + mutex_lock(&flow_block_cb_list_lock); + list_del(&block_cb->driver_list); + mutex_unlock(&flow_block_cb_list_lock); +} +EXPORT_SYMBOL(flow_block_cb_remove_driver); + static DEFINE_MUTEX(flow_indr_block_lock); static LIST_HEAD(flow_block_indr_list); static LIST_HEAD(flow_block_indr_dev_list); diff --git a/net/netfilter/nf_tables_offload.c b/net/netfilter/nf_tables_offload.c index fd30e205de84..d60838bceafb 100644 --- a/net/netfilter/nf_tables_offload.c +++ b/net/netfilter/nf_tables_offload.c @@ -414,7 +414,7 @@ static void nft_indr_block_cleanup(struct flow_block_cb *block_cb) basechain, &extack); nft_net = nft_pernet(net); mutex_lock(&nft_net->commit_mutex); - list_del(&block_cb->driver_list); + flow_block_cb_remove_driver(block_cb); list_move(&block_cb->list, &bo.cb_list); nft_flow_offload_unbind(&bo, basechain); mutex_unlock(&nft_net->commit_mutex); -- 2.52.0