NFS: add support for multiple sec= mount options
This patch adds support for multiple security options which can be specified using a colon-delimited list of security flavors (the same syntax as nfsd's exports file). This is useful, for instance, when NFSv4.x mounts cross SECINFO boundaries. With this patch a user can use "sec=krb5i,krb5p" to mount a remote filesystem using krb5i, but can still cross into krb5p-only exports. New mounts will try all security options before failing. NFSv4.x SECINFO results will be compared against the sec= flavors to find the first flavor in both lists or if no match is found will return -EPERM. Signed-off-by: Weston Andros Adamson <dros@netapp.com> Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
This commit is contained in:
parent
5837f6dfcb
commit
4d4b69dd84
6 changed files with 145 additions and 71 deletions
|
@ -326,6 +326,7 @@ extern struct file_system_type nfs_xdev_fs_type;
|
||||||
extern struct file_system_type nfs4_xdev_fs_type;
|
extern struct file_system_type nfs4_xdev_fs_type;
|
||||||
extern struct file_system_type nfs4_referral_fs_type;
|
extern struct file_system_type nfs4_referral_fs_type;
|
||||||
#endif
|
#endif
|
||||||
|
bool nfs_auth_info_match(const struct nfs_auth_info *, rpc_authflavor_t);
|
||||||
struct dentry *nfs_try_mount(int, const char *, struct nfs_mount_info *,
|
struct dentry *nfs_try_mount(int, const char *, struct nfs_mount_info *,
|
||||||
struct nfs_subversion *);
|
struct nfs_subversion *);
|
||||||
void nfs_initialise_sb(struct super_block *);
|
void nfs_initialise_sb(struct super_block *);
|
||||||
|
|
|
@ -964,6 +964,9 @@ static int nfs4_init_server(struct nfs_server *server,
|
||||||
server->options = data->options;
|
server->options = data->options;
|
||||||
server->auth_info = data->auth_info;
|
server->auth_info = data->auth_info;
|
||||||
|
|
||||||
|
/* Use the first specified auth flavor. If this flavor isn't
|
||||||
|
* allowed by the server, use the SECINFO path to try the
|
||||||
|
* other specified flavors */
|
||||||
if (data->auth_info.flavor_len >= 1)
|
if (data->auth_info.flavor_len >= 1)
|
||||||
data->selected_flavor = data->auth_info.flavors[0];
|
data->selected_flavor = data->auth_info.flavors[0];
|
||||||
else
|
else
|
||||||
|
|
|
@ -137,6 +137,7 @@ static size_t nfs_parse_server_name(char *string, size_t len,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* nfs_find_best_sec - Find a security mechanism supported locally
|
* nfs_find_best_sec - Find a security mechanism supported locally
|
||||||
|
* @server: NFS server struct
|
||||||
* @flavors: List of security tuples returned by SECINFO procedure
|
* @flavors: List of security tuples returned by SECINFO procedure
|
||||||
*
|
*
|
||||||
* Return the pseudoflavor of the first security mechanism in
|
* Return the pseudoflavor of the first security mechanism in
|
||||||
|
@ -145,7 +146,8 @@ static size_t nfs_parse_server_name(char *string, size_t len,
|
||||||
* is searched in the order returned from the server, per RFC 3530
|
* is searched in the order returned from the server, per RFC 3530
|
||||||
* recommendation.
|
* recommendation.
|
||||||
*/
|
*/
|
||||||
static rpc_authflavor_t nfs_find_best_sec(struct nfs4_secinfo_flavors *flavors)
|
static rpc_authflavor_t nfs_find_best_sec(struct nfs_server *server,
|
||||||
|
struct nfs4_secinfo_flavors *flavors)
|
||||||
{
|
{
|
||||||
rpc_authflavor_t pseudoflavor;
|
rpc_authflavor_t pseudoflavor;
|
||||||
struct nfs4_secinfo4 *secinfo;
|
struct nfs4_secinfo4 *secinfo;
|
||||||
|
@ -160,12 +162,19 @@ static rpc_authflavor_t nfs_find_best_sec(struct nfs4_secinfo_flavors *flavors)
|
||||||
case RPC_AUTH_GSS:
|
case RPC_AUTH_GSS:
|
||||||
pseudoflavor = rpcauth_get_pseudoflavor(secinfo->flavor,
|
pseudoflavor = rpcauth_get_pseudoflavor(secinfo->flavor,
|
||||||
&secinfo->flavor_info);
|
&secinfo->flavor_info);
|
||||||
if (pseudoflavor != RPC_AUTH_MAXFLAVOR)
|
/* make sure pseudoflavor matches sec= mount opt */
|
||||||
|
if (pseudoflavor != RPC_AUTH_MAXFLAVOR &&
|
||||||
|
nfs_auth_info_match(&server->auth_info,
|
||||||
|
pseudoflavor))
|
||||||
return pseudoflavor;
|
return pseudoflavor;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* if there were any sec= options then nothing matched */
|
||||||
|
if (server->auth_info.flavor_len > 0)
|
||||||
|
return -EPERM;
|
||||||
|
|
||||||
return RPC_AUTH_UNIX;
|
return RPC_AUTH_UNIX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +196,7 @@ static rpc_authflavor_t nfs4_negotiate_security(struct inode *inode, struct qstr
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
flavor = nfs_find_best_sec(flavors);
|
flavor = nfs_find_best_sec(NFS_SERVER(inode), flavors);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
put_page(page);
|
put_page(page);
|
||||||
|
@ -390,7 +399,7 @@ struct vfsmount *nfs4_submount(struct nfs_server *server, struct dentry *dentry,
|
||||||
|
|
||||||
if (client->cl_auth->au_flavor != flavor)
|
if (client->cl_auth->au_flavor != flavor)
|
||||||
flavor = client->cl_auth->au_flavor;
|
flavor = client->cl_auth->au_flavor;
|
||||||
else if (server->auth_info.flavor_len == 0) {
|
else {
|
||||||
rpc_authflavor_t new = nfs4_negotiate_security(dir, name);
|
rpc_authflavor_t new = nfs4_negotiate_security(dir, name);
|
||||||
if ((int)new >= 0)
|
if ((int)new >= 0)
|
||||||
flavor = new;
|
flavor = new;
|
||||||
|
|
|
@ -2873,11 +2873,24 @@ static int nfs4_find_root_sec(struct nfs_server *server, struct nfs_fh *fhandle,
|
||||||
int status = -EPERM;
|
int status = -EPERM;
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
||||||
for (i = 0; i < ARRAY_SIZE(flav_array); i++) {
|
if (server->auth_info.flavor_len > 0) {
|
||||||
status = nfs4_lookup_root_sec(server, fhandle, info, flav_array[i]);
|
/* try each flavor specified by user */
|
||||||
if (status == -NFS4ERR_WRONGSEC || status == -EACCES)
|
for (i = 0; i < server->auth_info.flavor_len; i++) {
|
||||||
continue;
|
status = nfs4_lookup_root_sec(server, fhandle, info,
|
||||||
break;
|
server->auth_info.flavors[i]);
|
||||||
|
if (status == -NFS4ERR_WRONGSEC || status == -EACCES)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* no flavors specified by user, try default list */
|
||||||
|
for (i = 0; i < ARRAY_SIZE(flav_array); i++) {
|
||||||
|
status = nfs4_lookup_root_sec(server, fhandle, info,
|
||||||
|
flav_array[i]);
|
||||||
|
if (status == -NFS4ERR_WRONGSEC || status == -EACCES)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2919,9 +2932,6 @@ int nfs4_proc_get_rootfh(struct nfs_server *server, struct nfs_fh *fhandle,
|
||||||
status = nfs4_lookup_root(server, fhandle, info);
|
status = nfs4_lookup_root(server, fhandle, info);
|
||||||
if (status != -NFS4ERR_WRONGSEC)
|
if (status != -NFS4ERR_WRONGSEC)
|
||||||
break;
|
break;
|
||||||
/* Did user force a 'sec=' mount option? */
|
|
||||||
if (server->auth_info.flavor_len > 0)
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
status = nfs4_do_find_root_sec(server, fhandle, info);
|
status = nfs4_do_find_root_sec(server, fhandle, info);
|
||||||
}
|
}
|
||||||
|
@ -3179,9 +3189,6 @@ static int nfs4_proc_lookup_common(struct rpc_clnt **clnt, struct inode *dir,
|
||||||
err = -EPERM;
|
err = -EPERM;
|
||||||
if (client != *clnt)
|
if (client != *clnt)
|
||||||
goto out;
|
goto out;
|
||||||
/* No security negotiation if the user specified 'sec=' */
|
|
||||||
if (NFS_SERVER(dir)->auth_info.flavor_len > 0)
|
|
||||||
goto out;
|
|
||||||
client = nfs4_create_sec_client(client, dir, name);
|
client = nfs4_create_sec_client(client, dir, name);
|
||||||
if (IS_ERR(client))
|
if (IS_ERR(client))
|
||||||
return PTR_ERR(client);
|
return PTR_ERR(client);
|
||||||
|
@ -7942,6 +7949,9 @@ nfs41_find_root_sec(struct nfs_server *server, struct nfs_fh *fhandle,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!nfs_auth_info_match(&server->auth_info, flavor))
|
||||||
|
flavor = RPC_AUTH_MAXFLAVOR;
|
||||||
|
|
||||||
if (flavor != RPC_AUTH_MAXFLAVOR) {
|
if (flavor != RPC_AUTH_MAXFLAVOR) {
|
||||||
err = nfs4_lookup_root_sec(server, fhandle,
|
err = nfs4_lookup_root_sec(server, fhandle,
|
||||||
info, flavor);
|
info, flavor);
|
||||||
|
|
160
fs/nfs/super.c
160
fs/nfs/super.c
|
@ -497,7 +497,8 @@ static const char *nfs_pseudoflavour_to_name(rpc_authflavor_t flavour)
|
||||||
static const struct {
|
static const struct {
|
||||||
rpc_authflavor_t flavour;
|
rpc_authflavor_t flavour;
|
||||||
const char *str;
|
const char *str;
|
||||||
} sec_flavours[] = {
|
} sec_flavours[NFS_AUTH_INFO_MAX_FLAVORS] = {
|
||||||
|
/* update NFS_AUTH_INFO_MAX_FLAVORS when this list changes! */
|
||||||
{ RPC_AUTH_NULL, "null" },
|
{ RPC_AUTH_NULL, "null" },
|
||||||
{ RPC_AUTH_UNIX, "sys" },
|
{ RPC_AUTH_UNIX, "sys" },
|
||||||
{ RPC_AUTH_GSS_KRB5, "krb5" },
|
{ RPC_AUTH_GSS_KRB5, "krb5" },
|
||||||
|
@ -1018,6 +1019,52 @@ static void nfs_set_mount_transport_protocol(struct nfs_parsed_mount_data *mnt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add 'flavor' to 'auth_info' if not already present.
|
||||||
|
* Returns true if 'flavor' ends up in the list, false otherwise
|
||||||
|
*/
|
||||||
|
static bool nfs_auth_info_add(struct nfs_auth_info *auth_info,
|
||||||
|
rpc_authflavor_t flavor)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
unsigned int max_flavor_len = (sizeof(auth_info->flavors) /
|
||||||
|
sizeof(auth_info->flavors[0]));
|
||||||
|
|
||||||
|
/* 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 true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth_info->flavor_len + 1 >= max_flavor_len) {
|
||||||
|
dfprintk(MOUNT, "NFS: too many sec= flavors\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auth_info->flavors[auth_info->flavor_len++] = flavor;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return true if 'match' is in auth_info or auth_info is empty.
|
||||||
|
* Return false otherwise.
|
||||||
|
*/
|
||||||
|
bool nfs_auth_info_match(const struct nfs_auth_info *auth_info,
|
||||||
|
rpc_authflavor_t match)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!auth_info->flavor_len)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (i = 0; i < auth_info->flavor_len; i++) {
|
||||||
|
if (auth_info->flavors[i] == match)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(nfs_auth_info_match);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parse the value of the 'sec=' option.
|
* Parse the value of the 'sec=' option.
|
||||||
*/
|
*/
|
||||||
|
@ -1026,49 +1073,55 @@ static int nfs_parse_security_flavors(char *value,
|
||||||
{
|
{
|
||||||
substring_t args[MAX_OPT_ARGS];
|
substring_t args[MAX_OPT_ARGS];
|
||||||
rpc_authflavor_t pseudoflavor;
|
rpc_authflavor_t pseudoflavor;
|
||||||
|
char *p;
|
||||||
|
|
||||||
dfprintk(MOUNT, "NFS: parsing sec=%s option\n", value);
|
dfprintk(MOUNT, "NFS: parsing sec=%s option\n", value);
|
||||||
|
|
||||||
switch (match_token(value, nfs_secflavor_tokens, args)) {
|
while ((p = strsep(&value, ":")) != NULL) {
|
||||||
case Opt_sec_none:
|
switch (match_token(p, nfs_secflavor_tokens, args)) {
|
||||||
pseudoflavor = RPC_AUTH_NULL;
|
case Opt_sec_none:
|
||||||
break;
|
pseudoflavor = RPC_AUTH_NULL;
|
||||||
case Opt_sec_sys:
|
break;
|
||||||
pseudoflavor = RPC_AUTH_UNIX;
|
case Opt_sec_sys:
|
||||||
break;
|
pseudoflavor = RPC_AUTH_UNIX;
|
||||||
case Opt_sec_krb5:
|
break;
|
||||||
pseudoflavor = RPC_AUTH_GSS_KRB5;
|
case Opt_sec_krb5:
|
||||||
break;
|
pseudoflavor = RPC_AUTH_GSS_KRB5;
|
||||||
case Opt_sec_krb5i:
|
break;
|
||||||
pseudoflavor = RPC_AUTH_GSS_KRB5I;
|
case Opt_sec_krb5i:
|
||||||
break;
|
pseudoflavor = RPC_AUTH_GSS_KRB5I;
|
||||||
case Opt_sec_krb5p:
|
break;
|
||||||
pseudoflavor = RPC_AUTH_GSS_KRB5P;
|
case Opt_sec_krb5p:
|
||||||
break;
|
pseudoflavor = RPC_AUTH_GSS_KRB5P;
|
||||||
case Opt_sec_lkey:
|
break;
|
||||||
pseudoflavor = RPC_AUTH_GSS_LKEY;
|
case Opt_sec_lkey:
|
||||||
break;
|
pseudoflavor = RPC_AUTH_GSS_LKEY;
|
||||||
case Opt_sec_lkeyi:
|
break;
|
||||||
pseudoflavor = RPC_AUTH_GSS_LKEYI;
|
case Opt_sec_lkeyi:
|
||||||
break;
|
pseudoflavor = RPC_AUTH_GSS_LKEYI;
|
||||||
case Opt_sec_lkeyp:
|
break;
|
||||||
pseudoflavor = RPC_AUTH_GSS_LKEYP;
|
case Opt_sec_lkeyp:
|
||||||
break;
|
pseudoflavor = RPC_AUTH_GSS_LKEYP;
|
||||||
case Opt_sec_spkm:
|
break;
|
||||||
pseudoflavor = RPC_AUTH_GSS_SPKM;
|
case Opt_sec_spkm:
|
||||||
break;
|
pseudoflavor = RPC_AUTH_GSS_SPKM;
|
||||||
case Opt_sec_spkmi:
|
break;
|
||||||
pseudoflavor = RPC_AUTH_GSS_SPKMI;
|
case Opt_sec_spkmi:
|
||||||
break;
|
pseudoflavor = RPC_AUTH_GSS_SPKMI;
|
||||||
case Opt_sec_spkmp:
|
break;
|
||||||
pseudoflavor = RPC_AUTH_GSS_SPKMP;
|
case Opt_sec_spkmp:
|
||||||
break;
|
pseudoflavor = RPC_AUTH_GSS_SPKMP;
|
||||||
default:
|
break;
|
||||||
return 0;
|
default:
|
||||||
|
dfprintk(MOUNT,
|
||||||
|
"NFS: sec= option '%s' not recognized\n", p);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nfs_auth_info_add(&mnt->auth_info, pseudoflavor))
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
mnt->auth_info.flavors[0] = pseudoflavor;
|
|
||||||
mnt->auth_info.flavor_len = 1;
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1615,12 +1668,14 @@ out_security_failure:
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ensure that the specified authtype in args->auth_info is supported by
|
* Ensure that a specified authtype in args->auth_info is supported by
|
||||||
* the server. Returns 0 if it's ok, and -EACCES if not.
|
* the server. Returns 0 and sets args->selected_flavor if it's ok, and
|
||||||
|
* -EACCES if not.
|
||||||
*/
|
*/
|
||||||
static int nfs_verify_authflavor(struct nfs_parsed_mount_data *args,
|
static int nfs_verify_authflavors(struct nfs_parsed_mount_data *args,
|
||||||
rpc_authflavor_t *server_authlist, unsigned int count)
|
rpc_authflavor_t *server_authlist, unsigned int count)
|
||||||
{
|
{
|
||||||
|
rpc_authflavor_t flavor = RPC_AUTH_MAXFLAVOR;
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1632,17 +1687,19 @@ static int nfs_verify_authflavor(struct nfs_parsed_mount_data *args,
|
||||||
* can be used.
|
* can be used.
|
||||||
*/
|
*/
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
if (args->auth_info.flavors[0] == server_authlist[i] ||
|
flavor = server_authlist[i];
|
||||||
server_authlist[i] == RPC_AUTH_NULL)
|
|
||||||
|
if (nfs_auth_info_match(&args->auth_info, flavor) ||
|
||||||
|
flavor == RPC_AUTH_NULL)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
dfprintk(MOUNT, "NFS: auth flavor %u not supported by server\n",
|
dfprintk(MOUNT,
|
||||||
args->auth_info.flavors[0]);
|
"NFS: specified auth flavors not supported by server\n");
|
||||||
return -EACCES;
|
return -EACCES;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
args->selected_flavor = args->auth_info.flavors[0];
|
args->selected_flavor = flavor;
|
||||||
dfprintk(MOUNT, "NFS: using auth flavor %u\n", args->selected_flavor);
|
dfprintk(MOUNT, "NFS: using auth flavor %u\n", args->selected_flavor);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1732,7 +1789,7 @@ static struct nfs_server *nfs_try_mount_request(struct nfs_mount_info *mount_inf
|
||||||
* whether the server supports it, and then just try to use it if so.
|
* whether the server supports it, and then just try to use it if so.
|
||||||
*/
|
*/
|
||||||
if (args->auth_info.flavor_len > 0) {
|
if (args->auth_info.flavor_len > 0) {
|
||||||
status = nfs_verify_authflavor(args, authlist, authlist_len);
|
status = nfs_verify_authflavors(args, authlist, authlist_len);
|
||||||
dfprintk(MOUNT, "NFS: using auth flavor %u\n",
|
dfprintk(MOUNT, "NFS: using auth flavor %u\n",
|
||||||
args->selected_flavor);
|
args->selected_flavor);
|
||||||
if (status)
|
if (status)
|
||||||
|
@ -2102,9 +2159,6 @@ static int nfs_validate_text_mount_data(void *options,
|
||||||
|
|
||||||
nfs_set_port(sap, &args->nfs_server.port, port);
|
nfs_set_port(sap, &args->nfs_server.port, port);
|
||||||
|
|
||||||
if (args->auth_info.flavor_len > 1)
|
|
||||||
goto out_bad_auth;
|
|
||||||
|
|
||||||
return nfs_parse_devname(dev_name,
|
return nfs_parse_devname(dev_name,
|
||||||
&args->nfs_server.hostname,
|
&args->nfs_server.hostname,
|
||||||
max_namelen,
|
max_namelen,
|
||||||
|
@ -2124,10 +2178,6 @@ out_invalid_transport_udp:
|
||||||
out_no_address:
|
out_no_address:
|
||||||
dfprintk(MOUNT, "NFS: mount program didn't pass remote address\n");
|
dfprintk(MOUNT, "NFS: mount program didn't pass remote address\n");
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
out_bad_auth:
|
|
||||||
dfprintk(MOUNT, "NFS: Too many RPC auth flavours specified\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
|
|
@ -592,9 +592,10 @@ struct nfs_renameres {
|
||||||
};
|
};
|
||||||
|
|
||||||
/* parsed sec= options */
|
/* parsed sec= options */
|
||||||
|
#define NFS_AUTH_INFO_MAX_FLAVORS 12 /* see fs/nfs/super.c */
|
||||||
struct nfs_auth_info {
|
struct nfs_auth_info {
|
||||||
unsigned int flavor_len;
|
unsigned int flavor_len;
|
||||||
rpc_authflavor_t flavors[1];
|
rpc_authflavor_t flavors[NFS_AUTH_INFO_MAX_FLAVORS];
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Reference in a new issue