--- embedaddon/sudo/src/sudo.c 2012/02/21 16:23:02 1.1.1.1 +++ embedaddon/sudo/src/sudo.c 2013/07/22 10:46:13 1.1.1.4 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2011 Todd C. Miller + * Copyright (c) 2009-2013 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 @@ -21,13 +21,9 @@ #include #include -#include #include #include #include -#ifdef HAVE_SYS_SELECT_H -# include -#endif /* HAVE_SYS_SELECT_H */ #include #include #include @@ -61,11 +57,11 @@ #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 @@ -86,14 +82,11 @@ # endif /* __hpux */ # include #endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */ -#ifdef HAVE_PRIV_SET -# include -#endif #include "sudo.h" #include "sudo_plugin.h" #include "sudo_plugin_int.h" -#include +#include "sudo_usage.h" /* * Local variables @@ -102,25 +95,18 @@ 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 */ -int debug_level; +static struct command_details command_details; +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); -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 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); /* Policy plugin convenience functions. */ static int policy_open(struct plugin_container *plugin, char * const settings[], @@ -135,8 +121,6 @@ static int policy_list(struct plugin_container *plugin 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); -static int policy_init_session(struct plugin_container *plugin, - struct passwd *pwd); /* I/O log plugin convenience functions. */ static int iolog_open(struct plugin_container *plugin, char * const settings[], @@ -145,40 +129,37 @@ static int iolog_open(struct plugin_container *plugin, 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); -#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL) +#ifdef RLIMIT_CORE static struct rlimit corelimit; -#endif /* RLIMIT_CORE && !SUDO_DEVEL */ +#endif /* RLIMIT_CORE */ #if defined(__linux__) static struct rlimit nproclimit; #endif +__dso_public int main(int argc, char *argv[], char *envp[]); + int main(int argc, char *argv[], char *envp[]) { - int nargc, sudo_mode, exitcode = 0; + 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; - int ok; -#if defined(SUDO_DEVEL) && defined(__OpenBSD__) - extern char *malloc_options; - malloc_options = "AFGJPR"; -#endif + debug_decl(main, SUDO_DEBUG_MAIN) -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) - if (argc > 0) - setprogname(argv[0]); -#endif + os_init(argc, argv, envp); -#ifdef HAVE_SETLOCALE setlocale(LC_ALL, ""); -#endif bindtextdomain(PACKAGE_NAME, LOCALEDIR); textdomain(PACKAGE_NAME); +#ifdef HAVE_TZSET + (void) tzset(); +#endif /* HAVE_TZSET */ + /* Must be done before we do any password lookups */ #if defined(HAVE_GETPRPWNAM) && defined(HAVE_SET_AUTH_PARAMETERS) (void) set_auth_parameters(argc, argv); @@ -187,22 +168,28 @@ main(int argc, char *argv[], char *envp[]) # endif #endif /* HAVE_GETPRPWNAM && HAVE_SET_AUTH_PARAMETERS */ - if (geteuid() != 0) - errorx(1, _("must be setuid root")); + /* Make sure we are setuid root. */ + sudo_check_suid(argv[0]); - /* Reset signal mask, disable core dumps and make sure fds 0-2 are open. */ + /* Reset signal mask, save signal state and make sure fds 0-2 are open. */ (void) sigemptyset(&mask); (void) sigprocmask(SIG_SETMASK, &mask, NULL); - disable_coredumps(); + save_signals(); fix_fds(); + /* Read sudo.conf. */ + sudo_conf_read(NULL); + /* Fill in user_info with user name, uid, cwd, etc. */ memset(&user_details, 0, sizeof(user_details)); user_info = get_user_info(&user_details); + /* 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(9, "sudo_mode %d", sudo_mode); + 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)) { @@ -211,33 +198,35 @@ main(int argc, char *argv[], char *envp[]) (void) printf(_("Configure options: %s\n"), CONFIGURE_ARGS); } - /* Read sudo.conf and load plugins. */ - if (!sudo_load_plugins(_PATH_SUDO_CONF, &policy_plugin, &io_plugins)) - errorx(1, _("fatal error, unable to load plugins")); + /* Load plugins. */ + if (!sudo_load_plugins(&policy_plugin, &io_plugins)) + fatalx(_("fatal error, unable to load plugins")); /* Open policy plugin. */ ok = policy_open(&policy_plugin, settings, user_info, envp); - if (ok != TRUE) { + if (ok != 1) { if (ok == -2) usage(1); else - errorx(1, _("unable to initialize policy plugin")); + fatalx(_("unable to initialize policy plugin")); } + init_signals(); + 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 == TRUE) + 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 != TRUE); + exit(ok != 1); case MODE_KILL: case MODE_INVALIDATE: policy_invalidate(&policy_plugin, sudo_mode == MODE_KILL); @@ -249,13 +238,13 @@ main(int argc, char *argv[], char *envp[]) case MODE_LIST|MODE_INVALIDATE: ok = policy_list(&policy_plugin, nargc, nargv, ISSET(sudo_mode, MODE_LONG_LIST), list_user); - exit(ok != TRUE); + 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(8, "policy plugin returns %d", ok); - if (ok != TRUE) { + 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 */ @@ -266,29 +255,34 @@ main(int argc, char *argv[], char *envp[]) ok = iolog_open(plugin, settings, user_info, command_info, nargc, nargv, envp); switch (ok) { - case TRUE: + case 1: break; - case FALSE: - /* I/O plugin asked to be disabled, remove from list. */ - tq_remove(&io_plugins, plugin); + 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"), + fatalx(_("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. */ + if (setuid(ROOT_UID) == -1) + warning("setuid(%d)", ROOT_UID); /* Restore coredumpsize resource limit before running. */ -#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL) - (void) setrlimit(RLIMIT_CORE, &corelimit); -#endif /* RLIMIT_CORE && !SUDO_DEVEL */ +#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 { @@ -297,11 +291,22 @@ main(int argc, char *argv[], char *envp[]) /* The close method was called by sudo_edit/run_command. */ break; default: - errorx(1, _("unexpected sudo mode 0x%x"), sudo_mode); + fatalx(_("unexpected sudo mode 0x%x"), sudo_mode); } + sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, exitcode); exit(exitcode); } +int +os_init_common(int argc, char *argv[], char *envp[]) +{ +#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) + if (argc > 0) + setprogname(argv[0]); +#endif + return 0; +} + /* * 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. @@ -310,6 +315,7 @@ 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 @@ -320,47 +326,55 @@ fix_fds(void) 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); + fatal(_("unable to open %s"), _PATH_DEVNULL); if (miss[STDIN_FILENO] && dup2(devnull, STDIN_FILENO) == -1) - error(1, "dup2"); + fatal("dup2"); if (miss[STDOUT_FILENO] && dup2(devnull, STDOUT_FILENO) == -1) - error(1, "dup2"); + fatal("dup2"); if (miss[STDERR_FILENO] && dup2(devnull, STDERR_FILENO) == -1) - error(1, "dup2"); + fatal("dup2"); if (devnull > STDERR_FILENO) close(devnull); } + debug_return; } /* * Allocate space for groups and fill in using getgrouplist() - * for when we cannot use getgroups(). + * for when we cannot (or don't want to) use getgroups(). */ static int -fill_group_list(struct user_details *ud) +fill_group_list(struct user_details *ud, int system_maxgroups) { - int maxgroups, tries, rval = -1; + int 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. + * If user specified a max number of groups, use it, otherwise keep + * trying getgrouplist() until we have enough room in the array. */ - ud->groups = NULL; - ud->ngroups = maxgroups; - for (tries = 0; tries < 10 && rval == -1; tries++) { - ud->ngroups *= 2; - efree(ud->groups); + ud->ngroups = sudo_conf_max_groups(); + if (ud->ngroups != -1) { ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T)); - rval = getgrouplist(ud->username, ud->gid, ud->groups, &ud->ngroups); + /* No error on insufficient space if user specified max_groups. */ + (void)getgrouplist(ud->username, ud->gid, ud->groups, &ud->ngroups); + rval = 0; + } else { + /* + * It is possible to belong to more groups in the group database + * than NGROUPS_MAX. We start off with NGROUPS_MAX * 4 entries + * and double this as needed. + */ + ud->groups = NULL; + ud->ngroups = system_maxgroups << 1; + for (tries = 0; tries < 10 && rval == -1; tries++) { + ud->ngroups <<= 1; + efree(ud->groups); + ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T)); + rval = getgrouplist(ud->username, ud->gid, ud->groups, &ud->ngroups); + } } - return rval; + debug_return_int(rval); } static char * @@ -368,25 +382,36 @@ get_user_groups(struct user_details *ud) { char *cp, *gid_list = NULL; size_t glsize; - int i, len; + int i, len, maxgroups, group_source; + debug_decl(get_user_groups, SUDO_DEBUG_UTIL) - /* - * Systems with mbr_check_membership() support more than NGROUPS_MAX - * groups so we cannot use getgroups(). - */ +#if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX) + maxgroups = (int)sysconf(_SC_NGROUPS_MAX); + if (maxgroups < 0) +#endif + maxgroups = NGROUPS_MAX; + 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; + group_source = sudo_conf_group_source(); + if (group_source != GROUP_SOURCE_DYNAMIC) { + if ((ud->ngroups = getgroups(0, NULL)) > 0) { + /* Use groups from kernel if not too many or source is static. */ + if (ud->ngroups < maxgroups || group_source == GROUP_SOURCE_STATIC) { + 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")); + /* + * Query group database if kernel list is too small or disabled. + * Typically, this is because NFS can only support up to 16 groups. + */ + if (fill_group_list(ud, maxgroups) == -1) + fatal(_("unable to get group vector")); } /* @@ -402,7 +427,7 @@ get_user_groups(struct user_details *ud) i ? "," : "", (unsigned int)ud->groups[i]); cp += len; } - return gid_list; + debug_return_str(gid_list); } /* @@ -412,15 +437,25 @@ get_user_groups(struct user_details *ud) static char ** get_user_info(struct user_details *ud) { - char cwd[PATH_MAX]; - char host[MAXHOSTNAMELEN]; - char **user_info, *cp; + char *cp, **user_info, cwd[PATH_MAX], host[HOST_NAME_MAX + 1]; struct passwd *pw; - int i = 0; + 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(); @@ -428,11 +463,11 @@ get_user_info(struct user_details *ud) pw = getpwuid(ud->uid); if (pw == NULL) - errorx(1, _("unknown uid %u: who are you?"), (unsigned int)ud->uid); + fatalx(_("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")); + fatalx(NULL); ud->username = user_info[i] + sizeof("user=") - 1; /* Stash user's shell for use with the -s flag; don't pass to plugin. */ @@ -441,6 +476,12 @@ get_user_info(struct user_details *ud) } 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); @@ -452,16 +493,16 @@ get_user_info(struct user_details *ud) if (getcwd(cwd, sizeof(cwd)) != NULL) { user_info[++i] = fmt_string("cwd", cwd); if (user_info[i] == NULL) - errorx(1, _("unable to allocate memory")); + fatalx(NULL); ud->cwd = user_info[i] + sizeof("cwd=") - 1; } - if ((cp = ttyname(STDIN_FILENO)) || (cp = ttyname(STDOUT_FILENO)) || - (cp = ttyname(STDERR_FILENO))) { + if ((cp = get_process_ttyname()) != NULL) { user_info[++i] = fmt_string("tty", cp); if (user_info[i] == NULL) - errorx(1, _("unable to allocate memory")); + fatalx(NULL); ud->tty = user_info[i] + sizeof("tty=") - 1; + efree(cp); } if (gethostname(host, sizeof(host)) == 0) @@ -470,7 +511,7 @@ get_user_info(struct user_details *ud) strlcpy(host, "localhost", sizeof(host)); user_info[++i] = fmt_string("host", host); if (user_info[i] == NULL) - errorx(1, _("unable to allocate memory")); + fatalx(NULL); ud->host = user_info[i] + sizeof("host=") - 1; get_ttysize(&ud->ts_lines, &ud->ts_cols); @@ -479,7 +520,7 @@ get_user_info(struct user_details *ud) user_info[++i] = NULL; - return user_info; + debug_return_ptr(user_info); } /* @@ -492,6 +533,7 @@ command_info_to_details(char * const info[], struct co 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; @@ -502,8 +544,9 @@ command_info_to_details(char * const info[], struct co break; \ } + sudo_debug_printf(SUDO_DEBUG_INFO, "command info from plugin:"); for (i = 0; info[i] != NULL; i++) { - sudo_debug(9, "command info: %s", info[i]); + sudo_debug_printf(SUDO_DEBUG_INFO, " %d: %s", i, info[i]); switch (info[i][0]) { case 'c': SET_STRING("chroot=", chroot) @@ -524,6 +567,13 @@ command_info_to_details(char * const info[], struct co break; } break; + case 'e': + if (strncmp("exec_background=", info[i], sizeof("exec_background=") - 1) == 0) { + if (atobool(info[i] + sizeof("exec_background=") - 1) == true) + SET(details->flags, CD_EXEC_BG); + break; + } + break; case 'l': SET_STRING("login_class=", login_class) break; @@ -545,21 +595,14 @@ command_info_to_details(char * const info[], struct co break; } if (strncmp("noexec=", info[i], sizeof("noexec=") - 1) == 0) { - if (atobool(info[i] + sizeof("noexec=") - 1) == TRUE) + if (atobool(info[i] + sizeof("noexec=") - 1) == true) SET(details->flags, CD_NOEXEC); break; } -#ifdef _PATH_SUDO_NOEXEC - /* XXX - deprecated */ - if (strncmp("noexec_file=", info[i], sizeof("noexec_file=") - 1) == 0) { - noexec_path = info[i] + sizeof("noexec_file=") - 1; - break; - } -#endif /* _PATH_SUDO_NOEXEC */ break; case 'p': if (strncmp("preserve_groups=", info[i], sizeof("preserve_groups=") - 1) == 0) { - if (atobool(info[i] + sizeof("preserve_groups=") - 1) == TRUE) + if (atobool(info[i] + sizeof("preserve_groups=") - 1) == true) SET(details->flags, CD_PRESERVE_GROUPS); break; } @@ -648,17 +691,39 @@ command_info_to_details(char * const info[], struct co } break; } +#ifdef HAVE_PRIV_SET + if (strncmp("runas_privs=", info[i], sizeof("runas_privs=") - 1) == 0) { + const char *endp; + cp = info[i] + sizeof("runas_privs=") - 1; + if (*cp == '\0') + break; + errno = 0; + details->privs = priv_str_to_set(cp, ",", &endp); + if (details->privs == NULL) + warning("invalid runas_privs %s", endp); + } + if (strncmp("runas_limitprivs=", info[i], sizeof("runas_limitprivs=") - 1) == 0) { + const char *endp; + cp = info[i] + sizeof("runas_limitprivs=") - 1; + if (*cp == '\0') + break; + errno = 0; + details->limitprivs = priv_str_to_set(cp, ",", &endp); + if (details->limitprivs == NULL) + warning("invalid runas_limitprivs %s", endp); + } +#endif /* HAVE_PRIV_SET */ 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) + 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) + if (atobool(info[i] + sizeof("sudoedit=") - 1) == true) SET(details->flags, CD_SUDOEDIT); break; } @@ -695,7 +760,7 @@ command_info_to_details(char * const info[], struct co break; } if (strncmp("use_pty=", info[i], sizeof("use_pty=") - 1) == 0) { - if (atobool(info[i] + sizeof("use_pty=") - 1) == TRUE) + if (atobool(info[i] + sizeof("use_pty=") - 1) == true) SET(details->flags, CD_USE_PTY); break; } @@ -707,12 +772,50 @@ command_info_to_details(char * const info[], struct co 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) + fatalx(NULL); +#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)) { + fatalx( + _("%s must be owned by uid %d and have the setuid bit set"), + path, ROOT_UID); + } else { + fatalx(_("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 { + fatalx( + _("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. @@ -721,9 +824,10 @@ command_info_to_details(char * const info[], struct co static void disable_coredumps(void) { -#if defined(__linux__) || (defined(RLIMIT_CORE) && !defined(SUDO_DEVEL)) +#if defined(__linux__) || defined(RLIMIT_CORE) struct rlimit rl; #endif + debug_decl(disable_coredumps, SUDO_DEBUG_UTIL) #if defined(__linux__) /* @@ -739,177 +843,30 @@ disable_coredumps(void) (void)setrlimit(RLIMIT_NPROC, &rl); } #endif /* __linux__ */ -#if defined(RLIMIT_CORE) && !defined(SUDO_DEVEL) +#ifdef RLIMIT_CORE /* - * Turn off core dumps. + * Turn off core dumps? */ - (void) getrlimit(RLIMIT_CORE, &corelimit); - memcpy(&rl, &corelimit, sizeof(struct rlimit)); - rl.rlim_cur = 0; - (void) setrlimit(RLIMIT_CORE, &rl); -#endif /* RLIMIT_CORE && !SUDO_DEVEL */ -} - -#ifdef HAVE_PROJECT_H -static void -set_project(struct passwd *pw) -{ - struct project proj; - char buf[PROJECT_BUFSZ]; - int errval; - - /* - * 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"); + 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); } - endprojent(); +#endif /* RLIMIT_CORE */ + debug_return; } -#endif /* HAVE_PROJECT_H */ /* - * Disable execution of child processes in the command we are about - * to run. On systems with privilege sets, we can remove the exec - * privilege. On other systems we use LD_PRELOAD and the like. - */ -static void -disable_execute(struct command_details *details) -{ -#ifdef _PATH_SUDO_NOEXEC - char *cp, **ev, **nenvp; - int env_len = 0, env_size = 128; -#endif /* _PATH_SUDO_NOEXEC */ - -#ifdef HAVE_PRIV_SET - /* Solaris privileges, remove PRIV_PROC_EXEC post-execve. */ - if (priv_set(PRIV_OFF, PRIV_LIMIT, "PRIV_PROC_EXEC", NULL) == 0) - return; - warning(_("unable to remove PRIV_PROC_EXEC from PRIV_LIMIT")); -#endif /* HAVE_PRIV_SET */ - -#ifdef _PATH_SUDO_NOEXEC - nenvp = emalloc2(env_size, sizeof(char *)); - for (ev = details->envp; *ev != NULL; ev++) { - if (env_len + 2 > env_size) { - env_size += 128; - nenvp = erealloc3(nenvp, env_size, sizeof(char *)); - } - /* - * Prune out existing preloaded libraries. - * XXX - should save and append instead of replacing. - */ -# if defined(__darwin__) || defined(__APPLE__) - if (strncmp(*ev, "DYLD_INSERT_LIBRARIES=", sizeof("DYLD_INSERT_LIBRARIES=") - 1) == 0) - continue; - if (strncmp(*ev, "DYLD_FORCE_FLAT_NAMESPACE=", sizeof("DYLD_INSERT_LIBRARIES=") - 1) == 0) - continue; -# elif defined(__osf__) || defined(__sgi) - if (strncmp(*ev, "_RLD_LIST=", sizeof("_RLD_LIST=") - 1) == 0) - continue; -# elif defined(_AIX) - if (strncmp(*ev, "LDR_PRELOAD=", sizeof("LDR_PRELOAD=") - 1) == 0) - continue; -# else - if (strncmp(*ev, "LD_PRELOAD=", sizeof("LD_PRELOAD=") - 1) == 0) - continue; -# endif - nenvp[env_len++] = *ev; - } - - /* - * Preload a noexec file? For a list of LD_PRELOAD-alikes, see - * http://www.fortran-2000.com/ArnaudRecipes/sharedlib.html - * XXX - need to support 32-bit and 64-bit variants - */ -# if defined(__darwin__) || defined(__APPLE__) - nenvp[env_len++] = "DYLD_FORCE_FLAT_NAMESPACE="; - cp = fmt_string("DYLD_INSERT_LIBRARIES", noexec_path); -# elif defined(__osf__) || defined(__sgi) - easprintf(&cp, "_RLD_LIST=%s:DEFAULT", noexec_path); -# elif defined(_AIX) - cp = fmt_string("LDR_PRELOAD", noexec_path); -# else - cp = fmt_string("LD_PRELOAD", noexec_path); -# endif - if (cp == NULL) - error(1, NULL); - nenvp[env_len++] = cp; - nenvp[env_len] = NULL; - - details->envp = nenvp; -#endif /* _PATH_SUDO_NOEXEC */ -} - -/* * Setup the execution environment immediately prior to the call to execve() - * Returns TRUE on success and FALSE on failure. + * Returns true on success and false on failure. */ -int +bool exec_setup(struct command_details *details, const char *ptyname, int ptyfd) { - int rval = FALSE; - struct passwd *pw; + bool rval = false; + debug_decl(exec_setup, SUDO_DEBUG_EXEC) -#ifdef HAVE_SETAUTHDB - aix_setauthdb(IDtouser(details->euid)); -#endif - pw = getpwuid(details->euid); -#ifdef HAVE_SETAUTHDB - aix_restoreauthdb(); -#endif - - /* - * Call policy plugin's session init before other setup occurs. - * The session init code is expected to print an error as needed. - */ - if (policy_init_session(&policy_plugin, pw) != TRUE) - goto done; - #ifdef HAVE_SELINUX if (ISSET(details->flags, CD_RBAC_ENABLED)) { if (selinux_setup(details->selinux_role, details->selinux_type, @@ -918,12 +875,32 @@ exec_setup(struct command_details *details, const char } #endif - if (pw != NULL) { + if (details->pw != NULL) { #ifdef HAVE_PROJECT_H - set_project(pw); + set_project(details->pw); #endif +#ifdef HAVE_PRIV_SET + if (details->privs != NULL) { + if (setppriv(PRIV_SET, PRIV_INHERITABLE, details->privs) != 0) { + warning("unable to set privileges"); + goto done; + } + } + if (details->limitprivs != NULL) { + if (setppriv(PRIV_SET, PRIV_LIMIT, details->limitprivs) != 0) { + warning("unable to set limit privileges"); + goto done; + } + } else if (details->privs != NULL) { + if (setppriv(PRIV_SET, PRIV_LIMIT, details->privs) != 0) { + warning("unable to set limit privileges"); + goto done; + } + } +#endif /* HAVE_PRIV_SET */ + #ifdef HAVE_GETUSERATTR - aix_prep_user(pw->pw_name, ptyname ? ptyname : user_details.tty); + aix_prep_user(details->pw->pw_name, ptyname ? ptyname : user_details.tty); #endif #ifdef HAVE_LOGIN_CAP_H if (details->login_class) { @@ -931,7 +908,8 @@ exec_setup(struct command_details *details, const char login_cap_t *lc; /* - * We only use setusercontext() to set the nice value and rlimits. + * 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) { @@ -939,9 +917,16 @@ exec_setup(struct command_details *details, const char errno = ENOENT; goto done; } - flags = LOGIN_SETRESOURCES|LOGIN_SETPRIORITY; - if (setusercontext(lc, pw, pw->pw_uid, flags)) { - if (pw->pw_uid != ROOT_UID) { + 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 @@ -954,6 +939,14 @@ exec_setup(struct command_details *details, const char /* * 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"), @@ -967,15 +960,6 @@ exec_setup(struct command_details *details, const char goto done; } - 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; - } - } - } - if (ISSET(details->flags, CD_SET_PRIORITY)) { if (setpriority(PRIO_PROCESS, 0, details->priority) != 0) { warning(_("unable to set process priority")); @@ -991,9 +975,6 @@ exec_setup(struct command_details *details, const char } } - if (ISSET(details->flags, CD_NOEXEC)) - disable_execute(details); - #ifdef HAVE_SETRESUID if (setresuid(details->uid, details->euid, details->euid) != 0) { warning(_("unable to change to runas uid (%u, %u)"), details->uid, @@ -1029,24 +1010,36 @@ exec_setup(struct command_details *details, const char } /* - * Restore nproc resource limit if pam_limits didn't do it for us. + * SuSE Enterprise Linux uses RLIMIT_NPROC and _SC_CHILD_MAX + * interchangably. This causes problems when setting RLIMIT_NPROC + * to RLIM_INFINITY due to a bug in bash where bash tries to honor + * the value of _SC_CHILD_MAX but treats a value of -1 as an error, + * and uses a default value of 32 instead. + * + * To work around this problem, we restore the nproc resource limit + * if sysconf(_SC_CHILD_MAX) is negative. In most cases, pam_limits + * will set RLIMIT_NPROC for us. + * * We must do this *after* the uid change to avoid potential EAGAIN * from setuid(). */ -#if defined(__linux__) +#if defined(__linux__) && defined(_SC_CHILD_MAX) { struct rlimit rl; - if (getrlimit(RLIMIT_NPROC, &rl) == 0) { + long l; + errno = 0; + l = sysconf(_SC_CHILD_MAX); + if (l == -1 && errno == 0 && getrlimit(RLIMIT_NPROC, &rl) == 0) { if (rl.rlim_cur == RLIM_INFINITY && rl.rlim_max == RLIM_INFINITY) (void) setrlimit(RLIMIT_NPROC, &nproclimit); } } #endif - rval = TRUE; + rval = true; done: - return rval; + debug_return_bool(rval); } /* @@ -1058,29 +1051,34 @@ 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_execve(details, &cstat); + sudo_execute(details, &cstat); switch (cstat.type) { case CMD_ERRNO: /* exec_setup() or execve() returned an error. */ - sudo_debug(9, "calling policy close with errno"); + 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(9, "calling I/O close with errno"); + 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(9, "calling policy close with wait status"); + 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(9, "calling I/O close with wait status"); + 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)) @@ -1092,27 +1090,51 @@ run_command(struct command_details *details) warningx(_("unexpected child termination condition: %d"), cstat.type); break; } - return exitcode; + debug_return_int(exitcode); } static int policy_open(struct plugin_container *plugin, char * const settings[], char * const user_info[], char * const user_env[]) { - return plugin->u.policy->open(SUDO_API_VERSION, sudo_conversation, - _sudo_printf, settings, user_info, 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) { - plugin->u.policy->close(exit_status, error); + debug_decl(policy_close, SUDO_DEBUG_PCOMM) + if (plugin->u.policy->close != NULL) + plugin->u.policy->close(exit_status, error); + else + warning(_("unable to execute %s"), command_details.command); + debug_return; } static int policy_show_version(struct plugin_container *plugin, int verbose) { - return plugin->u.policy->show_version(verbose); + debug_decl(policy_show_version, SUDO_DEBUG_PCOMM) + if (plugin->u.policy->show_version == NULL) + debug_return_bool(true); + debug_return_bool(plugin->u.policy->show_version(verbose)); } static int @@ -1120,49 +1142,73 @@ policy_check(struct plugin_container *plugin, int argc char *env_add[], char **command_info[], char **argv_out[], char **user_env_out[]) { - return plugin->u.policy->check_policy(argc, argv, env_add, command_info, - argv_out, user_env_out); + debug_decl(policy_check, SUDO_DEBUG_PCOMM) + if (plugin->u.policy->check_policy == NULL) { + fatalx(_("policy plugin %s is missing the `check_policy' method"), + plugin->name); + } + 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); - return FALSE; + debug_return_bool(false); } - return plugin->u.policy->list(argc, argv, verbose, list_user); + 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); - return FALSE; + debug_return_bool(false); } - return plugin->u.policy->validate(); + 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"), + fatalx(_("policy plugin %s does not support the -k/-K options"), plugin->name); } plugin->u.policy->invalidate(remove); + debug_return; } -static int -policy_init_session(struct plugin_container *plugin, struct passwd *pwd) +int +policy_init_session(struct command_details *details) { - if (plugin->u.policy->init_session) - return plugin->u.policy->init_session(pwd); - return TRUE; + 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 @@ -1171,9 +1217,10 @@ iolog_open(struct plugin_container *plugin, char * con int argc, char * const argv[], char * const user_env[]) { int rval; + debug_decl(iolog_open, SUDO_DEBUG_PCOMM) /* - * Backwards compatibility for API major 1, minor 0 + * Backwards compatibility for older API versions */ switch (plugin->u.generic->version) { case SUDO_API_MKVERSION(1, 0): @@ -1181,42 +1228,55 @@ iolog_open(struct plugin_container *plugin, char * con 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); + _sudo_printf, settings, user_info, command_info, + argc, argv, user_env, plugin->options); } - return rval; + debug_return_bool(rval); } static void iolog_close(struct plugin_container *plugin, int exit_status, int error) { - plugin->u.io->close(exit_status, error); + debug_decl(iolog_close, SUDO_DEBUG_PCOMM) + if (plugin->u.io->close != NULL) + plugin->u.io->close(exit_status, error); + debug_return; } static int iolog_show_version(struct plugin_container *plugin, int verbose) { - return plugin->u.io->show_version(verbose); + debug_decl(iolog_show_version, SUDO_DEBUG_PCOMM) + if (plugin->u.io->show_version == NULL) + debug_return_bool(true); + debug_return_bool(plugin->u.io->show_version(verbose)); } /* - * Simple debugging/logging. + * Remove the specified I/O logging plugin from the io_plugins list. + * Deregisters any hooks before unlinking, then frees the container. */ -void -sudo_debug(int level, const char *fmt, ...) +static void +iolog_unlink(struct plugin_container *plugin) { - va_list ap; - char *buf; + debug_decl(iolog_unlink, SUDO_DEBUG_PCOMM) - if (level > debug_level) - return; + /* 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); - /* Bracket fmt with program name and a newline to make it a single write */ - va_start(ap, fmt); - evasprintf(&buf, fmt, ap); - va_end(ap); - fprintf(stderr, "%s: %s\n", getprogname(), buf); - efree(buf); + debug_return; }