diff --git a/sys/kern/vfs_mount.c.export b/sys/kern/vfs_mount.c --- a/sys/kern/vfs_mount.c.export +++ b/sys/kern/vfs_mount.c @@ -806,7 +806,7 @@ struct vfsopt *opt, *tmp_opt; char *fstype, *fspath, *errmsg; int error, fstypelen, fspathlen, errmsg_len, errmsg_pos; - bool autoro; + bool autoro, has_nonexport; errmsg = fspath = NULL; errmsg_len = fspathlen = 0; @@ -843,113 +843,143 @@ } /* + * Make sure that "export" is only used with the "update", "fstype", + * "fspath", "from" and "errmsg" options when in a vnet jail. + * These are the ones used to set/update exports by mountd(8). + * Clear MNT_EXPORTED here, so that it cannot be passed in + * via the flags argument to nmount(2), to force use of "export". + * Use has_nonexport to note that options other than those + * listed above have been specified. + */ + if (jailed(td->td_ucred)) + fsflags &= ~MNT_EXPORTED; + + /* * We need to see if we have the "update" option * before we call vfs_domount(), since vfs_domount() has special * logic based on MNT_UPDATE. This is very important * when we want to update the root filesystem. */ + has_nonexport = false; TAILQ_FOREACH_SAFE(opt, optlist, link, tmp_opt) { int do_freeopt = 0; if (strcmp(opt->name, "update") == 0) { fsflags |= MNT_UPDATE; do_freeopt = 1; - } - else if (strcmp(opt->name, "async") == 0) + } else if (strcmp(opt->name, "async") == 0) { fsflags |= MNT_ASYNC; - else if (strcmp(opt->name, "force") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "force") == 0) { fsflags |= MNT_FORCE; do_freeopt = 1; - } - else if (strcmp(opt->name, "reload") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "reload") == 0) { fsflags |= MNT_RELOAD; do_freeopt = 1; - } - else if (strcmp(opt->name, "multilabel") == 0) + has_nonexport = true; + } else if (strcmp(opt->name, "multilabel") == 0) { fsflags |= MNT_MULTILABEL; - else if (strcmp(opt->name, "noasync") == 0) + has_nonexport = true; + } else if (strcmp(opt->name, "noasync") == 0) { fsflags &= ~MNT_ASYNC; - else if (strcmp(opt->name, "noatime") == 0) + has_nonexport = true; + } else if (strcmp(opt->name, "noatime") == 0) { fsflags |= MNT_NOATIME; - else if (strcmp(opt->name, "atime") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "atime") == 0) { free(opt->name, M_MOUNT); opt->name = strdup("nonoatime", M_MOUNT); - } - else if (strcmp(opt->name, "noclusterr") == 0) + has_nonexport = true; + } else if (strcmp(opt->name, "noclusterr") == 0) { fsflags |= MNT_NOCLUSTERR; - else if (strcmp(opt->name, "clusterr") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "clusterr") == 0) { free(opt->name, M_MOUNT); opt->name = strdup("nonoclusterr", M_MOUNT); - } - else if (strcmp(opt->name, "noclusterw") == 0) + has_nonexport = true; + } else if (strcmp(opt->name, "noclusterw") == 0) { fsflags |= MNT_NOCLUSTERW; - else if (strcmp(opt->name, "clusterw") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "clusterw") == 0) { free(opt->name, M_MOUNT); opt->name = strdup("nonoclusterw", M_MOUNT); - } - else if (strcmp(opt->name, "noexec") == 0) + has_nonexport = true; + } else if (strcmp(opt->name, "noexec") == 0) { fsflags |= MNT_NOEXEC; - else if (strcmp(opt->name, "exec") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "exec") == 0) { free(opt->name, M_MOUNT); opt->name = strdup("nonoexec", M_MOUNT); - } - else if (strcmp(opt->name, "nosuid") == 0) + has_nonexport = true; + } else if (strcmp(opt->name, "nosuid") == 0) { fsflags |= MNT_NOSUID; - else if (strcmp(opt->name, "suid") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "suid") == 0) { free(opt->name, M_MOUNT); opt->name = strdup("nonosuid", M_MOUNT); - } - else if (strcmp(opt->name, "nosymfollow") == 0) + has_nonexport = true; + } else if (strcmp(opt->name, "nosymfollow") == 0) { fsflags |= MNT_NOSYMFOLLOW; - else if (strcmp(opt->name, "symfollow") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "symfollow") == 0) { free(opt->name, M_MOUNT); opt->name = strdup("nonosymfollow", M_MOUNT); - } - else if (strcmp(opt->name, "noro") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "noro") == 0) { fsflags &= ~MNT_RDONLY; autoro = false; - } - else if (strcmp(opt->name, "rw") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "rw") == 0) { fsflags &= ~MNT_RDONLY; autoro = false; - } - else if (strcmp(opt->name, "ro") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "ro") == 0) { fsflags |= MNT_RDONLY; autoro = false; - } - else if (strcmp(opt->name, "rdonly") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "rdonly") == 0) { free(opt->name, M_MOUNT); opt->name = strdup("ro", M_MOUNT); fsflags |= MNT_RDONLY; autoro = false; - } - else if (strcmp(opt->name, "autoro") == 0) { + has_nonexport = true; + } else if (strcmp(opt->name, "autoro") == 0) { do_freeopt = 1; autoro = true; - } - else if (strcmp(opt->name, "suiddir") == 0) + has_nonexport = true; + } else if (strcmp(opt->name, "suiddir") == 0) { fsflags |= MNT_SUIDDIR; - else if (strcmp(opt->name, "sync") == 0) + has_nonexport = true; + } else if (strcmp(opt->name, "sync") == 0) { fsflags |= MNT_SYNCHRONOUS; - else if (strcmp(opt->name, "union") == 0) + has_nonexport = true; + } else if (strcmp(opt->name, "union") == 0) { fsflags |= MNT_UNION; - else if (strcmp(opt->name, "export") == 0) + has_nonexport = true; + } else if (strcmp(opt->name, "export") == 0) { fsflags |= MNT_EXPORTED; - else if (strcmp(opt->name, "automounted") == 0) { + autoro = false; + } else if (strcmp(opt->name, "automounted") == 0) { fsflags |= MNT_AUTOMOUNTED; do_freeopt = 1; + has_nonexport = true; } else if (strcmp(opt->name, "nocover") == 0) { fsflags |= MNT_NOCOVER; do_freeopt = 1; + has_nonexport = true; } else if (strcmp(opt->name, "cover") == 0) { fsflags &= ~MNT_NOCOVER; do_freeopt = 1; + has_nonexport = true; } else if (strcmp(opt->name, "emptydir") == 0) { fsflags |= MNT_EMPTYDIR; do_freeopt = 1; + has_nonexport = true; } else if (strcmp(opt->name, "noemptydir") == 0) { fsflags &= ~MNT_EMPTYDIR; do_freeopt = 1; + has_nonexport = true; } if (do_freeopt) vfs_freeopt(optlist, opt); @@ -965,6 +995,16 @@ goto bail; } + /* + * In a prison, only allow "export" to be combined with the + * options listed in the comment, above. + */ + if ((fsflags & MNT_EXPORTED) != 0 && jailed(td->td_ucred) && + (has_nonexport || (fsflags & MNT_UPDATE) == 0)) { + error = EINVAL; + goto bail; + } + error = vfs_domount(td, fstype, fspath, fsflags, &optlist); if (error == ENOENT) { error = EINVAL; @@ -1044,6 +1084,13 @@ */ flags &= ~MNT_ROOTFS; + /* + * Do not allow exports to be set with the old syscall, when + * inside a jail. + */ + if (jailed(td->td_ucred)) + flags &= ~MNT_EXPORTED; + fstype = malloc(MFSNAMELEN, M_TEMP, M_WAITOK); error = copyinstr(uap->type, fstype, MFSNAMELEN, NULL); if (error) { @@ -1312,7 +1359,18 @@ * Only privileged root, or (if MNT_USER is set) the user that * did the original mount is permitted to update it. */ - error = vfs_suser(mp, td); + /* + * For the case of mountd(8) doing exports in a jail, don't + * call vfs_suser(). vfs_domount() has already checked that + * "root" is doing this and vfs_suser() will fail when + * the file system has been mounted outside the jail. + * vfs_donmount() has ensured that MNT_EXPORTED is not + * mixed with other options that change mount behaviour. + */ + error = 0; + if ((fsflags & MNT_EXPORTED) == 0 || !jailed(td->td_ucred) || + !prison_check_nfsd(td->td_ucred)) + error = vfs_suser(mp, td); if (error != 0) { vput(vp); return (error); @@ -1342,9 +1400,19 @@ error = EBUSY; goto end; } - mp->mnt_flag &= ~MNT_UPDATEMASK; - mp->mnt_flag |= fsflags & (MNT_RELOAD | MNT_FORCE | MNT_UPDATE | - MNT_SNAPSHOT | MNT_ROOTFS | MNT_UPDATEMASK | MNT_RDONLY); + + /* + * For mountd(8) doing exports from within a jail, only allow the + * MNT_EXPORTED flag to be updated. + */ + if ((fsflags & MNT_EXPORTED) != 0 && jailed(td->td_ucred) && + prison_check_nfsd(td->td_ucred)) { + mp->mnt_flag |= MNT_EXPORTED; + } else { + mp->mnt_flag &= ~MNT_UPDATEMASK; + mp->mnt_flag |= fsflags & (MNT_RELOAD | MNT_FORCE | MNT_UPDATE | + MNT_SNAPSHOT | MNT_ROOTFS | MNT_UPDATEMASK | MNT_RDONLY); + } if ((mp->mnt_flag & MNT_ASYNC) == 0) mp->mnt_kern_flag &= ~MNTK_ASYNC; rootvp = vfs_cache_root_clear(mp); @@ -1357,7 +1425,18 @@ * XXX The final recipients of VFS_MOUNT just overwrite the ndp they * get. No freeing of cn_pnbuf. */ - error = VFS_MOUNT(mp); + /* + * For the case of mountd(8) doing exports from within a vnet jail, + * "from" is typically not set correctly such that VFS_MOUNT() will + * return ENOENT. It is not obvious that VFS_MOUNT() ever needs to be + * called when mountd is doing exports, but this check only applies to + * the specific case where it is running inside a vnet jail, to + * avoid any POLA violation. + */ + error = 0; + if ((fsflags & MNT_EXPORTED) == 0 || !jailed(td->td_ucred) || + !prison_check_nfsd(td->td_ucred)) + error = VFS_MOUNT(mp); export_error = 0; /* Process the export option. */ @@ -1400,7 +1479,7 @@ } else export_error = EINVAL; if (export_error == 0) - export_error = vfs_export(mp, &export); + export_error = vfs_export(mp, &export, true); free(export.ex_groups, M_TEMP); break; case (sizeof(export)): @@ -1422,7 +1501,7 @@ else export_error = EINVAL; if (export_error == 0) - export_error = vfs_export(mp, &export); + export_error = vfs_export(mp, &export, true); free(grps, M_TEMP); break; default: @@ -1512,7 +1591,12 @@ if (strlen(fstype) >= MFSNAMELEN || strlen(fspath) >= MNAMELEN) return (ENAMETOOLONG); - if (jailed(td->td_ucred) || usermount == 0) { + if ((fsflags & MNT_EXPORTED) != 0 && jailed(td->td_ucred) && + prison_check_nfsd(td->td_ucred)) { + error = priv_check(td, PRIV_NFS_DAEMON); + if (error) + return (error); + } else if (jailed(td->td_ucred) || usermount == 0) { if ((error = priv_check(td, PRIV_VFS_MOUNT)) != 0) return (error); }