/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "lint.h" #include "thr_uberdata.h" #include #include #include #include #include #include #define ALL_POSIX_SPAWN_FLAGS \ (POSIX_SPAWN_RESETIDS | \ POSIX_SPAWN_SETPGROUP | \ POSIX_SPAWN_SETSIGDEF | \ POSIX_SPAWN_SETSIGMASK | \ POSIX_SPAWN_SETSCHEDPARAM | \ POSIX_SPAWN_SETSCHEDULER | \ POSIX_SPAWN_SETSIGIGN_NP | \ POSIX_SPAWN_NOSIGCHLD_NP | \ POSIX_SPAWN_WAITPID_NP | \ POSIX_SPAWN_NOEXECERR_NP) typedef struct { int sa_psflags; /* POSIX_SPAWN_* flags */ int sa_priority; int sa_schedpolicy; pid_t sa_pgroup; sigset_t sa_sigdefault; sigset_t sa_sigignore; sigset_t sa_sigmask; } spawn_attr_t; typedef struct file_attr { struct file_attr *fa_next; /* circular list of file actions */ struct file_attr *fa_prev; enum {FA_OPEN, FA_CLOSE, FA_DUP2, FA_CLOSEFROM} fa_type; int fa_need_dirbuf; /* only consulted in the head action */ char *fa_path; /* copied pathname for open() */ uint_t fa_pathsize; /* size of fa_path[] array */ int fa_oflag; /* oflag for open() */ mode_t fa_mode; /* mode for open() */ int fa_filedes; /* file descriptor for open()/close() */ int fa_newfiledes; /* new file descriptor for dup2() */ } file_attr_t; extern int __lwp_sigmask(int, const sigset_t *, sigset_t *); extern int __sigaction(int, const struct sigaction *, struct sigaction *); #if defined(_LP64) #define __open64 __open #define getdents64 getdents #define dirent64_t dirent_t #else extern int __open64(const char *, int, ...); extern int getdents64(int, dirent64_t *, size_t); #endif /* * Support function: * Close all open file descriptors greater than or equal to lowfd. * This is executed in the child of vfork(), so we must not call * opendir() / readdir() because that would alter the parent's * address space. We use the low-level getdents64() system call. * Return non-zero on error. */ static int spawn_closefrom(int lowfd, void *buf) { int procfd; int fd; int buflen; dirent64_t *dp; dirent64_t *dpend; if (lowfd < 0) lowfd = 0; /* * Close lowfd right away as a hedge against failing * to open the /proc file descriptor directory due * all file descriptors being currently used up. */ (void) __close(lowfd++); if ((procfd = __open64("/proc/self/fd", O_RDONLY, 0)) < 0) { /* * We could not open the /proc file descriptor directory. * Just fail and be done with it. */ return (-1); } for (;;) { /* * Collect a bunch of open file descriptors and close them. * Repeat until the directory is exhausted. */ dp = (dirent64_t *)buf; if ((buflen = getdents64(procfd, dp, DIRBUF)) <= 0) { (void) __close(procfd); break; } dpend = (dirent64_t *)((uintptr_t)buf + buflen); do { /* skip '.', '..' and procfd */ if (dp->d_name[0] != '.' && (fd = atoi(dp->d_name)) != procfd && fd >= lowfd) (void) __close(fd); dp = (dirent64_t *)((uintptr_t)dp + dp->d_reclen); } while (dp < dpend); } return (0); } static int perform_flag_actions(spawn_attr_t *sap) { int sig; struct sigaction action; if (sap->sa_psflags & POSIX_SPAWN_SETSIGMASK) { (void) __lwp_sigmask(SIG_SETMASK, &sap->sa_sigmask, NULL); } if (sap->sa_psflags & POSIX_SPAWN_SETSIGIGN_NP) { (void) memset(&action, 0, sizeof (action)); action.sa_handler = SIG_IGN; for (sig = 1; sig < NSIG; sig++) { if (sigismember(&sap->sa_sigignore, sig)) (void) __sigaction(sig, &action, NULL); } } if (sap->sa_psflags & POSIX_SPAWN_SETSIGDEF) { (void) memset(&action, 0, sizeof (action)); action.sa_handler = SIG_DFL; for (sig = 1; sig < NSIG; sig++) { if (sigismember(&sap->sa_sigdefault, sig)) (void) __sigaction(sig, &action, NULL); } } if (sap->sa_psflags & POSIX_SPAWN_RESETIDS) { if (setgid(getgid()) != 0 || setuid(getuid()) != 0) return (errno); } if (sap->sa_psflags & POSIX_SPAWN_SETPGROUP) { if (setpgid(0, sap->sa_pgroup) != 0) return (errno); } if (sap->sa_psflags & POSIX_SPAWN_SETSCHEDULER) { if (setparam(P_LWPID, P_MYID, sap->sa_schedpolicy, sap->sa_priority) == -1) return (errno); } else if (sap->sa_psflags & POSIX_SPAWN_SETSCHEDPARAM) { if (setprio(P_LWPID, P_MYID, sap->sa_priority, NULL) == -1) return (errno); } return (0); } static int perform_file_actions(file_attr_t *fap, void *dirbuf) { file_attr_t *froot = fap; int fd; do { switch (fap->fa_type) { case FA_OPEN: fd = __open(fap->fa_path, fap->fa_oflag, fap->fa_mode); if (fd < 0) return (errno); if (fd != fap->fa_filedes) { if (__fcntl(fd, F_DUP2FD, fap->fa_filedes) < 0) return (errno); (void) __close(fd); } break; case FA_CLOSE: if (__close(fap->fa_filedes) == -1 && errno != EBADF) /* already closed, no error */ return (errno); break; case FA_DUP2: fd = __fcntl(fap->fa_filedes, F_DUP2FD, fap->fa_newfiledes); if (fd < 0) return (errno); break; case FA_CLOSEFROM: if (spawn_closefrom(fap->fa_filedes, dirbuf)) return (errno); break; } } while ((fap = fap->fa_next) != froot); return (0); } static int forkflags(spawn_attr_t *sap) { int flags = 0; if (sap != NULL) { if (sap->sa_psflags & POSIX_SPAWN_NOSIGCHLD_NP) flags |= FORK_NOSIGCHLD; if (sap->sa_psflags & POSIX_SPAWN_WAITPID_NP) flags |= FORK_WAITPID; } return (flags); } /* * set_error() / get_error() are used to guarantee that the local variable * 'error' is set correctly in memory on return from vfork() in the parent. */ static int set_error(int *errp, int err) { return (*errp = err); } static int get_error(int *errp) { return (*errp); } /* * For MT safety, do not invoke the dynamic linker after calling vfork(). * If some other thread was in the dynamic linker when this thread's parent * called vfork() then the dynamic linker's lock would still be held here * (with a defunct owner) and we would deadlock ourself if we invoked it. * * Therefore, all of the functions we call here after returning from * vforkx() in the child are not and must never be exported from libc * as global symbols. To do so would risk invoking the dynamic linker. */ int posix_spawn( pid_t *pidp, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) { spawn_attr_t *sap = attrp? attrp->__spawn_attrp : NULL; file_attr_t *fap = file_actions? file_actions->__file_attrp : NULL; void *dirbuf = NULL; int error; /* this will be set by the child */ pid_t pid; if (attrp != NULL && sap == NULL) return (EINVAL); if (fap != NULL && fap->fa_need_dirbuf) { /* * Preallocate the buffer for the call to getdents64() in * spawn_closefrom() since we can't do it in the vfork() child. */ if ((dirbuf = lmalloc(DIRBUF)) == NULL) return (ENOMEM); } switch (pid = vforkx(forkflags(sap))) { case 0: /* child */ break; case -1: /* parent, failure */ if (dirbuf) lfree(dirbuf, DIRBUF); return (errno); default: /* parent, success */ /* * We don't get here until the child exec()s or exit()s */ if (pidp != NULL && get_error(&error) == 0) *pidp = pid; if (dirbuf) lfree(dirbuf, DIRBUF); return (get_error(&error)); } if (sap != NULL) if (set_error(&error, perform_flag_actions(sap)) != 0) _exit(_EVAPORATE); if (fap != NULL) if (set_error(&error, perform_file_actions(fap, dirbuf)) != 0) _exit(_EVAPORATE); (void) set_error(&error, 0); (void) execve(path, argv, envp); if (sap != NULL && (sap->sa_psflags & POSIX_SPAWN_NOEXECERR_NP)) _exit(127); (void) set_error(&error, errno); _exit(_EVAPORATE); return (0); /* not reached */ } /* * Much of posix_spawnp() blatently stolen from execvp() (port/gen/execvp.c). */ extern int libc__xpg4; static const char * execat(const char *s1, const char *s2, char *si) { int cnt = PATH_MAX + 1; char *s; char c; for (s = si; (c = *s1) != '\0' && c != ':'; s1++) { if (cnt > 0) { *s++ = c; cnt--; } } if (si != s && cnt > 0) { *s++ = '/'; cnt--; } for (; (c = *s2) != '\0' && cnt > 0; s2++) { *s++ = c; cnt--; } *s = '\0'; return (*s1? ++s1: NULL); } /* ARGSUSED */ int posix_spawnp( pid_t *pidp, const char *file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]) { spawn_attr_t *sap = attrp? attrp->__spawn_attrp : NULL; file_attr_t *fap = file_actions? file_actions->__file_attrp : NULL; void *dirbuf = NULL; const char *pathstr = (strchr(file, '/') == NULL)? getenv("PATH") : ""; int xpg4 = libc__xpg4; int error = 0; /* this will be set by the child */ char path[PATH_MAX+4]; const char *cp; pid_t pid; char **newargs; int argc; int i; static const char *sun_path = "/bin/sh"; static const char *xpg4_path = "/usr/xpg4/bin/sh"; static const char *shell = "sh"; if (attrp != NULL && sap == NULL) return (EINVAL); if (*file == '\0') return (EACCES); if (fap != NULL && fap->fa_need_dirbuf) { /* * Preallocate the buffer for the call to getdents64() in * spawn_closefrom() since we can't do it in the vfork() child. */ if ((dirbuf = lmalloc(DIRBUF)) == NULL) return (ENOMEM); } /* * We may need to invoke the shell with a slightly modified * argv[] array. To do this we need to preallocate the array. * We must call alloca() before calling vfork() because doing * it after vfork() (in the child) would corrupt the parent. */ for (argc = 0; argv[argc] != NULL; argc++) continue; newargs = alloca((argc + 2) * sizeof (char *)); switch (pid = vforkx(forkflags(sap))) { case 0: /* child */ break; case -1: /* parent, failure */ if (dirbuf) lfree(dirbuf, DIRBUF); return (errno); default: /* parent, success */ /* * We don't get here until the child exec()s or exit()s */ if (pidp != NULL && get_error(&error) == 0) *pidp = pid; if (dirbuf) lfree(dirbuf, DIRBUF); return (get_error(&error)); } if (sap != NULL) if (set_error(&error, perform_flag_actions(sap)) != 0) _exit(_EVAPORATE); if (fap != NULL) if (set_error(&error, perform_file_actions(fap, dirbuf)) != 0) _exit(_EVAPORATE); if (pathstr == NULL) { /* * XPG4: pathstr is equivalent to _CS_PATH, except that * :/usr/sbin is appended when root, and pathstr must end * with a colon when not root. Keep these paths in sync * with _CS_PATH in confstr.c. Note that pathstr must end * with a colon when not root so that when file doesn't * contain '/', the last call to execat() will result in an * attempt to execv file from the current directory. */ if (geteuid() == 0 || getuid() == 0) { if (!xpg4) pathstr = "/usr/sbin:/usr/ccs/bin:/usr/bin"; else pathstr = "/usr/xpg4/bin:/usr/ccs/bin:" "/usr/bin:/opt/SUNWspro/bin:/usr/sbin"; } else { if (!xpg4) pathstr = "/usr/ccs/bin:/usr/bin:"; else pathstr = "/usr/xpg4/bin:/usr/ccs/bin:" "/usr/bin:/opt/SUNWspro/bin:"; } } cp = pathstr; do { cp = execat(cp, file, path); /* * 4025035 and 4038378 * if a filename begins with a "-" prepend "./" so that * the shell can't interpret it as an option */ if (*path == '-') { char *s; for (s = path; *s != '\0'; s++) continue; for (; s >= path; s--) *(s + 2) = *s; path[0] = '.'; path[1] = '/'; } (void) set_error(&error, 0); (void) execve(path, argv, envp); if (set_error(&error, errno) == ENOEXEC) { newargs[0] = (char *)shell; newargs[1] = path; for (i = 1; i <= argc; i++) newargs[i + 1] = argv[i]; (void) set_error(&error, 0); (void) execve(xpg4? xpg4_path : sun_path, newargs, envp); if (sap != NULL && (sap->sa_psflags & POSIX_SPAWN_NOEXECERR_NP)) _exit(127); (void) set_error(&error, errno); _exit(_EVAPORATE); } } while (cp); if (sap != NULL && (sap->sa_psflags & POSIX_SPAWN_NOEXECERR_NP)) { (void) set_error(&error, 0); _exit(127); } _exit(_EVAPORATE); return (0); /* not reached */ } int posix_spawn_file_actions_init( posix_spawn_file_actions_t *file_actions) { file_actions->__file_attrp = NULL; return (0); } int posix_spawn_file_actions_destroy( posix_spawn_file_actions_t *file_actions) { file_attr_t *froot = file_actions->__file_attrp; file_attr_t *fap; file_attr_t *next; if ((fap = froot) != NULL) { do { next = fap->fa_next; if (fap->fa_type == FA_OPEN) lfree(fap->fa_path, fap->fa_pathsize); lfree(fap, sizeof (*fap)); } while ((fap = next) != froot); } file_actions->__file_attrp = NULL; return (0); } static void add_file_attr(posix_spawn_file_actions_t *file_actions, file_attr_t *fap) { file_attr_t *froot = file_actions->__file_attrp; if (froot == NULL) { fap->fa_next = fap->fa_prev = fap; file_actions->__file_attrp = froot = fap; } else { fap->fa_next = froot; fap->fa_prev = froot->fa_prev; froot->fa_prev->fa_next = fap; froot->fa_prev = fap; } /* * Once set, __file_attrp no longer changes, so this assignment * always goes into the first element in the list, as required. */ if (fap->fa_type == FA_CLOSEFROM) froot->fa_need_dirbuf = 1; } int posix_spawn_file_actions_addopen( posix_spawn_file_actions_t *file_actions, int filedes, const char *path, int oflag, mode_t mode) { file_attr_t *fap; if (filedes < 0) return (EBADF); if ((fap = lmalloc(sizeof (*fap))) == NULL) return (ENOMEM); fap->fa_pathsize = strlen(path) + 1; if ((fap->fa_path = lmalloc(fap->fa_pathsize)) == NULL) { lfree(fap, sizeof (*fap)); return (ENOMEM); } (void) strcpy(fap->fa_path, path); fap->fa_type = FA_OPEN; fap->fa_oflag = oflag; fap->fa_mode = mode; fap->fa_filedes = filedes; add_file_attr(file_actions, fap); return (0); } int posix_spawn_file_actions_addclose( posix_spawn_file_actions_t *file_actions, int filedes) { file_attr_t *fap; if (filedes < 0) return (EBADF); if ((fap = lmalloc(sizeof (*fap))) == NULL) return (ENOMEM); fap->fa_type = FA_CLOSE; fap->fa_filedes = filedes; add_file_attr(file_actions, fap); return (0); } int posix_spawn_file_actions_adddup2( posix_spawn_file_actions_t *file_actions, int filedes, int newfiledes) { file_attr_t *fap; if (filedes < 0 || newfiledes < 0) return (EBADF); if ((fap = lmalloc(sizeof (*fap))) == NULL) return (ENOMEM); fap->fa_type = FA_DUP2; fap->fa_filedes = filedes; fap->fa_newfiledes = newfiledes; add_file_attr(file_actions, fap); return (0); } int posix_spawn_file_actions_addclosefrom_np( posix_spawn_file_actions_t *file_actions, int lowfiledes) { file_attr_t *fap; if (lowfiledes < 0) return (EBADF); if ((fap = lmalloc(sizeof (*fap))) == NULL) return (ENOMEM); fap->fa_type = FA_CLOSEFROM; fap->fa_filedes = lowfiledes; add_file_attr(file_actions, fap); return (0); } int posix_spawnattr_init( posix_spawnattr_t *attr) { if ((attr->__spawn_attrp = lmalloc(sizeof (posix_spawnattr_t))) == NULL) return (ENOMEM); /* * Add default stuff here? */ return (0); } int posix_spawnattr_destroy( posix_spawnattr_t *attr) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); /* * deallocate stuff here? */ lfree(sap, sizeof (*sap)); attr->__spawn_attrp = NULL; return (0); } int posix_spawnattr_setflags( posix_spawnattr_t *attr, short flags) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL || (flags & ~ALL_POSIX_SPAWN_FLAGS)) return (EINVAL); sap->sa_psflags = flags; return (0); } int posix_spawnattr_getflags( const posix_spawnattr_t *attr, short *flags) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); *flags = sap->sa_psflags; return (0); } int posix_spawnattr_setpgroup( posix_spawnattr_t *attr, pid_t pgroup) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); sap->sa_pgroup = pgroup; return (0); } int posix_spawnattr_getpgroup( const posix_spawnattr_t *attr, pid_t *pgroup) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); *pgroup = sap->sa_pgroup; return (0); } int posix_spawnattr_setschedparam( posix_spawnattr_t *attr, const struct sched_param *schedparam) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); /* * Check validity? */ sap->sa_priority = schedparam->sched_priority; return (0); } int posix_spawnattr_getschedparam( const posix_spawnattr_t *attr, struct sched_param *schedparam) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); schedparam->sched_priority = sap->sa_priority; return (0); } int posix_spawnattr_setschedpolicy( posix_spawnattr_t *attr, int schedpolicy) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL || schedpolicy == SCHED_SYS) return (EINVAL); /* * Cache the policy information for later use * by the vfork() child of posix_spawn(). */ if (get_info_by_policy(schedpolicy) == NULL) return (errno); sap->sa_schedpolicy = schedpolicy; return (0); } int posix_spawnattr_getschedpolicy( const posix_spawnattr_t *attr, int *schedpolicy) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); *schedpolicy = sap->sa_schedpolicy; return (0); } int posix_spawnattr_setsigdefault( posix_spawnattr_t *attr, const sigset_t *sigdefault) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); sap->sa_sigdefault = *sigdefault; return (0); } int posix_spawnattr_getsigdefault( const posix_spawnattr_t *attr, sigset_t *sigdefault) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); *sigdefault = sap->sa_sigdefault; return (0); } int posix_spawnattr_setsigignore_np( posix_spawnattr_t *attr, const sigset_t *sigignore) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); sap->sa_sigignore = *sigignore; return (0); } int posix_spawnattr_getsigignore_np( const posix_spawnattr_t *attr, sigset_t *sigignore) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); *sigignore = sap->sa_sigignore; return (0); } int posix_spawnattr_setsigmask( posix_spawnattr_t *attr, const sigset_t *sigmask) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); sap->sa_sigmask = *sigmask; return (0); } int posix_spawnattr_getsigmask( const posix_spawnattr_t *attr, sigset_t *sigmask) { spawn_attr_t *sap = attr->__spawn_attrp; if (sap == NULL) return (EINVAL); *sigmask = sap->sa_sigmask; return (0); }