// SPDX-License-Identifier: GPL-2.0 /* * NFS exporting and validation. * * We maintain a list of clients, each of which has a list of * exports. To export an fs to a given client, you first have * to create the client entry with NFSCTL_ADDCLIENT, which * creates a client control block and adds it to the hash * table. Then, you call NFSCTL_EXPORT for each fs. * * * Copyright (C) 1995, 1996 Olaf Kirch, <okir@monad.swb.de>
*/
/* * We have two caches. * One maps client+vfsmnt+dentry to export options - the export map * The other maps client+filehandle-fragment to export options. - the expkey map * * The export options are actually stored in the first map, and the * second map contains a reference to the entry in the first map.
*/
staticvoid expkey_flush(void)
{ /* * Take the nfsd_mutex here to ensure that the file cache is not * destroyed while we're in the middle of flushing.
*/
mutex_lock(&nfsd_mutex);
nfsd_file_cache_purge(current->nsproxy->net_ns);
mutex_unlock(&nfsd_mutex);
}
/* * We currently export only dirs, regular files, and (for v4 * pseudoroot) symlinks.
*/ if (!S_ISDIR(inode->i_mode) &&
!S_ISLNK(inode->i_mode) &&
!S_ISREG(inode->i_mode)) return -ENOTDIR;
/* * Mountd should never pass down a writeable V4ROOT export, but, * just to make sure:
*/ if (*flags & NFSEXP_V4ROOT)
*flags |= NFSEXP_READONLY;
/* There are two requirements on a filesystem to be exportable. * 1: We must be able to identify the filesystem from a number. * either a device number (so FS_REQUIRES_DEV needed) * or an FSID number (so NFSEXP_FSID or ->uuid is needed). * 2: We must be able to find an inode from a filehandle. * This means that s_export_op must be set. * 3: We must not currently be on an idmapped mount.
*/ if (!(inode->i_sb->s_type->fs_flags & FS_REQUIRES_DEV) &&
!(*flags & NFSEXP_FSID) &&
uuid == NULL) {
dprintk("exp_export: export of non-dev fs without fsid\n"); return -EINVAL;
}
if (!exportfs_can_decode_fh(inode->i_sb->s_export_op)) {
dprintk("exp_export: export of invalid fs type.\n"); return -EINVAL;
}
if (is_idmapped_mnt(path->mnt)) {
dprintk("exp_export: export of idmapped mounts not yet supported.\n"); return -EINVAL;
}
if (inode->i_sb->s_export_op->flags & EXPORT_OP_NOSUBTREECHK &&
!(*flags & NFSEXP_NOSUBTREECHECK)) {
dprintk("%s: %s does not support subtree checking!\n",
__func__, inode->i_sb->s_type->name); return -EINVAL;
} return 0;
}
#ifdef CONFIG_NFSD_V4
staticint
fsloc_parse(char **mesg, char *buf, struct nfsd4_fs_locations *fsloc)
{ int len; int migrated, i, err;
/* more than one fsloc */ if (fsloc->locations) return -EINVAL;
/* listsize */
err = get_uint(mesg, &fsloc->locations_count); if (err) return err; if (fsloc->locations_count > MAX_FS_LOCATIONS) return -EINVAL; if (fsloc->locations_count == 0) return 0;
fsloc->locations = kcalloc(fsloc->locations_count, sizeof(struct nfsd4_fs_location),
GFP_KERNEL); if (!fsloc->locations) return -ENOMEM; for (i=0; i < fsloc->locations_count; i++) { /* colon separated host list */
err = -EINVAL;
len = qword_get(mesg, buf, PAGE_SIZE); if (len <= 0) goto out_free_all;
err = -ENOMEM;
fsloc->locations[i].hosts = kstrdup(buf, GFP_KERNEL); if (!fsloc->locations[i].hosts) goto out_free_all;
err = -EINVAL; /* slash separated path component list */
len = qword_get(mesg, buf, PAGE_SIZE); if (len <= 0) goto out_free_all;
err = -ENOMEM;
fsloc->locations[i].path = kstrdup(buf, GFP_KERNEL); if (!fsloc->locations[i].path) goto out_free_all;
} /* migrated */
err = get_int(mesg, &migrated); if (err) goto out_free_all;
err = -EINVAL; if (migrated < 0 || migrated > 1) goto out_free_all;
fsloc->migrated = migrated; return 0;
out_free_all:
nfsd4_fslocs_free(fsloc); return err;
}
/* more than one secinfo */ if (exp->ex_nflavors) return -EINVAL;
err = get_uint(mesg, &listsize); if (err) return err; if (listsize > MAX_SECINFO_LIST) return -EINVAL;
for (f = exp->ex_flavors; f < exp->ex_flavors + listsize; f++) {
err = get_uint(mesg, &f->pseudoflavor); if (err) return err; /* * XXX: It would be nice to also check whether this * pseudoflavor is supported, so we can discover the * problem at export time instead of when a client fails * to authenticate.
*/
err = get_uint(mesg, &f->flags); if (err) return err; /* Only some flags are allowed to differ between flavors: */ if (~NFSEXP_SECINFO_FLAGS & (f->flags ^ exp->ex_flags)) return -EINVAL;
}
exp->ex_nflavors = listsize; return 0;
}
while (qword_get(&mesg, buf, PAGE_SIZE) > 0) { if (strcmp(buf, "fsloc") == 0)
err = fsloc_parse(&mesg, buf, &exp.ex_fslocs); elseif (strcmp(buf, "uuid") == 0)
err = nfsd_uuid_parse(&mesg, buf, &exp.ex_uuid); elseif (strcmp(buf, "secinfo") == 0)
err = secinfo_parse(&mesg, buf, &exp); elseif (strcmp(buf, "xprtsec") == 0)
err = xprtsec_parse(&mesg, buf, &exp); else /* quietly ignore unknown words and anything * following. Newer user-space can try to set * new values, then see what the result was.
*/ break; if (err) goto out4;
}
err = check_export(&exp.ex_path, &exp.ex_flags, exp.ex_uuid); if (err) goto out4;
/* * No point caching this if it would immediately expire. * Also, this protects exportfs's dummy export from the * anon_uid/anon_gid checks:
*/ if (exp.h.expiry_time < seconds_since_boot()) goto out4; /* * For some reason exportfs has been passing down an * invalid (-1) uid & gid on the "dummy" export which it * uses to test export support. To make sure exportfs * sees errors from check_export we therefore need to * delay these checks till after check_export:
*/
err = -EINVAL; if (!uid_valid(exp.ex_anon_uid)) goto out4; if (!gid_valid(exp.ex_anon_gid)) goto out4;
err = 0;
staticint is_export_stats_file(struct seq_file *m)
{ /* * The export_stats file uses the same ops as the exports file. * We use the file's name to determine the reported info per export. * There is no rename in nsfdfs, so d_name.name is stable.
*/ return !strcmp(m->file->f_path.dentry->d_name.name, "export_stats");
}
/* * Obtain the root fh on behalf of a client. * This could be done in user space, but I feel that it adds some safety * since its harder to fool a kernel module than a user space program.
*/ int
exp_rootfh(struct net *net, struct auth_domain *clp, char *name, struct knfsd_fh *f, int maxsize)
{ struct svc_export *exp; struct path path; struct inode *inode; struct svc_fh fh; int err; struct nfsd_net *nn = net_generic(net, nfsd_net_id); struct cache_detail *cd = nn->svc_export_cache;
err = -EPERM; /* NB: we probably ought to check that it's NUL-terminated */ if (kern_path(name, 0, &path)) {
printk("nfsd: exp_rootfh path not found %s", name); return err;
}
inode = d_inode(path.dentry);
if (IS_ERR(exp)) return ERR_CAST(exp); return exp;
}
/** * check_xprtsec_policy - check if access to export is allowed by the * xprtsec policy * @exp: svc_export that is being accessed. * @rqstp: svc_rqst attempting to access @exp. * * Helper function for check_nfsd_access(). Note that callers should be * using check_nfsd_access() instead of calling this function directly. The * one exception is __fh_verify() since it has logic that may result in one * or both of the helpers being skipped. * * Return values: * %nfs_ok if access is granted, or * %nfserr_wrongsec if access is denied
*/
__be32 check_xprtsec_policy(struct svc_export *exp, struct svc_rqst *rqstp)
{ struct svc_xprt *xprt = rqstp->rq_xprt;
if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_NONE) { if (!test_bit(XPT_TLS_SESSION, &xprt->xpt_flags)) return nfs_ok;
} if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_TLS) { if (test_bit(XPT_TLS_SESSION, &xprt->xpt_flags) &&
!test_bit(XPT_PEER_AUTH, &xprt->xpt_flags)) return nfs_ok;
} if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_MTLS) { if (test_bit(XPT_TLS_SESSION, &xprt->xpt_flags) &&
test_bit(XPT_PEER_AUTH, &xprt->xpt_flags)) return nfs_ok;
} return nfserr_wrongsec;
}
/** * check_security_flavor - check if access to export is allowed by the * security flavor * @exp: svc_export that is being accessed. * @rqstp: svc_rqst attempting to access @exp. * @may_bypass_gss: reduce strictness of authorization check * * Helper function for check_nfsd_access(). Note that callers should be * using check_nfsd_access() instead of calling this function directly. The * one exception is __fh_verify() since it has logic that may result in one * or both of the helpers being skipped. * * Return values: * %nfs_ok if access is granted, or * %nfserr_wrongsec if access is denied
*/
__be32 check_security_flavor(struct svc_export *exp, struct svc_rqst *rqstp, bool may_bypass_gss)
{ struct exp_flavor_info *f, *end = exp->ex_flavors + exp->ex_nflavors;
/* legacy gss-only clients are always OK: */ if (exp->ex_client == rqstp->rq_gssclient) return nfs_ok; /* ip-address based client; check sec= export option: */ for (f = exp->ex_flavors; f < end; f++) { if (f->pseudoflavor == rqstp->rq_cred.cr_flavor) return nfs_ok;
} /* defaults in absence of sec= options: */ if (exp->ex_nflavors == 0) { if (rqstp->rq_cred.cr_flavor == RPC_AUTH_NULL ||
rqstp->rq_cred.cr_flavor == RPC_AUTH_UNIX) return nfs_ok;
}
/* If the compound op contains a spo_must_allowed op, * it will be sent with integrity/protection which * will have to be expressly allowed on mounts that * don't support it
*/
if (nfsd4_spo_must_allow(rqstp)) return nfs_ok;
/* Some calls may be processed without authentication * on GSS exports. For example NFS2/3 calls on root * directory, see section 2.3.2 of rfc 2623. * For "may_bypass_gss" check that export has really * enabled some flavor with authentication (GSS or any * other) and also check that the used auth flavor is * without authentication (none or sys).
*/ if (may_bypass_gss && (
rqstp->rq_cred.cr_flavor == RPC_AUTH_NULL ||
rqstp->rq_cred.cr_flavor == RPC_AUTH_UNIX)) { for (f = exp->ex_flavors; f < end; f++) { if (f->pseudoflavor >= RPC_AUTH_DES) return 0;
}
}
return nfserr_wrongsec;
}
/** * check_nfsd_access - check if access to export is allowed. * @exp: svc_export that is being accessed. * @rqstp: svc_rqst attempting to access @exp. * @may_bypass_gss: reduce strictness of authorization check * * Return values: * %nfs_ok if access is granted, or * %nfserr_wrongsec if access is denied
*/
__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp, bool may_bypass_gss)
{
__be32 status;
status = check_xprtsec_policy(exp, rqstp); if (status != nfs_ok) return status; return check_security_flavor(exp, rqstp, may_bypass_gss);
}
/* * Uses rq_client and rq_gssclient to find an export; uses rq_client (an * auth_unix client) if it's available and has secinfo information; * otherwise, will try to use rq_gssclient. * * Called from functions that handle requests; functions that do work on * behalf of mountd are passed a single client name to use, and should * use exp_get_by_name() or exp_find().
*/ struct svc_export *
rqst_exp_get_by_name(struct svc_rqst *rqstp, struct path *path)
{ struct svc_export *gssexp, *exp = ERR_PTR(-ENOENT); struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id); struct cache_detail *cd = nn->svc_export_cache;
if (rqstp->rq_client == NULL) goto gss;
/* First try the auth_unix client: */
exp = exp_get_by_name(cd, rqstp->rq_client, path, &rqstp->rq_chandle); if (PTR_ERR(exp) == -ENOENT) goto gss; if (IS_ERR(exp)) return exp; /* If it has secinfo, assume there are no gss/... clients */ if (exp->ex_nflavors > 0) return exp;
gss: /* Otherwise, try falling back on gss client */ if (rqstp->rq_gssclient == NULL) return exp;
gssexp = exp_get_by_name(cd, rqstp->rq_gssclient, path, &rqstp->rq_chandle); if (PTR_ERR(gssexp) == -ENOENT) return exp; if (!IS_ERR(exp))
exp_put(exp); return gssexp;
}
/** * rqst_exp_find - Find an svc_export in the context of a rqst or similar * @reqp: The handle to be used to suspend the request if a cache-upcall is needed * If NULL, missing in-cache information will result in failure. * @net: The network namespace in which the request exists * @cl: default auth_domain to use for looking up the export * @gsscl: an alternate auth_domain defined using deprecated gss/krb5 format. * @fsid_type: The type of fsid to look for * @fsidv: The actual fsid to look up in the context of either client. * * Perform a lookup for @cl/@fsidv in the given @net for an export. If * none found and @gsscl specified, repeat the lookup. * * Returns an export, or an error pointer.
*/ struct svc_export *
rqst_exp_find(struct cache_req *reqp, struct net *net, struct auth_domain *cl, struct auth_domain *gsscl, int fsid_type, u32 *fsidv)
{ struct nfsd_net *nn = net_generic(net, nfsd_net_id); struct svc_export *gssexp, *exp = ERR_PTR(-ENOENT); struct cache_detail *cd = nn->svc_export_cache;
if (!cl) goto gss;
/* First try the auth_unix client: */
exp = exp_find(cd, cl, fsid_type, fsidv, reqp); if (PTR_ERR(exp) == -ENOENT) goto gss; if (IS_ERR(exp)) return exp; /* If it has secinfo, assume there are no gss/... clients */ if (exp->ex_nflavors > 0) return exp;
gss: /* Otherwise, try falling back on gss client */ if (!gsscl) return exp;
gssexp = exp_find(cd, gsscl, fsid_type, fsidv, reqp); if (PTR_ERR(gssexp) == -ENOENT) return exp; if (!IS_ERR(exp))
exp_put(exp); return gssexp;
}
/* * Called when we need the filehandle for the root of the pseudofs, * for a given NFSv4 client. The root is defined to be the * export point with fsid==0
*/
__be32
exp_pseudoroot(struct svc_rqst *rqstp, struct svc_fh *fhp)
{ struct svc_export *exp;
__be32 rv;
/* * Flush exports table - called when last nfsd thread is killed
*/ void
nfsd_export_flush(struct net *net)
{ struct nfsd_net *nn = net_generic(net, nfsd_net_id);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.