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:
Todd C. Miller
2021-11-05 12:24:51 -06:00
parent 733fe4bd1a
commit a527d6dfdd

View File

@@ -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);
return switch_user_int(euid, egid, ngroups, groups, 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)");
void
switch_user(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups)
{
(void)switch_user_int(euid, egid, ngroups, groups, false);
}
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);
}
#else
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 */