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)
|
#if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID)
|
||||||
|
|
||||||
void
|
static int
|
||||||
switch_user(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups)
|
switch_user_int(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups,
|
||||||
|
bool nonfatal)
|
||||||
{
|
{
|
||||||
int serrno = errno;
|
int serrno = errno;
|
||||||
|
int ret = -1;
|
||||||
debug_decl(switch_user, SUDO_DEBUG_EDIT);
|
debug_decl(switch_user, SUDO_DEBUG_EDIT);
|
||||||
|
|
||||||
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
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. */
|
/* When restoring root, change euid first; otherwise change it last. */
|
||||||
if (euid == ROOT_UID) {
|
if (euid == ROOT_UID) {
|
||||||
if (seteuid(ROOT_UID) != 0)
|
if (seteuid(ROOT_UID) != 0) {
|
||||||
|
if (nonfatal)
|
||||||
|
goto done;
|
||||||
sudo_fatal("seteuid(ROOT_UID)");
|
sudo_fatal("seteuid(ROOT_UID)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (setegid(egid) != 0)
|
if (setegid(egid) != 0) {
|
||||||
|
if (nonfatal)
|
||||||
|
goto done;
|
||||||
sudo_fatal("setegid(%d)", (int)egid);
|
sudo_fatal("setegid(%d)", (int)egid);
|
||||||
|
}
|
||||||
if (ngroups != -1) {
|
if (ngroups != -1) {
|
||||||
if (sudo_setgroups(ngroups, groups) != 0)
|
if (sudo_setgroups(ngroups, groups) != 0) {
|
||||||
|
if (nonfatal)
|
||||||
|
goto done;
|
||||||
sudo_fatal("setgroups");
|
sudo_fatal("setgroups");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (euid != ROOT_UID) {
|
if (euid != ROOT_UID) {
|
||||||
if (seteuid(euid) != 0)
|
if (seteuid(euid) != 0) {
|
||||||
|
if (nonfatal)
|
||||||
|
goto done;
|
||||||
sudo_fatal("seteuid(%u)", (unsigned int)euid);
|
sudo_fatal("seteuid(%u)", (unsigned int)euid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
done:
|
||||||
errno = serrno;
|
errno = serrno;
|
||||||
|
debug_return_int(ret);
|
||||||
debug_return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(HAVE_FACCESSAT) && defined(AT_EACCESS)
|
static int
|
||||||
/*
|
switch_user_nonfatal(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups)
|
||||||
* 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)
|
|
||||||
{
|
{
|
||||||
struct stat sb;
|
return switch_user_int(euid, egid, ngroups, groups, true);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
#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
|
static bool
|
||||||
group_matches(gid_t target, struct sudo_cred *cred)
|
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);
|
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
|
int
|
||||||
dir_is_writable(int dfd, struct sudo_cred *user_cred, struct sudo_cred *cur_cred)
|
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);
|
debug_return_int(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Other writable? */
|
debug_return_int(is_writable(user_cred, &sb));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
#endif /* HAVE_FACCESSAT && AT_EACCESS */
|
#endif /* HAVE_FACCESSAT && AT_EACCESS */
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user