lockd, nfsd: RCU-protect nlmsvc_ops dispatch

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: 1da177e4c3 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Link: https://patch.msgid.link/20260611-nfsd-testing-v2-16-5b90e276f2d9@kernel.org
Signed-off-by: Chuck Lever <cel@kernel.org>
This commit is contained in:
Jeff Layton 2026-06-11 16:00:59 -04:00 committed by Chuck Lever
parent 2327ba1d95
commit 641e5e2085
6 changed files with 64 additions and 18 deletions

View File

@ -47,7 +47,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);
@ -142,7 +142,7 @@ lockd(void *vrqstp)
nlmsvc_retry_blocked(rqstp);
svc_recv(rqstp, 0);
}
if (nlmsvc_ops)
if (rcu_access_pointer(nlmsvc_ops))
nlmsvc_invalidate_all();
nlm_shutdown_hosts();
cancel_delayed_work_sync(&ln->grace_period_end);

View File

@ -128,7 +128,7 @@ nlm4svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored)
{
struct nlm_host *host;
if (!nlmsvc_ops)
if (!rcu_access_pointer(nlmsvc_ops))
return NULL;
host = nlmsvc_lookup_host(rqstp, caller.data, caller.len);
if (!host)
@ -894,7 +894,7 @@ static __be32 nlm4svc_proc_granted_res(struct svc_rqst *rqstp)
{
struct nlm4_res_wrapper *argp = rqstp->rq_argp;
if (!nlmsvc_ops)
if (!rcu_access_pointer(nlmsvc_ops))
return rpc_success;
if (nlm4_netobj_to_cookie(&argp->cookie, &argp->xdrgen.cookie))

View File

@ -133,7 +133,7 @@ nlm3svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored)
{
struct nlm_host *host;
if (!nlmsvc_ops)
if (!rcu_access_pointer(nlmsvc_ops))
return NULL;
host = nlmsvc_lookup_host(rqstp, caller.data, caller.len);
if (!host)
@ -924,7 +924,7 @@ static __be32 nlmsvc_proc_granted_res(struct svc_rqst *rqstp)
{
struct nlm_res_wrapper *argp = rqstp->rq_argp;
if (!nlmsvc_ops)
if (!rcu_access_pointer(nlmsvc_ops))
return rpc_success;
if (nlm_netobj_to_cookie(&argp->cookie, &argp->xdrgen.cookie))

View File

@ -90,22 +90,35 @@ 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 nlmerr = nlm__int__failed;
__be32 deferred = 0;
int error;
int m;
rcu_read_lock();
ops = rcu_dereference(nlmsvc_ops);
if (!ops || !try_module_get(ops->owner)) {
rcu_read_unlock();
return nlm__int__failed;
}
rcu_read_unlock();
for (m = O_RDONLY; m <= O_WRONLY; m++) {
struct file **fp = &file->f_file[m];
if (mode != O_RDWR && mode != m)
continue;
if (*fp)
if (*fp) {
module_put(ops->owner);
return nlm_granted;
}
error = nlmsvc_ops->fopen(rqstp, &file->f_handle, fp, m);
if (!error)
error = ops->fopen(rqstp, &file->f_handle, fp, m);
if (!error) {
module_put(ops->owner);
return nlm_granted;
}
dprintk("lockd: open failed (errno %d)\n", error);
switch (error) {
@ -122,6 +135,7 @@ static __be32 nlm_do_fopen(struct svc_rqst *rqstp,
}
}
module_put(ops->owner);
return deferred ? deferred : nlmerr;
}
@ -185,6 +199,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
*/
@ -194,10 +235,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");

View File

@ -92,6 +92,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 */
};
@ -100,11 +101,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();
}

View File

@ -16,17 +16,23 @@ struct svc_rqst;
struct rpc_task;
struct rpc_clnt;
struct super_block;
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;
int (*fopen)(struct svc_rqst *rqstp, struct nfs_fh *f,
struct file **filp, int flags);
void (*fclose)(struct file *filp);
};
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