From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E93633D891D; Tue, 13 Oct 2026 12:18:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791893928; cv=none; b=E41OTZum1u5hR7U/uPudCynjeRbmmK4zwGr4nzlmLXkDzvjzbZNmQIqZO+vNVRfnt9z2cg3u0Tx6hNtSRZIGL4zKG3jL13WZrA1lwkQ0rcfqzTl4YC1ofGUuAtkbkZI3+0uFATse4Nowc6z3IPNQoI5RtEkg6gPJn8DAtW4AnPM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791893928; c=relaxed/simple; bh=LIpVGZVWjdaRWC1jTX6gvHEGMziCoNrbU+6WF2f0Uws=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bZxUQVghW/AL8Zt+iNxosM93QnacM5dPAeu2n03OJB/6xKd87upyqpdHOP7gC7mYJu6lYODQbgeqkOMPXww7iRHpckK2WdcN8lQgFXH/OUEA1JrtXzTVvrXyUVd5hW/8yDFtkCuD3yo5ogy9ZqFWmms3BMcAwnrbHfnc3plsN/U= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b=nNqtw36E; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.b="nNqtw36E" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 4CA531F000FF; Tue, 13 Oct 2026 12:18:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1791893926; bh=w2TfkZWKlINF13rUQgKX8DpNHiPqmnQv/10SZ2LB/+4=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=nNqtw36ENXEmKGIrzlW2W07ZygCJF4nxQkwLtvoMr91BGzo6pkyPpMA+dpun+0DzF iJ3XioleIDL3hjHKaxZEh4+pzs1n3RN5W2DeAYUJliPubtfmL+sjCiTGKzMGJOeRI7 1pXB1ZFecRU7Hnhoj7/fk6s5JkZG9TshCnVkaqDA= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Jeff Layton , Chuck Lever , Sasha Levin Subject: [PATCH 6.18 387/621] lockd, nfsd: RCU-protect nlmsvc_ops dispatch Date: Tue, 13 Oct 2026 13:36:22 +0200 Message-ID: <20261013112930.027432722@linuxfoundation.org> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261013112920.855194808@linuxfoundation.org> References: <20261013112920.855194808@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore Precedence: bulk X-Mailing-List: patches@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 6.18-stable review patch. If anyone has any objections, please let me know. ------------------ From: Jeff Layton [ Upstream commit 641e5e20852359b8c31149be4598884e30652f60 ] nlmsvc_ops is published by nfsd_lockd_init() and cleared by nfsd_lockd_shutdown() with plain stores, while lockd dereferences it unguarded from dispatch sites in fs/lockd/svcsubs.c. The pointer targets nfsd's .rodata and the fopen/fclose callbacks live in nfsd's .text, so a stale load after rmmod nfsd results in either a NULL deref or a module-text use-after-free. Declare nlmsvc_ops as __rcu, publish via rcu_assign_pointer(), clear via RCU_INIT_POINTER() + synchronize_rcu(). Add a struct module *owner field to nlmsvc_binding and pin the module across indirect calls with try_module_get/module_put. When the binding is torn down, fall back to fput() to avoid leaking struct file references. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260611-nfsd-testing-v2-16-5b90e276f2d9@kernel.org Signed-off-by: Chuck Lever Signed-off-by: Sasha Levin --- fs/lockd/svc.c | 4 +-- fs/lockd/svc4proc.c | 4 +-- fs/lockd/svcproc.c | 4 +-- fs/lockd/svcsubs.c | 52 +++++++++++++++++++++++++++++++++----- fs/nfsd/lockd.c | 6 +++-- include/linux/lockd/bind.h | 12 ++++++--- 6 files changed, 64 insertions(+), 18 deletions(-) diff --git a/fs/lockd/svc.c b/fs/lockd/svc.c index d68afa196535a..28b9886394c8f 100644 --- a/fs/lockd/svc.c +++ b/fs/lockd/svc.c @@ -48,7 +48,7 @@ static struct svc_program nlmsvc_program; -const struct nlmsvc_binding *nlmsvc_ops; +const struct nlmsvc_binding __rcu *nlmsvc_ops; EXPORT_SYMBOL_GPL(nlmsvc_ops); static DEFINE_MUTEX(nlmsvc_mutex); @@ -143,7 +143,7 @@ lockd(void *vrqstp) nlmsvc_retry_blocked(rqstp); svc_recv(rqstp); } - if (nlmsvc_ops) + if (rcu_access_pointer(nlmsvc_ops)) nlmsvc_invalidate_all(); nlm_shutdown_hosts(); cancel_delayed_work_sync(&ln->grace_period_end); diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index 75e020a8bfd07..a1adfd66dd230 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -31,7 +31,7 @@ nlm4svc_retrieve_args(struct svc_rqst *rqstp, struct nlm_args *argp, __be32 error = 0; /* nfsd callbacks must have been installed for this procedure */ - if (!nlmsvc_ops) + if (!rcu_access_pointer(nlmsvc_ops)) return nlm_lck_denied_nolocks; if (lock->lock_start > OFFSET_MAX || @@ -484,7 +484,7 @@ nlm4svc_proc_granted_res(struct svc_rqst *rqstp) { struct nlm_res *argp = rqstp->rq_argp; - if (!nlmsvc_ops) + if (!rcu_access_pointer(nlmsvc_ops)) return rpc_success; dprintk("lockd: GRANTED_RES called\n"); diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index d98e8d684376b..49d20df3b8ae3 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -61,7 +61,7 @@ nlmsvc_retrieve_args(struct svc_rqst *rqstp, struct nlm_args *argp, __be32 error = 0; /* nfsd callbacks must have been installed for this procedure */ - if (!nlmsvc_ops) + if (!rcu_access_pointer(nlmsvc_ops)) return nlm_lck_denied_nolocks; /* Obtain host handle */ @@ -519,7 +519,7 @@ nlmsvc_proc_granted_res(struct svc_rqst *rqstp) { struct nlm_res *argp = rqstp->rq_argp; - if (!nlmsvc_ops) + if (!rcu_access_pointer(nlmsvc_ops)) return rpc_success; dprintk("lockd: GRANTED_RES called\n"); diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c index 4075d0eab263f..405debfdf3ee0 100644 --- a/fs/lockd/svcsubs.c +++ b/fs/lockd/svcsubs.c @@ -90,24 +90,38 @@ int lock_to_openmode(struct file_lock *lock) static __be32 nlm_do_fopen(struct svc_rqst *rqstp, struct nlm_file *file, int mode) { + const struct nlmsvc_binding *ops; __be32 nfserr = nlm_lck_denied_nolocks; __be32 deferred = 0; struct file **fp; int m; + rcu_read_lock(); + ops = rcu_dereference(nlmsvc_ops); + if (!ops || !try_module_get(ops->owner)) { + rcu_read_unlock(); + return nlm_lck_denied_nolocks; + } + rcu_read_unlock(); + for (m = O_RDONLY ; m <= O_WRONLY ; m++) { if (mode != O_RDWR && mode != m) continue; fp = &file->f_file[m]; - if (*fp) + if (*fp) { + module_put(ops->owner); return 0; - nfserr = nlmsvc_ops->fopen(rqstp, &file->f_handle, fp, m); - if (!nfserr) + } + nfserr = ops->fopen(rqstp, &file->f_handle, fp, m); + if (!nfserr) { + module_put(ops->owner); return 0; + } if (nfserr == nlm_drop_reply) deferred = nfserr; } + module_put(ops->owner); if (deferred) return deferred; dprintk("lockd: open failed (error %d)\n", ntohl(nfserr)); @@ -174,6 +188,33 @@ nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result, goto out_unlock; } +/* + * Release the struct file references held by a nlm_file. + */ +static void nlm_release_files(struct nlm_file *file) +{ + const struct nlmsvc_binding *ops; + bool have_ops; + + rcu_read_lock(); + ops = rcu_dereference(nlmsvc_ops); + have_ops = ops && try_module_get(ops->owner); + rcu_read_unlock(); + + if (have_ops) { + if (file->f_file[O_RDONLY]) + ops->fclose(file->f_file[O_RDONLY]); + if (file->f_file[O_WRONLY]) + ops->fclose(file->f_file[O_WRONLY]); + module_put(ops->owner); + } else { + if (file->f_file[O_RDONLY]) + fput(file->f_file[O_RDONLY]); + if (file->f_file[O_WRONLY]) + fput(file->f_file[O_WRONLY]); + } +} + /* * Delete a file after having released all locks, blocks and shares */ @@ -183,10 +224,7 @@ nlm_delete_file(struct nlm_file *file) nlm_debug_print_file("closing file", file); if (!hlist_unhashed(&file->f_list)) { hlist_del(&file->f_list); - if (file->f_file[O_RDONLY]) - nlmsvc_ops->fclose(file->f_file[O_RDONLY]); - if (file->f_file[O_WRONLY]) - nlmsvc_ops->fclose(file->f_file[O_WRONLY]); + nlm_release_files(file); kfree(file); } else { printk(KERN_WARNING "lockd: attempt to release unknown file!\n"); diff --git a/fs/nfsd/lockd.c b/fs/nfsd/lockd.c index c774ce9aa2965..6cd028c142fe3 100644 --- a/fs/nfsd/lockd.c +++ b/fs/nfsd/lockd.c @@ -86,6 +86,7 @@ nlm_fclose(struct file *filp) } static const struct nlmsvc_binding nfsd_nlm_ops = { + .owner = THIS_MODULE, .fopen = nlm_fopen, /* open file for locking */ .fclose = nlm_fclose, /* close file */ }; @@ -94,11 +95,12 @@ void nfsd_lockd_init(void) { dprintk("nfsd: initializing lockd\n"); - nlmsvc_ops = &nfsd_nlm_ops; + rcu_assign_pointer(nlmsvc_ops, &nfsd_nlm_ops); } void nfsd_lockd_shutdown(void) { - nlmsvc_ops = NULL; + RCU_INIT_POINTER(nlmsvc_ops, NULL); + synchronize_rcu(); } diff --git a/include/linux/lockd/bind.h b/include/linux/lockd/bind.h index c53c81242e727..e25943e485c5c 100644 --- a/include/linux/lockd/bind.h +++ b/include/linux/lockd/bind.h @@ -21,11 +21,17 @@ struct svc_rqst; struct rpc_task; struct rpc_clnt; +struct module; -/* - * This is the set of functions for lockd->nfsd communication +/** + * struct nlmsvc_binding - lockd -> nfsd callback table + * @owner: module that provides this binding. + * @fopen: open a file by NFS file handle on behalf of an NLM request. + * @fclose: close a file that was previously opened via @fopen. + * Implementations MUST be semantically equivalent to fput(). */ struct nlmsvc_binding { + struct module *owner; __be32 (*fopen)(struct svc_rqst *, struct nfs_fh *, struct file **, @@ -33,7 +39,7 @@ struct nlmsvc_binding { void (*fclose)(struct file *); }; -extern const struct nlmsvc_binding *nlmsvc_ops; +extern const struct nlmsvc_binding __rcu *nlmsvc_ops; /* * Similar to nfs_client_initdata, but without the NFS-specific -- 2.53.0