From: Todd C. Miller Date: Thu, 21 Aug 2014 21:28:36 +0000 (-0600) Subject: Make sudoedit work with SELinux RBAC. X-Git-Tag: SUDO_1_8_11^2~54 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=db35c7c0e42b2226bcc95619025f9932b60aab10;p=sudo Make sudoedit work with SELinux RBAC. Adapted from RedHat patches (Daniel Kopecek) but made to behave a bit more like the non-SELinux bits. --- diff --git a/src/sesh.c b/src/sesh.c index d6881152f..df0b75553 100644 --- a/src/sesh.c +++ b/src/sesh.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2010-2013 Todd C. Miller + * Copyright (c) 2008, 2010-2014 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 @@ -19,6 +19,10 @@ #include #include +#include +#include +#include +#include #include #include #include @@ -30,6 +34,9 @@ #else # include "compat/stdbool.h" #endif /* HAVE_STDBOOL_H */ +#ifdef TIME_WITH_SYS_TIME +# include +#endif #include "sudo_gettext.h" /* must be included before sudo_compat.h */ @@ -44,11 +51,21 @@ __dso_public int main(int argc, char *argv[], char *envp[]); +static int sesh_sudoedit(int argc, char *argv[]); + +/* + * Exit codes defined in sudo_exec.h: + * SESH_SUCCESS (0) ... successful operation + * SESH_ERR_FAILURE (1) ... unspecified error + * SESH_ERR_INVALID (30) ... invalid -e arg value + * SESH_ERR_BAD_PATHS (31) ... odd number of paths + * SESH_ERR_NO_FILES (32) ... copy error, no files copied + * SESH_ERR_SOME_FILES (33) ... copy error, no files copied + */ int main(int argc, char *argv[], char *envp[]) { - char *cp, *cmnd; - bool login_shell, noexec = false; + int ret; debug_decl(main, SUDO_DEBUG_MAIN) initprogname(argc > 0 ? argv[0] : "sesh"); @@ -63,27 +80,165 @@ main(int argc, char *argv[], char *envp[]) /* Read sudo.conf. */ sudo_conf_read(NULL); - /* If the first char of argv[0] is '-', we are running as a login shell. */ - login_shell = argv[0][0] == '-'; + if (strcmp(argv[1], "-e") == 0) { + ret = sesh_sudoedit(argc, argv); + } else { + bool login_shell, noexec = false; + char *cp, *cmnd; + + /* If the first char of argv[0] is '-', we are running a login shell. */ + login_shell = argv[0][0] == '-'; + + /* If argv[0] ends in -noexec, pass the flag to sudo_execve() */ + if ((cp = strrchr(argv[0], '-')) != NULL && cp != argv[0]) + noexec = strcmp(cp, "-noexec") == 0; - /* If argv[0] ends in -noexec, pass the flag to sudo_execve() */ - if ((cp = strrchr(argv[0], '-')) != NULL && cp != argv[0]) - noexec = strcmp(cp, "-noexec") == 0; + /* Shift argv and make a copy of the command to execute. */ + argv++; + argc--; + cmnd = sudo_estrdup(argv[0]); + + /* If invoked as a login shell, modify argv[0] accordingly. */ + if (login_shell) { + if ((cp = strrchr(argv[0], '/')) == NULL) + sudo_fatal(U_("unable to run %s as a login shell"), argv[0]); + *cp = '-'; + argv[0] = cp; + } + sudo_execve(cmnd, argv, envp, noexec); + sudo_warn(U_("unable to execute %s"), cmnd); + ret = SESH_ERR_FAILURE; + } + sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, ret); + _exit(ret); +} + +static int +sesh_sudoedit(int argc, char *argv[]) +{ + int fd_src, fd_dst, i, oflags_dst, post, ret = SESH_ERR_FAILURE; + ssize_t nread, nwritten; + struct stat sb; + struct timeval times[2]; + char buf[BUFSIZ]; + debug_decl(sesh_sudoedit, SUDO_DEBUG_MAIN) - /* Shift argv and make a copy of the command to execute. */ - argv++; - argc--; - cmnd = sudo_estrdup(argv[0]); + if (argc < 3) + debug_return_int(SESH_ERR_FAILURE); + + /* + * We need to know whether we are performing the copy operation + * before or after the editing. Without this we would not know + * which files are temporary and which are the originals. + * post = 0 ... before + * post = 1 ... after + */ + if (strcmp(argv[2], "0") == 0) + post = 0; + else if (strcmp(argv[2], "1") == 0) + post = 1; + else /* invalid value */ + debug_return_int(SESH_ERR_INVALID); + + /* Align argv & argc to the beggining of the file list. */ + argv += 3; + argc -= 3; + + /* no files specified, nothing to do */ + if (argc == 0) + debug_return_int(SESH_SUCCESS); + /* odd number of paths specified */ + if (argc & 1) + debug_return_int(SESH_ERR_BAD_PATHS); + + /* + * Use O_EXCL if we are not in the post editing stage + * so that it's ensured that the temporary files are + * created by us and that we are not opening any symlinks. + */ + oflags_dst = O_WRONLY|O_TRUNC|O_CREAT|(post ? 0 : O_EXCL); + for (i = 0; i < argc - 1; i += 2) { + const char *path_src = argv[i]; + const char *path_dst = argv[i + 1]; + /* + * Try to open the source file for reading. If it + * doesn't exist, that's OK, we'll create an empty + * destination file. + */ + if ((fd_src = open(path_src, O_RDONLY, 0600)) < 0) { + if (errno != ENOENT) { + sudo_warn("%s", path_src); + if (post) { + ret = SESH_ERR_SOME_FILES; + goto nocleanup; + } else + goto cleanup_0; + } + } + + if ((fd_dst = open(path_dst, oflags_dst, post ? 0644 : 0600)) < 0) { + /* error - cleanup */ + sudo_warn("%s", path_dst); + if (post) { + ret = SESH_ERR_SOME_FILES; + goto nocleanup; + } else + goto cleanup_0; + } + + if (fd_src != -1) { + while ((nread = read(fd_src, buf, sizeof(buf))) > 0) { + if ((nwritten = write(fd_dst, buf, nread)) != nread) { + sudo_warn("%s", path_src); + if (post) { + ret = SESH_ERR_SOME_FILES; + goto nocleanup; + } else + goto cleanup_0; + } + } + } + + if (fd_dst != -1) { + if (!post) { + if (fd_src == -1 || fstat(fd_src, &sb) != 0) + memset(&sb, 0, sizeof(sb)); + /* Make mtime on temp file match src. */ + mtim_get(&sb, ×[0]); + times[1].tv_sec = times[0].tv_sec; + times[1].tv_usec = times[0].tv_usec; +#ifdef HAVE_FUTIMES + (void) futimes(fd_dst, times); +#else + (void) utimes(path_dst, times); +#endif + } + close(fd_dst); + } + if (fd_src != -1) + close(fd_src); + fd_dst = fd_src = -1; + } - /* If invoked as a login shell, modify argv[0] accordingly. */ - if (login_shell) { - if ((cp = strrchr(argv[0], '/')) == NULL) - sudo_fatal(U_("unable to run %s as a login shell"), argv[0]); - *cp = '-'; - argv[0] = cp; + ret = SESH_SUCCESS; + if (post) { + /* Remove temporary files (post=1) */ + for (i = 0; i < argc - 1; i += 2) + unlink(argv[i]); } - sudo_execve(cmnd, argv, envp, noexec); - sudo_warn(U_("unable to execute %s"), cmnd); - sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, EXIT_FAILURE); - _exit(EXIT_FAILURE); +nocleanup: + if (fd_dst != -1) + close(fd_dst); + if (fd_src != -1) + close(fd_src); + return(ret); +cleanup_0: + /* Remove temporary files (post=0) */ + for (i = 0; i < argc - 1; i += 2) + unlink(argv[i + 1]); + if (fd_dst != -1) + close(fd_dst); + if (fd_src != -1) + close(fd_src); + return(SESH_ERR_NO_FILES); } diff --git a/src/sudo.c b/src/sudo.c index 2bede4ee0..3538c674b 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -1045,6 +1045,14 @@ run_command(struct command_details *details) break; case CMD_WSTATUS: /* Command ran, exited or was killed. */ + if (WIFEXITED(cstat.val)) + exitcode = WEXITSTATUS(cstat.val); + else if (WIFSIGNALED(cstat.val)) + exitcode = WTERMSIG(cstat.val) | 128; +#ifdef HAVE_SELINUX + if (ISSET(details->flags, CD_SUDOEDIT_COPY)) + break; +#endif sudo_debug_printf(SUDO_DEBUG_DEBUG, "calling policy close with wait status %d", cstat.val); policy_close(&policy_plugin, cstat.val, 0); @@ -1053,10 +1061,6 @@ run_command(struct command_details *details) "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: sudo_warnx(U_("unexpected child termination condition: %d"), cstat.type); diff --git a/src/sudo.h b/src/sudo.h index 91a60cadf..19d34cc87 100644 --- a/src/sudo.h +++ b/src/sudo.h @@ -121,6 +121,7 @@ struct user_details { #define CD_USE_PTY 0x1000 #define CD_SET_UTMP 0x2000 #define CD_EXEC_BG 0x4000 +#define CD_SUDOEDIT_COPY 0x8000 struct preserved_fd { TAILQ_ENTRY(preserved_fd) entries; diff --git a/src/sudo_edit.c b/src/sudo_edit.c index 5d4d59328..39f7f3ecf 100644 --- a/src/sudo_edit.c +++ b/src/sudo_edit.c @@ -48,8 +48,12 @@ #ifdef TIME_WITH_SYS_TIME # include #endif +#ifdef HAVE_SELINUX +# include +#endif #include "sudo.h" +#include "sudo_exec.h" #if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID) @@ -327,15 +331,193 @@ sudo_edit_copy_tfiles(struct command_details *command_details, debug_return_int(errors); } +#ifdef HAVE_SELINUX +static int +selinux_edit_create_tfiles(struct command_details *command_details, + struct tempfile *tf, char * const files[], int nfiles) +{ + char **sesh_args, **sesh_ap; + int i, rc, sesh_nargs; + struct stat sb; + struct command_details saved_command_details; + debug_decl(selinux_edit_create_tfiles, SUDO_DEBUG_EDIT); + + /* Prepare selinux stuff (setexeccon) */ + if (selinux_setup(command_details->selinux_role, + command_details->selinux_type, NULL, -1) != 0) + debug_return_int(-1); + + if (nfiles < 1) + debug_return_int(0); + + /* Construct common args for sesh */ + memcpy(&saved_command_details, command_details, sizeof(struct command_details)); + command_details->command = _PATH_SUDO_SESH; + command_details->flags |= CD_SUDOEDIT_COPY; + + sesh_nargs = 3 + (nfiles * 2) + 1; + sesh_args = sesh_ap = sudo_emallocarray(sesh_nargs, sizeof(char *)); + *sesh_ap++ = "sesh"; + *sesh_ap++ = "-e"; + *sesh_ap++ = "0"; + + for (i = 0; i < nfiles; i++) { + char *tfile, *ofile = files[i]; + int tfd; + *sesh_ap++ = ofile; + tf[i].ofile = ofile; + if (stat(ofile, &sb) == -1) + memset(&sb, 0, sizeof(sb)); /* new file */ + tf[i].osize = sb.st_size; + mtim_get(&sb, &tf[i].omtim); + /* + * The temp file must be created by the sesh helper, + * which uses O_EXCL | O_NOFOLLOW to make this safe. + */ + tfd = sudo_edit_mktemp(ofile, &tfile); + if (tfd == -1) { + sudo_warn("mkstemps"); + sudo_efree(tfile); + sudo_efree(sesh_args); + debug_return_int(-1); + } + /* Helper will re-create temp file with proper security context. */ + close(tfd); + unlink(tfile); + *sesh_ap++ = tfile; + tf[i].tfile = tfile; + } + *sesh_ap = NULL; + + /* Run sesh -e 0 ... */ + command_details->argv = sesh_args; + rc = run_command(command_details); + switch (rc) { + case SESH_SUCCESS: + break; + case SESH_ERR_BAD_PATHS: + sudo_fatalx(_("sesh: internal error: odd number of paths")); + case SESH_ERR_NO_FILES: + sudo_fatalx(_("sesh: unable to create temporary files")); + default: + sudo_fatalx(_("sesh: unknown error %d"), rc); + } + + /* Restore saved command_details. */ + command_details->command = saved_command_details.command; + command_details->flags = saved_command_details.flags; + command_details->argv = saved_command_details.argv; + + /* Chown to user's UID so they can edit the temporary files. */ + for (i = 0; i < nfiles; i++) { + if (chown(tf[i].tfile, user_details.uid, user_details.gid) != 0) { + sudo_warn("unable to chown(%s) to %d:%d for editing", + tf[i].tfile, user_details.uid, user_details.gid); + } + } + + /* Contents of tf will be freed by caller. */ + sudo_efree(sesh_args); + + return (nfiles); +} + +static int +selinux_edit_copy_tfiles(struct command_details *command_details, + struct tempfile *tf, int nfiles, struct timeval *times) +{ + char **sesh_args, **sesh_ap; + int i, rc, sesh_nargs, rval = 1; + struct command_details saved_command_details; + struct timeval tv; + struct stat sb; + debug_decl(selinux_edit_copy_tfiles, SUDO_DEBUG_EDIT) + + /* Prepare selinux stuff (setexeccon) */ + if (selinux_setup(command_details->selinux_role, + command_details->selinux_type, NULL, -1) != 0) + debug_return_int(1); + + if (nfiles < 1) + debug_return_int(0); + + /* Construct common args for sesh */ + memcpy(&saved_command_details, command_details, sizeof(struct command_details)); + command_details->command = _PATH_SUDO_SESH; + command_details->flags |= CD_SUDOEDIT_COPY; + + sesh_nargs = 3 + (nfiles * 2) + 1; + sesh_args = sesh_ap = sudo_emallocarray(sesh_nargs, sizeof(char *)); + *sesh_ap++ = "sesh"; + *sesh_ap++ = "-e"; + *sesh_ap++ = "1"; + + /* Construct args for sesh -e 1 */ + for (i = 0; i < nfiles; i++) { + if (stat(tf[i].tfile, &sb) == 0) { + mtim_get(&sb, &tv); + if (tf[i].osize == sb.st_size && sudo_timevalcmp(&tf[i].omtim, &tv, ==)) { + /* + * If mtime and size match but the user spent no measurable + * time in the editor we can't tell if the file was changed. + */ + if (sudo_timevalcmp(×[0], ×[1], !=)) { + sudo_warnx(U_("%s unchanged"), tf[i].ofile); + unlink(tf[i].tfile); + continue; + } + } + } + *sesh_ap++ = tf[i].tfile; + *sesh_ap++ = tf[i].ofile; + if (chown(tf[i].tfile, command_details->uid, command_details->gid) != 0) { + sudo_warn("unable to chown(%s) back to %d:%d", tf[i].tfile, + command_details->uid, command_details->gid); + } + } + *sesh_ap = NULL; + + if (sesh_ap - sesh_args > 3) { + /* Run sesh -e 1 ... */ + command_details->argv = sesh_args; + rc = run_command(command_details); + switch (rc) { + case SESH_SUCCESS: + rval = 0; + break; + case SESH_ERR_NO_FILES: + sudo_warnx(_("unable to copy temporary files back to their original location")); + sudo_warnx(U_("contents of edit session left in %s"), edit_tmpdir); + break; + case SESH_ERR_SOME_FILES: + sudo_warnx(_("unable to copy some of the temporary files back to their original location")); + sudo_warnx(U_("contents of edit session left in %s"), edit_tmpdir); + break; + default: + sudo_warnx(_("sesh: unknown error %d"), rc); + break; + } + } + + /* Restore saved command_details. */ + command_details->command = saved_command_details.command; + command_details->flags = saved_command_details.flags; + command_details->argv = saved_command_details.argv; + + debug_return_int(rval); +} +#endif /* HAVE_SELINUX */ + /* * Wrapper to allow users to edit privileged files with their own uid. + * Returns 0 on success and 1 on failure. */ int sudo_edit(struct command_details *command_details) { struct command_details saved_command_details; char **nargv = NULL, **ap, **files = NULL; - int i, ac, nargc, rval; + int errors, i, ac, nargc, rval; int editor_argc = 0, nfiles = 0; struct timeval times[2]; struct tempfile *tf = NULL; @@ -372,7 +554,12 @@ sudo_edit(struct command_details *command_details) /* Copy editor files to temporaries. */ tf = sudo_ecalloc(nfiles, sizeof(*tf)); - nfiles = sudo_edit_create_tfiles(command_details, tf, files, nfiles); +#ifdef HAVE_SELINUX + if (ISSET(command_details->flags, CD_RBAC_ENABLED)) + nfiles = selinux_edit_create_tfiles(command_details, tf, files, nfiles); + else +#endif + nfiles = sudo_edit_create_tfiles(command_details, tf, files, nfiles); if (nfiles <= 0) goto cleanup; @@ -415,12 +602,16 @@ sudo_edit(struct command_details *command_details) command_details->argv = saved_command_details.argv; /* Copy contents of temp files to real ones. */ - if (sudo_edit_copy_tfiles(command_details, tf, nfiles, times) != 0) - rval = 1; +#ifdef HAVE_SELINUX + if (ISSET(command_details->flags, CD_RBAC_ENABLED)) + errors = selinux_edit_copy_tfiles(command_details, tf, nfiles, times); + else +#endif + errors = sudo_edit_copy_tfiles(command_details, tf, nfiles, times); sudo_efree(tf); sudo_efree(nargv); - debug_return_int(rval); + debug_return_int(errors ? 1 : rval); cleanup: /* Clean up temp files and return. */ diff --git a/src/sudo_exec.h b/src/sudo_exec.h index 4c09e4649..768a06bd4 100644 --- a/src/sudo_exec.h +++ b/src/sudo_exec.h @@ -58,6 +58,16 @@ #define SAVED_SIGUSR1 11 #define SAVED_SIGUSR2 12 +/* + * Error codes for sesh + */ +#define SESH_SUCCESS 0 /* successful operation */ +#define SESH_ERR_FAILURE 1 /* unspecified error */ +#define SESH_ERR_INVALID 30 /* invalid -e arg value */ +#define SESH_ERR_BAD_PATHS 31 /* odd number of paths */ +#define SESH_ERR_NO_FILES 32 /* copy error, no files copied */ +#define SESH_ERR_SOME_FILES 33 /* copy error, some files copied */ + /* * Symbols shared between exec.c and exec_pty.c */