diff --git a/sys/kern/vfs_mount.c.vnet b/sys/kern/vfs_mount.c --- a/sys/kern/vfs_mount.c.vnet +++ b/sys/kern/vfs_mount.c @@ -797,7 +797,7 @@ struct vfsopt *opt, *tmp_opt; char *fstype, *fspath, *errmsg; int error, fstypelen, fspathlen, errmsg_len, errmsg_pos; - bool autoro; + bool autoro, done_nonexport, has_nonexport; errmsg = fspath = NULL; errmsg_len = fspathlen = 0; @@ -834,6 +834,19 @@ } /* + * 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). + * Use has_nonexport and done_nonexport to track whether or + * not other options have been specified. + */ + has_nonexport = false; + if (jailed(td->td_ucred) && prison_check_nfsd(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 @@ -842,9 +855,11 @@ TAILQ_FOREACH_SAFE(opt, optlist, link, tmp_opt) { int do_freeopt = 0; + done_nonexport = true; if (strcmp(opt->name, "update") == 0) { fsflags |= MNT_UPDATE; do_freeopt = 1; + done_nonexport = false; } else if (strcmp(opt->name, "async") == 0) fsflags |= MNT_ASYNC; @@ -924,9 +939,11 @@ fsflags |= MNT_SYNCHRONOUS; else if (strcmp(opt->name, "union") == 0) fsflags |= MNT_UNION; - else if (strcmp(opt->name, "export") == 0) + else if (strcmp(opt->name, "export") == 0) { fsflags |= MNT_EXPORTED; - else if (strcmp(opt->name, "automounted") == 0) { + done_nonexport = false; + autoro = false; + } else if (strcmp(opt->name, "automounted") == 0) { fsflags |= MNT_AUTOMOUNTED; do_freeopt = 1; } else if (strcmp(opt->name, "nocover") == 0) { @@ -941,9 +958,21 @@ } else if (strcmp(opt->name, "noemptydir") == 0) { fsflags &= ~MNT_EMPTYDIR; do_freeopt = 1; + } else if (strcmp(opt->name, "fstype") == 0 || + strcmp(opt->name, "fspath") == 0 || + strcmp(opt->name, "from") == 0 || + strcmp(opt->name, "errmsg") == 0) { + /* + * These four options are used along + * with "export" and "update" by + * mountd(8) for file system exporting. + */ + done_nonexport = false; } if (do_freeopt) vfs_freeopt(optlist, opt); + if (done_nonexport) + has_nonexport = true; } /* @@ -956,6 +985,18 @@ goto bail; } + /* + * Do not allow "export" to be mixed with any other options + * that change behaviour and only allow "export" with "update", + * when in a vnet jail. + */ + if ((fsflags & MNT_EXPORTED) != 0 && jailed(td->td_ucred) && + prison_check_nfsd(td->td_ucred) && ((fsflags & MNT_UPDATE) == 0 || + has_nonexport)) { + error = EINVAL; + goto bail; + } + error = vfs_domount(td, fstype, fspath, fsflags, &optlist); if (error == ENOENT) { error = EINVAL; @@ -1035,6 +1076,13 @@ */ flags &= ~MNT_ROOTFS; + /* + * Do not allow exports to be set with the old syscall, when + * inside a vnet jail. + */ + if (jailed(td->td_ucred) && prison_check_nfsd(td->td_ucred)) + flags &= ~MNT_EXPORTED; + fstype = malloc(MFSNAMELEN, M_TEMP, M_WAITOK); error = copyinstr(uap->type, fstype, MFSNAMELEN, NULL); if (error) { @@ -1303,7 +1351,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); @@ -1348,7 +1407,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 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. */ @@ -1503,7 +1573,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); }