git: e20ed58d265c - main - lockf: Truncate the active lock list earlier in lf_purgelocks()

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Mon, 21 Sep 2026 14:23:47 UTC
The branch main has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=e20ed58d265cd6eba9b28efc8e8956324937d4c3

commit e20ed58d265cd6eba9b28efc8e8956324937d4c3
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-09-21 14:14:45 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-09-21 14:14:45 +0000

    lockf: Truncate the active lock list earlier in lf_purgelocks()
    
    Otherwise vfs_report_lockf() can race with lf_purgelocks() while the
    latter is freeing active lock entries without any locks held.
    
    Reviewed by:    kib
    MFC after:      2 weeks
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D59768
---
 sys/kern/kern_lockf.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/sys/kern/kern_lockf.c b/sys/kern/kern_lockf.c
index a1aee233fdbd..a318c26484b4 100644
--- a/sys/kern/kern_lockf.c
+++ b/sys/kern/kern_lockf.c
@@ -752,6 +752,7 @@ lf_purgelocks(struct vnode *vp, struct lockf **statep)
 {
 	struct lockf *state;
 	struct lockf_entry *lock, *nlock;
+	struct lockf_entry_list active;
 
 	/*
 	 * For this to work correctly, the caller must ensure that no
@@ -778,6 +779,7 @@ lf_purgelocks(struct vnode *vp, struct lockf **statep)
 	state->ls_threads++;
 	VI_UNLOCK(vp);
 
+	LIST_INIT(&active);
 	sx_xlock(&state->ls_lock);
 	sx_xlock(&lf_owner_graph_lock);
 	LIST_FOREACH_SAFE(lock, &state->ls_pending, lf_link, nlock) {
@@ -797,6 +799,7 @@ lf_purgelocks(struct vnode *vp, struct lockf **statep)
 			wakeup(lock);
 		}
 	}
+	LIST_SWAP(&active, &state->ls_active, lockf_entry, lf_link);
 	sx_xunlock(&lf_owner_graph_lock);
 	sx_xunlock(&state->ls_lock);
 
@@ -810,19 +813,17 @@ lf_purgelocks(struct vnode *vp, struct lockf **statep)
 	VI_UNLOCK(vp);
 
 	/*
-	 * We can just free all the active locks since they
-	 * will have no dependencies (we removed them all
-	 * above). We don't need to bother locking since we
-	 * are the last thread using this state structure.
+	 * We can just free all the active locks since they will have no
+	 * dependencies (we removed them all above).
 	 */
-	KASSERT(LIST_EMPTY(&state->ls_pending),
-	    ("lock pending for %p", state));
-	LIST_FOREACH_SAFE(lock, &state->ls_active, lf_link, nlock) {
+	LIST_FOREACH_SAFE(lock, &active, lf_link, nlock) {
 		LIST_REMOVE(lock, lf_link);
 		lf_free_lock(lock);
 	}
 out_free:
 	sx_xlock(&lf_lock_states_lock);
+	KASSERT(LIST_EMPTY(&state->ls_pending), ("lock pending for %p", state));
+	KASSERT(LIST_EMPTY(&state->ls_active), ("lock active for %p", state));
 	LIST_REMOVE(state, ls_link);
 	sx_xunlock(&lf_lock_states_lock);
 	sx_destroy(&state->ls_lock);