FAILED: patch "[PATCH] NFSD: Guard admin state-revocation walks with NFSD_NET_UP" failed to apply to 7.2-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 7.2-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-7.2.y
git checkout FETCH_HEAD
git cherry-pick -x 2f3e6638aebc0ab8afb8b4e9816ea9a1cad85378
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090352-fading-liquefy-e98c@gregkh' --subject-prefix 'PATCH 7.2.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 2f3e6638aebc0ab8afb8b4e9816ea9a1cad85378 Mon Sep 17 00:00:00 2001
From: Chuck Lever <[email protected]>
Date: Sun, 21 Jun 2026 12:25:51 -0400
Subject: [PATCH] NFSD: Guard admin state-revocation walks with NFSD_NET_UP
Writing to /proc/fs/nfsd/unlock_filesystem, or sending the NFSD_CMD_UNLOCK_FILESYSTEM or NFSD_CMD_UNLOCK_EXPORT netlink command, walks the NFSv4 client hash tables to revoke open state and cancel async COPY operations. All three handlers gate that walk on nn->nfsd_serv, but a listener added via portlist or netlink listener_set sets nn->nfsd_serv before any nfsd thread starts. nfsd_startup_net() has not yet allocated nn->conf_id_hashtbl, so the walkers dereference a NULL table. A local administrator with CAP_SYS_ADMIN can crash the kernel this way without ever starting the server.
nn->nfsd_serv is set when the service is created, which precedes table allocation. NFSD_NET_UP instead brackets the window where the tables are live: set at the end of nfsd_startup_net() and cleared in nfsd_shutdown_net() after they are freed, both under nfsd_mutex. Gating the three unlock paths on NFSD_NET_UP fixes the startup-time NULL dereference while preserving the earlier post-shutdown use-after-free fix.
fs/nfsd/nfs4proc.c +3 -4
@@ -1588,10 +1588,9 @@ static bool nfsd4_copy_on_sb(const struct nfsd4_copy *copy,* @net: net namespace containing the copy operations * @sb: targeted superblock * -* Context: Caller must hold nfsd_mutex with nn->nfsd_serv confirmed -* non-NULL. nfs4_state_destroy_net() frees conf_id_hashtbl -* at server shutdown without clearing the pointer, so a -* walk without these guarantees iterates freed slab memory. +* Context: Caller must hold nfsd_mutex with NFSD_NET_UP set. Outside +* that window nn->conf_id_hashtbl is unallocated or freed, +* so the walk would dereference a NULL or dangling pointer. */ void nfsd4_cancel_copy_by_sb (struct net *net,struct super_block *sb) {
fs/nfsd/nfs4state.c +6 -8
@@ -1946,10 +1946,9 @@ static void revoke_one_stid(struct nfsd_net *nn, struct nfs4_client *clp,* The clients which own the states will subsequently be notified that the * states have been "admin-revoked". * -* Context: Caller must hold nfsd_mutex with nn->nfsd_serv confirmed -* non-NULL. nfs4_state_destroy_net() frees conf_id_hashtbl -* at server shutdown without clearing the pointer, so a -* walk without these guarantees iterates freed slab memory. +* Context: Caller must hold nfsd_mutex with NFSD_NET_UP set. Outside +* that window nn->conf_id_hashtbl is unallocated or freed, +* so the walk would dereference a NULL or dangling pointer. */ void nfsd4_revoke_states (struct nfsd_net *nn,struct super_block *sb) {
@@ -2024,10 +2023,9 @@ static struct nfs4_stid *find_one_export_stid(struct nfs4_client *clp,* Userspace (exportfs -u) sends this after removing the last client * for a path, enabling the underlying filesystem to be unmounted. * -* Context: Caller must hold nfsd_mutex with nn->nfsd_serv confirmed -* non-NULL. nfs4_state_destroy_net() frees conf_id_hashtbl -* at server shutdown without clearing the pointer, so a -* walk without these guarantees iterates freed slab memory. +* Context: Caller must hold nfsd_mutex with NFSD_NET_UP set. Outside +* that window nn->conf_id_hashtbl is unallocated or freed, +* so the walk would dereference a NULL or dangling pointer. */ void nfsd4_revoke_export_states (struct nfsd_net *nn,const struct path *path) {
fs/nfsd/nfsctl.c +3 -3
@@ -299,7 +299,7 @@ static ssize_t write_unlock_fs(struct file *file, char *buf, size_t size) error =nlmsvc_unlock_all_by_sb (path.dentry->d_sb);mutex_lock (&nfsd_mutex); nn =net_generic (netns (file), nfsd_net_id); -if (nn->nfsd_serv) { +if (test_bit (NFSD_NET_UP , &nn->flags)) {nfsd4_cancel_copy_by_sb (netns (file), path.dentry->d_sb);nfsd4_revoke_states (nn, path.dentry->d_sb); }else {
@@ -2424,7 +2424,7 @@ int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, error =nlmsvc_unlock_all_by_sb (path.dentry->d_sb);mutex_lock (&nfsd_mutex); -if (nn->nfsd_serv) { +if (test_bit (NFSD_NET_UP , &nn->flags)) {nfsd4_cancel_copy_by_sb (net, path.dentry->d_sb);nfsd4_revoke_states (nn, path.dentry->d_sb); }else {
@@ -2471,7 +2471,7 @@ int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info)return error;mutex_lock (&nfsd_mutex); -if (nn->nfsd_serv) { +if (test_bit (NFSD_NET_UP , &nn->flags)) {nfsd_file_close_export (net, &path);nfsd4_revoke_export_states (nn, &path); }else
Sasha Levin
maintainerPATCH 1/4 nfsd: convert nfsd_net boolean flags to unsigned long flags word
From: Chris Mason <[email protected]>
[ Upstream commit 11a5fe42e1811f793e04ef885b639ea7668f439d ]
nfsd_net contains several boolean fields that are accessed from concurrent contexts without serialization. In particular, nfsd4_end_grace() guards its drain path with a plain bool:
if (nn->grace_ended)
return;
nn->grace_ended = true;The read and the write are independent, and nothing in struct nfsd_net serializes them. At least two contexts can reach this code with no lock held:
laundromat path
laundry_wq kworker
nfs4_laundromat()
nfsd4_end_grace()
RECLAIM_COMPLETE path
nfsd compound kthread
nfsd4_reclaim_complete()
inc_reclaim_complete()
nfsd4_end_grace()Both callers can observe grace_ended == false on different CPUs, both store true, and both proceed into nfsd4_record_grace_done(), which invokes the active client_tracking_ops->grace_done callback. For tracking ops that drain reclaim_str_hashtbl (legacy_tracking_ops via nfsd4_recdir_purge_old, and the cld v1+ ops via nfsd4_cld_grace_done), grace_done calls nfs4_release_reclaim(), which walks every bucket of reclaim_str_hashtbl with no lock and calls nfs4_remove_reclaim_record() (list_del + kfree) on each entry. Two concurrent walkers corrupt the list and double-free every nfs4_client_reclaim. A concurrent nfsd4_find_reclaim_client() iterating the same bucket reads through freed memory.
A third call site exists in nfs4_state_start_net() on the skip_grace startup path, but it runs under nfsd_mutex before any client has connected and before the laundromat's first delayed work fires, so it cannot race with the two callers above.
Replace the scattered boolean fields in nfsd_net with a single unsigned long flags word and an enum nfsd_net_flag for the bit positions. The grace_ended race is fixed by using test_and_set_bit(), which is atomic on all architectures. The remaining flags (grace_end_forced, in_grace, somebody_reclaimed, track_reclaim_completes, nfsd_net_up, lockd_up) are converted to use test_bit/set_bit/clear_bit for consistency. This avoids sub-word cmpxchg issues on architectures like Hexagon that only support word-sized atomic operations.
Stable-dep-of: 2f3e6638aebc ("NFSD: Guard admin state-revocation walks with NFSD_NET_UP")
--- fs/nfsd/netns.h | 19 +++++++++++-------- fs/nfsd/nfs4proc.c | 2 +- fs/nfsd/nfs4recover.c | 12 ++++++------ fs/nfsd/nfs4state.c | 40 ++++++++++++++++++++++++---------------- fs/nfsd/nfsctl.c | 2 +- fs/nfsd/nfssvc.c | 22 +++++++++++----------- 6 files changed, 54 insertions(+), 43 deletions(-)
fs/nfsd/netns.h +11 -8
@@ -28,6 +28,16 @@ struct cld_net;struct nfsd_net_cb ;struct nfsd4_client_tracking_ops ; +enum nfsd_net_flag { +NFSD_NET_GRACE_ENDED , +NFSD_NET_GRACE_END_FORCED , +NFSD_NET_IN_GRACE , +NFSD_NET_SOMEBODY_RECLAIMED , +NFSD_NET_TRACK_RECLAIM_COMPLETES , +NFSD_NET_UP , +NFSD_NET_LOCKD_UP , +}; +enum {/* cache misses due only to checksum comparison failures */ NFSD_STATS_PAYLOAD_MISSES ,
@@ -66,8 +76,7 @@ struct nfsd_net {struct cache_detail *nametoid_cache;struct lock_manager nfsd4_manager; -bool grace_ended; -bool grace_end_forced; +unsigned long flags;time64_t boot_time;struct dentry *nfsd_client_dir;
@@ -117,19 +126,13 @@ struct nfsd_net {spinlock_t blocked_locks_lock;struct file *rec_file; -bool in_grace;const struct nfsd4_client_tracking_ops *client_tracking_ops;time64_t nfsd4_lease;time64_t nfsd4_grace; -bool somebody_reclaimed; -bool track_reclaim_completes;atomic_t nr_reclaim_complete; -bool nfsd_net_up; -bool lockd_up; -seqlock_t writeverf_lock;unsigned char writeverf[8 ];
fs/nfsd/nfs4proc.c +1 -1
@@ -663,7 +663,7 @@ nfsd4_open(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,pr_warn ("nfsd4_process_open2 failed to open newly-created file: status=%u\n" ,be32_to_cpu (status));if (reclaim && !status) - nn->somebody_reclaimed = true; +set_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags); out:if (open->op_filp) {fput (open->op_filp);
fs/nfsd/nfs4recover.c +6 -6
@@ -167,7 +167,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)end_creating (dentry); out:if (status ==0 ) { -if (nn->in_grace) +if (test_bit (NFSD_NET_IN_GRACE , &nn->flags))__nfsd4_create_reclaim_record_grace (clp, dname, nn);vfs_fsync (nn->rec_file,0 ); }else {
@@ -317,7 +317,7 @@ nfsd4_remove_clid_dir(struct nfs4_client *clp)nfs4_reset_creds (original_cred);if (status ==0 ) {vfs_fsync (nn->rec_file,0 ); -if (nn->in_grace) +if (test_bit (NFSD_NET_IN_GRACE , &nn->flags))__nfsd4_remove_reclaim_record_grace (dname,HEXDIR_LEN , nn); }
@@ -373,7 +373,7 @@ nfsd4_recdir_purge_old(struct nfsd_net *nn) {int status; - nn->in_grace = false; +clear_bit (NFSD_NET_IN_GRACE , &nn->flags);if (!nn->rec_file)return ; status =mnt_want_write_file (nn->rec_file);
@@ -455,7 +455,7 @@ nfsd4_init_recdir(struct net *net)nfs4_reset_creds (original_cred);if (!status) - nn->in_grace = true; +set_bit (NFSD_NET_IN_GRACE , &nn->flags);return status; }
@@ -1362,7 +1362,7 @@ nfs4_cld_state_init(struct net *net)for (i =0 ; i <CLIENT_HASH_SIZE ; i++)INIT_LIST_HEAD (&nn->reclaim_str_hashtbl[i]); nn->reclaim_str_hashtbl_size =0 ; - nn->track_reclaim_completes = true; +set_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags);atomic_set (&nn->nr_reclaim_complete,0 );return 0 ;
@@ -1373,7 +1373,7 @@ nfs4_cld_state_shutdown(struct net *net) {struct nfsd_net *nn =net_generic (net, nfsd_net_id); - nn->track_reclaim_completes = false; +clear_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags);kfree (nn->reclaim_str_hashtbl); }
fs/nfsd/nfs4state.c +24 -16
@@ -2675,7 +2675,7 @@ static void inc_reclaim_complete(struct nfs4_client *clp) {struct nfsd_net *nn =net_generic (clp->net, nfsd_net_id); -if (!nn->track_reclaim_completes) +if (!test_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags))return ;if (!nfsd4_find_reclaim_client (clp->cl_name, nn))return ;
@@ -5018,8 +5018,6 @@ nfsd4_init_leases_net(struct nfsd_net *nn) nn->nfsd4_lease =90 ;/* default lease time */ nn->nfsd4_grace =90 ; - nn->somebody_reclaimed = false; - nn->track_reclaim_completes = false; nn->clverifier_counter =get_random_u32 (); nn->clientid_base =get_random_u32 (); nn->clientid_counter = nn->clientid_base +1 ;
@@ -6728,12 +6726,21 @@ nfsd4_renew(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,static void nfsd4_end_grace (struct nfsd_net *nn) { -/* do nothing if grace period already ended */ -if (nn->grace_ended) +/* +* nfsd4_end_grace() can be entered concurrently from the +* laundromat workqueue and from an nfsd compound thread +* handling RECLAIM_COMPLETE. Without serialization, both +* callers can observe NFSD_NET_GRACE_ENDED clear and proceed +* into nfsd4_record_grace_done(). For tracking ops whose +* grace_done drains reclaim_str_hashtbl, that results in +* list corruption and a double free of every +* nfs4_client_reclaim entry. Use an atomic test-and-set so +* exactly one caller proceeds. +*/ +if (test_and_set_bit (NFSD_NET_GRACE_ENDED , &nn->flags))return ;trace_nfsd_grace_complete (nn); - nn->grace_ended = true;/* * If the server goes down again right now, an NFSv4 * client will still be allowed to reclaim after it comes back up,
@@ -6774,10 +6781,10 @@ bool nfsd4_force_end_grace(struct nfsd_net *nn) {if (!nn->client_tracking_ops)return false; -if (READ_ONCE (nn->grace_ended)) +if (test_bit (NFSD_NET_GRACE_ENDED , &nn->flags))return false;/* laundromat_work must be initialised now, though it might be disabled */ -WRITE_ONCE (nn->grace_end_forced, true); +set_bit (NFSD_NET_GRACE_END_FORCED , &nn->flags);/* mod_delayed_work() doesn't queue work after * nfs4_state_shutdown_net() has called disable_delayed_work_sync() */
@@ -6794,15 +6801,15 @@ static bool clients_still_reclaiming(struct nfsd_net *nn)time64_t double_grace_period_end = nn->boot_time +2 * nn->nfsd4_lease; -if (READ_ONCE (nn->grace_end_forced)) +if (test_bit (NFSD_NET_GRACE_END_FORCED , &nn->flags))return false; -if (nn->track_reclaim_completes && +if (test_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags) &&atomic_read (&nn->nr_reclaim_complete) == nn->reclaim_str_hashtbl_size)return false; -if (!nn->somebody_reclaimed) +if (!test_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags))return false; - nn->somebody_reclaimed = false; +clear_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags);/* * If we've given them *two* lease times to reclaim, and they're * still not done, give up:
@@ -8587,7 +8594,7 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,nfs4_inc_and_copy_stateid (&lock->lk_resp_stateid, &lock_stp->st_stid); status =0 ;if (lock->lk_reclaim) - nn->somebody_reclaimed = true; +set_bit (NFSD_NET_SOMEBODY_RECLAIMED , &nn->flags);break ;case FILE_LOCK_DEFERRED :kref_put (&nbl->nbl_kref, free_nbl);
@@ -9113,8 +9120,8 @@ static int nfs4_state_create_net(struct net *net) nn->conf_name_tree =RB_ROOT ; nn->unconf_name_tree =RB_ROOT ; nn->boot_time =ktime_get_real_seconds (); - nn->grace_ended = false; - nn->grace_end_forced = false; +clear_bit (NFSD_NET_GRACE_ENDED , &nn->flags); +clear_bit (NFSD_NET_GRACE_END_FORCED , &nn->flags); nn->nfsd4_manager.block_opens = true;INIT_LIST_HEAD (&nn->nfsd4_manager.list);INIT_LIST_HEAD (&nn->client_lru);
@@ -9200,7 +9207,8 @@ nfs4_state_start_net(struct net *net)nfsd4_client_tracking_init (net);/* safe for laundromat to run now */ enable_delayed_work (&nn->laundromat_work); -if (nn->track_reclaim_completes && nn->reclaim_str_hashtbl_size ==0 ) +if (test_bit (NFSD_NET_TRACK_RECLAIM_COMPLETES , &nn->flags) && + nn->reclaim_str_hashtbl_size ==0 ) goto skip_grace;printk (KERN_INFO "NFSD: starting %lld-second grace period (net %x)\n" , nn->nfsd4_grace, net->ns.inum);
fs/nfsd/nfsctl.c +1 -1
@@ -1111,7 +1111,7 @@ static ssize_t write_v4_end_grace(struct file *file, char *buf, size_t size) }return scnprintf (buf,SIMPLE_TRANSACTION_LIMIT ,"%c\n" , - nn->grace_ended ?'Y' :'N' ); +test_bit (NFSD_NET_GRACE_ENDED , &nn->flags) ?'Y' :'N' ); }#endif
fs/nfsd/nfssvc.c +11 -11
@@ -351,7 +351,7 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred)struct nfsd_net *nn =net_generic (net, nfsd_net_id);int ret; -if (nn->nfsd_net_up) +if (test_bit (NFSD_NET_UP , &nn->flags))return 0 ; ret =nfsd_startup_generic ();
@@ -364,11 +364,11 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred) goto out_socks; } -if (nfsd_needs_lockd (nn) && !nn->lockd_up) { +if (nfsd_needs_lockd (nn) && !test_bit (NFSD_NET_LOCKD_UP , &nn->flags)) { ret =lockd_up (net, cred);if (ret) goto out_socks; - nn->lockd_up = true; +set_bit (NFSD_NET_LOCKD_UP , &nn->flags); } ret =nfsd_file_cache_start_net (net);
@@ -386,7 +386,7 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred)if (ret) goto out_reply_cache; - nn->nfsd_net_up = true; +set_bit (NFSD_NET_UP , &nn->flags);return 0 ; out_reply_cache:
@@ -394,9 +394,9 @@ static int nfsd_startup_net(struct net *net, const struct cred *cred) out_filecache:nfsd_file_cache_shutdown_net (net); out_lockd: -if (nn->lockd_up) { +if (test_bit (NFSD_NET_LOCKD_UP , &nn->flags)) {lockd_down (net); - nn->lockd_up = false; +clear_bit (NFSD_NET_LOCKD_UP , &nn->flags); } out_socks:nfsd_shutdown_generic ();
@@ -407,7 +407,7 @@ static void nfsd_shutdown_net(struct net *net) {struct nfsd_net *nn =net_generic (net, nfsd_net_id); -if (nn->nfsd_net_up) { +if (test_bit (NFSD_NET_UP , &nn->flags)) {percpu_ref_kill_and_confirm (&nn->nfsd_net_ref, nfsd_net_done);wait_for_completion (&nn->nfsd_net_confirm_done);
@@ -415,18 +415,18 @@ static void nfsd_shutdown_net(struct net *net)nfs4_state_shutdown_net (net);nfsd_reply_cache_shutdown (nn);nfsd_file_cache_shutdown_net (net); -if (nn->lockd_up) { +if (test_bit (NFSD_NET_LOCKD_UP , &nn->flags)) {lockd_down (net); - nn->lockd_up = false; +clear_bit (NFSD_NET_LOCKD_UP , &nn->flags); }wait_for_completion (&nn->nfsd_net_free_done); }percpu_ref_exit (&nn->nfsd_net_ref); -if (nn->nfsd_net_up) +if (test_bit (NFSD_NET_UP , &nn->flags))nfsd_shutdown_generic (); - nn->nfsd_net_up = false; +clear_bit (NFSD_NET_UP , &nn->flags); }static DEFINE_SPINLOCK (nfsd_notifier_lock);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/4 NFSD: Prevent post-shutdown use-after-free in unlock_filesystem
From: Chuck Lever <[email protected]>
[ Upstream commit 292d915d3ba6fd15eeb88351fa10581683073109 ]
Writing a filesystem path to /proc/fs/nfsd/unlock_filesystem runs nfsd4_cancel_copy_by_sb() before nfsd_mutex is held and before the handler confirms that nn->nfsd_serv is set. Once nfsd has shut down, nfs4_state_destroy_net() has freed nn->conf_id_hashtbl but left the pointer intact, so the cancel helper iterates freed slab memory as an array of struct list_head and then dereferences a bogus nfs4_client when it takes clp->async_lock. A local administrator holding CAP_SYS_ADMIN can reach this use-after-free by stopping the server and then writing to unlock_filesystem; KASAN reports a slab-use-after-free read in nfsd4_cancel_copy_by_sb().
nfsd4_revoke_states() walks the same state tables and for that reason already runs only under nfsd_mutex with nn->nfsd_serv confirmed present. Move the async COPY cancel into that protected section so every NFSv4 state-table walker on this path observes a running server. Async copies exist only while the server runs, so gating the cancel on nn->nfsd_serv loses nothing.
Stable-dep-of: 2f3e6638aebc ("NFSD: Guard admin state-revocation walks with NFSD_NET_UP")
--- fs/nfsd/nfsctl.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
fs/nfsd/nfsctl.c +4 -3
@@ -296,14 +296,15 @@ static ssize_t write_unlock_fs(struct file *file, char *buf, size_t size)* 2. Is that directory a mount point, or * 3. Is that directory the root of an exported file system? */ -nfsd4_cancel_copy_by_sb (netns (file), path.dentry->d_sb); error =nlmsvc_unlock_all_by_sb (path.dentry->d_sb);mutex_lock (&nfsd_mutex); nn =net_generic (netns (file), nfsd_net_id); -if (nn->nfsd_serv) +if (nn->nfsd_serv) { +nfsd4_cancel_copy_by_sb (netns (file), path.dentry->d_sb);nfsd4_revoke_states (nn, path.dentry->d_sb); -else + }else { error = -EINVAL ; + }mutex_unlock (&nfsd_mutex);path_put (&path);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/4 NFSD: Annotate caller preconditions for the state-table walkers
In reply to Sasha LevinFrom: Chuck Lever <[email protected]>
[ Upstream commit 5f367f05481d56be90f8c13eee4cb421cd7af2d8 ]
The state-table walkers now assert nfsd_mutex with lockdep_assert_held() and document the nfsd_mutex / nn->nfsd_serv precondition in a Context: kdoc section, so the next caller added to this path cannot silently reintroduce the same use-after-free.
Stable-dep-of: 2f3e6638aebc ("NFSD: Guard admin state-revocation walks with NFSD_NET_UP")
--- fs/nfsd/nfs4proc.c | 6 ++++++ fs/nfsd/nfs4state.c | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-)
fs/nfsd/nfs4proc.c +6 -0
@@ -1562,6 +1562,11 @@ static bool nfsd4_copy_on_sb(const struct nfsd4_copy *copy,* nfsd4_cancel_copy_by_sb - cancel async copy operations on @sb * @net: net namespace containing the copy operations * @sb: targeted superblock +* +* Context: Caller must hold nfsd_mutex with nn->nfsd_serv confirmed +* non-NULL. nfs4_state_destroy_net() frees conf_id_hashtbl +* at server shutdown without clearing the pointer, so a +* walk without these guarantees iterates freed slab memory. */ void nfsd4_cancel_copy_by_sb (struct net *net,struct super_block *sb) {
@@ -1571,6 +1576,7 @@ void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb)unsigned int idhashval;LIST_HEAD (to_cancel); +lockdep_assert_held (&nfsd_mutex);spin_lock (&nn->client_lock);for (idhashval =0 ; idhashval <CLIENT_HASH_SIZE ; idhashval++) {struct list_head *head = &nn->conf_id_hashtbl[idhashval];
fs/nfsd/nfs4state.c +15 -1
@@ -1873,14 +1873,21 @@ static void revoke_one_stid(struct nfsd_net *nn, struct nfs4_client *clp,* being released. Thus nfsd will no longer prevent the filesystem from being * unmounted. * -* The clients which own the states will subsequently being notified that the +* The clients which own the states will subsequently be notified that the * states have been "admin-revoked". +* +* Context: Caller must hold nfsd_mutex with nn->nfsd_serv confirmed +* non-NULL. nfs4_state_destroy_net() frees conf_id_hashtbl +* at server shutdown without clearing the pointer, so a +* walk without these guarantees iterates freed slab memory. */ void nfsd4_revoke_states (struct nfsd_net *nn,struct super_block *sb) {unsigned int idhashval;unsigned int sc_types; +lockdep_assert_held (&nfsd_mutex); + sc_types =SC_TYPE_OPEN |SC_TYPE_LOCK |SC_TYPE_DELEG |SC_TYPE_LAYOUT ;spin_lock (&nn->client_lock);
@@ -1946,12 +1953,19 @@ static struct nfs4_stid *find_one_export_stid(struct nfs4_client *clp,* * Userspace (exportfs -u) sends this after removing the last client * for a path, enabling the underlying filesystem to be unmounted. +* +* Context: Caller must hold nfsd_mutex with nn->nfsd_serv confirmed +* non-NULL. nfs4_state_destroy_net() frees conf_id_hashtbl +* at server shutdown without clearing the pointer, so a +* walk without these guarantees iterates freed slab memory. */ void nfsd4_revoke_export_states (struct nfsd_net *nn,const struct path *path) {unsigned int idhashval;unsigned int sc_types; +lockdep_assert_held (&nfsd_mutex); + sc_types =SC_TYPE_OPEN |SC_TYPE_LOCK |SC_TYPE_DELEG |SC_TYPE_LAYOUT ;spin_lock (&nn->client_lock);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 4/4 NFSD: Guard admin state-revocation walks with NFSD_NET_UP
In reply to Sasha LevinFrom: Chuck Lever <[email protected]>
[ Upstream commit 2f3e6638aebc0ab8afb8b4e9816ea9a1cad85378 ]
Writing to /proc/fs/nfsd/unlock_filesystem, or sending the NFSD_CMD_UNLOCK_FILESYSTEM or NFSD_CMD_UNLOCK_EXPORT netlink command, walks the NFSv4 client hash tables to revoke open state and cancel async COPY operations. All three handlers gate that walk on nn->nfsd_serv, but a listener added via portlist or netlink listener_set sets nn->nfsd_serv before any nfsd thread starts. nfsd_startup_net() has not yet allocated nn->conf_id_hashtbl, so the walkers dereference a NULL table. A local administrator with CAP_SYS_ADMIN can crash the kernel this way without ever starting the server.
nn->nfsd_serv is set when the service is created, which precedes table allocation. NFSD_NET_UP instead brackets the window where the tables are live: set at the end of nfsd_startup_net() and cleared in nfsd_shutdown_net() after they are freed, both under nfsd_mutex. Gating the three unlock paths on NFSD_NET_UP fixes the startup-time NULL dereference while preserving the earlier post-shutdown use-after-free fix.
--- fs/nfsd/nfs4proc.c | 7 +++---- fs/nfsd/nfs4state.c | 14 ++++++-------- fs/nfsd/nfsctl.c | 6 +++--- 3 files changed, 12 insertions(+), 15 deletions(-)
fs/nfsd/nfs4proc.c +3 -4
@@ -1563,10 +1563,9 @@ static bool nfsd4_copy_on_sb(const struct nfsd4_copy *copy,* @net: net namespace containing the copy operations * @sb: targeted superblock * -* Context: Caller must hold nfsd_mutex with nn->nfsd_serv confirmed -* non-NULL. nfs4_state_destroy_net() frees conf_id_hashtbl -* at server shutdown without clearing the pointer, so a -* walk without these guarantees iterates freed slab memory. +* Context: Caller must hold nfsd_mutex with NFSD_NET_UP set. Outside +* that window nn->conf_id_hashtbl is unallocated or freed, +* so the walk would dereference a NULL or dangling pointer. */ void nfsd4_cancel_copy_by_sb (struct net *net,struct super_block *sb) {
fs/nfsd/nfs4state.c +6 -8
@@ -1876,10 +1876,9 @@ static void revoke_one_stid(struct nfsd_net *nn, struct nfs4_client *clp,* The clients which own the states will subsequently be notified that the * states have been "admin-revoked". * -* Context: Caller must hold nfsd_mutex with nn->nfsd_serv confirmed -* non-NULL. nfs4_state_destroy_net() frees conf_id_hashtbl -* at server shutdown without clearing the pointer, so a -* walk without these guarantees iterates freed slab memory. +* Context: Caller must hold nfsd_mutex with NFSD_NET_UP set. Outside +* that window nn->conf_id_hashtbl is unallocated or freed, +* so the walk would dereference a NULL or dangling pointer. */ void nfsd4_revoke_states (struct nfsd_net *nn,struct super_block *sb) {
@@ -1954,10 +1953,9 @@ static struct nfs4_stid *find_one_export_stid(struct nfs4_client *clp,* Userspace (exportfs -u) sends this after removing the last client * for a path, enabling the underlying filesystem to be unmounted. * -* Context: Caller must hold nfsd_mutex with nn->nfsd_serv confirmed -* non-NULL. nfs4_state_destroy_net() frees conf_id_hashtbl -* at server shutdown without clearing the pointer, so a -* walk without these guarantees iterates freed slab memory. +* Context: Caller must hold nfsd_mutex with NFSD_NET_UP set. Outside +* that window nn->conf_id_hashtbl is unallocated or freed, +* so the walk would dereference a NULL or dangling pointer. */ void nfsd4_revoke_export_states (struct nfsd_net *nn,const struct path *path) {
fs/nfsd/nfsctl.c +3 -3
@@ -299,7 +299,7 @@ static ssize_t write_unlock_fs(struct file *file, char *buf, size_t size) error =nlmsvc_unlock_all_by_sb (path.dentry->d_sb);mutex_lock (&nfsd_mutex); nn =net_generic (netns (file), nfsd_net_id); -if (nn->nfsd_serv) { +if (test_bit (NFSD_NET_UP , &nn->flags)) {nfsd4_cancel_copy_by_sb (netns (file), path.dentry->d_sb);nfsd4_revoke_states (nn, path.dentry->d_sb); }else {
@@ -2344,7 +2344,7 @@ int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, error =nlmsvc_unlock_all_by_sb (path.dentry->d_sb);mutex_lock (&nfsd_mutex); -if (nn->nfsd_serv) { +if (test_bit (NFSD_NET_UP , &nn->flags)) {nfsd4_cancel_copy_by_sb (net, path.dentry->d_sb);nfsd4_revoke_states (nn, path.dentry->d_sb); }else {
@@ -2391,7 +2391,7 @@ int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info)return error;mutex_lock (&nfsd_mutex); -if (nn->nfsd_serv) { +if (test_bit (NFSD_NET_UP , &nn->flags)) {nfsd_file_close_export (net, &path);nfsd4_revoke_export_states (nn, &path); }else
Signature · 1 line
2.53.0