/* * Copyright (c) 2009-2012 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef __TANDEM # include #endif #include #include #include #include #include #include #include #include #include #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif /* STDC_HEADERS */ #ifdef HAVE_STRING_H # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS) # include # endif # include #endif /* HAVE_STRING_H */ #ifdef HAVE_STRINGS_H # include #endif /* HAVE_STRINGS_H */ #ifdef HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include #endif #ifdef HAVE_SETLOCALE # include #endif #ifdef HAVE_LOGIN_CAP_H # include # ifndef LOGIN_SETENV # define LOGIN_SETENV 0 # endif #endif #ifdef HAVE_PROJECT_H # include # include #endif #ifdef HAVE_SELINUX # include #endif #ifdef HAVE_SETAUTHDB # include #endif /* HAVE_SETAUTHDB */ #if defined(HAVE_GETPRPWNAM) && defined(HAVE_SET_AUTH_PARAMETERS) # ifdef __hpux # undef MAXINT # include # else # include # endif /* __hpux */ # include #endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */ #if defined(HAVE_STRUCT_KINFO_PROC_P_TDEV) || defined (HAVE_STRUCT_KINFO_PROC_KP_EPROC_E_TDEV) # include #elif defined(HAVE_STRUCT_KINFO_PROC_KI_TDEV) # include # include #endif #include "sudo.h" #include "sudo_plugin.h" #include "sudo_plugin_int.h" #include /* * Local variables */ struct plugin_container policy_plugin; struct plugin_container_list io_plugins; struct user_details user_details; const char *list_user, *runas_user, *runas_group; /* extern for parse_args.c */ static int sudo_mode; /* * Local functions */ static void fix_fds(void); static void disable_coredumps(void); static void sudo_check_suid(const char *path); static char **get_user_info(struct user_details *); static void command_info_to_details(char * const info[], struct command_details *details); /* Policy plugin convenience functions. */ static int policy_open(struct plugin_container *plugin, char * const settings[], char * const user_info[], char * const user_env[]); static void policy_close(struct plugin_container *plugin, int exit_status, int error); static int policy_show_version(struct plugin_container *plugin, int verbose); static int policy_check(struct plugin_container *plugin, int argc, char * const argv[], char *env_add[], char **command_info[], char **argv_out[], char **user_env_out[]); static int policy_list(struct plugin_container *plugin, int argc, char * const argv[], int verbose, const char *list_user); static int policy_validate(struct plugin_container *plugin); static void policy_invalidate(struct plugin_container *plugin, int remove); /* I/O log plugin convenience functions. */ static int iolog_open(struct plugin_container *plugin, char * const settings[], char * const user_info[], char * const command_details[], int argc, char * const argv[], char * const user_env[]); static void iolog_close(struct plugin_container *plugin, int exit_status, int error); static int iolog_show_version(struct plugin_container *plugin, int verbose); static void iolog_unlink(struct plugin_container *plugin); #ifdef RLIMIT_CORE static struct rlimit corelimit; #endif /* RLIMIT_CORE */ #if defined(__linux__) static struct rlimit nproclimit; #endif int main(int argc, char *argv[], char *envp[]) { int nargc, ok, exitcode = 0; char **nargv, **settings, **env_add; char **user_info, **command_info, **argv_out, **user_env_out; struct plugin_container *plugin, *next; struct command_details command_details; sigset_t mask; debug_decl(main, SUDO_DEBUG_MAIN) #if defined(SUDO_DEVEL) && defined(__OpenBSD__) { extern char *malloc_options; malloc_options = "AFGJPR"; } #endif #if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) if (argc > 0) setprogname(argv[0]); #endif #ifdef HAVE_SETLOCALE setlocale(LC_ALL, ""); #endif bindtextdomain(PACKAGE_NAME, LOCALEDIR); textdomain(PACKAGE_NAME); /* Must be done before we do any password lookups */ #if defined(HAVE_GETPRPWNAM) && defined(HAVE_SET_AUTH_PARAMETERS) (void) set_auth_parameters(argc, argv); # ifdef HAVE_INITPRIVS initprivs(); # endif #endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */ /* Make sure we are setuid root. */ sudo_check_suid(argv[0]); /* Reset signal mask and make sure fds 0-2 are open. */ (void) sigemptyset(&mask); (void) sigprocmask(SIG_SETMASK, &mask, NULL); fix_fds(); /* Fill in user_info with user name, uid, cwd, etc. */ memset(&user_details, 0, sizeof(user_details)); user_info = get_user_info(&user_details); /* Read sudo.conf. */ sudo_conf_read(); /* Disable core dumps if not enabled in sudo.conf. */ disable_coredumps(); /* Parse command line arguments. */ sudo_mode = parse_args(argc, argv, &nargc, &nargv, &settings, &env_add); sudo_debug_printf(SUDO_DEBUG_DEBUG, "sudo_mode %d", sudo_mode); /* Print sudo version early, in case of plugin init failure. */ if (ISSET(sudo_mode, MODE_VERSION)) { printf(_("Sudo version %s\n"), PACKAGE_VERSION); if (user_details.uid == ROOT_UID) (void) printf(_("Configure options: %s\n"), CONFIGURE_ARGS); } /* Load plugins. */ if (!sudo_load_plugins(&policy_plugin, &io_plugins)) errorx(1, _("fatal error, unable to load plugins")); /* Open policy plugin. */ ok = policy_open(&policy_plugin, settings, user_info, envp); if (ok != 1) { if (ok == -2) usage(1); else errorx(1, _("unable to initialize policy plugin")); } switch (sudo_mode & MODE_MASK) { case MODE_VERSION: policy_show_version(&policy_plugin, !user_details.uid); tq_foreach_fwd(&io_plugins, plugin) { ok = iolog_open(plugin, settings, user_info, NULL, nargc, nargv, envp); if (ok == 1) iolog_show_version(plugin, !user_details.uid); } break; case MODE_VALIDATE: case MODE_VALIDATE|MODE_INVALIDATE: ok = policy_validate(&policy_plugin); exit(ok != 1); case MODE_KILL: case MODE_INVALIDATE: policy_invalidate(&policy_plugin, sudo_mode == MODE_KILL); exit(0); break; case MODE_CHECK: case MODE_CHECK|MODE_INVALIDATE: case MODE_LIST: case MODE_LIST|MODE_INVALIDATE: ok = policy_list(&policy_plugin, nargc, nargv, ISSET(sudo_mode, MODE_LONG_LIST), list_user); exit(ok != 1); case MODE_EDIT: case MODE_RUN: ok = policy_check(&policy_plugin, nargc, nargv, env_add, &command_info, &argv_out, &user_env_out); sudo_debug_printf(SUDO_DEBUG_INFO, "policy plugin returns %d", ok); if (ok != 1) { if (ok == -2) usage(1); exit(1); /* plugin printed error message */ } /* Open I/O plugins once policy plugin succeeds. */ for (plugin = io_plugins.first; plugin != NULL; plugin = next) { next = plugin->next; ok = iolog_open(plugin, settings, user_info, command_info, nargc, nargv, envp); switch (ok) { case 1: break; case 0: /* I/O plugin asked to be disabled, remove and free. */ iolog_unlink(plugin); break; case -2: usage(1); break; default: errorx(1, _("error initializing I/O plugin %s"), plugin->name); } } /* Setup command details and run command/edit. */ command_info_to_details(command_info, &command_details); command_details.argv = argv_out; command_details.envp = user_env_out; if (ISSET(sudo_mode, MODE_BACKGROUND)) SET(command_details.flags, CD_BACKGROUND); /* Become full root (not just setuid) so user cannot kill us. */ (void) setuid(ROOT_UID); /* Restore coredumpsize resource limit before running. */ #ifdef RLIMIT_CORE if (sudo_conf_disable_coredump()) (void) setrlimit(RLIMIT_CORE, &corelimit); #endif /* RLIMIT_CORE */ if (ISSET(command_details.flags, CD_SUDOEDIT)) { exitcode = sudo_edit(&command_details); } else { exitcode = run_command(&command_details); } /* The close method was called by sudo_edit/run_command. */ break; default: errorx(1, _("unexpected sudo mode 0x%x"), sudo_mode); } sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode); exit(exitcode); } /* * Ensure that stdin, stdout and stderr are open; set to /dev/null if not. * Some operating systems do this automatically in the kernel or libc. */ static void fix_fds(void) { int miss[3], devnull = -1; debug_decl(fix_fds, SUDO_DEBUG_UTIL) /* * stdin, stdout and stderr must be open; set them to /dev/null * if they are closed. */ miss[STDIN_FILENO] = fcntl(STDIN_FILENO, F_GETFL, 0) == -1; miss[STDOUT_FILENO] = fcntl(STDOUT_FILENO, F_GETFL, 0) == -1; miss[STDERR_FILENO] = fcntl(STDERR_FILENO, F_GETFL, 0) == -1; if (miss[STDIN_FILENO] || miss[STDOUT_FILENO] || miss[STDERR_FILENO]) { if ((devnull = open(_PATH_DEVNULL, O_RDWR, 0644)) == -1) error(1, _("unable to open %s"), _PATH_DEVNULL); if (miss[STDIN_FILENO] && dup2(devnull, STDIN_FILENO) == -1) error(1, "dup2"); if (miss[STDOUT_FILENO] && dup2(devnull, STDOUT_FILENO) == -1) error(1, "dup2"); if (miss[STDERR_FILENO] && dup2(devnull, STDERR_FILENO) == -1) error(1, "dup2"); if (devnull > STDERR_FILENO) close(devnull); } debug_return; } /* * Allocate space for groups and fill in using getgrouplist() * for when we cannot use getgroups(). */ static int fill_group_list(struct user_details *ud) { int maxgroups, tries, rval = -1; debug_decl(fill_group_list, SUDO_DEBUG_UTIL) #if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX) maxgroups = (int)sysconf(_SC_NGROUPS_MAX); if (maxgroups < 0) #endif maxgroups = NGROUPS_MAX; /* * It is possible to belong to more groups in the group database * than NGROUPS_MAX. We start off with NGROUPS_MAX * 2 entries * and double this as needed. */ ud->groups = NULL; ud->ngroups = maxgroups; for (tries = 0; tries < 10 && rval == -1; tries++) { ud->ngroups *= 2; efree(ud->groups); ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T)); rval = getgrouplist(ud->username, ud->gid, ud->groups, &ud->ngroups); } debug_return_int(rval); } static char * get_user_groups(struct user_details *ud) { char *cp, *gid_list = NULL; size_t glsize; int i, len; debug_decl(get_user_groups, SUDO_DEBUG_UTIL) /* * Systems with mbr_check_membership() support more than NGROUPS_MAX * groups so we cannot use getgroups(). */ ud->groups = NULL; #ifndef HAVE_MBR_CHECK_MEMBERSHIP if ((ud->ngroups = getgroups(0, NULL)) > 0) { ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T)); if (getgroups(ud->ngroups, ud->groups) < 0) { efree(ud->groups); ud->groups = NULL; } } #endif /* HAVE_MBR_CHECK_MEMBERSHIP */ if (ud->groups == NULL) { if (fill_group_list(ud) == -1) error(1, _("unable to get group vector")); } /* * Format group list as a comma-separated string of gids. */ glsize = sizeof("groups=") - 1 + (ud->ngroups * (MAX_UID_T_LEN + 1)); gid_list = emalloc(glsize); memcpy(gid_list, "groups=", sizeof("groups=") - 1); cp = gid_list + sizeof("groups=") - 1; for (i = 0; i < ud->ngroups; i++) { /* XXX - check rval */ len = snprintf(cp, glsize - (cp - gid_list), "%s%u", i ? "," : "", (unsigned int)ud->groups[i]); cp += len; } debug_return_str(gid_list); } /* * Return user information as an array of name=value pairs. * and fill in struct user_details (which shares the same strings). */ static char ** get_user_info(struct user_details *ud) { char *cp, **user_info, cwd[PATH_MAX], host[MAXHOSTNAMELEN]; struct passwd *pw; int fd, i = 0; debug_decl(get_user_info, SUDO_DEBUG_UTIL) /* XXX - bound check number of entries */ user_info = emalloc2(32, sizeof(char *)); ud->pid = getpid(); ud->ppid = getppid(); ud->pgid = getpgid(0); ud->tcpgid = (pid_t)-1; fd = open(_PATH_TTY, O_RDWR|O_NOCTTY|O_NONBLOCK, 0); if (fd != -1) { ud->tcpgid = tcgetpgrp(fd); close(fd); } ud->sid = getsid(0); ud->uid = getuid(); ud->euid = geteuid(); ud->gid = getgid(); ud->egid = getegid(); pw = getpwuid(ud->uid); if (pw == NULL) errorx(1, _("unknown uid %u: who are you?"), (unsigned int)ud->uid); user_info[i] = fmt_string("user", pw->pw_name); if (user_info[i] == NULL) errorx(1, _("unable to allocate memory")); ud->username = user_info[i] + sizeof("user=") - 1; /* Stash user's shell for use with the -s flag; don't pass to plugin. */ if ((ud->shell = getenv("SHELL")) == NULL || ud->shell[0] == '\0') { ud->shell = pw->pw_shell[0] ? pw->pw_shell : _PATH_BSHELL; } ud->shell = estrdup(ud->shell); easprintf(&user_info[++i], "pid=%d", (int)ud->pid); easprintf(&user_info[++i], "ppid=%d", (int)ud->ppid); easprintf(&user_info[++i], "pgid=%d", (int)ud->pgid); easprintf(&user_info[++i], "tcpgid=%d", (int)ud->tcpgid); easprintf(&user_info[++i], "sid=%d", (int)ud->sid); easprintf(&user_info[++i], "uid=%u", (unsigned int)ud->uid); easprintf(&user_info[++i], "euid=%u", (unsigned int)ud->euid); easprintf(&user_info[++i], "gid=%u", (unsigned int)ud->gid); easprintf(&user_info[++i], "egid=%u", (unsigned int)ud->egid); if ((cp = get_user_groups(ud)) != NULL) user_info[++i] = cp; if (getcwd(cwd, sizeof(cwd)) != NULL) { user_info[++i] = fmt_string("cwd", cwd); if (user_info[i] == NULL) errorx(1, _("unable to allocate memory")); ud->cwd = user_info[i] + sizeof("cwd=") - 1; } if ((cp = get_process_ttyname()) != NULL) { user_info[++i] = fmt_string("tty", cp); if (user_info[i] == NULL) errorx(1, _("unable to allocate memory")); ud->tty = user_info[i] + sizeof("tty=") - 1; efree(cp); } if (gethostname(host, sizeof(host)) == 0) host[sizeof(host) - 1] = '\0'; else strlcpy(host, "localhost", sizeof(host)); user_info[++i] = fmt_string("host", host); if (user_info[i] == NULL) errorx(1, _("unable to allocate memory")); ud->host = user_info[i] + sizeof("host=") - 1; get_ttysize(&ud->ts_lines, &ud->ts_cols); easprintf(&user_info[++i], "lines=%d", ud->ts_lines); easprintf(&user_info[++i], "cols=%d", ud->ts_cols); user_info[++i] = NULL; debug_return_ptr(user_info); } /* * Convert a command_info array into a command_details structure. */ static void command_info_to_details(char * const info[], struct command_details *details) { int i; long lval; unsigned long ulval; char *cp, *ep; debug_decl(command_info_to_details, SUDO_DEBUG_PCOMM) memset(details, 0, sizeof(*details)); details->closefrom = -1; #define SET_STRING(s, n) \ if (strncmp(s, info[i], sizeof(s) - 1) == 0 && info[i][sizeof(s) - 1]) { \ details->n = info[i] + sizeof(s) - 1; \ break; \ } sudo_debug_printf(SUDO_DEBUG_INFO, "command info from plugin:"); for (i = 0; info[i] != NULL; i++) { sudo_debug_printf(SUDO_DEBUG_INFO, " %d: %s", i, info[i]); switch (info[i][0]) { case 'c': SET_STRING("chroot=", chroot) SET_STRING("command=", command) SET_STRING("cwd=", cwd) if (strncmp("closefrom=", info[i], sizeof("closefrom=") - 1) == 0) { cp = info[i] + sizeof("closefrom=") - 1; if (*cp == '\0') break; errno = 0; lval = strtol(cp, &ep, 0); if (*cp != '\0' && *ep == '\0' && !(errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) && lval < INT_MAX && lval > INT_MIN) { details->closefrom = (int)lval; } break; } break; case 'l': SET_STRING("login_class=", login_class) break; case 'n': /* XXX - bounds check -NZERO to NZERO (inclusive). */ if (strncmp("nice=", info[i], sizeof("nice=") - 1) == 0) { cp = info[i] + sizeof("nice=") - 1; if (*cp == '\0') break; errno = 0; lval = strtol(cp, &ep, 0); if (*cp != '\0' && *ep == '\0' && !(errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) && lval < INT_MAX && lval > INT_MIN) { details->priority = (int)lval; SET(details->flags, CD_SET_PRIORITY); } break; } if (strncmp("noexec=", info[i], sizeof("noexec=") - 1) == 0) { if (atobool(info[i] + sizeof("noexec=") - 1) == true) SET(details->flags, CD_NOEXEC); break; } break; case 'p': if (strncmp("preserve_groups=", info[i], sizeof("preserve_groups=") - 1) == 0) { if (atobool(info[i] + sizeof("preserve_groups=") - 1) == true) SET(details->flags, CD_PRESERVE_GROUPS); break; } break; case 'r': if (strncmp("runas_egid=", info[i], sizeof("runas_egid=") - 1) == 0) { cp = info[i] + sizeof("runas_egid=") - 1; if (*cp == '\0') break; errno = 0; ulval = strtoul(cp, &ep, 0); if (*cp != '\0' && *ep == '\0' && (errno != ERANGE || ulval != ULONG_MAX)) { details->egid = (gid_t)ulval; SET(details->flags, CD_SET_EGID); } break; } if (strncmp("runas_euid=", info[i], sizeof("runas_euid=") - 1) == 0) { cp = info[i] + sizeof("runas_euid=") - 1; if (*cp == '\0') break; errno = 0; ulval = strtoul(cp, &ep, 0); if (*cp != '\0' && *ep == '\0' && (errno != ERANGE || ulval != ULONG_MAX)) { details->euid = (uid_t)ulval; SET(details->flags, CD_SET_EUID); } break; } if (strncmp("runas_gid=", info[i], sizeof("runas_gid=") - 1) == 0) { cp = info[i] + sizeof("runas_gid=") - 1; if (*cp == '\0') break; errno = 0; ulval = strtoul(cp, &ep, 0); if (*cp != '\0' && *ep == '\0' && (errno != ERANGE || ulval != ULONG_MAX)) { details->gid = (gid_t)ulval; SET(details->flags, CD_SET_GID); } break; } if (strncmp("runas_groups=", info[i], sizeof("runas_groups=") - 1) == 0) { int j; /* count groups, alloc and fill in */ cp = info[i] + sizeof("runas_groups=") - 1; if (*cp == '\0') break; for (;;) { details->ngroups++; if ((cp = strchr(cp, ',')) == NULL) break; cp++; } if (details->ngroups != 0) { details->groups = emalloc2(details->ngroups, sizeof(GETGROUPS_T)); cp = info[i] + sizeof("runas_groups=") - 1; for (j = 0; j < details->ngroups;) { errno = 0; ulval = strtoul(cp, &ep, 0); if (*cp == '\0' || (*ep != ',' && *ep != '\0') || (ulval == ULONG_MAX && errno == ERANGE)) { break; } details->groups[j++] = (gid_t)ulval; cp = ep + 1; } details->ngroups = j; } break; } if (strncmp("runas_uid=", info[i], sizeof("runas_uid=") - 1) == 0) { cp = info[i] + sizeof("runas_uid=") - 1; if (*cp == '\0') break; errno = 0; ulval = strtoul(cp, &ep, 0); if (*cp != '\0' && *ep == '\0' && (errno != ERANGE || ulval != ULONG_MAX)) { details->uid = (uid_t)ulval; SET(details->flags, CD_SET_UID); } break; } break; case 's': SET_STRING("selinux_role=", selinux_role) SET_STRING("selinux_type=", selinux_type) if (strncmp("set_utmp=", info[i], sizeof("set_utmp=") - 1) == 0) { if (atobool(info[i] + sizeof("set_utmp=") - 1) == true) SET(details->flags, CD_SET_UTMP); break; } if (strncmp("sudoedit=", info[i], sizeof("sudoedit=") - 1) == 0) { if (atobool(info[i] + sizeof("sudoedit=") - 1) == true) SET(details->flags, CD_SUDOEDIT); break; } break; case 't': if (strncmp("timeout=", info[i], sizeof("timeout=") - 1) == 0) { cp = info[i] + sizeof("timeout=") - 1; if (*cp == '\0') break; errno = 0; lval = strtol(cp, &ep, 0); if (*cp != '\0' && *ep == '\0' && !(errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) && lval <= INT_MAX && lval >= 0) { details->timeout = (int)lval; SET(details->flags, CD_SET_TIMEOUT); } break; } break; case 'u': if (strncmp("umask=", info[i], sizeof("umask=") - 1) == 0) { cp = info[i] + sizeof("umask=") - 1; if (*cp == '\0') break; errno = 0; ulval = strtoul(cp, &ep, 8); if (*cp != '\0' && *ep == '\0' && (errno != ERANGE || ulval != ULONG_MAX)) { details->umask = (uid_t)ulval; SET(details->flags, CD_SET_UMASK); } break; } if (strncmp("use_pty=", info[i], sizeof("use_pty=") - 1) == 0) { if (atobool(info[i] + sizeof("use_pty=") - 1) == true) SET(details->flags, CD_USE_PTY); break; } SET_STRING("utmp_user=", utmp_user) break; } } if (!ISSET(details->flags, CD_SET_EUID)) details->euid = details->uid; #ifdef HAVE_SETAUTHDB aix_setauthdb(IDtouser(details->euid)); #endif details->pw = getpwuid(details->euid); if (details->pw != NULL && (details->pw = pw_dup(details->pw)) == NULL) errorx(1, _("unable to allocate memory")); #ifdef HAVE_SETAUTHDB aix_restoreauthdb(); #endif #ifdef HAVE_SELINUX if (details->selinux_role != NULL && is_selinux_enabled() > 0) SET(details->flags, CD_RBAC_ENABLED); #endif debug_return; } static void sudo_check_suid(const char *path) { struct stat sb; debug_decl(sudo_check_suid, SUDO_DEBUG_PCOMM) if (geteuid() != 0) { if (strchr(path, '/') != NULL && stat(path, &sb) == 0) { /* Try to determine why sudo was not running as root. */ if (sb.st_uid != ROOT_UID || !ISSET(sb.st_mode, S_ISUID)) { errorx(1, _("%s must be owned by uid %d and have the setuid bit set"), path, ROOT_UID); } else { errorx(1, _("effective uid is not %d, is %s on a file system " "with the 'nosuid' option set or an NFS file system without" " root privileges?"), ROOT_UID, path); } } else { errorx(1, _("effective uid is not %d, is sudo installed setuid root?"), ROOT_UID); } } debug_return; } /* * Disable core dumps to avoid dropping a core with user password in it. * We will reset this limit before executing the command. * Not all operating systems disable core dumps for setuid processes. */ static void disable_coredumps(void) { #if defined(__linux__) || defined(RLIMIT_CORE) struct rlimit rl; #endif debug_decl(disable_coredumps, SUDO_DEBUG_UTIL) #if defined(__linux__) /* * Unlimit the number of processes since Linux's setuid() will * apply resource limits when changing uid and return EAGAIN if * nproc would be violated by the uid switch. */ (void) getrlimit(RLIMIT_NPROC, &nproclimit); rl.rlim_cur = rl.rlim_max = RLIM_INFINITY; if (setrlimit(RLIMIT_NPROC, &rl)) { memcpy(&rl, &nproclimit, sizeof(struct rlimit)); rl.rlim_cur = rl.rlim_max; (void)setrlimit(RLIMIT_NPROC, &rl); } #endif /* __linux__ */ #ifdef RLIMIT_CORE /* * Turn off core dumps? */ if (sudo_conf_disable_coredump()) { (void) getrlimit(RLIMIT_CORE, &corelimit); memcpy(&rl, &corelimit, sizeof(struct rlimit)); rl.rlim_cur = 0; (void) setrlimit(RLIMIT_CORE, &rl); } #endif /* RLIMIT_CORE */ debug_return; } #ifdef HAVE_PROJECT_H static void set_project(struct passwd *pw) { struct project proj; char buf[PROJECT_BUFSZ]; int errval; debug_decl(set_project, SUDO_DEBUG_UTIL) /* * Collect the default project for the user and settaskid */ setprojent(); if (getdefaultproj(pw->pw_name, &proj, buf, sizeof(buf)) != NULL) { errval = setproject(proj.pj_name, pw->pw_name, TASK_NORMAL); switch(errval) { case 0: break; case SETPROJ_ERR_TASK: switch (errno) { case EAGAIN: warningx(_("resource control limit has been reached")); break; case ESRCH: warningx(_("user \"%s\" is not a member of project \"%s\""), pw->pw_name, proj.pj_name); break; case EACCES: warningx(_("the invoking task is final")); break; default: warningx(_("could not join project \"%s\""), proj.pj_name); } case SETPROJ_ERR_POOL: switch (errno) { case EACCES: warningx(_("no resource pool accepting default bindings " "exists for project \"%s\""), proj.pj_name); break; case ESRCH: warningx(_("specified resource pool does not exist for " "project \"%s\""), proj.pj_name); break; default: warningx(_("could not bind to default resource pool for " "project \"%s\""), proj.pj_name); } break; default: if (errval <= 0) { warningx(_("setproject failed for project \"%s\""), proj.pj_name); } else { warningx(_("warning, resource control assignment failed for " "project \"%s\""), proj.pj_name); } } } else { warning("getdefaultproj"); } endprojent(); debug_return; } #endif /* HAVE_PROJECT_H */ /* * Setup the execution environment immediately prior to the call to execve() * Returns true on success and false on failure. */ bool exec_setup(struct command_details *details, const char *ptyname, int ptyfd) { bool rval = false; debug_decl(exec_setup, SUDO_DEBUG_EXEC) #ifdef HAVE_SELINUX if (ISSET(details->flags, CD_RBAC_ENABLED)) { if (selinux_setup(details->selinux_role, details->selinux_type, ptyname ? ptyname : user_details.tty, ptyfd) == -1) goto done; } #endif if (details->pw != NULL) { #ifdef HAVE_PROJECT_H set_project(details->pw); #endif #ifdef HAVE_GETUSERATTR aix_prep_user(details->pw->pw_name, ptyname ? ptyname : user_details.tty); #endif #ifdef HAVE_LOGIN_CAP_H if (details->login_class) { int flags; login_cap_t *lc; /* * We only use setusercontext() to set the nice value and rlimits * unless this is a login shell (sudo -i). */ lc = login_getclass((char *)details->login_class); if (!lc) { warningx(_("unknown login class %s"), details->login_class); errno = ENOENT; goto done; } if (ISSET(sudo_mode, MODE_LOGIN_SHELL)) { /* Set everything except user, group and login name. */ flags = LOGIN_SETALL; CLR(flags, LOGIN_SETGROUP|LOGIN_SETLOGIN|LOGIN_SETUSER|LOGIN_SETENV|LOGIN_SETPATH); CLR(details->flags, CD_SET_UMASK); /* LOGIN_UMASK instead */ } else { flags = LOGIN_SETRESOURCES|LOGIN_SETPRIORITY; } if (setusercontext(lc, details->pw, details->pw->pw_uid, flags)) { if (details->pw->pw_uid != ROOT_UID) { warning(_("unable to set user context")); goto done; } else warning(_("unable to set user context")); } } #endif /* HAVE_LOGIN_CAP_H */ } /* * Set groups, including supplementary group vector. */ if (!ISSET(details->flags, CD_PRESERVE_GROUPS)) { if (details->ngroups >= 0) { if (sudo_setgroups(details->ngroups, details->groups) < 0) { warning(_("unable to set supplementary group IDs")); goto done; } } } #ifdef HAVE_SETEUID if (ISSET(details->flags, CD_SET_EGID) && setegid(details->egid)) { warning(_("unable to set effective gid to runas gid %u"), (unsigned int)details->egid); goto done; } #endif if (ISSET(details->flags, CD_SET_GID) && setgid(details->gid)) { warning(_("unable to set gid to runas gid %u"), (unsigned int)details->gid); goto done; } if (ISSET(details->flags, CD_SET_PRIORITY)) { if (setpriority(PRIO_PROCESS, 0, details->priority) != 0) { warning(_("unable to set process priority")); goto done; } } if (ISSET(details->flags, CD_SET_UMASK)) (void) umask(details->umask); if (details->chroot) { if (chroot(details->chroot) != 0 || chdir("/") != 0) { warning(_("unable to change root to %s"), details->chroot); goto done; } } #ifdef HAVE_SETRESUID if (setresuid(details->uid, details->euid, details->euid) != 0) { warning(_("unable to change to runas uid (%u, %u)"), details->uid, details->euid); goto done; } #elif HAVE_SETREUID if (setreuid(details->uid, details->euid) != 0) { warning(_("unable to change to runas uid (%u, %u)"), (unsigned int)details->uid, (unsigned int)details->euid); goto done; } #else if (seteuid(details->euid) != 0 || setuid(details->euid) != 0) { warning(_("unable to change to runas uid (%u, %u)"), details->uid, details->euid); goto done; } #endif /* !HAVE_SETRESUID && !HAVE_SETREUID */ /* * Only change cwd if we have chroot()ed or the policy modules * specifies a different cwd. Must be done after uid change. */ if (details->cwd) { if (details->chroot || strcmp(details->cwd, user_details.cwd) != 0) { /* Note: cwd is relative to the new root, if any. */ if (chdir(details->cwd) != 0) { warning(_("unable to change directory to %s"), details->cwd); goto done; } } } /* * Restore nproc resource limit if pam_limits didn't do it for us. * We must do this *after* the uid change to avoid potential EAGAIN * from setuid(). */ #if defined(__linux__) { struct rlimit rl; if (getrlimit(RLIMIT_NPROC, &rl) == 0) { if (rl.rlim_cur == RLIM_INFINITY && rl.rlim_max == RLIM_INFINITY) (void) setrlimit(RLIMIT_NPROC, &nproclimit); } } #endif rval = true; done: debug_return_bool(rval); } /* * Run the command and wait for it to complete. */ int run_command(struct command_details *details) { struct plugin_container *plugin; struct command_status cstat; int exitcode = 1; debug_decl(run_command, SUDO_DEBUG_EXEC) cstat.type = CMD_INVALID; cstat.val = 0; sudo_execute(details, &cstat); switch (cstat.type) { case CMD_ERRNO: /* exec_setup() or execve() returned an error. */ sudo_debug_printf(SUDO_DEBUG_DEBUG, "calling policy close with errno %d", cstat.val); policy_close(&policy_plugin, 0, cstat.val); tq_foreach_fwd(&io_plugins, plugin) { sudo_debug_printf(SUDO_DEBUG_DEBUG, "calling I/O close with errno %d", cstat.val); iolog_close(plugin, 0, cstat.val); } exitcode = 1; break; case CMD_WSTATUS: /* Command ran, exited or was killed. */ sudo_debug_printf(SUDO_DEBUG_DEBUG, "calling policy close with wait status %d", cstat.val); policy_close(&policy_plugin, cstat.val, 0); tq_foreach_fwd(&io_plugins, plugin) { sudo_debug_printf(SUDO_DEBUG_DEBUG, "calling I/O close with wait status %d", cstat.val); iolog_close(plugin, cstat.val, 0); } if (WIFEXITED(cstat.val)) exitcode = WEXITSTATUS(cstat.val); else if (WIFSIGNALED(cstat.val)) exitcode = WTERMSIG(cstat.val) | 128; break; default: warningx(_("unexpected child termination condition: %d"), cstat.type); break; } debug_return_int(exitcode); } static int policy_open(struct plugin_container *plugin, char * const settings[], char * const user_info[], char * const user_env[]) { int rval; debug_decl(policy_open, SUDO_DEBUG_PCOMM) /* * Backwards compatibility for older API versions */ switch (plugin->u.generic->version) { case SUDO_API_MKVERSION(1, 0): case SUDO_API_MKVERSION(1, 1): rval = plugin->u.policy_1_0->open(plugin->u.io_1_0->version, sudo_conversation, _sudo_printf, settings, user_info, user_env); break; default: rval = plugin->u.policy->open(SUDO_API_VERSION, sudo_conversation, _sudo_printf, settings, user_info, user_env, plugin->options); } debug_return_bool(rval); } static void policy_close(struct plugin_container *plugin, int exit_status, int error) { debug_decl(policy_close, SUDO_DEBUG_PCOMM) plugin->u.policy->close(exit_status, error); debug_return; } static int policy_show_version(struct plugin_container *plugin, int verbose) { debug_decl(policy_show_version, SUDO_DEBUG_PCOMM) debug_return_bool(plugin->u.policy->show_version(verbose)); } static int policy_check(struct plugin_container *plugin, int argc, char * const argv[], char *env_add[], char **command_info[], char **argv_out[], char **user_env_out[]) { debug_decl(policy_check, SUDO_DEBUG_PCOMM) debug_return_bool(plugin->u.policy->check_policy(argc, argv, env_add, command_info, argv_out, user_env_out)); } static int policy_list(struct plugin_container *plugin, int argc, char * const argv[], int verbose, const char *list_user) { debug_decl(policy_list, SUDO_DEBUG_PCOMM) if (plugin->u.policy->list == NULL) { warningx(_("policy plugin %s does not support listing privileges"), plugin->name); debug_return_bool(false); } debug_return_bool(plugin->u.policy->list(argc, argv, verbose, list_user)); } static int policy_validate(struct plugin_container *plugin) { debug_decl(policy_validate, SUDO_DEBUG_PCOMM) if (plugin->u.policy->validate == NULL) { warningx(_("policy plugin %s does not support the -v option"), plugin->name); debug_return_bool(false); } debug_return_bool(plugin->u.policy->validate()); } static void policy_invalidate(struct plugin_container *plugin, int remove) { debug_decl(policy_invalidate, SUDO_DEBUG_PCOMM) if (plugin->u.policy->invalidate == NULL) { errorx(1, _("policy plugin %s does not support the -k/-K options"), plugin->name); } plugin->u.policy->invalidate(remove); debug_return; } int policy_init_session(struct command_details *details) { int rval = true; debug_decl(policy_init_session, SUDO_DEBUG_PCOMM) if (policy_plugin.u.policy->init_session) { /* * Backwards compatibility for older API versions */ switch (policy_plugin.u.generic->version) { case SUDO_API_MKVERSION(1, 0): case SUDO_API_MKVERSION(1, 1): rval = policy_plugin.u.policy_1_0->init_session(details->pw); break; default: rval = policy_plugin.u.policy->init_session(details->pw, &details->envp); } } debug_return_bool(rval); } static int iolog_open(struct plugin_container *plugin, char * const settings[], char * const user_info[], char * const command_info[], int argc, char * const argv[], char * const user_env[]) { int rval; debug_decl(iolog_open, SUDO_DEBUG_PCOMM) /* * Backwards compatibility for older API versions */ switch (plugin->u.generic->version) { case SUDO_API_MKVERSION(1, 0): rval = plugin->u.io_1_0->open(plugin->u.io_1_0->version, sudo_conversation, _sudo_printf, settings, user_info, argc, argv, user_env); break; case SUDO_API_MKVERSION(1, 1): rval = plugin->u.io_1_1->open(plugin->u.io_1_1->version, sudo_conversation, _sudo_printf, settings, user_info, command_info, argc, argv, user_env); break; default: rval = plugin->u.io->open(SUDO_API_VERSION, sudo_conversation, _sudo_printf, settings, user_info, command_info, argc, argv, user_env, plugin->options); } debug_return_bool(rval); } static void iolog_close(struct plugin_container *plugin, int exit_status, int error) { debug_decl(iolog_close, SUDO_DEBUG_PCOMM) plugin->u.io->close(exit_status, error); debug_return; } static int iolog_show_version(struct plugin_container *plugin, int verbose) { debug_decl(iolog_show_version, SUDO_DEBUG_PCOMM) debug_return_bool(plugin->u.io->show_version(verbose)); } /* * Remove the specified I/O logging plugin from the io_plugins list. * Deregisters any hooks before unlinking, then frees the container. */ static void iolog_unlink(struct plugin_container *plugin) { debug_decl(iolog_unlink, SUDO_DEBUG_PCOMM) /* Deregister hooks, if any. */ if (plugin->u.io->version >= SUDO_API_MKVERSION(1, 2)) { if (plugin->u.io->deregister_hooks != NULL) plugin->u.io->deregister_hooks(SUDO_HOOK_VERSION, deregister_hook); } /* Remove from io_plugins list and free. */ tq_remove(&io_plugins, plugin); efree(plugin); debug_return; }