dir_is_writable: add fallback if changing UIDs fails
The SELinux policy may not allow uid/gid changes which will break the writability checks and cause sudoedit to fail.
This commit is contained in:
198
src/edit_open.c
198
src/edit_open.c
@@ -39,10 +39,12 @@
|
||||
|
||||
#if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID)
|
||||
|
||||
void
|
||||
switch_user(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups)
|
||||
static int
|
||||
switch_user_int(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups,
|
||||
bool nonfatal)
|
||||
{
|
||||
int serrno = errno;
|
||||
int ret = -1;
|
||||
debug_decl(switch_user, SUDO_DEBUG_EDIT);
|
||||
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
||||
@@ -51,72 +53,50 @@ switch_user(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups)
|
||||
|
||||
/* When restoring root, change euid first; otherwise change it last. */
|
||||
if (euid == ROOT_UID) {
|
||||
if (seteuid(ROOT_UID) != 0)
|
||||
if (seteuid(ROOT_UID) != 0) {
|
||||
if (nonfatal)
|
||||
goto done;
|
||||
sudo_fatal("seteuid(ROOT_UID)");
|
||||
}
|
||||
}
|
||||
if (setegid(egid) != 0)
|
||||
if (setegid(egid) != 0) {
|
||||
if (nonfatal)
|
||||
goto done;
|
||||
sudo_fatal("setegid(%d)", (int)egid);
|
||||
}
|
||||
if (ngroups != -1) {
|
||||
if (sudo_setgroups(ngroups, groups) != 0)
|
||||
if (sudo_setgroups(ngroups, groups) != 0) {
|
||||
if (nonfatal)
|
||||
goto done;
|
||||
sudo_fatal("setgroups");
|
||||
}
|
||||
}
|
||||
if (euid != ROOT_UID) {
|
||||
if (seteuid(euid) != 0)
|
||||
if (seteuid(euid) != 0) {
|
||||
if (nonfatal)
|
||||
goto done;
|
||||
sudo_fatal("seteuid(%u)", (unsigned int)euid);
|
||||
}
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
done:
|
||||
errno = serrno;
|
||||
|
||||
debug_return;
|
||||
debug_return_int(ret);
|
||||
}
|
||||
|
||||
#if defined(HAVE_FACCESSAT) && defined(AT_EACCESS)
|
||||
/*
|
||||
* Returns true if the open directory fd is owned or writable by the user.
|
||||
*/
|
||||
int
|
||||
dir_is_writable(int dfd, struct sudo_cred *user_cred, struct sudo_cred *cur_cred)
|
||||
static int
|
||||
switch_user_nonfatal(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups)
|
||||
{
|
||||
struct stat sb;
|
||||
int rc;
|
||||
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 == user_cred->uid) {
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
||||
"user uid %u matches directory uid %u",
|
||||
(unsigned int)user_cred->uid, (unsigned int)sb.st_uid);
|
||||
debug_return_int(true);
|
||||
}
|
||||
|
||||
/* Change uid/gid/groups to invoking user, usually needs root perms. */
|
||||
if (cur_cred->euid != ROOT_UID) {
|
||||
if (seteuid(ROOT_UID) != 0)
|
||||
sudo_fatal("seteuid(ROOT_UID)");
|
||||
}
|
||||
switch_user(user_cred->uid, user_cred->gid, user_cred->ngroups,
|
||||
user_cred->groups);
|
||||
|
||||
/* Access checks are done using the euid/egid and group vector. */
|
||||
rc = faccessat(dfd, ".", W_OK, AT_EACCESS);
|
||||
|
||||
/* Restore uid/gid/groups, may need root perms. */
|
||||
if (user_cred->uid != ROOT_UID) {
|
||||
if (seteuid(ROOT_UID) != 0)
|
||||
sudo_fatal("seteuid(ROOT_UID)");
|
||||
}
|
||||
switch_user(cur_cred->euid, cur_cred->egid, cur_cred->ngroups,
|
||||
cur_cred->groups);
|
||||
|
||||
if (rc == 0)
|
||||
debug_return_int(true);
|
||||
if (errno == EACCES || errno == EROFS)
|
||||
debug_return_int(false);
|
||||
debug_return_int(-1);
|
||||
return switch_user_int(euid, egid, ngroups, groups, true);
|
||||
}
|
||||
#else
|
||||
|
||||
void
|
||||
switch_user(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups)
|
||||
{
|
||||
(void)switch_user_int(euid, egid, ngroups, groups, false);
|
||||
}
|
||||
|
||||
static bool
|
||||
group_matches(gid_t target, struct sudo_cred *cred)
|
||||
{
|
||||
@@ -140,8 +120,97 @@ group_matches(gid_t target, struct sudo_cred *cred)
|
||||
debug_return_bool(false);
|
||||
}
|
||||
|
||||
static bool
|
||||
is_writable(struct sudo_cred *user_cred, struct stat *sb)
|
||||
{
|
||||
debug_decl(is_writable, SUDO_DEBUG_EDIT);
|
||||
|
||||
/* 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, user_cred)) {
|
||||
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);
|
||||
}
|
||||
|
||||
#if defined(HAVE_FACCESSAT) && defined(AT_EACCESS)
|
||||
/*
|
||||
* Returns true if the open directory fd is owned or writable by the user.
|
||||
* Checks whether the open directory dfd is owned or writable by the user.
|
||||
* Returns true if writable, false if not, or -1 on error.
|
||||
*/
|
||||
int
|
||||
dir_is_writable(int dfd, struct sudo_cred *user_cred, struct sudo_cred *cur_cred)
|
||||
{
|
||||
struct stat sb;
|
||||
int rc;
|
||||
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 == user_cred->uid) {
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
||||
"user uid %u matches directory uid %u",
|
||||
(unsigned int)user_cred->uid, (unsigned int)sb.st_uid);
|
||||
debug_return_int(true);
|
||||
}
|
||||
|
||||
/* Change uid/gid/groups to invoking user, usually needs root perms. */
|
||||
if (cur_cred->euid != ROOT_UID) {
|
||||
if (seteuid(ROOT_UID) != 0) {
|
||||
sudo_debug_printf(
|
||||
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
|
||||
"seteuid(ROOT_UID)");
|
||||
goto fallback;
|
||||
}
|
||||
}
|
||||
if (switch_user_nonfatal(user_cred->uid, user_cred->gid, user_cred->ngroups,
|
||||
user_cred->groups) == -1) {
|
||||
sudo_debug_printf(
|
||||
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
|
||||
"unable to switch to user_cred");
|
||||
goto fallback;
|
||||
}
|
||||
|
||||
/* Access checks are done using the euid/egid and group vector. */
|
||||
rc = faccessat(dfd, ".", W_OK, AT_EACCESS);
|
||||
|
||||
/* Restore uid/gid/groups, may need root perms. */
|
||||
if (user_cred->uid != ROOT_UID) {
|
||||
if (seteuid(ROOT_UID) != 0)
|
||||
sudo_fatal("seteuid(ROOT_UID)");
|
||||
}
|
||||
switch_user(cur_cred->euid, cur_cred->egid, cur_cred->ngroups,
|
||||
cur_cred->groups);
|
||||
|
||||
if (rc == 0)
|
||||
debug_return_int(true);
|
||||
if (errno == EACCES || errno == EROFS)
|
||||
debug_return_int(false);
|
||||
debug_return_int(-1);
|
||||
|
||||
fallback:
|
||||
debug_return_int(is_writable(user_cred, &sb));
|
||||
}
|
||||
#endif /* HAVE_FACCESSAT && AT_EACCESS */
|
||||
|
||||
#if !defined(HAVE_FACCESSAT) || !defined(AT_EACCESS)
|
||||
/*
|
||||
* Checks whether the open directory dfd is owned or writable by the user.
|
||||
* Returns true if writable, false if not, or -1 on error.
|
||||
*/
|
||||
int
|
||||
dir_is_writable(int dfd, struct sudo_cred *user_cred, struct sudo_cred *cur_cred)
|
||||
@@ -160,24 +229,7 @@ dir_is_writable(int dfd, struct sudo_cred *user_cred, struct sudo_cred *cur_cred
|
||||
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, user_cred)) {
|
||||
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);
|
||||
debug_return_int(is_writable(user_cred, &sb));
|
||||
}
|
||||
#endif /* HAVE_FACCESSAT && AT_EACCESS */
|
||||
|
||||
|
Reference in New Issue
Block a user