mirror of
https://github.com/torvalds/linux.git
synced 2024-12-13 06:32:50 +00:00
65f2a5c366
Add nfs_to_nfsd_file_put_local() interface to fix race with nfsd module unload. Similarly, use RCU around nfs_open_local_fh()'s error path call to nfs_to->nfsd_serv_put(). Holding RCU ensures that NFS will safely _call and return_ from its nfs_to calls into the NFSD functions nfsd_file_put_local() and nfsd_serv_put(). Otherwise, if RCU isn't used then there is a narrow window when NFS's reference for the nfsd_file and nfsd_serv are dropped and the NFSD module could be unloaded, which could result in a crash from the return instruction for either nfs_to->nfsd_file_put_local() or nfs_to->nfsd_serv_put(). Reported-by: NeilBrown <neilb@suse.de> Signed-off-by: Mike Snitzer <snitzer@kernel.org> Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
170 lines
4.6 KiB
C
170 lines
4.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* NFS server support for local clients to bypass network stack
|
|
*
|
|
* Copyright (C) 2014 Weston Andros Adamson <dros@primarydata.com>
|
|
* Copyright (C) 2019 Trond Myklebust <trond.myklebust@hammerspace.com>
|
|
* Copyright (C) 2024 Mike Snitzer <snitzer@hammerspace.com>
|
|
* Copyright (C) 2024 NeilBrown <neilb@suse.de>
|
|
*/
|
|
|
|
#include <linux/exportfs.h>
|
|
#include <linux/sunrpc/svcauth.h>
|
|
#include <linux/sunrpc/clnt.h>
|
|
#include <linux/nfs.h>
|
|
#include <linux/nfs_common.h>
|
|
#include <linux/nfslocalio.h>
|
|
#include <linux/nfs_fs.h>
|
|
#include <linux/nfs_xdr.h>
|
|
#include <linux/string.h>
|
|
|
|
#include "nfsd.h"
|
|
#include "vfs.h"
|
|
#include "netns.h"
|
|
#include "filecache.h"
|
|
#include "cache.h"
|
|
|
|
static const struct nfsd_localio_operations nfsd_localio_ops = {
|
|
.nfsd_serv_try_get = nfsd_serv_try_get,
|
|
.nfsd_serv_put = nfsd_serv_put,
|
|
.nfsd_open_local_fh = nfsd_open_local_fh,
|
|
.nfsd_file_put_local = nfsd_file_put_local,
|
|
.nfsd_file_file = nfsd_file_file,
|
|
};
|
|
|
|
void nfsd_localio_ops_init(void)
|
|
{
|
|
nfs_to = &nfsd_localio_ops;
|
|
}
|
|
|
|
/**
|
|
* nfsd_open_local_fh - lookup a local filehandle @nfs_fh and map to nfsd_file
|
|
*
|
|
* @net: 'struct net' to get the proper nfsd_net required for LOCALIO access
|
|
* @dom: 'struct auth_domain' required for LOCALIO access
|
|
* @rpc_clnt: rpc_clnt that the client established
|
|
* @cred: cred that the client established
|
|
* @nfs_fh: filehandle to lookup
|
|
* @fmode: fmode_t to use for open
|
|
*
|
|
* This function maps a local fh to a path on a local filesystem.
|
|
* This is useful when the nfs client has the local server mounted - it can
|
|
* avoid all the NFS overhead with reads, writes and commits.
|
|
*
|
|
* On successful return, returned nfsd_file will have its nf_net member
|
|
* set. Caller (NFS client) is responsible for calling nfsd_serv_put and
|
|
* nfsd_file_put (via nfs_to_nfsd_file_put_local).
|
|
*/
|
|
struct nfsd_file *
|
|
nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
|
|
struct rpc_clnt *rpc_clnt, const struct cred *cred,
|
|
const struct nfs_fh *nfs_fh, const fmode_t fmode)
|
|
{
|
|
int mayflags = NFSD_MAY_LOCALIO;
|
|
struct svc_cred rq_cred;
|
|
struct svc_fh fh;
|
|
struct nfsd_file *localio;
|
|
__be32 beres;
|
|
|
|
if (nfs_fh->size > NFS4_FHSIZE)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
/* nfs_fh -> svc_fh */
|
|
fh_init(&fh, NFS4_FHSIZE);
|
|
fh.fh_handle.fh_size = nfs_fh->size;
|
|
memcpy(fh.fh_handle.fh_raw, nfs_fh->data, nfs_fh->size);
|
|
|
|
if (fmode & FMODE_READ)
|
|
mayflags |= NFSD_MAY_READ;
|
|
if (fmode & FMODE_WRITE)
|
|
mayflags |= NFSD_MAY_WRITE;
|
|
|
|
svcauth_map_clnt_to_svc_cred_local(rpc_clnt, cred, &rq_cred);
|
|
|
|
beres = nfsd_file_acquire_local(net, &rq_cred, dom,
|
|
&fh, mayflags, &localio);
|
|
if (beres)
|
|
localio = ERR_PTR(nfs_stat_to_errno(be32_to_cpu(beres)));
|
|
|
|
fh_put(&fh);
|
|
if (rq_cred.cr_group_info)
|
|
put_group_info(rq_cred.cr_group_info);
|
|
|
|
return localio;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nfsd_open_local_fh);
|
|
|
|
/*
|
|
* UUID_IS_LOCAL XDR functions
|
|
*/
|
|
|
|
static __be32 localio_proc_null(struct svc_rqst *rqstp)
|
|
{
|
|
return rpc_success;
|
|
}
|
|
|
|
struct localio_uuidarg {
|
|
uuid_t uuid;
|
|
};
|
|
|
|
static __be32 localio_proc_uuid_is_local(struct svc_rqst *rqstp)
|
|
{
|
|
struct localio_uuidarg *argp = rqstp->rq_argp;
|
|
struct net *net = SVC_NET(rqstp);
|
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
|
|
|
nfs_uuid_is_local(&argp->uuid, &nn->local_clients,
|
|
net, rqstp->rq_client, THIS_MODULE);
|
|
|
|
return rpc_success;
|
|
}
|
|
|
|
static bool localio_decode_uuidarg(struct svc_rqst *rqstp,
|
|
struct xdr_stream *xdr)
|
|
{
|
|
struct localio_uuidarg *argp = rqstp->rq_argp;
|
|
u8 uuid[UUID_SIZE];
|
|
|
|
if (decode_opaque_fixed(xdr, uuid, UUID_SIZE))
|
|
return false;
|
|
import_uuid(&argp->uuid, uuid);
|
|
|
|
return true;
|
|
}
|
|
|
|
static const struct svc_procedure localio_procedures1[] = {
|
|
[LOCALIOPROC_NULL] = {
|
|
.pc_func = localio_proc_null,
|
|
.pc_decode = nfssvc_decode_voidarg,
|
|
.pc_encode = nfssvc_encode_voidres,
|
|
.pc_argsize = sizeof(struct nfsd_voidargs),
|
|
.pc_ressize = sizeof(struct nfsd_voidres),
|
|
.pc_cachetype = RC_NOCACHE,
|
|
.pc_xdrressize = 0,
|
|
.pc_name = "NULL",
|
|
},
|
|
[LOCALIOPROC_UUID_IS_LOCAL] = {
|
|
.pc_func = localio_proc_uuid_is_local,
|
|
.pc_decode = localio_decode_uuidarg,
|
|
.pc_encode = nfssvc_encode_voidres,
|
|
.pc_argsize = sizeof(struct localio_uuidarg),
|
|
.pc_argzero = sizeof(struct localio_uuidarg),
|
|
.pc_ressize = sizeof(struct nfsd_voidres),
|
|
.pc_cachetype = RC_NOCACHE,
|
|
.pc_name = "UUID_IS_LOCAL",
|
|
},
|
|
};
|
|
|
|
#define LOCALIO_NR_PROCEDURES ARRAY_SIZE(localio_procedures1)
|
|
static DEFINE_PER_CPU_ALIGNED(unsigned long,
|
|
localio_count[LOCALIO_NR_PROCEDURES]);
|
|
const struct svc_version localio_version1 = {
|
|
.vs_vers = 1,
|
|
.vs_nproc = LOCALIO_NR_PROCEDURES,
|
|
.vs_proc = localio_procedures1,
|
|
.vs_dispatch = nfsd_dispatch,
|
|
.vs_count = localio_count,
|
|
.vs_xdrsize = XDR_QUADLEN(UUID_SIZE),
|
|
.vs_hidden = true,
|
|
};
|