Technical Analysis of the Root Cause
1. Background and Lockdep Architecture
Standard userspace AF_INET sockets initialize their socket lock (sk->sk_lock) with the generic lockdep key sk_lock-AF_INET in sock_init_data() / sock_lock_init() (net/core/sock.c). Standard userspace socket operations legitimately hold lock_sock(sk) while performing memory allocations with GFP_KERNEL (for example, tcp_sendmsg_locked() allocating skbs via sk->sk_allocation, or mptcp_close() -> mptcp_pm_connection_closed() -> mptcp_event(..., GFP_KERNEL) allocating netlink messages). This establishes the legitimate dependency edge:
$$\text{sk_lock-AF_INET} \rightarrow \text{fs_reclaim}$$
Because the Network Block Device (NBD) is a block device, its request queue is part of the memory reclaim path (fs_reclaim -> &q->q_usage_counter(io) as explicitly annotated in blk_alloc_queue() in block/blk-core.c). Request dispatch and error handling in NBD acquire device and socket mutexes (&q->elevator_lock, &cmd->lock, &nsock->tx_lock). Furthermore, when NBD closes or marks a connection dead (e.g., in recv_work() or nbd_send_cmd() via nbd_mark_nsock_dead() calling kernel_sock_shutdown()), it acquires lock_sock(nsock->sock->sk) while holding &nsock->tx_lock.
To prevent false-positive circular locking warnings between fs_reclaim and generic socket locks, storage drivers that communicate over sockets (such as NVMe-oF TCP, CIFS, NFS/RPC, and NBD) reclassify their transport sockets under private lockdep classes (e.g., sk_lock-AF_INET-NBD in nbd_reclassify_socket()).
2. The Failure Mechanism
-
Socket Attachment in NBD (drivers/block/nbd.c):
When userspace configures an NBD device via nbd_add_socket() (from ioctl(NBD_SET_SOCK) or nbd_genl_connect_sock_cb()), NBD retrieves the socket via nbd_get_socket(nbd, fd, &err) and invokes nbd_reclassify_socket(sock).
-
Silent Failure to Reclassify:
nbd_reclassify_socket() checks:
if (!sock_allow_reclassification(sk))
return;
where sock_allow_reclassification(sk) verifies:
return !sock_owned_by_user_nocheck(sk) && !spin_is_locked(&sk->sk_lock.slock);
Because NBD attaches an existing userspace socket whose descriptor remains accessible to userspace, concurrent socket operations (or socket activity handling) can hold the socket lock when nbd_add_socket() executes. In this case, sock_allow_reclassification() returns false.
Instead of retrying, serializing, or rejecting socket setup, nbd_reclassify_socket() silently returns without reclassifying the socket's lockdep map (sk->sk_lock.dep_map). The NBD transport socket therefore retains the default userspace lockdep class: sk_lock-AF_INET.
-
Establishing the Inverted Dependency:
Later, when NBD operations execute (such as recv_work() calling nbd_mark_nsock_dead() under mutex_lock(&nsock->tx_lock)), NBD invokes kernel_sock_shutdown(nsock->sock, SHUT_RDWR) -> inet_shutdown(), taking lock_sock(sk).
Because the socket's lockdep class was not reclassified, lockdep records:
$$\text{fs_reclaim} \rightarrow \dots \rightarrow \text{&nsock->tx_lock} \rightarrow \text{sk_lock-AF_INET}$$
-
Circular Lockdep Detection:
When an unrelated AF_INET socket (here, an MPTCP socket) is subsequently closed in mptcp_close(), it acquires lock_sock(sk) and calls mptcp_pm_connection_closed() -> mptcp_event(MPTCP_EVENT_CLOSED, msk, NULL, GFP_KERNEL).
Allocating an skb with nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL) calls kmem_cache_alloc_node() -> might_alloc() -> fs_reclaim_acquire(), attempting to add the edge:
$$\text{sk_lock-AF_INET} \rightarrow \text{fs_reclaim}$$
Lockdep evaluates both chains on the shared class sk_lock-AF_INET and triggers the circular locking dependency warning:
$$\text{fs_reclaim} \rightarrow \text{&nsock->tx_lock} \rightarrow \text{sk_lock-AF_INET} \rightarrow \text{fs_reclaim}$$
3. Involved Functions, Structs, and Files
drivers/block/nbd.c:
nbd_add_socket() / nbd_reclassify_socket(): Silently leaves the socket under sk_lock-AF_INET if sock_allow_reclassification() is false.
recv_work() / nbd_mark_nsock_dead(): Calls kernel_sock_shutdown() under nsock->tx_lock.
include/net/sock.h & net/core/sock.c:
sock_allow_reclassification(), sock_lock_init_class_and_name(): Reclassification validation and lockdep map initialization.
block/blk-core.c:
blk_alloc_queue(): Registers the block device dependency between fs_reclaim and q->q_usage_counter(io).
net/mptcp/protocol.c & net/mptcp/pm.c / net/mptcp/pm_netlink.c:
mptcp_close(), __mptcp_close(), mptcp_pm_connection_closed(), mptcp_event(): Legitimate socket close and netlink event notification paths allocating under lock_sock().