diff --git a/sys/kern/kern_exec.c b/sys/kern/kern_exec.c --- a/sys/kern/kern_exec.c +++ b/sys/kern/kern_exec.c @@ -450,6 +450,592 @@ } } +/* + * Prepare credentials and run image activators. + * + * Determine new credentials based on setuid/setgid bits and MAC + * transitions, then loop through the list of image activators, + * calling each one. An activator returns -1 if there is no match, + * 0 on success, and an error otherwise. We map -1 to ENOEXEC + * before returning. + * + * On success, *credential_changingp (if non-NULL) and + * *will_transitionp are set for use by the credential installation + * phase later. *euipp receives the uidinfo for setuid, if any + * (caller must uifree). + */ +int +exec_activate(struct image_params *imgp, struct ucred *oldcred, + struct vattr *attr, struct uidinfo **euipp, + bool *credential_changingp +#ifdef MAC + , struct label *interpvplabel, bool *will_transitionp +#endif + ) +{ + struct proc *p; + bool credential_changing; + int error, i; + + p = imgp->proc; + /* + * *euipp is owned by the caller and left as it initialized it + * (NULL). Only the setuid branch below sets it, and it must + * persist across the interpreter re-activation loop so the + * reference is released exactly once. + */ + + /* + * Don't honor setuid/setgid if the filesystem prohibits it or if + * the process is being traced. + * + * We disable setuid/setgid/etc in capability mode on the basis + * that most setugid applications are not written with that + * environment in mind, and will therefore almost certainly operate + * incorrectly. In principle there's no reason that setugid + * applications might not be useful in capability mode, so we may + * want to reconsider this conservative design choice in the future. + * + * XXXMAC: For the time being, use NOSUID to also prohibit + * transitions on the file system. + */ + credential_changing = false; + credential_changing |= (attr->va_mode & S_ISUID) && + oldcred->cr_uid != attr->va_uid; + credential_changing |= (attr->va_mode & S_ISGID) && + oldcred->cr_gid != attr->va_gid; +#ifdef MAC + *will_transitionp = + mac_vnode_execve_will_transition(oldcred, imgp->vp, + interpvplabel, imgp) != 0; + credential_changing |= *will_transitionp; +#endif + + /* Don't inherit PROC_PDEATHSIG_CTL value if setuid/setgid. */ + if (credential_changing) + p->p_pdeathsig = 0; + + if (credential_changing && +#ifdef CAPABILITY_MODE + ((oldcred->cr_flags & CRED_FLAG_CAPMODE) == 0) && +#endif + (imgp->vp->v_mount->mnt_flag & MNT_NOSUID) == 0 && + (p->p_flag & P_TRACED) == 0) { + imgp->credential_setid = true; + VOP_UNLOCK(imgp->vp); + imgp->newcred = crdup(oldcred); + if (attr->va_mode & S_ISUID) { + *euipp = uifind(attr->va_uid); + change_euid(imgp->newcred, *euipp); + } + vn_lock(imgp->vp, LK_SHARED | LK_RETRY); + if (attr->va_mode & S_ISGID) + change_egid(imgp->newcred, attr->va_gid); + /* + * Implement correct POSIX saved-id behavior. + * + * XXXMAC: Note that the current logic will save the + * uid and gid if a MAC domain transition occurs, even + * though maybe it shouldn't. + */ + change_svuid(imgp->newcred, imgp->newcred->cr_uid); + change_svgid(imgp->newcred, imgp->newcred->cr_gid); + } else { + /* + * Implement correct POSIX saved-id behavior. + * + * XXX: It's not clear that the existing behavior is + * POSIX-compliant. A number of sources indicate that the + * saved uid/gid should only be updated if the new ruid is + * not equal to the old ruid, or the new euid is not equal + * to the old euid and the new euid is not equal to the old + * ruid. The FreeBSD code always updates the saved uid/gid. + * Also, this code uses the new (replaced) euid and egid as + * the source, which may or may not be the right ones to use. + */ + if (oldcred->cr_svuid != oldcred->cr_uid || + oldcred->cr_svgid != oldcred->cr_gid) { + VOP_UNLOCK(imgp->vp); + imgp->newcred = crdup(oldcred); + vn_lock(imgp->vp, LK_SHARED | LK_RETRY); + change_svuid(imgp->newcred, imgp->newcred->cr_uid); + change_svgid(imgp->newcred, imgp->newcred->cr_gid); + } + } + + if (credential_changingp != NULL) + *credential_changingp = credential_changing; + + /* + * Loop through the list of image activators, calling each one. + * An activator returns -1 if there is no match, 0 on success, + * and an error otherwise. + */ + error = -1; + for (i = 0; error == -1 && execsw[i]; ++i) { + if (execsw[i]->ex_imgact == NULL) + continue; + error = (*execsw[i]->ex_imgact)(imgp); + } + if (error == -1) + error = ENOEXEC; + return (error); +} + +/* + * Clean up the current image state when restarting exec for an + * interpreter (shebang script). Unmaps the first page, unsets text + * mode, closes and releases the vnode, deallocates the vm object, + * clears setid state, and frees the exec path. + * + * Called with imgp->vp locked. On return, imgp->vp is NULL. + */ +void +exec_interpreter_cleanup(struct image_params *imgp, struct thread *td +#ifdef MAC + , struct label **interpvplabelp +#endif +) +{ + + exec_unmap_first_page(imgp); + /* + * The text reference needs to be removed for scripts. + * There is a short period before we determine that + * something is a script where text reference is active. + * The vnode lock is held over this entire period + * so nothing should illegitimately be blocked. + */ + MPASS(imgp->textset); + VOP_UNSET_TEXT_CHECKED(imgp->vp); + imgp->textset = false; +#ifdef MAC + mac_execve_interpreter_enter(imgp->vp, interpvplabelp); +#endif + if (imgp->opened) { + VOP_CLOSE(imgp->vp, FREAD, td->td_ucred, td); + imgp->opened = false; + } + vput(imgp->vp); + imgp->vp = NULL; + vm_object_deallocate(imgp->object); + imgp->object = NULL; + execve_nosetid(imgp); + imgp->execpath = NULL; + free(imgp->freepath, M_TEMP); + imgp->freepath = NULL; +} + +/* + * Resolve the interpreter vnode when the image activator has already + * provided it (e.g. binmisc). Sets imgp->vp and imgp->execpath. + * + * On return, newtextvp is set and imgp->vp is locked shared. + */ +void +exec_interpreter_vp(struct image_params *imgp, struct vnode **newtextvpp) +{ + struct vnode *newtextvp; + + newtextvp = imgp->interpreter_vp; + imgp->interpreter_vp = NULL; + if (vn_fullpath(newtextvp, &imgp->execpath, + &imgp->freepath) != 0) + imgp->execpath = imgp->args->fname; + vn_lock(newtextvp, LK_SHARED | LK_RETRY); + AUDIT_ARG_VNODE1(newtextvp); + imgp->vp = newtextvp; + *newtextvpp = newtextvp; +} + +/* + * Resolve the interpreter by pathname using namei. + * Sets imgp->vp and imgp->execpath. + * + * On success, *newtextvpp is set and imgp->vp is locked shared. + * Caller is responsible for NDFREE_PNBUF(ndp) and freeing *newbinnamep. + */ +int +exec_interpreter_namei(struct image_params *imgp, struct thread *td, + struct nameidata *ndp, struct vnode **newtextvpp, + struct vnode **newtextdvpp, char **newbinnamep) +{ + struct vnode *newtextvp; + size_t freepath_size; + int error; + +#ifdef CAPABILITY_MODE + if (CAP_TRACING(td)) + ktrcapfail(CAPFAIL_NAMEI, imgp->args->fname); + /* + * While capability mode can't reach this point via direct + * path arguments to execve(), we also don't allow + * interpreters to be used in capability mode (for now). + * Catch indirect lookups and return a permissions error. + */ + if (IN_CAPABILITY_MODE(td)) + return (ECAPMODE); +#endif + + /* + * Translate the file name. namei() returns a vnode + * pointer in ni_vp among other things. + */ + NDINIT(ndp, LOOKUP, ISOPEN | LOCKLEAF | LOCKSHARED | FOLLOW | + AUDITVNODE1 | WANTPARENT, UIO_SYSSPACE, + imgp->args->fname); + + error = namei(ndp); + if (error) + return (error); + + newtextvp = ndp->ni_vp; + *newtextdvpp = ndp->ni_dvp; + ndp->ni_dvp = NULL; + *newbinnamep = malloc(ndp->ni_cnd.cn_namelen + 1, M_PARGS, + M_WAITOK); + memcpy(*newbinnamep, ndp->ni_cnd.cn_nameptr, ndp->ni_cnd.cn_namelen); + (*newbinnamep)[ndp->ni_cnd.cn_namelen] = '\0'; + imgp->vp = newtextvp; + + if (atomic_load_8(&(*newtextdvpp)->v_type) != VDIR) { + struct vnode *dvp1; + char *buf1; + size_t buf1len; + + /* + * The newtextdvp vnode might be not a + * directory when reclaimed or when the image + * is mounted over a regular file. In the + * latter case, try to resolve the containing + * directory. + * + * In any case, p_textdvp must be either a + * directory or reclaimed. + */ + VOP_UNLOCK(imgp->vp); + dvp1 = *newtextdvpp; + buf1len = MAXNAMLEN + 1; + buf1 = malloc(buf1len, M_TEMP, M_WAITOK); + error = vn_vptocnp(&dvp1, buf1, &buf1len); + if (error == 0) { + if (atomic_load_8(&dvp1->v_type) == VDIR) { + *newtextdvpp = dvp1; + } else { + vrele(dvp1); + *newtextdvpp = NULL; + } + } else { + *newtextdvpp = NULL; + } + free(buf1, M_TEMP); + vn_lock(imgp->vp, LK_SHARED | LK_RETRY); + } + + /* + * Do the best to calculate the full path to the image file. + */ + if (imgp->args->fname[0] == '/') { + imgp->execpath = imgp->args->fname; + } else if (*newtextdvpp != NULL) { + VOP_UNLOCK(imgp->vp); + freepath_size = MAXPATHLEN; + if (vn_fullpath_hardlink(newtextvp, *newtextdvpp, + *newbinnamep, ndp->ni_cnd.cn_namelen, &imgp->execpath, + &imgp->freepath, &freepath_size) != 0) + imgp->execpath = imgp->args->fname; + vn_lock(imgp->vp, LK_SHARED | LK_RETRY); + } + *newtextvpp = newtextvp; + return (0); +} + +/* + * Cache the process argument strings for ps(1). + * Returns a pargs structure, or NULL if args are too large. + */ +struct pargs * +exec_cache_args(struct image_args *args) +{ + struct pargs *pa; + int len; + + /* Cache arguments if they fit inside our allowance */ + len = exec_args_get_begin_envv(args) - args->begin_argv; + if (ps_arg_cache_limit < len + sizeof(struct pargs)) + return (NULL); + pa = pargs_alloc(len); + bcopy(args->begin_argv, pa->ar_args, len); + return (pa); +} + +/* + * Clean up image_params resources after exec (success or failure). + * Handles firstpage, vnode close/unlock, vm object, and freepath. + * Caller is responsible for any additional per-path cleanup + * (e.g. namei buffer, newtextdvp, newbinname). + */ +void +exec_cleanup_imgp(struct image_params *imgp, struct thread *td, int error) +{ + + if (imgp->firstpage != NULL) + exec_unmap_first_page(imgp); + + if (imgp->vp != NULL) { + if (imgp->opened) + VOP_CLOSE(imgp->vp, FREAD, td->td_ucred, td); + if (imgp->textset) + VOP_UNSET_TEXT_CHECKED(imgp->vp); + if (error != 0) + vput(imgp->vp); + else + VOP_UNLOCK(imgp->vp); + } + + if (imgp->object != NULL) + vm_object_deallocate(imgp->object); + + free(imgp->freepath, M_TEMP); +} + +/* + * Clean up credential, MAC, args, and uidinfo after exec. + */ +void +exec_cleanup_cred(struct image_params *imgp, struct ucred *oldcred, +#ifdef MAC + struct label *interpvplabel, +#endif + struct image_args *args, + struct pargs *newargs, struct uidinfo *euip) +{ + + if (imgp->newcred != NULL && oldcred != NULL) + crfree(imgp->newcred); + +#ifdef MAC + mac_execve_exit(imgp); + mac_execve_interpreter_exit(interpvplabel); +#endif + exec_free_args(args); + + pargs_drop(newargs); + if (euip != NULL) + uifree(euip); +} + +/* + * NB: We unlock the vnode here because it is believed that none + * of the sv_copyout_strings/sv_fixup operations require the vnode. + * + * Copy out strings (args and env) and initialize stack base. + * On success, the vnode is left unlocked and *stack_basep is set. + * On failure, the vnode is re-locked and an error is returned. + */ +int +exec_copyout_stack(struct image_params *imgp, uintptr_t *stack_basep) +{ + struct proc *p; + int error; + + p = imgp->proc; + + VOP_UNLOCK(imgp->vp); + + if (disallow_high_osrel && + P_OSREL_MAJOR(p->p_osrel) > P_OSREL_MAJOR(__FreeBSD_version)) { + error = ENOEXEC; + uprintf("Osrel %d for image %s too high\n", p->p_osrel, + imgp->execpath != NULL ? imgp->execpath : ""); + vn_lock(imgp->vp, LK_SHARED | LK_RETRY); + return (error); + } + + /* + * Copy out strings (args and env) and initialize stack base. + */ + error = (*p->p_sysent->sv_copyout_strings)(imgp, stack_basep); + if (error != 0) { + vn_lock(imgp->vp, LK_SHARED | LK_RETRY); + return (error); + } + + /* + * Stack setup. + */ + error = (*p->p_sysent->sv_fixup)(stack_basep, imgp); + if (error != 0) { + vn_lock(imgp->vp, LK_SHARED | LK_RETRY); + return (error); + } + + return (0); +} + +/* + * Look up an executable by file descriptor from the given thread's fd + * table, resolve its full path, lock its vnode, and set imgp->vp. + * On success, newtextvp is set and imgp->vp is locked shared. + * + * If the descriptors was not opened with O_PATH, then + * we require that it was opened with O_EXEC or + * O_RDONLY. In either case, exec_check_permissions() + * below checks _current_ file access mode regardless + * of the permissions additionally checked at the + * open(2). + */ +int +exec_fgetvp(struct image_params *imgp, struct thread *td, int fd, + struct vnode **newtextvpp) +{ + struct vnode *newtextvp; + int error; + + AUDIT_ARG_FD(fd); + error = fgetvp_exec(td, fd, &cap_fexecve_rights, &newtextvp); + if (error != 0) + return (error); + + if (vn_fullpath(newtextvp, &imgp->execpath, + &imgp->freepath) != 0) + imgp->execpath = imgp->args->fname; + vn_lock(newtextvp, LK_SHARED | LK_RETRY); + AUDIT_ARG_VNODE1(newtextvp); + imgp->vp = newtextvp; + *newtextvpp = newtextvp; + return (0); +} + +/* + * Check file permissions. Also 'opens' file and sets its vnode to + * text mode. Then grab the vm object reference and map the first + * page of the executable. Called after imgp->vp is set and locked. + */ +int +exec_prepare_image(struct image_params *imgp) +{ + int error; + + error = exec_check_permissions(imgp); + if (error) + return (error); + + imgp->object = imgp->vp->v_object; + if (imgp->object != NULL) + vm_object_reference(imgp->object); + + error = exec_map_first_page(imgp); + return (error); +} + +/* + * Set the process name (p_comm) and thread name (td_name) from the + * executable. If fname_for_namei is non-NULL, use it (execve path); + * otherwise derive the name from the vnode (fexecve path). + * + * Must be called with PROC_LOCK(p) held. + */ +void +exec_set_comm(struct image_params *imgp, const char *namei_name, + int namei_namelen) +{ + struct proc *p; + struct thread *td2; + static const char fexecv_proc_title[] = "(fexecv)"; + + p = imgp->proc; + td2 = imgp->td; + + bzero(p->p_comm, sizeof(p->p_comm)); + if (namei_name != NULL) + bcopy(namei_name, p->p_comm, + min(namei_namelen, MAXCOMLEN)); + else if (vn_commname(imgp->vp, p->p_comm, sizeof(p->p_comm)) != 0) + bcopy(fexecv_proc_title, p->p_comm, sizeof(fexecv_proc_title)); + bcopy(p->p_comm, td2->td_name, sizeof(td2->td_name)); +#ifdef KTR + sched_clear_tdname(td2); +#endif +} + +/* + * Apply the image's set-id policy after activation: honor or refuse the + * setuid/setgid bits (per sv_setid_allowed and P2_NO_NEW_PRIVS) and + * record P_SUGID. The caller installs the new credentials afterward, + * running any per-caller work in between (do_execve() does its KTRACE / + * fd-safety pass first, so the credentials are only installed once that + * pass succeeds). + * + * Must be called with PROC_LOCK(imgp->proc) held. + */ +void +exec_install_setid(struct image_params *imgp, struct thread *caller_td, + struct ucred *oldcred) +{ + struct proc *p; + + p = imgp->proc; + PROC_LOCK_ASSERT(p, MA_OWNED); + + if ((imgp->sysent->sv_setid_allowed != NULL && + !(*imgp->sysent->sv_setid_allowed)(caller_td, imgp)) || + (p->p_flag2 & P2_NO_NEW_PRIVS) != 0) + execve_nosetid(imgp); + + if (imgp->credential_setid) + setsugid(p); + else if (oldcred->cr_uid == oldcred->cr_ruid && + oldcred->cr_gid == oldcred->cr_rgid) + p->p_flag &= ~P_SUGID; +} + +/* + * Finalize exec: install text vnode, binname, args cache into the + * process, notify kqueue, set registers, and mark vnode as mmapped. + * + * Must be called with PROC_LOCK(imgp->proc) held. + * PROC_LOCK is released before setting registers. + * + * Caller must save any old p_textvp/p_binname/p_args values before + * calling this if deferred release is needed (as in do_execve). + */ +void +exec_finalize(struct image_params *imgp, struct vnode *newtextvp, + char **newbinnamep, struct pargs **newargsp, uintptr_t stack_base) +{ + struct proc *p; + + p = imgp->proc; + PROC_LOCK_ASSERT(p, MA_OWNED); + + /* + * Store the vp for use in kern.proc.pathname. This vnode was + * referenced by namei() or by fexecve variant of fname handling. + */ + p->p_textvp = newtextvp; + p->p_binname = *newbinnamep; + *newbinnamep = NULL; + + KNOTE_LOCKED(p->p_klist, NOTE_EXEC); + + /* + * Free any previous argument cache and replace it with + * the new argument cache, if any. + */ + p->p_args = *newargsp; + *newargsp = NULL; + + PROC_UNLOCK(p); + + /* Set values passed into the program in registers. */ + (*p->p_sysent->sv_setregs)(imgp->td, imgp, stack_base); + + VOP_UPDATE_ATIME(imgp->vp, NULL); + + SDT_PROBE1(proc, , , exec__success, imgp->args->fname); +} + /* * In-kernel implementation of execve(). All arguments are assumed to be * userspace pointers from the passed thread. @@ -481,11 +1067,9 @@ #ifdef HWPMC_HOOKS struct pmckern_procexec pe; #endif - int error, i, orig_osrel; + int error, orig_osrel; uint32_t orig_fctl0; const Elf_Brandinfo *orig_brandinfo; - size_t freepath_size; - static const char fexecv_proc_title[] = "(fexecv)"; imgp = &image_params; oldtextvp = oldtextdvp = NULL; @@ -532,139 +1116,19 @@ interpret: if (args->fname != NULL) { -#ifdef CAPABILITY_MODE - if (CAP_TRACING(td)) - ktrcapfail(CAPFAIL_NAMEI, args->fname); - /* - * While capability mode can't reach this point via direct - * path arguments to execve(), we also don't allow - * interpreters to be used in capability mode (for now). - * Catch indirect lookups and return a permissions error. - */ - if (IN_CAPABILITY_MODE(td)) { - error = ECAPMODE; - goto exec_fail; - } -#endif - - /* - * Translate the file name. namei() returns a vnode - * pointer in ni_vp among other things. - */ - NDINIT(&nd, LOOKUP, ISOPEN | LOCKLEAF | LOCKSHARED | FOLLOW | - AUDITVNODE1 | WANTPARENT, UIO_SYSSPACE, - args->fname); - - error = namei(&nd); + error = exec_interpreter_namei(imgp, td, &nd, + &newtextvp, &newtextdvp, &newbinname); if (error) goto exec_fail; - - newtextvp = nd.ni_vp; - newtextdvp = nd.ni_dvp; - nd.ni_dvp = NULL; - newbinname = malloc(nd.ni_cnd.cn_namelen + 1, M_PARGS, - M_WAITOK); - memcpy(newbinname, nd.ni_cnd.cn_nameptr, nd.ni_cnd.cn_namelen); - newbinname[nd.ni_cnd.cn_namelen] = '\0'; - imgp->vp = newtextvp; - - if (atomic_load_8(&newtextdvp->v_type) != VDIR) { - struct vnode *dvp1; - char *buf1; - size_t buf1len; - - /* - * The newtextdvp vnode might be not a - * directory when reclaimed or when the image - * is mounted over a regular file. In the - * latter case, try to resolve the containing - * directory. - * - * In any case, p_textdvp must be either a - * directory or reclaimed. - */ - VOP_UNLOCK(imgp->vp); - dvp1 = newtextdvp; - buf1len = MAXNAMLEN + 1; - buf1 = malloc(buf1len, M_TEMP, M_WAITOK); - error = vn_vptocnp(&dvp1, buf1, &buf1len); - if (error == 0) { - if (atomic_load_8(&dvp1->v_type) == VDIR) { - newtextdvp = dvp1; - } else { - vrele(dvp1); - newtextdvp = NULL; - } - } else { - newtextdvp = NULL; - } - free(buf1, M_TEMP); - vn_lock(imgp->vp, LK_SHARED | LK_RETRY); - } - - /* - * Do the best to calculate the full path to the image file. - */ - if (args->fname[0] == '/') { - imgp->execpath = args->fname; - } else if (newtextdvp != NULL) { - VOP_UNLOCK(imgp->vp); - freepath_size = MAXPATHLEN; - if (vn_fullpath_hardlink(newtextvp, newtextdvp, - newbinname, nd.ni_cnd.cn_namelen, &imgp->execpath, - &imgp->freepath, &freepath_size) != 0) - imgp->execpath = args->fname; - vn_lock(imgp->vp, LK_SHARED | LK_RETRY); - } } else if (imgp->interpreter_vp) { - /* - * An image activator has already provided an open vnode - */ - newtextvp = imgp->interpreter_vp; - imgp->interpreter_vp = NULL; - if (vn_fullpath(newtextvp, &imgp->execpath, - &imgp->freepath) != 0) - imgp->execpath = args->fname; - vn_lock(newtextvp, LK_SHARED | LK_RETRY); - AUDIT_ARG_VNODE1(newtextvp); - imgp->vp = newtextvp; + exec_interpreter_vp(imgp, &newtextvp); } else { - AUDIT_ARG_FD(args->fd); - - /* - * If the descriptors was not opened with O_PATH, then - * we require that it was opened with O_EXEC or - * O_RDONLY. In either case, exec_check_permissions() - * below checks _current_ file access mode regardless - * of the permissions additionally checked at the - * open(2). - */ - error = fgetvp_exec(td, args->fd, &cap_fexecve_rights, - &newtextvp); + error = exec_fgetvp(imgp, td, args->fd, &newtextvp); if (error != 0) goto exec_fail; - - if (vn_fullpath(newtextvp, &imgp->execpath, - &imgp->freepath) != 0) - imgp->execpath = args->fname; - vn_lock(newtextvp, LK_SHARED | LK_RETRY); - AUDIT_ARG_VNODE1(newtextvp); - imgp->vp = newtextvp; } - /* - * Check file permissions. Also 'opens' file and sets its vnode to - * text mode. - */ - error = exec_check_permissions(imgp); - if (error) - goto exec_fail_dealloc; - - imgp->object = imgp->vp->v_object; - if (imgp->object != NULL) - vm_object_reference(imgp->object); - - error = exec_map_first_page(imgp); + error = exec_prepare_image(imgp); if (error) goto exec_fail_dealloc; @@ -673,133 +1137,34 @@ imgp->proc->p_elf_brandinfo = NULL; /* - * Implement image setuid/setgid. + * Implement image setuid/setgid, then run image activators. * * Determine new credentials before attempting image activators * so that it can be used by process_exec handlers to determine - * credential/setid changes. - * - * Don't honor setuid/setgid if the filesystem prohibits it or if - * the process is being traced. - * - * We disable setuid/setgid/etc in capability mode on the basis - * that most setugid applications are not written with that - * environment in mind, and will therefore almost certainly operate - * incorrectly. In principle there's no reason that setugid - * applications might not be useful in capability mode, so we may want - * to reconsider this conservative design choice in the future. - * - * XXXMAC: For the time being, use NOSUID to also prohibit - * transitions on the file system. + * credential/setid changes. The new credentials are installed + * into the process later. */ - credential_changing = false; - credential_changing |= (attr.va_mode & S_ISUID) && - oldcred->cr_uid != attr.va_uid; - credential_changing |= (attr.va_mode & S_ISGID) && - oldcred->cr_gid != attr.va_gid; + error = exec_activate(imgp, oldcred, &attr, &euip, + &credential_changing #ifdef MAC - will_transition = mac_vnode_execve_will_transition(oldcred, imgp->vp, - interpvplabel, imgp) != 0; - credential_changing |= will_transition; -#endif - - /* Don't inherit PROC_PDEATHSIG_CTL value if setuid/setgid. */ - if (credential_changing) - imgp->proc->p_pdeathsig = 0; - - if (credential_changing && -#ifdef CAPABILITY_MODE - ((oldcred->cr_flags & CRED_FLAG_CAPMODE) == 0) && + , interpvplabel, &will_transition #endif - (imgp->vp->v_mount->mnt_flag & MNT_NOSUID) == 0 && - (p->p_flag & P_TRACED) == 0) { - imgp->credential_setid = true; - VOP_UNLOCK(imgp->vp); - imgp->newcred = crdup(oldcred); - if (attr.va_mode & S_ISUID) { - euip = uifind(attr.va_uid); - change_euid(imgp->newcred, euip); - } - vn_lock(imgp->vp, LK_SHARED | LK_RETRY); - if (attr.va_mode & S_ISGID) - change_egid(imgp->newcred, attr.va_gid); - /* - * Implement correct POSIX saved-id behavior. - * - * XXXMAC: Note that the current logic will save the - * uid and gid if a MAC domain transition occurs, even - * though maybe it shouldn't. - */ - change_svuid(imgp->newcred, imgp->newcred->cr_uid); - change_svgid(imgp->newcred, imgp->newcred->cr_gid); - } else { - /* - * Implement correct POSIX saved-id behavior. - * - * XXX: It's not clear that the existing behavior is - * POSIX-compliant. A number of sources indicate that the - * saved uid/gid should only be updated if the new ruid is - * not equal to the old ruid, or the new euid is not equal - * to the old euid and the new euid is not equal to the old - * ruid. The FreeBSD code always updates the saved uid/gid. - * Also, this code uses the new (replaced) euid and egid as - * the source, which may or may not be the right ones to use. - */ - if (oldcred->cr_svuid != oldcred->cr_uid || - oldcred->cr_svgid != oldcred->cr_gid) { - VOP_UNLOCK(imgp->vp); - imgp->newcred = crdup(oldcred); - vn_lock(imgp->vp, LK_SHARED | LK_RETRY); - change_svuid(imgp->newcred, imgp->newcred->cr_uid); - change_svgid(imgp->newcred, imgp->newcred->cr_gid); - } - } - /* The new credentials are installed into the process later. */ - - /* - * Loop through the list of image activators, calling each one. - * An activator returns -1 if there is no match, 0 on success, - * and an error otherwise. - */ - error = -1; - for (i = 0; error == -1 && execsw[i]; ++i) { - if (execsw[i]->ex_imgact == NULL) - continue; - error = (*execsw[i]->ex_imgact)(imgp); - } - - if (error) { - if (error == -1) - error = ENOEXEC; + ); + if (error) goto exec_fail_dealloc; - } /* * Special interpreter operation, cleanup and loop up to try to * activate the interpreter. */ if ((imgp->interpreted & ~IMGACT_INTERP_ELF) != 0) { - exec_unmap_first_page(imgp); - /* - * The text reference needs to be removed for scripts. - * There is a short period before we determine that - * something is a script where text reference is active. - * The vnode lock is held over this entire period - * so nothing should illegitimately be blocked. - */ - MPASS(imgp->textset); - VOP_UNSET_TEXT_CHECKED(newtextvp); - imgp->textset = false; - /* free name buffer and old vnode */ + exec_interpreter_cleanup(imgp, td #ifdef MAC - mac_execve_interpreter_enter(newtextvp, &interpvplabel); + , &interpvplabel #endif - if (imgp->opened) { - VOP_CLOSE(newtextvp, FREAD, td->td_ucred, td); - imgp->opened = false; - } - vput(newtextvp); - imgp->vp = newtextvp = NULL; + ); + newtextvp = NULL; + /* do_execve-specific: free namei buffer and dir vnode. */ if (args->fname != NULL) { if (newtextdvp != NULL) { vrele(newtextdvp); @@ -809,12 +1174,6 @@ free(newbinname, M_PARGS); newbinname = NULL; } - vm_object_deallocate(imgp->object); - imgp->object = NULL; - execve_nosetid(imgp); - imgp->execpath = NULL; - free(imgp->freepath, M_TEMP); - imgp->freepath = NULL; /* set new name to that of the interpreter */ if (imgp->interpreter_vp) { args->fname = NULL; @@ -824,38 +1183,9 @@ goto interpret; } - /* - * NB: We unlock the vnode here because it is believed that none - * of the sv_copyout_strings/sv_fixup operations require the vnode. - */ - VOP_UNLOCK(imgp->vp); - - if (disallow_high_osrel && - P_OSREL_MAJOR(p->p_osrel) > P_OSREL_MAJOR(__FreeBSD_version)) { - error = ENOEXEC; - uprintf("Osrel %d for image %s too high\n", p->p_osrel, - imgp->execpath != NULL ? imgp->execpath : ""); - vn_lock(imgp->vp, LK_SHARED | LK_RETRY); - goto exec_fail_dealloc; - } - - /* - * Copy out strings (args and env) and initialize stack base. - */ - error = (*p->p_sysent->sv_copyout_strings)(imgp, &stack_base); - if (error != 0) { - vn_lock(imgp->vp, LK_SHARED | LK_RETRY); - goto exec_fail_dealloc; - } - - /* - * Stack setup. - */ - error = (*p->p_sysent->sv_fixup)(&stack_base, imgp); - if (error != 0) { - vn_lock(imgp->vp, LK_SHARED | LK_RETRY); + error = exec_copyout_stack(imgp, &stack_base); + if (error != 0) goto exec_fail_dealloc; - } /* * For security and other reasons, the file descriptor table cannot be @@ -869,12 +1199,7 @@ /* * Malloc things before we need locks. */ - i = exec_args_get_begin_envv(imgp->args) - imgp->args->begin_argv; - /* Cache arguments if they fit inside our allowance */ - if (ps_arg_cache_limit >= i + sizeof(struct pargs)) { - newargs = pargs_alloc(i); - bcopy(imgp->args->begin_argv, newargs->ar_args, i); - } + newargs = exec_cache_args(imgp->args); /* * For security and other reasons, signal handlers cannot @@ -900,16 +1225,9 @@ execsigs(p); /* name this process - nameiexec(p, ndp) */ - bzero(p->p_comm, sizeof(p->p_comm)); - if (args->fname) - bcopy(nd.ni_cnd.cn_nameptr, p->p_comm, - min(nd.ni_cnd.cn_namelen, MAXCOMLEN)); - else if (vn_commname(newtextvp, p->p_comm, sizeof(p->p_comm)) != 0) - bcopy(fexecv_proc_title, p->p_comm, sizeof(fexecv_proc_title)); - bcopy(p->p_comm, td->td_name, sizeof(td->td_name)); -#ifdef KTR - sched_clear_tdname(td); -#endif + exec_set_comm(imgp, + args->fname != NULL ? nd.ni_cnd.cn_nameptr : NULL, + args->fname != NULL ? nd.ni_cnd.cn_namelen : 0); /* * mark as execed, wakeup the process that vforked (if any) and tell @@ -930,21 +1248,17 @@ signotify(td); } - if ((imgp->sysent->sv_setid_allowed != NULL && - !(*imgp->sysent->sv_setid_allowed)(td, imgp)) || - (p->p_flag2 & P2_NO_NEW_PRIVS) != 0) - execve_nosetid(imgp); - /* * Implement image setuid/setgid installation. */ + exec_install_setid(imgp, td, oldcred); + if (imgp->credential_setid) { /* * Turn off syscall tracing for set-id programs, except for * root. Record any set-id flags first to make sure that * we do not regain any tracing during a possible block. */ - setsugid(p); #ifdef KTRACE kiop = ktrprocexec(p); #endif @@ -969,11 +1283,8 @@ imgp->vp, interpvplabel, imgp); } #endif - } else { - if (oldcred->cr_uid == oldcred->cr_ruid && - oldcred->cr_gid == oldcred->cr_rgid) - p->p_flag &= ~P_SUGID; } + /* * Set the new credentials. */ @@ -984,17 +1295,15 @@ } /* - * Store the vp for use in kern.proc.pathname. This vnode was - * referenced by namei() or by fexecve variant of fname handling. + * Save old values for deferred release, then install new ones. + * do_execve-specific: textdvp handling, P_INEXEC clear, DTrace/PMC. */ oldtextvp = p->p_textvp; - p->p_textvp = newtextvp; oldtextdvp = p->p_textdvp; p->p_textdvp = newtextdvp; newtextdvp = NULL; oldbinname = p->p_binname; - p->p_binname = newbinname; - newbinname = NULL; + oldargs = p->p_args; #ifdef KDTRACE_HOOKS /* @@ -1009,7 +1318,6 @@ * Notify others that we exec'd, and clear the P_INEXEC flag * as we're now a bona fide freshly-execed process. */ - KNOTE_LOCKED(p->p_klist, NOTE_EXEC); MPASS(p->p_execblock == 0); if ((p->p_flag & P_INEXEC_WAIT) != 0) wakeup(&p->p_execblock); @@ -1018,15 +1326,9 @@ /* clear "fork but no exec" flag, as we _are_ execing */ p->p_acflag &= ~AFORK; - /* - * Free any previous argument cache and replace it with - * the new argument cache, if any. - */ - oldargs = p->p_args; - p->p_args = newargs; - newargs = NULL; + exec_finalize(imgp, newtextvp, &newbinname, &newargs, stack_base); - PROC_UNLOCK(p); + /* exec_finalize released PROC_LOCK. */ #ifdef HWPMC_HOOKS /* @@ -1060,13 +1362,6 @@ } #endif - /* Set values passed into the program in registers. */ - (*p->p_sysent->sv_setregs)(td, imgp, stack_base); - - VOP_UPDATE_ATIME(imgp->vp, NULL); - - SDT_PROBE1(proc, , , exec__success, args->fname); - exec_fail_dealloc: if (error != 0) { p->p_osrel = orig_osrel; @@ -1074,18 +1369,10 @@ p->p_elf_brandinfo = orig_brandinfo; } - if (imgp->firstpage != NULL) - exec_unmap_first_page(imgp); + exec_cleanup_imgp(imgp, td, error); + /* do_execve-specific: namei buffer, directory vnode, binname. */ if (imgp->vp != NULL) { - if (imgp->opened) - VOP_CLOSE(imgp->vp, FREAD, td->td_ucred, td); - if (imgp->textset) - VOP_UNSET_TEXT_CHECKED(imgp->vp); - if (error != 0) - vput(imgp->vp); - else - VOP_UNLOCK(imgp->vp); if (args->fname != NULL) NDFREE_PNBUF(&nd); if (newtextdvp != NULL) @@ -1093,11 +1380,6 @@ free(newbinname, M_PARGS); } - if (imgp->object != NULL) - vm_object_deallocate(imgp->object); - - free(imgp->freepath, M_TEMP); - if (error == 0) { if (p->p_ptevents & PTRACE_EXEC) { PROC_LOCK(p); @@ -1117,14 +1399,11 @@ SDT_PROBE1(proc, , , exec__failure, error); } - if (imgp->newcred != NULL && oldcred != NULL) - crfree(imgp->newcred); - + exec_cleanup_cred(imgp, oldcred, #ifdef MAC - mac_execve_exit(imgp); - mac_execve_interpreter_exit(interpvplabel); + interpvplabel, #endif - exec_free_args(args); + args, newargs, euip); /* * Handle deferred decrement of ref counts. @@ -1138,11 +1417,8 @@ ktr_io_params_free(kiop); #endif pargs_drop(oldargs); - pargs_drop(newargs); if (oldsigacts != NULL) sigacts_free(oldsigacts); - if (euip != NULL) - uifree(euip); if (error && imgp->vmspace_destroyed) { /* sorry, no more process anymore. exit gracefully */ diff --git a/sys/sys/imgact.h b/sys/sys/imgact.h --- a/sys/sys/imgact.h +++ b/sys/sys/imgact.h @@ -99,9 +99,15 @@ }; #ifdef _KERNEL +struct label; +struct nameidata; +struct pargs; struct sysentvec; struct thread; +struct uidinfo; +struct vattr; struct vmspace; +struct vnode; int exec_alloc_args(struct image_args *); int exec_args_add_arg(struct image_args *args, const char *argp, @@ -114,9 +120,39 @@ ssize_t extend); char *exec_args_get_begin_envv(struct image_args *args); int exec_check_permissions(struct image_params *); +int exec_fgetvp(struct image_params *, struct thread *, int, + struct vnode **); +int exec_prepare_image(struct image_params *); +void exec_set_comm(struct image_params *, const char *, int); void exec_cleanup(struct thread *td, struct vmspace *); +void exec_cleanup_imgp(struct image_params *, struct thread *, int); +void exec_cleanup_cred(struct image_params *, struct ucred *, +#ifdef MAC + struct label *, +#endif + struct image_args *, struct pargs *, struct uidinfo *); int exec_copyout_strings(struct image_params *, uintptr_t *); +int exec_copyout_stack(struct image_params *, uintptr_t *); +struct pargs *exec_cache_args(struct image_args *); +int exec_activate(struct image_params *, struct ucred *, + struct vattr *, struct uidinfo **, bool * +#ifdef MAC + , struct label *, bool * +#endif + ); +void exec_install_setid(struct image_params *, struct thread *, + struct ucred *); +void exec_finalize(struct image_params *, struct vnode *, + char **, struct pargs **, uintptr_t); void exec_free_args(struct image_args *); +void exec_interpreter_cleanup(struct image_params *, struct thread * +#ifdef MAC + , struct label ** +#endif + ); +void exec_interpreter_vp(struct image_params *, struct vnode **); +int exec_interpreter_namei(struct image_params *, struct thread *, + struct nameidata *, struct vnode **, struct vnode **, char **); int exec_map_stack(struct image_params *); int exec_new_vmspace(struct image_params *, struct sysentvec *); void exec_setregs(struct thread *, struct image_params *, uintptr_t);