mirror of
https://github.com/torvalds/linux.git
synced 2026-09-14 16:10:02 +02:00
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:
parent
2327ba1d95
commit
641e5e2085
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user