diff --git a/sys/kern/vfs_mount.c.ident b/sys/kern/vfs_mount.c.ident2 --- a/sys/kern/vfs_mount.c.ident +++ b/sys/kern/vfs_mount.c.ident2 @@ -801,7 +801,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; @@ -838,14 +838,35 @@ } /* + * 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 (jailed(td->td_ucred) && + strcmp(opt->name, "export") != 0 && + strcmp(opt->name, "update") != 0 && + strcmp(opt->name, "fstype") != 0 && + strcmp(opt->name, "fspath") != 0 && + strcmp(opt->name, "from") != 0 && + strcmp(opt->name, "errmsg") != 0) + has_nonexport = true; if (strcmp(opt->name, "update") == 0) { fsflags |= MNT_UPDATE; do_freeopt = 1; @@ -960,6 +981,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; @@ -1039,6 +1070,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) { @@ -1307,7 +1345,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); @@ -1337,9 +1386,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); @@ -1352,7 +1411,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. */ @@ -1507,7 +1577,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); }