From: Todd C. Miller Date: Thu, 5 Apr 2018 03:05:59 +0000 (-0600) Subject: Check sudoedit temporary directory for writability before using it. X-Git-Tag: SUDO_1_8_23^2~43 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=5ae557e308d45e8195f6cb086f1cfe5094e0edd5;p=sudo Check sudoedit temporary directory for writability before using it. --- diff --git a/src/sudo_edit.c b/src/sudo_edit.c index 7c7219940..7b01fc44b 100644 --- a/src/sudo_edit.c +++ b/src/sudo_edit.c @@ -55,40 +55,6 @@ struct tempfile { static char edit_tmpdir[MAX(sizeof(_PATH_VARTMP), sizeof(_PATH_TMP))]; -/* - * Find our temporary directory, one of /var/tmp, /usr/tmp, or /tmp - * Returns true on success, else false; - */ -static bool -set_tmpdir(void) -{ - const char *tdir; - struct stat sb; - size_t len; - debug_decl(set_tmpdir, SUDO_DEBUG_EDIT) - - if (stat(_PATH_VARTMP, &sb) == 0 && S_ISDIR(sb.st_mode)) { - tdir = _PATH_VARTMP; - } -#ifdef _PATH_USRTMP - else if (stat(_PATH_USRTMP, &sb) == 0 && S_ISDIR(sb.st_mode)) { - tdir = _PATH_USRTMP; - } -#endif - else { - tdir = _PATH_TMP; - } - len = strlcpy(edit_tmpdir, tdir, sizeof(edit_tmpdir)); - if (len >= sizeof(edit_tmpdir)) { - errno = ENAMETOOLONG; - sudo_warn("%s", tdir); - debug_return_bool(false); - } - while (len > 0 && edit_tmpdir[--len] == '/') - edit_tmpdir[len] = '\0'; - debug_return_bool(true); -} - static void switch_user(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups) { @@ -118,6 +84,145 @@ switch_user(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups) debug_return; } +#ifdef HAVE_FACCESSAT +/* + * Returns true if the open directory fd is writable by the user. + */ +static int +dir_is_writable(int dfd, struct user_details *ud, struct command_details *cd) +{ + debug_decl(dir_is_writable, SUDO_DEBUG_EDIT) + int rc; + + /* Change uid/gid/groups to invoking user, usually needs root perms. */ + if (cd->euid != ROOT_UID) { + if (seteuid(ROOT_UID) != 0) + sudo_fatal("seteuid(ROOT_UID)"); + } + switch_user(ud->uid, ud->gid, ud->ngroups, ud->groups); + + /* Access checks are done using the euid/egid and group vector. */ + rc = faccessat(dfd, ".", W_OK, AT_EACCESS); + + /* Change uid/gid/groups back to target user, may need root perms. */ + if (ud->uid != ROOT_UID) { + if (seteuid(ROOT_UID) != 0) + sudo_fatal("seteuid(ROOT_UID)"); + } + switch_user(cd->euid, cd->egid, cd->ngroups, cd->groups); + + if (rc == 0) + debug_return_int(true); + if (errno == EACCES) + debug_return_int(false); + debug_return_int(-1); +} +#else +static bool +group_matches(gid_t target, gid_t gid, int ngroups, GETGROUPS_T *groups) +{ + int i; + debug_decl(group_matches, SUDO_DEBUG_EDIT) + + if (target == gid) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "user gid %u matches directory gid %u", (unsigned int)gid, + (unsigned int)target); + debug_return_bool(true); + } + for (i = 0; i < ngroups; i++) { + if (target == groups[i]) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "user gid %u matches directory gid %u", (unsigned int)gid, + (unsigned int)target); + debug_return_bool(true); + } + } + debug_return_bool(false); +} + +/* + * Returns true if the open directory fd is writable by the user. + */ +static int +dir_is_writable(int dfd, struct user_details *ud, struct command_details *cd) +{ + struct stat sb; + debug_decl(dir_is_writable, SUDO_DEBUG_EDIT) + + if (fstat(dfd, &sb) == -1) + debug_return_int(-1); + + /* If the user owns the dir we always consider it writable. */ + if (sb.st_uid == ud->uid) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "user uid %u matches directory uid %u", (unsigned int)ud->uid, + (unsigned int)sb.st_uid); + debug_return_int(true); + } + + /* Other writable? */ + if (ISSET(sb.st_mode, S_IWOTH)) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "directory is writable by other"); + debug_return_int(true); + } + + /* Group writable? */ + if (ISSET(sb.st_mode, S_IWGRP)) { + if (group_matches(sb.st_gid, ud->gid, ud->ngroups, ud->groups)) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "directory is writable by one of the user's groups"); + debug_return_int(true); + } + } + + errno = EACCES; + debug_return_int(false); +} +#endif /* HAVE_FACCESSAT */ + +/* + * Find our temporary directory, one of /var/tmp, /usr/tmp, or /tmp + * Returns true on success, else false; + */ +static bool +set_tmpdir(struct command_details *command_details) +{ + const char *tdir = NULL; + const char *tmpdirs[] = { + _PATH_VARTMP, +#ifdef _PATH_USRTMP + _PATH_USRTMP, +#endif + _PATH_TMP + }; + unsigned int i; + size_t len; + int dfd; + debug_decl(set_tmpdir, SUDO_DEBUG_EDIT) + + for (i = 0; tdir == NULL && i < nitems(tmpdirs); i++) { + if ((dfd = open(tmpdirs[i], O_RDONLY)) != -1) { + if (dir_is_writable(dfd, &user_details, command_details) == true) + tdir = tmpdirs[i]; + close(dfd); + } + } + if (tdir == NULL) + sudo_fatalx(U_("no writable temporary directory found")); + + len = strlcpy(edit_tmpdir, tdir, sizeof(edit_tmpdir)); + if (len >= sizeof(edit_tmpdir)) { + errno = ENAMETOOLONG; + sudo_warn("%s", tdir); + debug_return_bool(false); + } + while (len > 0 && edit_tmpdir[--len] == '/') + edit_tmpdir[len] = '\0'; + debug_return_bool(true); +} + /* * Construct a temporary file name for file and return an * open file descriptor. The temporary file name is stored @@ -269,104 +374,6 @@ done: } #endif /* O_NOFOLLOW */ -#ifdef HAVE_FACCESSAT -/* - * Returns true if the open directory fd is writable by the user. - */ -static int -dir_is_writable(int dfd, struct user_details *ud, struct command_details *cd) -{ - debug_decl(dir_is_writable, SUDO_DEBUG_EDIT) - int rc; - - /* Change uid/gid/groups to invoking user, usually needs root perms. */ - if (cd->euid != ROOT_UID) { - if (seteuid(ROOT_UID) != 0) - sudo_fatal("seteuid(ROOT_UID)"); - } - switch_user(ud->uid, ud->gid, ud->ngroups, ud->groups); - - /* Access checks are done using the euid/egid and group vector. */ - rc = faccessat(dfd, ".", W_OK, AT_EACCESS); - - /* Change uid/gid/groups back to target user, may need root perms. */ - if (ud->uid != ROOT_UID) { - if (seteuid(ROOT_UID) != 0) - sudo_fatal("seteuid(ROOT_UID)"); - } - switch_user(cd->euid, cd->egid, cd->ngroups, cd->groups); - - if (rc == 0) - debug_return_int(true); - if (errno == EACCES) - debug_return_int(false); - debug_return_int(-1); -} -#else -static bool -group_matches(gid_t target, gid_t gid, int ngroups, GETGROUPS_T *groups) -{ - int i; - debug_decl(group_matches, SUDO_DEBUG_EDIT) - - if (target == gid) { - sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, - "user gid %u matches directory gid %u", (unsigned int)gid, - (unsigned int)target); - debug_return_bool(true); - } - for (i = 0; i < ngroups; i++) { - if (target == groups[i]) { - sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, - "user gid %u matches directory gid %u", (unsigned int)gid, - (unsigned int)target); - debug_return_bool(true); - } - } - debug_return_bool(false); -} - -/* - * Returns true if the open directory fd is writable by the user. - */ -static int -dir_is_writable(int dfd, struct user_details *ud, struct command_details *cd) -{ - struct stat sb; - debug_decl(dir_is_writable, SUDO_DEBUG_EDIT) - - if (fstat(dfd, &sb) == -1) - debug_return_int(-1); - - /* If the user owns the dir we always consider it writable. */ - if (sb.st_uid == ud->uid) { - sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, - "user uid %u matches directory uid %u", (unsigned int)ud->uid, - (unsigned int)sb.st_uid); - debug_return_int(true); - } - - /* Other writable? */ - if (ISSET(sb.st_mode, S_IWOTH)) { - sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, - "directory is writable by other"); - debug_return_int(true); - } - - /* Group writable? */ - if (ISSET(sb.st_mode, S_IWGRP)) { - if (group_matches(sb.st_gid, ud->gid, ud->ngroups, ud->groups)) { - sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, - "directory is writable by one of the user's groups"); - debug_return_int(true); - } - } - - errno = EACCES; - debug_return_int(false); -} -#endif /* HAVE_FACCESSAT */ - /* * Directory open flags for use with openat(2). * Use O_SEARCH/O_PATH and/or O_DIRECTORY where possible. @@ -942,7 +949,7 @@ sudo_edit(struct command_details *command_details) struct tempfile *tf = NULL; debug_decl(sudo_edit, SUDO_DEBUG_EDIT) - if (!set_tmpdir()) + if (!set_tmpdir(command_details)) goto cleanup; /*