From nobody Sun May 24 19:33:42 2026 Received: from mail-qv1-f45.google.com (mail-qv1-f45.google.com [209.85.219.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E2A173976A7 for ; Sat, 23 May 2026 12:31:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779539470; cv=none; b=IzUTKE1rf992uz7kmEA72kS3NGKotOo33Z7cd8AlE8BXkPUT6rqXCYpByL4E4c8YgoAMaehp0xTnH86aFdA7kyWSvtsqH9w0pFeSKHmykGTuShcWLpj9bVT8vWW1iagGvteJetoAuZDsloR53TuFfNeNxlQvUQwIOCTTXsrSCZY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779539470; c=relaxed/simple; bh=lPfLsuEru7s7Zjp+c+Ro6vfvvRixGRxpQtLo7wbk/pA=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=BQd4/vh5yLs4fiG0SabPy1Mn1VmowuamfUI4ohxfdfXXLJKcRjAYSqn2wFH7B1E6PB/l5Q8qjoQP0L/8Rhl32ixL44kX4V2Cus9HLICNkQ0l6zAoEcGm9WnfKVGvSrQzdJ1BhZ++pNgB7xy4gQi20/7HCJwjr8JtMlawVZlprK8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=PU+LJbLB; arc=none smtp.client-ip=209.85.219.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="PU+LJbLB" Received: by mail-qv1-f45.google.com with SMTP id 6a1803df08f44-8acb3daf2aaso142488826d6.0 for ; Sat, 23 May 2026 05:31:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779539467; x=1780144267; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=x7dxo5nogsk0I0nc3SICs60+I3meZih1pCQB0Y6CwFM=; b=PU+LJbLBE/nFHaw57DAyQATmzdYwHg7s8yZyA/cagqSEPX+Vcf68Cwoc+rdAOOVYMC 0yUriDIA58EpW4lce5wELO6cj+mluMIDNmxyuacvRmTeH6pJIuichtghwt8lAvSTHths B9hvpX0FVE0Y4H46NVzbHmqxJnWS02mAOvx79YBEW6rmxNyqDfN/+YCIxeMt9UL2IN+C w0+eLXGJyCd9nhWlO06CwwV3xDFGwyPEQnG1UL76DG1uMeWDqqqa1zgZ/AqJLtPkLenR ENlUPUy+cMgVGDTYp22NZmzdYuAmdfBWG+gF8puVNScKY9OC/cQX3fLQUCk8PIiRKTqF PAkw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779539467; x=1780144267; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=x7dxo5nogsk0I0nc3SICs60+I3meZih1pCQB0Y6CwFM=; b=QGqcYz12Es0qoFC2+5Pc1MdV+CYncYhaMyYEuVTQ5mCOUQVHXUUWqCWCdcEyv14Atw slATK7JHs1zHwgjnzV9j0ywyJLtfZ7rROfvN1r1+igfgo415nN5pUiV2xePhDhKX5W/J OezzrYquE+5SpEu0Rmdb1aRRORBoZ1uuAZ2vFgpmftmotu3y5eWQbjXpet6R7+/skimX xOS0/WzO5Ix/IbURtXCeEr4hZUD4l9dVAa0wL9DWAe/vz2EYK0jDFcHa2XUPTvjk66tA Ta2ZuKoufPiA8Xs7uDnmGP5UooeOc2G3twR0M72CClG9vPhgSQfxZrrvWFJO4XsY95Se iwfw== X-Forwarded-Encrypted: i=1; AFNElJ/p6uzTNOyRRtjYsKGkxSQ6Y4UIU2zOPaV9tQa2vVW/4qK1dq8NuysRWM60UAGyrPMJ0pCsqRD02idKqcs=@vger.kernel.org X-Gm-Message-State: AOJu0YzrS7lZPizgyv6qRNwILqxveKlR9xYJy2QyYXLZgCNS/HUj6O/4 oMjcevg3ckHAUPhLFRhn/yVy+GfLKsFpvZvcNsKnpnf6P1ocrTOda7GJ X-Gm-Gg: Acq92OFo37JQhMb4FCRwWjRiIzjj1TQ2pd4D74X5ZBYM3XIfyaY5JTZdlmizRn2w15w jd0qjvDJ5R7wINmVP9H+/FH1gnFEe3rWF6aBN3nNwNgsLKrZsQno/l4LzMnbKv6l8GhdKtLSJ9s /LO1Bafgr8+b72NGh3E6OYXJlHeynku2SA5D4n9xXpfvcvUvSglZYuFGPhkN7njD2g8S8GbXnKK djq3vGZ+kmuG/1ZRsNGvDMUirY8MpquFlZ7C/xfrSF7mbjIQYIx2TQPEUSoz36UaQNac7k9cR2P XknEzsTUuwSi6Ryhfhj5v9hstNkPZvvyyCSd7DyJ+zwRfYVqX1nUWnLbyxyex1/OhKQKZFQbgtV gwocaTFthbcLpE4hG8EEY8aHDFOyS45cW6GsMbO+qKpQIesFOU7Ji8licQD65VtzfA0Umd1HD5i OEymnHj+vf6I8gXNoViiYQ3lKCPNZNcni1vntUpJ+8KcdHJ72hclkeBueVuxvUhuPasnzxn6UL4 ArwFOMLZEmeIUL35iRl X-Received: by 2002:a05:6214:242c:b0:8cc:3546:260b with SMTP id 6a1803df08f44-8cc7b620d46mr126661786d6.14.1779539466787; Sat, 23 May 2026 05:31:06 -0700 (PDT) Received: from server0 (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8cc8132f780sm45564746d6.49.2026.05.23.05.31.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 23 May 2026 05:31:06 -0700 (PDT) From: Michael Bommarito To: Trond Myklebust , Anna Schumaker , Chuck Lever , Jeff Layton Cc: NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, stable@vger.kernel.org Subject: [PATCH v2] lockd: pin next file across nlm_inspect_file lock-drop Date: Sat, 23 May 2026 08:30:53 -0400 Message-ID: <20260523123053.3480369-1-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" nlm_traverse_files() pins the current file with f_count++ across a mutex_unlock for nlm_inspect_file(), but nothing pins the saved next pointer. A concurrent nlm_release_file() can kfree the next file during the unlock window, and the iterator dereferences freed memory on the next loop step. Pin both current and next before the lock-drop. Advance by swapping the pinned cursors at the end of each iteration so next is always held alive across the unlock. Only call nlm_file_release() for files that matched the predicate and were inspected. Skipped files just get f_count-- to undo the iteration pin; their f_locks is stale and must not drive cleanup. Cc: stable@vger.kernel.org Fixes: 01df9c5e918a ("LOCKD: Fix a deadlock in nlm_traverse_files()") Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Michael Bommarito --- fs/lockd/svcsubs.c | 64 +++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 20 deletions(-) Changes since v1: - Fixed premature kfree of non-matching files: nlm_file_release() is now called only for files that matched the predicate and were inspected. Non-matching files just get f_count-- to undo the iteration pin. (Spotted by sashiko.dev automated review.) Reproduced under UML + KASAN with 768 concurrent POSIX holders and parallel /proc/fs/nfsd/unlock_filesystem writes. Stock kernel: BUG: KASAN: slab-use-after-free in nlm_traverse_files+0x71d/0x9d0 Allocated by: nlm_lookup_file via nlm4svc_proc_lock Freed by: another nlm_traverse_files instance Patched v2 UML kernel ran the same harness silently. diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c index dd0214dcb6950..0b38125cf86ab 100644 --- a/fs/lockd/svcsubs.c +++ b/fs/lockd/svcsubs.c @@ -295,36 +295,60 @@ static void nlm_close_files(struct nlm_file *file) /* * Loop over all files in the file table. */ +static void nlm_file_release(struct nlm_file *file) +{ + if (list_empty(&file->f_blocks) && !file->f_locks + && !file->f_shares && !file->f_count) { + hlist_del(&file->f_list); + nlm_close_files(file); + kfree(file); + } +} + static int nlm_traverse_files(void *data, nlm_host_match_fn_t match, int (*is_failover_file)(void *data, struct nlm_file *file)) { - struct hlist_node *next; - struct nlm_file *file; + struct nlm_file *file, *next; int i, ret =3D 0; =20 mutex_lock(&nlm_file_mutex); for (i =3D 0; i < FILE_NRHASH; i++) { - hlist_for_each_entry_safe(file, next, &nlm_files[i], f_list) { - if (is_failover_file && !is_failover_file(data, file)) - continue; + file =3D hlist_entry_safe(nlm_files[i].first, + struct nlm_file, f_list); + if (file) file->f_count++; - mutex_unlock(&nlm_file_mutex); - - /* Traverse locks, blocks and shares of this file - * and update file->f_locks count */ - if (nlm_inspect_file(data, file, match)) - ret =3D 1; - - mutex_lock(&nlm_file_mutex); - file->f_count--; - /* No more references to this file. Let go of it. */ - if (list_empty(&file->f_blocks) && !file->f_locks - && !file->f_shares && !file->f_count) { - hlist_del(&file->f_list); - nlm_close_files(file); - kfree(file); + while (file) { + /* + * Pin the next neighbour before we drop the mutex + * for nlm_inspect_file(); a concurrent + * nlm_release_file() under the same mutex would + * otherwise be free to unlink and kfree it during + * the unlock window, leaving us to dereference a + * freed slab when we walked to next afterwards. + */ + next =3D hlist_entry_safe(file->f_list.next, + struct nlm_file, f_list); + if (next) + next->f_count++; + + if (!is_failover_file || is_failover_file(data, file)) { + mutex_unlock(&nlm_file_mutex); + + /* + * Traverse locks, blocks and shares of this + * file and update file->f_locks count. + */ + if (nlm_inspect_file(data, file, match)) + ret =3D 1; + + mutex_lock(&nlm_file_mutex); + file->f_count--; + nlm_file_release(file); + } else { + file->f_count--; } + file =3D next; } } mutex_unlock(&nlm_file_mutex); --=20 2.53.0