mirror of
https://github.com/torvalds/linux.git
synced 2024-11-27 14:41:39 +00:00
cf0d7e7f45
The 'nfs_server' and 'mount_server' structures include a union of 'struct sockaddr' (with the older 16 bytes max address size) and 'struct sockaddr_storage' which is large enough to hold all the supported sa_family types (128 bytes max size). The runtime memcpy() buffer overflow checker is seeing attempts to write beyond the 16 bytes as an overflow, but the actual expected size is that of 'struct sockaddr_storage'. Plumb the use of 'struct sockaddr_storage' more completely through-out NFS, which results in adjusting the memcpy() buffers to the correct union members. Avoids this false positive run-time warning under CONFIG_FORTIFY_SOURCE: memcpy: detected field-spanning write (size 28) of single field "&ctx->nfs_server.address" at fs/nfs/namespace.c:178 (size 16) Reported-by: kernel test robot <yujie.liu@intel.com> Link: https://lore.kernel.org/all/202210110948.26b43120-yujie.liu@intel.com Cc: Trond Myklebust <trond.myklebust@hammerspace.com> Cc: Anna Schumaker <anna@kernel.org> Cc: linux-nfs@vger.kernel.org Signed-off-by: Kees Cook <keescook@chromium.org> Signed-off-by: Anna Schumaker <Anna.Schumaker@Netapp.com>
1598 lines
40 KiB
C
1598 lines
40 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* linux/fs/nfs/fs_context.c
|
|
*
|
|
* Copyright (C) 1992 Rick Sladkey
|
|
* Conversion to new mount api Copyright (C) David Howells
|
|
*
|
|
* NFS mount handling.
|
|
*
|
|
* Split from fs/nfs/super.c by David Howells <dhowells@redhat.com>
|
|
*/
|
|
|
|
#include <linux/compat.h>
|
|
#include <linux/module.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/fs_context.h>
|
|
#include <linux/fs_parser.h>
|
|
#include <linux/nfs_fs.h>
|
|
#include <linux/nfs_mount.h>
|
|
#include <linux/nfs4_mount.h>
|
|
#include "nfs.h"
|
|
#include "internal.h"
|
|
|
|
#include "nfstrace.h"
|
|
|
|
#define NFSDBG_FACILITY NFSDBG_MOUNT
|
|
|
|
#if IS_ENABLED(CONFIG_NFS_V3)
|
|
#define NFS_DEFAULT_VERSION 3
|
|
#else
|
|
#define NFS_DEFAULT_VERSION 2
|
|
#endif
|
|
|
|
#define NFS_MAX_CONNECTIONS 16
|
|
|
|
enum nfs_param {
|
|
Opt_ac,
|
|
Opt_acdirmax,
|
|
Opt_acdirmin,
|
|
Opt_acl,
|
|
Opt_acregmax,
|
|
Opt_acregmin,
|
|
Opt_actimeo,
|
|
Opt_addr,
|
|
Opt_bg,
|
|
Opt_bsize,
|
|
Opt_clientaddr,
|
|
Opt_cto,
|
|
Opt_fg,
|
|
Opt_fscache,
|
|
Opt_fscache_flag,
|
|
Opt_hard,
|
|
Opt_intr,
|
|
Opt_local_lock,
|
|
Opt_lock,
|
|
Opt_lookupcache,
|
|
Opt_migration,
|
|
Opt_minorversion,
|
|
Opt_mountaddr,
|
|
Opt_mounthost,
|
|
Opt_mountport,
|
|
Opt_mountproto,
|
|
Opt_mountvers,
|
|
Opt_namelen,
|
|
Opt_nconnect,
|
|
Opt_max_connect,
|
|
Opt_port,
|
|
Opt_posix,
|
|
Opt_proto,
|
|
Opt_rdirplus,
|
|
Opt_rdma,
|
|
Opt_resvport,
|
|
Opt_retrans,
|
|
Opt_retry,
|
|
Opt_rsize,
|
|
Opt_sec,
|
|
Opt_sharecache,
|
|
Opt_sloppy,
|
|
Opt_soft,
|
|
Opt_softerr,
|
|
Opt_softreval,
|
|
Opt_source,
|
|
Opt_tcp,
|
|
Opt_timeo,
|
|
Opt_trunkdiscovery,
|
|
Opt_udp,
|
|
Opt_v,
|
|
Opt_vers,
|
|
Opt_wsize,
|
|
Opt_write,
|
|
};
|
|
|
|
enum {
|
|
Opt_local_lock_all,
|
|
Opt_local_lock_flock,
|
|
Opt_local_lock_none,
|
|
Opt_local_lock_posix,
|
|
};
|
|
|
|
static const struct constant_table nfs_param_enums_local_lock[] = {
|
|
{ "all", Opt_local_lock_all },
|
|
{ "flock", Opt_local_lock_flock },
|
|
{ "posix", Opt_local_lock_posix },
|
|
{ "none", Opt_local_lock_none },
|
|
{}
|
|
};
|
|
|
|
enum {
|
|
Opt_lookupcache_all,
|
|
Opt_lookupcache_none,
|
|
Opt_lookupcache_positive,
|
|
};
|
|
|
|
static const struct constant_table nfs_param_enums_lookupcache[] = {
|
|
{ "all", Opt_lookupcache_all },
|
|
{ "none", Opt_lookupcache_none },
|
|
{ "pos", Opt_lookupcache_positive },
|
|
{ "positive", Opt_lookupcache_positive },
|
|
{}
|
|
};
|
|
|
|
enum {
|
|
Opt_write_lazy,
|
|
Opt_write_eager,
|
|
Opt_write_wait,
|
|
};
|
|
|
|
static const struct constant_table nfs_param_enums_write[] = {
|
|
{ "lazy", Opt_write_lazy },
|
|
{ "eager", Opt_write_eager },
|
|
{ "wait", Opt_write_wait },
|
|
{}
|
|
};
|
|
|
|
static const struct fs_parameter_spec nfs_fs_parameters[] = {
|
|
fsparam_flag_no("ac", Opt_ac),
|
|
fsparam_u32 ("acdirmax", Opt_acdirmax),
|
|
fsparam_u32 ("acdirmin", Opt_acdirmin),
|
|
fsparam_flag_no("acl", Opt_acl),
|
|
fsparam_u32 ("acregmax", Opt_acregmax),
|
|
fsparam_u32 ("acregmin", Opt_acregmin),
|
|
fsparam_u32 ("actimeo", Opt_actimeo),
|
|
fsparam_string("addr", Opt_addr),
|
|
fsparam_flag ("bg", Opt_bg),
|
|
fsparam_u32 ("bsize", Opt_bsize),
|
|
fsparam_string("clientaddr", Opt_clientaddr),
|
|
fsparam_flag_no("cto", Opt_cto),
|
|
fsparam_flag ("fg", Opt_fg),
|
|
fsparam_flag_no("fsc", Opt_fscache_flag),
|
|
fsparam_string("fsc", Opt_fscache),
|
|
fsparam_flag ("hard", Opt_hard),
|
|
__fsparam(NULL, "intr", Opt_intr,
|
|
fs_param_neg_with_no|fs_param_deprecated, NULL),
|
|
fsparam_enum ("local_lock", Opt_local_lock, nfs_param_enums_local_lock),
|
|
fsparam_flag_no("lock", Opt_lock),
|
|
fsparam_enum ("lookupcache", Opt_lookupcache, nfs_param_enums_lookupcache),
|
|
fsparam_flag_no("migration", Opt_migration),
|
|
fsparam_u32 ("minorversion", Opt_minorversion),
|
|
fsparam_string("mountaddr", Opt_mountaddr),
|
|
fsparam_string("mounthost", Opt_mounthost),
|
|
fsparam_u32 ("mountport", Opt_mountport),
|
|
fsparam_string("mountproto", Opt_mountproto),
|
|
fsparam_u32 ("mountvers", Opt_mountvers),
|
|
fsparam_u32 ("namlen", Opt_namelen),
|
|
fsparam_u32 ("nconnect", Opt_nconnect),
|
|
fsparam_u32 ("max_connect", Opt_max_connect),
|
|
fsparam_string("nfsvers", Opt_vers),
|
|
fsparam_u32 ("port", Opt_port),
|
|
fsparam_flag_no("posix", Opt_posix),
|
|
fsparam_string("proto", Opt_proto),
|
|
fsparam_flag_no("rdirplus", Opt_rdirplus),
|
|
fsparam_flag ("rdma", Opt_rdma),
|
|
fsparam_flag_no("resvport", Opt_resvport),
|
|
fsparam_u32 ("retrans", Opt_retrans),
|
|
fsparam_string("retry", Opt_retry),
|
|
fsparam_u32 ("rsize", Opt_rsize),
|
|
fsparam_string("sec", Opt_sec),
|
|
fsparam_flag_no("sharecache", Opt_sharecache),
|
|
fsparam_flag ("sloppy", Opt_sloppy),
|
|
fsparam_flag ("soft", Opt_soft),
|
|
fsparam_flag ("softerr", Opt_softerr),
|
|
fsparam_flag ("softreval", Opt_softreval),
|
|
fsparam_string("source", Opt_source),
|
|
fsparam_flag ("tcp", Opt_tcp),
|
|
fsparam_u32 ("timeo", Opt_timeo),
|
|
fsparam_flag_no("trunkdiscovery", Opt_trunkdiscovery),
|
|
fsparam_flag ("udp", Opt_udp),
|
|
fsparam_flag ("v2", Opt_v),
|
|
fsparam_flag ("v3", Opt_v),
|
|
fsparam_flag ("v4", Opt_v),
|
|
fsparam_flag ("v4.0", Opt_v),
|
|
fsparam_flag ("v4.1", Opt_v),
|
|
fsparam_flag ("v4.2", Opt_v),
|
|
fsparam_string("vers", Opt_vers),
|
|
fsparam_enum ("write", Opt_write, nfs_param_enums_write),
|
|
fsparam_u32 ("wsize", Opt_wsize),
|
|
{}
|
|
};
|
|
|
|
enum {
|
|
Opt_vers_2,
|
|
Opt_vers_3,
|
|
Opt_vers_4,
|
|
Opt_vers_4_0,
|
|
Opt_vers_4_1,
|
|
Opt_vers_4_2,
|
|
};
|
|
|
|
static const struct constant_table nfs_vers_tokens[] = {
|
|
{ "2", Opt_vers_2 },
|
|
{ "3", Opt_vers_3 },
|
|
{ "4", Opt_vers_4 },
|
|
{ "4.0", Opt_vers_4_0 },
|
|
{ "4.1", Opt_vers_4_1 },
|
|
{ "4.2", Opt_vers_4_2 },
|
|
{}
|
|
};
|
|
|
|
enum {
|
|
Opt_xprt_rdma,
|
|
Opt_xprt_rdma6,
|
|
Opt_xprt_tcp,
|
|
Opt_xprt_tcp6,
|
|
Opt_xprt_udp,
|
|
Opt_xprt_udp6,
|
|
nr__Opt_xprt
|
|
};
|
|
|
|
static const struct constant_table nfs_xprt_protocol_tokens[] = {
|
|
{ "rdma", Opt_xprt_rdma },
|
|
{ "rdma6", Opt_xprt_rdma6 },
|
|
{ "tcp", Opt_xprt_tcp },
|
|
{ "tcp6", Opt_xprt_tcp6 },
|
|
{ "udp", Opt_xprt_udp },
|
|
{ "udp6", Opt_xprt_udp6 },
|
|
{}
|
|
};
|
|
|
|
enum {
|
|
Opt_sec_krb5,
|
|
Opt_sec_krb5i,
|
|
Opt_sec_krb5p,
|
|
Opt_sec_lkey,
|
|
Opt_sec_lkeyi,
|
|
Opt_sec_lkeyp,
|
|
Opt_sec_none,
|
|
Opt_sec_spkm,
|
|
Opt_sec_spkmi,
|
|
Opt_sec_spkmp,
|
|
Opt_sec_sys,
|
|
nr__Opt_sec
|
|
};
|
|
|
|
static const struct constant_table nfs_secflavor_tokens[] = {
|
|
{ "krb5", Opt_sec_krb5 },
|
|
{ "krb5i", Opt_sec_krb5i },
|
|
{ "krb5p", Opt_sec_krb5p },
|
|
{ "lkey", Opt_sec_lkey },
|
|
{ "lkeyi", Opt_sec_lkeyi },
|
|
{ "lkeyp", Opt_sec_lkeyp },
|
|
{ "none", Opt_sec_none },
|
|
{ "null", Opt_sec_none },
|
|
{ "spkm3", Opt_sec_spkm },
|
|
{ "spkm3i", Opt_sec_spkmi },
|
|
{ "spkm3p", Opt_sec_spkmp },
|
|
{ "sys", Opt_sec_sys },
|
|
{}
|
|
};
|
|
|
|
/*
|
|
* Sanity-check a server address provided by the mount command.
|
|
*
|
|
* Address family must be initialized, and address must not be
|
|
* the ANY address for that family.
|
|
*/
|
|
static int nfs_verify_server_address(struct sockaddr_storage *addr)
|
|
{
|
|
switch (addr->ss_family) {
|
|
case AF_INET: {
|
|
struct sockaddr_in *sa = (struct sockaddr_in *)addr;
|
|
return sa->sin_addr.s_addr != htonl(INADDR_ANY);
|
|
}
|
|
case AF_INET6: {
|
|
struct in6_addr *sa = &((struct sockaddr_in6 *)addr)->sin6_addr;
|
|
return !ipv6_addr_any(sa);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_NFS_DISABLE_UDP_SUPPORT
|
|
static bool nfs_server_transport_udp_invalid(const struct nfs_fs_context *ctx)
|
|
{
|
|
return true;
|
|
}
|
|
#else
|
|
static bool nfs_server_transport_udp_invalid(const struct nfs_fs_context *ctx)
|
|
{
|
|
if (ctx->version == 4)
|
|
return true;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Sanity check the NFS transport protocol.
|
|
*/
|
|
static int nfs_validate_transport_protocol(struct fs_context *fc,
|
|
struct nfs_fs_context *ctx)
|
|
{
|
|
switch (ctx->nfs_server.protocol) {
|
|
case XPRT_TRANSPORT_UDP:
|
|
if (nfs_server_transport_udp_invalid(ctx))
|
|
goto out_invalid_transport_udp;
|
|
break;
|
|
case XPRT_TRANSPORT_TCP:
|
|
case XPRT_TRANSPORT_RDMA:
|
|
break;
|
|
default:
|
|
ctx->nfs_server.protocol = XPRT_TRANSPORT_TCP;
|
|
}
|
|
return 0;
|
|
out_invalid_transport_udp:
|
|
return nfs_invalf(fc, "NFS: Unsupported transport protocol udp");
|
|
}
|
|
|
|
/*
|
|
* For text based NFSv2/v3 mounts, the mount protocol transport default
|
|
* settings should depend upon the specified NFS transport.
|
|
*/
|
|
static void nfs_set_mount_transport_protocol(struct nfs_fs_context *ctx)
|
|
{
|
|
if (ctx->mount_server.protocol == XPRT_TRANSPORT_UDP ||
|
|
ctx->mount_server.protocol == XPRT_TRANSPORT_TCP)
|
|
return;
|
|
switch (ctx->nfs_server.protocol) {
|
|
case XPRT_TRANSPORT_UDP:
|
|
ctx->mount_server.protocol = XPRT_TRANSPORT_UDP;
|
|
break;
|
|
case XPRT_TRANSPORT_TCP:
|
|
case XPRT_TRANSPORT_RDMA:
|
|
ctx->mount_server.protocol = XPRT_TRANSPORT_TCP;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Add 'flavor' to 'auth_info' if not already present.
|
|
* Returns true if 'flavor' ends up in the list, false otherwise
|
|
*/
|
|
static int nfs_auth_info_add(struct fs_context *fc,
|
|
struct nfs_auth_info *auth_info,
|
|
rpc_authflavor_t flavor)
|
|
{
|
|
unsigned int i;
|
|
unsigned int max_flavor_len = ARRAY_SIZE(auth_info->flavors);
|
|
|
|
/* make sure this flavor isn't already in the list */
|
|
for (i = 0; i < auth_info->flavor_len; i++) {
|
|
if (flavor == auth_info->flavors[i])
|
|
return 0;
|
|
}
|
|
|
|
if (auth_info->flavor_len + 1 >= max_flavor_len)
|
|
return nfs_invalf(fc, "NFS: too many sec= flavors");
|
|
|
|
auth_info->flavors[auth_info->flavor_len++] = flavor;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Parse the value of the 'sec=' option.
|
|
*/
|
|
static int nfs_parse_security_flavors(struct fs_context *fc,
|
|
struct fs_parameter *param)
|
|
{
|
|
struct nfs_fs_context *ctx = nfs_fc2context(fc);
|
|
rpc_authflavor_t pseudoflavor;
|
|
char *string = param->string, *p;
|
|
int ret;
|
|
|
|
trace_nfs_mount_assign(param->key, string);
|
|
|
|
while ((p = strsep(&string, ":")) != NULL) {
|
|
if (!*p)
|
|
continue;
|
|
switch (lookup_constant(nfs_secflavor_tokens, p, -1)) {
|
|
case Opt_sec_none:
|
|
pseudoflavor = RPC_AUTH_NULL;
|
|
break;
|
|
case Opt_sec_sys:
|
|
pseudoflavor = RPC_AUTH_UNIX;
|
|
break;
|
|
case Opt_sec_krb5:
|
|
pseudoflavor = RPC_AUTH_GSS_KRB5;
|
|
break;
|
|
case Opt_sec_krb5i:
|
|
pseudoflavor = RPC_AUTH_GSS_KRB5I;
|
|
break;
|
|
case Opt_sec_krb5p:
|
|
pseudoflavor = RPC_AUTH_GSS_KRB5P;
|
|
break;
|
|
case Opt_sec_lkey:
|
|
pseudoflavor = RPC_AUTH_GSS_LKEY;
|
|
break;
|
|
case Opt_sec_lkeyi:
|
|
pseudoflavor = RPC_AUTH_GSS_LKEYI;
|
|
break;
|
|
case Opt_sec_lkeyp:
|
|
pseudoflavor = RPC_AUTH_GSS_LKEYP;
|
|
break;
|
|
case Opt_sec_spkm:
|
|
pseudoflavor = RPC_AUTH_GSS_SPKM;
|
|
break;
|
|
case Opt_sec_spkmi:
|
|
pseudoflavor = RPC_AUTH_GSS_SPKMI;
|
|
break;
|
|
case Opt_sec_spkmp:
|
|
pseudoflavor = RPC_AUTH_GSS_SPKMP;
|
|
break;
|
|
default:
|
|
return nfs_invalf(fc, "NFS: sec=%s option not recognized", p);
|
|
}
|
|
|
|
ret = nfs_auth_info_add(fc, &ctx->auth_info, pseudoflavor);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nfs_parse_version_string(struct fs_context *fc,
|
|
const char *string)
|
|
{
|
|
struct nfs_fs_context *ctx = nfs_fc2context(fc);
|
|
|
|
ctx->flags &= ~NFS_MOUNT_VER3;
|
|
switch (lookup_constant(nfs_vers_tokens, string, -1)) {
|
|
case Opt_vers_2:
|
|
ctx->version = 2;
|
|
break;
|
|
case Opt_vers_3:
|
|
ctx->flags |= NFS_MOUNT_VER3;
|
|
ctx->version = 3;
|
|
break;
|
|
case Opt_vers_4:
|
|
/* Backward compatibility option. In future,
|
|
* the mount program should always supply
|
|
* a NFSv4 minor version number.
|
|
*/
|
|
ctx->version = 4;
|
|
break;
|
|
case Opt_vers_4_0:
|
|
ctx->version = 4;
|
|
ctx->minorversion = 0;
|
|
break;
|
|
case Opt_vers_4_1:
|
|
ctx->version = 4;
|
|
ctx->minorversion = 1;
|
|
break;
|
|
case Opt_vers_4_2:
|
|
ctx->version = 4;
|
|
ctx->minorversion = 2;
|
|
break;
|
|
default:
|
|
return nfs_invalf(fc, "NFS: Unsupported NFS version");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Parse a single mount parameter.
|
|
*/
|
|
static int nfs_fs_context_parse_param(struct fs_context *fc,
|
|
struct fs_parameter *param)
|
|
{
|
|
struct fs_parse_result result;
|
|
struct nfs_fs_context *ctx = nfs_fc2context(fc);
|
|
unsigned short protofamily, mountfamily;
|
|
unsigned int len;
|
|
int ret, opt;
|
|
|
|
trace_nfs_mount_option(param);
|
|
|
|
opt = fs_parse(fc, nfs_fs_parameters, param, &result);
|
|
if (opt < 0)
|
|
return (opt == -ENOPARAM && ctx->sloppy) ? 1 : opt;
|
|
|
|
if (fc->security)
|
|
ctx->has_sec_mnt_opts = 1;
|
|
|
|
switch (opt) {
|
|
case Opt_source:
|
|
if (fc->source)
|
|
return nfs_invalf(fc, "NFS: Multiple sources not supported");
|
|
fc->source = param->string;
|
|
param->string = NULL;
|
|
break;
|
|
|
|
/*
|
|
* boolean options: foo/nofoo
|
|
*/
|
|
case Opt_soft:
|
|
ctx->flags |= NFS_MOUNT_SOFT;
|
|
ctx->flags &= ~NFS_MOUNT_SOFTERR;
|
|
break;
|
|
case Opt_softerr:
|
|
ctx->flags |= NFS_MOUNT_SOFTERR | NFS_MOUNT_SOFTREVAL;
|
|
ctx->flags &= ~NFS_MOUNT_SOFT;
|
|
break;
|
|
case Opt_hard:
|
|
ctx->flags &= ~(NFS_MOUNT_SOFT |
|
|
NFS_MOUNT_SOFTERR |
|
|
NFS_MOUNT_SOFTREVAL);
|
|
break;
|
|
case Opt_softreval:
|
|
if (result.negated)
|
|
ctx->flags &= ~NFS_MOUNT_SOFTREVAL;
|
|
else
|
|
ctx->flags |= NFS_MOUNT_SOFTREVAL;
|
|
break;
|
|
case Opt_posix:
|
|
if (result.negated)
|
|
ctx->flags &= ~NFS_MOUNT_POSIX;
|
|
else
|
|
ctx->flags |= NFS_MOUNT_POSIX;
|
|
break;
|
|
case Opt_cto:
|
|
if (result.negated)
|
|
ctx->flags |= NFS_MOUNT_NOCTO;
|
|
else
|
|
ctx->flags &= ~NFS_MOUNT_NOCTO;
|
|
break;
|
|
case Opt_trunkdiscovery:
|
|
if (result.negated)
|
|
ctx->flags &= ~NFS_MOUNT_TRUNK_DISCOVERY;
|
|
else
|
|
ctx->flags |= NFS_MOUNT_TRUNK_DISCOVERY;
|
|
break;
|
|
case Opt_ac:
|
|
if (result.negated)
|
|
ctx->flags |= NFS_MOUNT_NOAC;
|
|
else
|
|
ctx->flags &= ~NFS_MOUNT_NOAC;
|
|
break;
|
|
case Opt_lock:
|
|
if (result.negated) {
|
|
ctx->flags |= NFS_MOUNT_NONLM;
|
|
ctx->flags |= (NFS_MOUNT_LOCAL_FLOCK | NFS_MOUNT_LOCAL_FCNTL);
|
|
} else {
|
|
ctx->flags &= ~NFS_MOUNT_NONLM;
|
|
ctx->flags &= ~(NFS_MOUNT_LOCAL_FLOCK | NFS_MOUNT_LOCAL_FCNTL);
|
|
}
|
|
break;
|
|
case Opt_udp:
|
|
ctx->flags &= ~NFS_MOUNT_TCP;
|
|
ctx->nfs_server.protocol = XPRT_TRANSPORT_UDP;
|
|
break;
|
|
case Opt_tcp:
|
|
case Opt_rdma:
|
|
ctx->flags |= NFS_MOUNT_TCP; /* for side protocols */
|
|
ret = xprt_find_transport_ident(param->key);
|
|
if (ret < 0)
|
|
goto out_bad_transport;
|
|
ctx->nfs_server.protocol = ret;
|
|
break;
|
|
case Opt_acl:
|
|
if (result.negated)
|
|
ctx->flags |= NFS_MOUNT_NOACL;
|
|
else
|
|
ctx->flags &= ~NFS_MOUNT_NOACL;
|
|
break;
|
|
case Opt_rdirplus:
|
|
if (result.negated)
|
|
ctx->flags |= NFS_MOUNT_NORDIRPLUS;
|
|
else
|
|
ctx->flags &= ~NFS_MOUNT_NORDIRPLUS;
|
|
break;
|
|
case Opt_sharecache:
|
|
if (result.negated)
|
|
ctx->flags |= NFS_MOUNT_UNSHARED;
|
|
else
|
|
ctx->flags &= ~NFS_MOUNT_UNSHARED;
|
|
break;
|
|
case Opt_resvport:
|
|
if (result.negated)
|
|
ctx->flags |= NFS_MOUNT_NORESVPORT;
|
|
else
|
|
ctx->flags &= ~NFS_MOUNT_NORESVPORT;
|
|
break;
|
|
case Opt_fscache_flag:
|
|
if (result.negated)
|
|
ctx->options &= ~NFS_OPTION_FSCACHE;
|
|
else
|
|
ctx->options |= NFS_OPTION_FSCACHE;
|
|
kfree(ctx->fscache_uniq);
|
|
ctx->fscache_uniq = NULL;
|
|
break;
|
|
case Opt_fscache:
|
|
ctx->options |= NFS_OPTION_FSCACHE;
|
|
kfree(ctx->fscache_uniq);
|
|
ctx->fscache_uniq = param->string;
|
|
param->string = NULL;
|
|
break;
|
|
case Opt_migration:
|
|
if (result.negated)
|
|
ctx->options &= ~NFS_OPTION_MIGRATION;
|
|
else
|
|
ctx->options |= NFS_OPTION_MIGRATION;
|
|
break;
|
|
|
|
/*
|
|
* options that take numeric values
|
|
*/
|
|
case Opt_port:
|
|
if (result.uint_32 > USHRT_MAX)
|
|
goto out_of_bounds;
|
|
ctx->nfs_server.port = result.uint_32;
|
|
break;
|
|
case Opt_rsize:
|
|
ctx->rsize = result.uint_32;
|
|
break;
|
|
case Opt_wsize:
|
|
ctx->wsize = result.uint_32;
|
|
break;
|
|
case Opt_bsize:
|
|
ctx->bsize = result.uint_32;
|
|
break;
|
|
case Opt_timeo:
|
|
if (result.uint_32 < 1 || result.uint_32 > INT_MAX)
|
|
goto out_of_bounds;
|
|
ctx->timeo = result.uint_32;
|
|
break;
|
|
case Opt_retrans:
|
|
if (result.uint_32 > INT_MAX)
|
|
goto out_of_bounds;
|
|
ctx->retrans = result.uint_32;
|
|
break;
|
|
case Opt_acregmin:
|
|
ctx->acregmin = result.uint_32;
|
|
break;
|
|
case Opt_acregmax:
|
|
ctx->acregmax = result.uint_32;
|
|
break;
|
|
case Opt_acdirmin:
|
|
ctx->acdirmin = result.uint_32;
|
|
break;
|
|
case Opt_acdirmax:
|
|
ctx->acdirmax = result.uint_32;
|
|
break;
|
|
case Opt_actimeo:
|
|
ctx->acregmin = result.uint_32;
|
|
ctx->acregmax = result.uint_32;
|
|
ctx->acdirmin = result.uint_32;
|
|
ctx->acdirmax = result.uint_32;
|
|
break;
|
|
case Opt_namelen:
|
|
ctx->namlen = result.uint_32;
|
|
break;
|
|
case Opt_mountport:
|
|
if (result.uint_32 > USHRT_MAX)
|
|
goto out_of_bounds;
|
|
ctx->mount_server.port = result.uint_32;
|
|
break;
|
|
case Opt_mountvers:
|
|
if (result.uint_32 < NFS_MNT_VERSION ||
|
|
result.uint_32 > NFS_MNT3_VERSION)
|
|
goto out_of_bounds;
|
|
ctx->mount_server.version = result.uint_32;
|
|
break;
|
|
case Opt_minorversion:
|
|
if (result.uint_32 > NFS4_MAX_MINOR_VERSION)
|
|
goto out_of_bounds;
|
|
ctx->minorversion = result.uint_32;
|
|
break;
|
|
|
|
/*
|
|
* options that take text values
|
|
*/
|
|
case Opt_v:
|
|
ret = nfs_parse_version_string(fc, param->key + 1);
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
case Opt_vers:
|
|
trace_nfs_mount_assign(param->key, param->string);
|
|
ret = nfs_parse_version_string(fc, param->string);
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
case Opt_sec:
|
|
ret = nfs_parse_security_flavors(fc, param);
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
|
|
case Opt_proto:
|
|
trace_nfs_mount_assign(param->key, param->string);
|
|
protofamily = AF_INET;
|
|
switch (lookup_constant(nfs_xprt_protocol_tokens, param->string, -1)) {
|
|
case Opt_xprt_udp6:
|
|
protofamily = AF_INET6;
|
|
fallthrough;
|
|
case Opt_xprt_udp:
|
|
ctx->flags &= ~NFS_MOUNT_TCP;
|
|
ctx->nfs_server.protocol = XPRT_TRANSPORT_UDP;
|
|
break;
|
|
case Opt_xprt_tcp6:
|
|
protofamily = AF_INET6;
|
|
fallthrough;
|
|
case Opt_xprt_tcp:
|
|
ctx->flags |= NFS_MOUNT_TCP;
|
|
ctx->nfs_server.protocol = XPRT_TRANSPORT_TCP;
|
|
break;
|
|
case Opt_xprt_rdma6:
|
|
protofamily = AF_INET6;
|
|
fallthrough;
|
|
case Opt_xprt_rdma:
|
|
/* vector side protocols to TCP */
|
|
ctx->flags |= NFS_MOUNT_TCP;
|
|
ret = xprt_find_transport_ident(param->string);
|
|
if (ret < 0)
|
|
goto out_bad_transport;
|
|
ctx->nfs_server.protocol = ret;
|
|
break;
|
|
default:
|
|
goto out_bad_transport;
|
|
}
|
|
|
|
ctx->protofamily = protofamily;
|
|
break;
|
|
|
|
case Opt_mountproto:
|
|
trace_nfs_mount_assign(param->key, param->string);
|
|
mountfamily = AF_INET;
|
|
switch (lookup_constant(nfs_xprt_protocol_tokens, param->string, -1)) {
|
|
case Opt_xprt_udp6:
|
|
mountfamily = AF_INET6;
|
|
fallthrough;
|
|
case Opt_xprt_udp:
|
|
ctx->mount_server.protocol = XPRT_TRANSPORT_UDP;
|
|
break;
|
|
case Opt_xprt_tcp6:
|
|
mountfamily = AF_INET6;
|
|
fallthrough;
|
|
case Opt_xprt_tcp:
|
|
ctx->mount_server.protocol = XPRT_TRANSPORT_TCP;
|
|
break;
|
|
case Opt_xprt_rdma: /* not used for side protocols */
|
|
default:
|
|
goto out_bad_transport;
|
|
}
|
|
ctx->mountfamily = mountfamily;
|
|
break;
|
|
|
|
case Opt_addr:
|
|
trace_nfs_mount_assign(param->key, param->string);
|
|
len = rpc_pton(fc->net_ns, param->string, param->size,
|
|
&ctx->nfs_server.address,
|
|
sizeof(ctx->nfs_server._address));
|
|
if (len == 0)
|
|
goto out_invalid_address;
|
|
ctx->nfs_server.addrlen = len;
|
|
break;
|
|
case Opt_clientaddr:
|
|
trace_nfs_mount_assign(param->key, param->string);
|
|
kfree(ctx->client_address);
|
|
ctx->client_address = param->string;
|
|
param->string = NULL;
|
|
break;
|
|
case Opt_mounthost:
|
|
trace_nfs_mount_assign(param->key, param->string);
|
|
kfree(ctx->mount_server.hostname);
|
|
ctx->mount_server.hostname = param->string;
|
|
param->string = NULL;
|
|
break;
|
|
case Opt_mountaddr:
|
|
trace_nfs_mount_assign(param->key, param->string);
|
|
len = rpc_pton(fc->net_ns, param->string, param->size,
|
|
&ctx->mount_server.address,
|
|
sizeof(ctx->mount_server._address));
|
|
if (len == 0)
|
|
goto out_invalid_address;
|
|
ctx->mount_server.addrlen = len;
|
|
break;
|
|
case Opt_nconnect:
|
|
if (result.uint_32 < 1 || result.uint_32 > NFS_MAX_CONNECTIONS)
|
|
goto out_of_bounds;
|
|
ctx->nfs_server.nconnect = result.uint_32;
|
|
break;
|
|
case Opt_max_connect:
|
|
if (result.uint_32 < 1 || result.uint_32 > NFS_MAX_TRANSPORTS)
|
|
goto out_of_bounds;
|
|
ctx->nfs_server.max_connect = result.uint_32;
|
|
break;
|
|
case Opt_lookupcache:
|
|
switch (result.uint_32) {
|
|
case Opt_lookupcache_all:
|
|
ctx->flags &= ~(NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE);
|
|
break;
|
|
case Opt_lookupcache_positive:
|
|
ctx->flags &= ~NFS_MOUNT_LOOKUP_CACHE_NONE;
|
|
ctx->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG;
|
|
break;
|
|
case Opt_lookupcache_none:
|
|
ctx->flags |= NFS_MOUNT_LOOKUP_CACHE_NONEG|NFS_MOUNT_LOOKUP_CACHE_NONE;
|
|
break;
|
|
default:
|
|
goto out_invalid_value;
|
|
}
|
|
break;
|
|
case Opt_local_lock:
|
|
switch (result.uint_32) {
|
|
case Opt_local_lock_all:
|
|
ctx->flags |= (NFS_MOUNT_LOCAL_FLOCK |
|
|
NFS_MOUNT_LOCAL_FCNTL);
|
|
break;
|
|
case Opt_local_lock_flock:
|
|
ctx->flags |= NFS_MOUNT_LOCAL_FLOCK;
|
|
break;
|
|
case Opt_local_lock_posix:
|
|
ctx->flags |= NFS_MOUNT_LOCAL_FCNTL;
|
|
break;
|
|
case Opt_local_lock_none:
|
|
ctx->flags &= ~(NFS_MOUNT_LOCAL_FLOCK |
|
|
NFS_MOUNT_LOCAL_FCNTL);
|
|
break;
|
|
default:
|
|
goto out_invalid_value;
|
|
}
|
|
break;
|
|
case Opt_write:
|
|
switch (result.uint_32) {
|
|
case Opt_write_lazy:
|
|
ctx->flags &=
|
|
~(NFS_MOUNT_WRITE_EAGER | NFS_MOUNT_WRITE_WAIT);
|
|
break;
|
|
case Opt_write_eager:
|
|
ctx->flags |= NFS_MOUNT_WRITE_EAGER;
|
|
ctx->flags &= ~NFS_MOUNT_WRITE_WAIT;
|
|
break;
|
|
case Opt_write_wait:
|
|
ctx->flags |=
|
|
NFS_MOUNT_WRITE_EAGER | NFS_MOUNT_WRITE_WAIT;
|
|
break;
|
|
default:
|
|
goto out_invalid_value;
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* Special options
|
|
*/
|
|
case Opt_sloppy:
|
|
ctx->sloppy = true;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
|
|
out_invalid_value:
|
|
return nfs_invalf(fc, "NFS: Bad mount option value specified");
|
|
out_invalid_address:
|
|
return nfs_invalf(fc, "NFS: Bad IP address specified");
|
|
out_of_bounds:
|
|
return nfs_invalf(fc, "NFS: Value for '%s' out of range", param->key);
|
|
out_bad_transport:
|
|
return nfs_invalf(fc, "NFS: Unrecognized transport protocol");
|
|
}
|
|
|
|
/*
|
|
* Split fc->source into "hostname:export_path".
|
|
*
|
|
* The leftmost colon demarks the split between the server's hostname
|
|
* and the export path. If the hostname starts with a left square
|
|
* bracket, then it may contain colons.
|
|
*
|
|
* Note: caller frees hostname and export path, even on error.
|
|
*/
|
|
static int nfs_parse_source(struct fs_context *fc,
|
|
size_t maxnamlen, size_t maxpathlen)
|
|
{
|
|
struct nfs_fs_context *ctx = nfs_fc2context(fc);
|
|
const char *dev_name = fc->source;
|
|
size_t len;
|
|
const char *end;
|
|
|
|
if (unlikely(!dev_name || !*dev_name))
|
|
return -EINVAL;
|
|
|
|
/* Is the host name protected with square brakcets? */
|
|
if (*dev_name == '[') {
|
|
end = strchr(++dev_name, ']');
|
|
if (end == NULL || end[1] != ':')
|
|
goto out_bad_devname;
|
|
|
|
len = end - dev_name;
|
|
end++;
|
|
} else {
|
|
const char *comma;
|
|
|
|
end = strchr(dev_name, ':');
|
|
if (end == NULL)
|
|
goto out_bad_devname;
|
|
len = end - dev_name;
|
|
|
|
/* kill possible hostname list: not supported */
|
|
comma = memchr(dev_name, ',', len);
|
|
if (comma)
|
|
len = comma - dev_name;
|
|
}
|
|
|
|
if (len > maxnamlen)
|
|
goto out_hostname;
|
|
|
|
kfree(ctx->nfs_server.hostname);
|
|
|
|
/* N.B. caller will free nfs_server.hostname in all cases */
|
|
ctx->nfs_server.hostname = kmemdup_nul(dev_name, len, GFP_KERNEL);
|
|
if (!ctx->nfs_server.hostname)
|
|
goto out_nomem;
|
|
len = strlen(++end);
|
|
if (len > maxpathlen)
|
|
goto out_path;
|
|
ctx->nfs_server.export_path = kmemdup_nul(end, len, GFP_KERNEL);
|
|
if (!ctx->nfs_server.export_path)
|
|
goto out_nomem;
|
|
|
|
trace_nfs_mount_path(ctx->nfs_server.export_path);
|
|
return 0;
|
|
|
|
out_bad_devname:
|
|
return nfs_invalf(fc, "NFS: device name not in host:path format");
|
|
out_nomem:
|
|
nfs_errorf(fc, "NFS: not enough memory to parse device name");
|
|
return -ENOMEM;
|
|
out_hostname:
|
|
nfs_errorf(fc, "NFS: server hostname too long");
|
|
return -ENAMETOOLONG;
|
|
out_path:
|
|
nfs_errorf(fc, "NFS: export pathname too long");
|
|
return -ENAMETOOLONG;
|
|
}
|
|
|
|
static inline bool is_remount_fc(struct fs_context *fc)
|
|
{
|
|
return fc->root != NULL;
|
|
}
|
|
|
|
/*
|
|
* Parse monolithic NFS2/NFS3 mount data
|
|
* - fills in the mount root filehandle
|
|
*
|
|
* For option strings, user space handles the following behaviors:
|
|
*
|
|
* + DNS: mapping server host name to IP address ("addr=" option)
|
|
*
|
|
* + failure mode: how to behave if a mount request can't be handled
|
|
* immediately ("fg/bg" option)
|
|
*
|
|
* + retry: how often to retry a mount request ("retry=" option)
|
|
*
|
|
* + breaking back: trying proto=udp after proto=tcp, v2 after v3,
|
|
* mountproto=tcp after mountproto=udp, and so on
|
|
*/
|
|
static int nfs23_parse_monolithic(struct fs_context *fc,
|
|
struct nfs_mount_data *data)
|
|
{
|
|
struct nfs_fs_context *ctx = nfs_fc2context(fc);
|
|
struct nfs_fh *mntfh = ctx->mntfh;
|
|
struct sockaddr_storage *sap = &ctx->nfs_server._address;
|
|
int extra_flags = NFS_MOUNT_LEGACY_INTERFACE;
|
|
int ret;
|
|
|
|
if (data == NULL)
|
|
goto out_no_data;
|
|
|
|
ctx->version = NFS_DEFAULT_VERSION;
|
|
switch (data->version) {
|
|
case 1:
|
|
data->namlen = 0;
|
|
fallthrough;
|
|
case 2:
|
|
data->bsize = 0;
|
|
fallthrough;
|
|
case 3:
|
|
if (data->flags & NFS_MOUNT_VER3)
|
|
goto out_no_v3;
|
|
data->root.size = NFS2_FHSIZE;
|
|
memcpy(data->root.data, data->old_root.data, NFS2_FHSIZE);
|
|
/* Turn off security negotiation */
|
|
extra_flags |= NFS_MOUNT_SECFLAVOUR;
|
|
fallthrough;
|
|
case 4:
|
|
if (data->flags & NFS_MOUNT_SECFLAVOUR)
|
|
goto out_no_sec;
|
|
fallthrough;
|
|
case 5:
|
|
memset(data->context, 0, sizeof(data->context));
|
|
fallthrough;
|
|
case 6:
|
|
if (data->flags & NFS_MOUNT_VER3) {
|
|
if (data->root.size > NFS3_FHSIZE || data->root.size == 0)
|
|
goto out_invalid_fh;
|
|
mntfh->size = data->root.size;
|
|
ctx->version = 3;
|
|
} else {
|
|
mntfh->size = NFS2_FHSIZE;
|
|
ctx->version = 2;
|
|
}
|
|
|
|
|
|
memcpy(mntfh->data, data->root.data, mntfh->size);
|
|
if (mntfh->size < sizeof(mntfh->data))
|
|
memset(mntfh->data + mntfh->size, 0,
|
|
sizeof(mntfh->data) - mntfh->size);
|
|
|
|
/*
|
|
* for proto == XPRT_TRANSPORT_UDP, which is what uses
|
|
* to_exponential, implying shift: limit the shift value
|
|
* to BITS_PER_LONG (majortimeo is unsigned long)
|
|
*/
|
|
if (!(data->flags & NFS_MOUNT_TCP)) /* this will be UDP */
|
|
if (data->retrans >= 64) /* shift value is too large */
|
|
goto out_invalid_data;
|
|
|
|
/*
|
|
* Translate to nfs_fs_context, which nfs_fill_super
|
|
* can deal with.
|
|
*/
|
|
ctx->flags = data->flags & NFS_MOUNT_FLAGMASK;
|
|
ctx->flags |= extra_flags;
|
|
ctx->rsize = data->rsize;
|
|
ctx->wsize = data->wsize;
|
|
ctx->timeo = data->timeo;
|
|
ctx->retrans = data->retrans;
|
|
ctx->acregmin = data->acregmin;
|
|
ctx->acregmax = data->acregmax;
|
|
ctx->acdirmin = data->acdirmin;
|
|
ctx->acdirmax = data->acdirmax;
|
|
ctx->need_mount = false;
|
|
|
|
memcpy(sap, &data->addr, sizeof(data->addr));
|
|
ctx->nfs_server.addrlen = sizeof(data->addr);
|
|
ctx->nfs_server.port = ntohs(data->addr.sin_port);
|
|
if (sap->ss_family != AF_INET ||
|
|
!nfs_verify_server_address(sap))
|
|
goto out_no_address;
|
|
|
|
if (!(data->flags & NFS_MOUNT_TCP))
|
|
ctx->nfs_server.protocol = XPRT_TRANSPORT_UDP;
|
|
/* N.B. caller will free nfs_server.hostname in all cases */
|
|
ctx->nfs_server.hostname = kstrdup(data->hostname, GFP_KERNEL);
|
|
if (!ctx->nfs_server.hostname)
|
|
goto out_nomem;
|
|
|
|
ctx->namlen = data->namlen;
|
|
ctx->bsize = data->bsize;
|
|
|
|
if (data->flags & NFS_MOUNT_SECFLAVOUR)
|
|
ctx->selected_flavor = data->pseudoflavor;
|
|
else
|
|
ctx->selected_flavor = RPC_AUTH_UNIX;
|
|
|
|
if (!(data->flags & NFS_MOUNT_NONLM))
|
|
ctx->flags &= ~(NFS_MOUNT_LOCAL_FLOCK|
|
|
NFS_MOUNT_LOCAL_FCNTL);
|
|
else
|
|
ctx->flags |= (NFS_MOUNT_LOCAL_FLOCK|
|
|
NFS_MOUNT_LOCAL_FCNTL);
|
|
|
|
/*
|
|
* The legacy version 6 binary mount data from userspace has a
|
|
* field used only to transport selinux information into the
|
|
* kernel. To continue to support that functionality we
|
|
* have a touch of selinux knowledge here in the NFS code. The
|
|
* userspace code converted context=blah to just blah so we are
|
|
* converting back to the full string selinux understands.
|
|
*/
|
|
if (data->context[0]){
|
|
#ifdef CONFIG_SECURITY_SELINUX
|
|
int ret;
|
|
|
|
data->context[NFS_MAX_CONTEXT_LEN] = '\0';
|
|
ret = vfs_parse_fs_string(fc, "context",
|
|
data->context, strlen(data->context));
|
|
if (ret < 0)
|
|
return ret;
|
|
#else
|
|
return -EINVAL;
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
default:
|
|
goto generic;
|
|
}
|
|
|
|
ret = nfs_validate_transport_protocol(fc, ctx);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ctx->skip_reconfig_option_check = true;
|
|
return 0;
|
|
|
|
generic:
|
|
return generic_parse_monolithic(fc, data);
|
|
|
|
out_no_data:
|
|
if (is_remount_fc(fc)) {
|
|
ctx->skip_reconfig_option_check = true;
|
|
return 0;
|
|
}
|
|
return nfs_invalf(fc, "NFS: mount program didn't pass any mount data");
|
|
|
|
out_no_v3:
|
|
return nfs_invalf(fc, "NFS: nfs_mount_data version does not support v3");
|
|
|
|
out_no_sec:
|
|
return nfs_invalf(fc, "NFS: nfs_mount_data version supports only AUTH_SYS");
|
|
|
|
out_nomem:
|
|
return -ENOMEM;
|
|
|
|
out_no_address:
|
|
return nfs_invalf(fc, "NFS: mount program didn't pass remote address");
|
|
|
|
out_invalid_fh:
|
|
return nfs_invalf(fc, "NFS: invalid root filehandle");
|
|
|
|
out_invalid_data:
|
|
return nfs_invalf(fc, "NFS: invalid binary mount data");
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_NFS_V4)
|
|
struct compat_nfs_string {
|
|
compat_uint_t len;
|
|
compat_uptr_t data;
|
|
};
|
|
|
|
static inline void compat_nfs_string(struct nfs_string *dst,
|
|
struct compat_nfs_string *src)
|
|
{
|
|
dst->data = compat_ptr(src->data);
|
|
dst->len = src->len;
|
|
}
|
|
|
|
struct compat_nfs4_mount_data_v1 {
|
|
compat_int_t version;
|
|
compat_int_t flags;
|
|
compat_int_t rsize;
|
|
compat_int_t wsize;
|
|
compat_int_t timeo;
|
|
compat_int_t retrans;
|
|
compat_int_t acregmin;
|
|
compat_int_t acregmax;
|
|
compat_int_t acdirmin;
|
|
compat_int_t acdirmax;
|
|
struct compat_nfs_string client_addr;
|
|
struct compat_nfs_string mnt_path;
|
|
struct compat_nfs_string hostname;
|
|
compat_uint_t host_addrlen;
|
|
compat_uptr_t host_addr;
|
|
compat_int_t proto;
|
|
compat_int_t auth_flavourlen;
|
|
compat_uptr_t auth_flavours;
|
|
};
|
|
|
|
static void nfs4_compat_mount_data_conv(struct nfs4_mount_data *data)
|
|
{
|
|
struct compat_nfs4_mount_data_v1 *compat =
|
|
(struct compat_nfs4_mount_data_v1 *)data;
|
|
|
|
/* copy the fields backwards */
|
|
data->auth_flavours = compat_ptr(compat->auth_flavours);
|
|
data->auth_flavourlen = compat->auth_flavourlen;
|
|
data->proto = compat->proto;
|
|
data->host_addr = compat_ptr(compat->host_addr);
|
|
data->host_addrlen = compat->host_addrlen;
|
|
compat_nfs_string(&data->hostname, &compat->hostname);
|
|
compat_nfs_string(&data->mnt_path, &compat->mnt_path);
|
|
compat_nfs_string(&data->client_addr, &compat->client_addr);
|
|
data->acdirmax = compat->acdirmax;
|
|
data->acdirmin = compat->acdirmin;
|
|
data->acregmax = compat->acregmax;
|
|
data->acregmin = compat->acregmin;
|
|
data->retrans = compat->retrans;
|
|
data->timeo = compat->timeo;
|
|
data->wsize = compat->wsize;
|
|
data->rsize = compat->rsize;
|
|
data->flags = compat->flags;
|
|
data->version = compat->version;
|
|
}
|
|
|
|
/*
|
|
* Validate NFSv4 mount options
|
|
*/
|
|
static int nfs4_parse_monolithic(struct fs_context *fc,
|
|
struct nfs4_mount_data *data)
|
|
{
|
|
struct nfs_fs_context *ctx = nfs_fc2context(fc);
|
|
struct sockaddr_storage *sap = &ctx->nfs_server._address;
|
|
int ret;
|
|
char *c;
|
|
|
|
if (!data) {
|
|
if (is_remount_fc(fc))
|
|
goto done;
|
|
return nfs_invalf(fc,
|
|
"NFS4: mount program didn't pass any mount data");
|
|
}
|
|
|
|
ctx->version = 4;
|
|
|
|
if (data->version != 1)
|
|
return generic_parse_monolithic(fc, data);
|
|
|
|
if (in_compat_syscall())
|
|
nfs4_compat_mount_data_conv(data);
|
|
|
|
if (data->host_addrlen > sizeof(ctx->nfs_server.address))
|
|
goto out_no_address;
|
|
if (data->host_addrlen == 0)
|
|
goto out_no_address;
|
|
ctx->nfs_server.addrlen = data->host_addrlen;
|
|
if (copy_from_user(sap, data->host_addr, data->host_addrlen))
|
|
return -EFAULT;
|
|
if (!nfs_verify_server_address(sap))
|
|
goto out_no_address;
|
|
ctx->nfs_server.port = ntohs(((struct sockaddr_in *)sap)->sin_port);
|
|
|
|
if (data->auth_flavourlen) {
|
|
rpc_authflavor_t pseudoflavor;
|
|
|
|
if (data->auth_flavourlen > 1)
|
|
goto out_inval_auth;
|
|
if (copy_from_user(&pseudoflavor, data->auth_flavours,
|
|
sizeof(pseudoflavor)))
|
|
return -EFAULT;
|
|
ctx->selected_flavor = pseudoflavor;
|
|
} else {
|
|
ctx->selected_flavor = RPC_AUTH_UNIX;
|
|
}
|
|
|
|
c = strndup_user(data->hostname.data, NFS4_MAXNAMLEN);
|
|
if (IS_ERR(c))
|
|
return PTR_ERR(c);
|
|
ctx->nfs_server.hostname = c;
|
|
|
|
c = strndup_user(data->mnt_path.data, NFS4_MAXPATHLEN);
|
|
if (IS_ERR(c))
|
|
return PTR_ERR(c);
|
|
ctx->nfs_server.export_path = c;
|
|
trace_nfs_mount_path(c);
|
|
|
|
c = strndup_user(data->client_addr.data, 16);
|
|
if (IS_ERR(c))
|
|
return PTR_ERR(c);
|
|
ctx->client_address = c;
|
|
|
|
/*
|
|
* Translate to nfs_fs_context, which nfs_fill_super
|
|
* can deal with.
|
|
*/
|
|
|
|
ctx->flags = data->flags & NFS4_MOUNT_FLAGMASK;
|
|
ctx->rsize = data->rsize;
|
|
ctx->wsize = data->wsize;
|
|
ctx->timeo = data->timeo;
|
|
ctx->retrans = data->retrans;
|
|
ctx->acregmin = data->acregmin;
|
|
ctx->acregmax = data->acregmax;
|
|
ctx->acdirmin = data->acdirmin;
|
|
ctx->acdirmax = data->acdirmax;
|
|
ctx->nfs_server.protocol = data->proto;
|
|
ret = nfs_validate_transport_protocol(fc, ctx);
|
|
if (ret)
|
|
return ret;
|
|
done:
|
|
ctx->skip_reconfig_option_check = true;
|
|
return 0;
|
|
|
|
out_inval_auth:
|
|
return nfs_invalf(fc, "NFS4: Invalid number of RPC auth flavours %d",
|
|
data->auth_flavourlen);
|
|
|
|
out_no_address:
|
|
return nfs_invalf(fc, "NFS4: mount program didn't pass remote address");
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Parse a monolithic block of data from sys_mount().
|
|
*/
|
|
static int nfs_fs_context_parse_monolithic(struct fs_context *fc,
|
|
void *data)
|
|
{
|
|
if (fc->fs_type == &nfs_fs_type)
|
|
return nfs23_parse_monolithic(fc, data);
|
|
|
|
#if IS_ENABLED(CONFIG_NFS_V4)
|
|
if (fc->fs_type == &nfs4_fs_type)
|
|
return nfs4_parse_monolithic(fc, data);
|
|
#endif
|
|
|
|
return nfs_invalf(fc, "NFS: Unsupported monolithic data version");
|
|
}
|
|
|
|
/*
|
|
* Validate the preparsed information in the config.
|
|
*/
|
|
static int nfs_fs_context_validate(struct fs_context *fc)
|
|
{
|
|
struct nfs_fs_context *ctx = nfs_fc2context(fc);
|
|
struct nfs_subversion *nfs_mod;
|
|
struct sockaddr_storage *sap = &ctx->nfs_server._address;
|
|
int max_namelen = PAGE_SIZE;
|
|
int max_pathlen = NFS_MAXPATHLEN;
|
|
int port = 0;
|
|
int ret;
|
|
|
|
if (!fc->source)
|
|
goto out_no_device_name;
|
|
|
|
/* Check for sanity first. */
|
|
if (ctx->minorversion && ctx->version != 4)
|
|
goto out_minorversion_mismatch;
|
|
|
|
if (ctx->options & NFS_OPTION_MIGRATION &&
|
|
(ctx->version != 4 || ctx->minorversion != 0))
|
|
goto out_migration_misuse;
|
|
|
|
/* Verify that any proto=/mountproto= options match the address
|
|
* families in the addr=/mountaddr= options.
|
|
*/
|
|
if (ctx->protofamily != AF_UNSPEC &&
|
|
ctx->protofamily != ctx->nfs_server.address.sa_family)
|
|
goto out_proto_mismatch;
|
|
|
|
if (ctx->mountfamily != AF_UNSPEC) {
|
|
if (ctx->mount_server.addrlen) {
|
|
if (ctx->mountfamily != ctx->mount_server.address.sa_family)
|
|
goto out_mountproto_mismatch;
|
|
} else {
|
|
if (ctx->mountfamily != ctx->nfs_server.address.sa_family)
|
|
goto out_mountproto_mismatch;
|
|
}
|
|
}
|
|
|
|
if (!nfs_verify_server_address(sap))
|
|
goto out_no_address;
|
|
|
|
ret = nfs_validate_transport_protocol(fc, ctx);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (ctx->version == 4) {
|
|
if (IS_ENABLED(CONFIG_NFS_V4)) {
|
|
if (ctx->nfs_server.protocol == XPRT_TRANSPORT_RDMA)
|
|
port = NFS_RDMA_PORT;
|
|
else
|
|
port = NFS_PORT;
|
|
max_namelen = NFS4_MAXNAMLEN;
|
|
max_pathlen = NFS4_MAXPATHLEN;
|
|
ctx->flags &= ~(NFS_MOUNT_NONLM | NFS_MOUNT_NOACL |
|
|
NFS_MOUNT_VER3 | NFS_MOUNT_LOCAL_FLOCK |
|
|
NFS_MOUNT_LOCAL_FCNTL);
|
|
} else {
|
|
goto out_v4_not_compiled;
|
|
}
|
|
} else {
|
|
nfs_set_mount_transport_protocol(ctx);
|
|
if (ctx->nfs_server.protocol == XPRT_TRANSPORT_RDMA)
|
|
port = NFS_RDMA_PORT;
|
|
}
|
|
|
|
nfs_set_port(sap, &ctx->nfs_server.port, port);
|
|
|
|
ret = nfs_parse_source(fc, max_namelen, max_pathlen);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Load the NFS protocol module if we haven't done so yet */
|
|
if (!ctx->nfs_mod) {
|
|
nfs_mod = get_nfs_version(ctx->version);
|
|
if (IS_ERR(nfs_mod)) {
|
|
ret = PTR_ERR(nfs_mod);
|
|
goto out_version_unavailable;
|
|
}
|
|
ctx->nfs_mod = nfs_mod;
|
|
}
|
|
|
|
/* Ensure the filesystem context has the correct fs_type */
|
|
if (fc->fs_type != ctx->nfs_mod->nfs_fs) {
|
|
module_put(fc->fs_type->owner);
|
|
__module_get(ctx->nfs_mod->nfs_fs->owner);
|
|
fc->fs_type = ctx->nfs_mod->nfs_fs;
|
|
}
|
|
return 0;
|
|
|
|
out_no_device_name:
|
|
return nfs_invalf(fc, "NFS: Device name not specified");
|
|
out_v4_not_compiled:
|
|
nfs_errorf(fc, "NFS: NFSv4 is not compiled into kernel");
|
|
return -EPROTONOSUPPORT;
|
|
out_no_address:
|
|
return nfs_invalf(fc, "NFS: mount program didn't pass remote address");
|
|
out_mountproto_mismatch:
|
|
return nfs_invalf(fc, "NFS: Mount server address does not match mountproto= option");
|
|
out_proto_mismatch:
|
|
return nfs_invalf(fc, "NFS: Server address does not match proto= option");
|
|
out_minorversion_mismatch:
|
|
return nfs_invalf(fc, "NFS: Mount option vers=%u does not support minorversion=%u",
|
|
ctx->version, ctx->minorversion);
|
|
out_migration_misuse:
|
|
return nfs_invalf(fc, "NFS: 'Migration' not supported for this NFS version");
|
|
out_version_unavailable:
|
|
nfs_errorf(fc, "NFS: Version unavailable");
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Create an NFS superblock by the appropriate method.
|
|
*/
|
|
static int nfs_get_tree(struct fs_context *fc)
|
|
{
|
|
struct nfs_fs_context *ctx = nfs_fc2context(fc);
|
|
int err = nfs_fs_context_validate(fc);
|
|
|
|
if (err)
|
|
return err;
|
|
if (!ctx->internal)
|
|
return ctx->nfs_mod->rpc_ops->try_get_tree(fc);
|
|
else
|
|
return nfs_get_tree_common(fc);
|
|
}
|
|
|
|
/*
|
|
* Handle duplication of a configuration. The caller copied *src into *sc, but
|
|
* it can't deal with resource pointers in the filesystem context, so we have
|
|
* to do that. We need to clear pointers, copy data or get extra refs as
|
|
* appropriate.
|
|
*/
|
|
static int nfs_fs_context_dup(struct fs_context *fc, struct fs_context *src_fc)
|
|
{
|
|
struct nfs_fs_context *src = nfs_fc2context(src_fc), *ctx;
|
|
|
|
ctx = kmemdup(src, sizeof(struct nfs_fs_context), GFP_KERNEL);
|
|
if (!ctx)
|
|
return -ENOMEM;
|
|
|
|
ctx->mntfh = nfs_alloc_fhandle();
|
|
if (!ctx->mntfh) {
|
|
kfree(ctx);
|
|
return -ENOMEM;
|
|
}
|
|
nfs_copy_fh(ctx->mntfh, src->mntfh);
|
|
|
|
__module_get(ctx->nfs_mod->owner);
|
|
ctx->client_address = NULL;
|
|
ctx->mount_server.hostname = NULL;
|
|
ctx->nfs_server.export_path = NULL;
|
|
ctx->nfs_server.hostname = NULL;
|
|
ctx->fscache_uniq = NULL;
|
|
ctx->clone_data.fattr = NULL;
|
|
fc->fs_private = ctx;
|
|
return 0;
|
|
}
|
|
|
|
static void nfs_fs_context_free(struct fs_context *fc)
|
|
{
|
|
struct nfs_fs_context *ctx = nfs_fc2context(fc);
|
|
|
|
if (ctx) {
|
|
if (ctx->server)
|
|
nfs_free_server(ctx->server);
|
|
if (ctx->nfs_mod)
|
|
put_nfs_version(ctx->nfs_mod);
|
|
kfree(ctx->client_address);
|
|
kfree(ctx->mount_server.hostname);
|
|
kfree(ctx->nfs_server.export_path);
|
|
kfree(ctx->nfs_server.hostname);
|
|
kfree(ctx->fscache_uniq);
|
|
nfs_free_fhandle(ctx->mntfh);
|
|
nfs_free_fattr(ctx->clone_data.fattr);
|
|
kfree(ctx);
|
|
}
|
|
}
|
|
|
|
static const struct fs_context_operations nfs_fs_context_ops = {
|
|
.free = nfs_fs_context_free,
|
|
.dup = nfs_fs_context_dup,
|
|
.parse_param = nfs_fs_context_parse_param,
|
|
.parse_monolithic = nfs_fs_context_parse_monolithic,
|
|
.get_tree = nfs_get_tree,
|
|
.reconfigure = nfs_reconfigure,
|
|
};
|
|
|
|
/*
|
|
* Prepare superblock configuration. We use the namespaces attached to the
|
|
* context. This may be the current process's namespaces, or it may be a
|
|
* container's namespaces.
|
|
*/
|
|
static int nfs_init_fs_context(struct fs_context *fc)
|
|
{
|
|
struct nfs_fs_context *ctx;
|
|
|
|
ctx = kzalloc(sizeof(struct nfs_fs_context), GFP_KERNEL);
|
|
if (unlikely(!ctx))
|
|
return -ENOMEM;
|
|
|
|
ctx->mntfh = nfs_alloc_fhandle();
|
|
if (unlikely(!ctx->mntfh)) {
|
|
kfree(ctx);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ctx->protofamily = AF_UNSPEC;
|
|
ctx->mountfamily = AF_UNSPEC;
|
|
ctx->mount_server.port = NFS_UNSPEC_PORT;
|
|
|
|
if (fc->root) {
|
|
/* reconfigure, start with the current config */
|
|
struct nfs_server *nfss = fc->root->d_sb->s_fs_info;
|
|
struct net *net = nfss->nfs_client->cl_net;
|
|
|
|
ctx->flags = nfss->flags;
|
|
ctx->rsize = nfss->rsize;
|
|
ctx->wsize = nfss->wsize;
|
|
ctx->retrans = nfss->client->cl_timeout->to_retries;
|
|
ctx->selected_flavor = nfss->client->cl_auth->au_flavor;
|
|
ctx->acregmin = nfss->acregmin / HZ;
|
|
ctx->acregmax = nfss->acregmax / HZ;
|
|
ctx->acdirmin = nfss->acdirmin / HZ;
|
|
ctx->acdirmax = nfss->acdirmax / HZ;
|
|
ctx->timeo = 10U * nfss->client->cl_timeout->to_initval / HZ;
|
|
ctx->nfs_server.port = nfss->port;
|
|
ctx->nfs_server.addrlen = nfss->nfs_client->cl_addrlen;
|
|
ctx->version = nfss->nfs_client->rpc_ops->version;
|
|
ctx->minorversion = nfss->nfs_client->cl_minorversion;
|
|
|
|
memcpy(&ctx->nfs_server._address, &nfss->nfs_client->cl_addr,
|
|
ctx->nfs_server.addrlen);
|
|
|
|
if (fc->net_ns != net) {
|
|
put_net(fc->net_ns);
|
|
fc->net_ns = get_net(net);
|
|
}
|
|
|
|
ctx->nfs_mod = nfss->nfs_client->cl_nfs_mod;
|
|
__module_get(ctx->nfs_mod->owner);
|
|
} else {
|
|
/* defaults */
|
|
ctx->timeo = NFS_UNSPEC_TIMEO;
|
|
ctx->retrans = NFS_UNSPEC_RETRANS;
|
|
ctx->acregmin = NFS_DEF_ACREGMIN;
|
|
ctx->acregmax = NFS_DEF_ACREGMAX;
|
|
ctx->acdirmin = NFS_DEF_ACDIRMIN;
|
|
ctx->acdirmax = NFS_DEF_ACDIRMAX;
|
|
ctx->nfs_server.port = NFS_UNSPEC_PORT;
|
|
ctx->nfs_server.protocol = XPRT_TRANSPORT_TCP;
|
|
ctx->selected_flavor = RPC_AUTH_MAXFLAVOR;
|
|
ctx->minorversion = 0;
|
|
ctx->need_mount = true;
|
|
|
|
fc->s_iflags |= SB_I_STABLE_WRITES;
|
|
}
|
|
fc->fs_private = ctx;
|
|
fc->ops = &nfs_fs_context_ops;
|
|
return 0;
|
|
}
|
|
|
|
struct file_system_type nfs_fs_type = {
|
|
.owner = THIS_MODULE,
|
|
.name = "nfs",
|
|
.init_fs_context = nfs_init_fs_context,
|
|
.parameters = nfs_fs_parameters,
|
|
.kill_sb = nfs_kill_super,
|
|
.fs_flags = FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
|
|
};
|
|
MODULE_ALIAS_FS("nfs");
|
|
EXPORT_SYMBOL_GPL(nfs_fs_type);
|
|
|
|
#if IS_ENABLED(CONFIG_NFS_V4)
|
|
struct file_system_type nfs4_fs_type = {
|
|
.owner = THIS_MODULE,
|
|
.name = "nfs4",
|
|
.init_fs_context = nfs_init_fs_context,
|
|
.parameters = nfs_fs_parameters,
|
|
.kill_sb = nfs_kill_super,
|
|
.fs_flags = FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
|
|
};
|
|
MODULE_ALIAS_FS("nfs4");
|
|
MODULE_ALIAS("nfs4");
|
|
EXPORT_SYMBOL_GPL(nfs4_fs_type);
|
|
#endif /* CONFIG_NFS_V4 */
|