git: ec48222a648f - stable/15 - lockf: Truncate the active lock list earlier in lf_purgelocks()

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Thu, 22 Oct 2026 17:31:52 UTC
The branch stable/15 has been updated by markj:

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

commit ec48222a648f6e6b617582efeefcdefb1b075405
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-09-21 14:14:45 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-10-22 15:45:40 +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
    
    (cherry picked from commit e20ed58d265cd6eba9b28efc8e8956324937d4c3)
---
 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 da2f619d8d99..2cb02b21cd78 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);