#include <usergroup.h>
#include <errno.h>
#include <limits.h>
#include <sys/stat.h>
#include <new>
#include <heap.h>
#include <kernel.h>
#include <syscalls.h>
#include <team.h>
#include <thread.h>
#include <thread_types.h>
#include <util/AutoLock.h>
#include <util/ThreadAutoLock.h>
#include <vfs.h>
#include <AutoDeleter.h>
static bool
is_privileged(Team* team)
{
return team->effective_uid == 0;
}
static status_t
common_setresgid(gid_t rgid, gid_t egid, gid_t ssgid, bool setAllIfPrivileged, bool kernel)
{
Team* team = thread_get_current_thread()->team;
TeamLocker teamLocker(team);
bool privileged = kernel || is_privileged(team);
if (ssgid == (gid_t)-1 || !privileged)
ssgid = team->saved_set_gid;
if (rgid == (gid_t)-1) {
rgid = team->real_gid;
} else {
if (setAllIfPrivileged) {
if (privileged) {
team->saved_set_gid = rgid;
team->real_gid = rgid;
team->effective_gid = rgid;
return B_OK;
}
egid = rgid;
rgid = team->real_gid;
} else {
if (!privileged && rgid != team->real_gid
&& rgid != team->effective_gid) {
return EPERM;
}
if (rgid != team->real_gid)
ssgid = rgid;
}
}
if (egid == (gid_t)-1) {
egid = team->effective_gid;
} else {
if (!privileged && egid != team->effective_gid
&& egid != team->real_gid && egid != team->saved_set_gid) {
return EPERM;
}
}
team->real_gid = rgid;
team->effective_gid = egid;
team->saved_set_gid = ssgid;
return B_OK;
}
static status_t
common_setresuid(uid_t ruid, uid_t euid, uid_t ssuid, bool setAllIfPrivileged, bool kernel)
{
Team* team = thread_get_current_thread()->team;
TeamLocker teamLocker(team);
bool privileged = kernel || is_privileged(team);
if (ssuid == (uid_t)-1)
ssuid = team->saved_set_uid;
else {
if (!privileged && ssuid != team->effective_uid
&& ssuid != team->real_uid && ssuid != team->saved_set_uid) {
return EPERM;
}
}
if (ruid == (uid_t)-1) {
ruid = team->real_uid;
} else {
if (setAllIfPrivileged) {
if (privileged) {
team->saved_set_uid = ruid;
team->real_uid = ruid;
team->effective_uid = ruid;
return B_OK;
}
euid = ruid;
ruid = team->real_uid;
} else {
if (!privileged && ruid != team->real_uid
&& ruid != team->effective_uid) {
return EPERM;
}
if (ruid != team->real_uid)
ssuid = ruid;
}
}
if (euid == (uid_t)-1) {
euid = team->effective_uid;
} else {
if (!privileged && euid != team->effective_uid
&& euid != team->real_uid && euid != team->saved_set_uid) {
return EPERM;
}
}
team->real_uid = ruid;
team->effective_uid = euid;
team->saved_set_uid = ssuid;
return B_OK;
}
static ssize_t
common_getgroups(int groupCount, gid_t* groupList, bool kernel)
{
Team* team = thread_get_current_thread()->team;
TeamLocker teamLocker(team);
const gid_t* groups = NULL;
int actualCount = 0;
if (team->supplementary_groups != NULL) {
groups = team->supplementary_groups->groups;
actualCount = team->supplementary_groups->count;
}
if (actualCount == 0) {
groups = &team->effective_gid;
actualCount = 1;
}
if (groupCount == 0)
return actualCount;
if (groupCount < actualCount)
return B_BAD_VALUE;
if (kernel) {
memcpy(groupList, groups, actualCount * sizeof(gid_t));
} else {
if (!IS_USER_ADDRESS(groupList)
|| user_memcpy(groupList, groups,
actualCount * sizeof(gid_t)) != B_OK) {
return B_BAD_ADDRESS;
}
}
return actualCount;
}
static status_t
common_setgroups(int groupCount, const gid_t* groupList, bool kernel)
{
if (groupCount < 0 || groupCount > NGROUPS_MAX)
return B_BAD_VALUE;
BKernel::GroupsArray* newGroups = NULL;
if (groupCount > 0) {
newGroups = (BKernel::GroupsArray*)malloc(sizeof(BKernel::GroupsArray)
+ (sizeof(gid_t) * groupCount));
if (newGroups == NULL)
return B_NO_MEMORY;
new(newGroups) BKernel::GroupsArray;
if (kernel) {
memcpy(newGroups->groups, groupList, sizeof(gid_t) * groupCount);
} else {
if (!IS_USER_ADDRESS(groupList)
|| user_memcpy(newGroups->groups, groupList, sizeof(gid_t) * groupCount) != B_OK) {
free(newGroups);
return B_BAD_ADDRESS;
}
}
newGroups->count = groupCount;
}
Team* team = thread_get_current_thread()->team;
TeamLocker teamLocker(team);
BReference<BKernel::GroupsArray> previous = team->supplementary_groups;
team->supplementary_groups.SetTo(newGroups, true);
teamLocker.Unlock();
return B_OK;
}
void
inherit_parent_user_and_group(Team* team, Team* parent)
{
team->saved_set_uid = parent->saved_set_uid;
team->real_uid = parent->real_uid;
team->effective_uid = parent->effective_uid;
team->saved_set_gid = parent->saved_set_gid;
team->real_gid = parent->real_gid;
team->effective_gid = parent->effective_gid;
team->supplementary_groups = parent->supplementary_groups;
}
status_t
update_set_id_user_and_group(Team* team, const char* file)
{
struct stat st;
status_t status = vfs_read_stat(-1, file, true, &st, false);
if (status != B_OK)
return status;
TeamLocker teamLocker(team);
if ((st.st_mode & S_ISUID) != 0) {
team->saved_set_uid = st.st_uid;
team->effective_uid = st.st_uid;
}
if ((st.st_mode & S_ISGID) != 0) {
team->saved_set_gid = st.st_gid;
team->effective_gid = st.st_gid;
}
return B_OK;
}
bool
is_in_group(Team* team, gid_t gid)
{
TeamLocker teamLocker(team);
if (team->effective_gid == gid)
return true;
if (team->supplementary_groups == NULL)
return false;
for (int i = 0; i < team->supplementary_groups->count; i++) {
if (gid == team->supplementary_groups->groups[i])
return true;
}
return false;
}
status_t
_kern_setresgid(gid_t rgid, gid_t egid, gid_t ssgid, bool setAllIfPrivileged)
{
return common_setresgid(rgid, egid, ssgid, setAllIfPrivileged, true);
}
status_t
_kern_setresuid(uid_t ruid, uid_t euid, uid_t ssuid, bool setAllIfPrivileged)
{
return common_setresuid(ruid, euid, ssuid, setAllIfPrivileged, true);
}
status_t
_kern_getresgid(gid_t *rgid, gid_t *egid, gid_t *ssgid)
{
Team* team = thread_get_current_thread()->team;
if (rgid != NULL)
*rgid = team->real_gid;
if (egid != NULL)
*egid = team->effective_gid;
if (ssgid != NULL)
*ssgid = team->saved_set_gid;
return B_OK;
}
status_t
_kern_getresuid(uid_t *ruid, uid_t *euid, gid_t *ssuid)
{
Team* team = thread_get_current_thread()->team;
if (ruid != NULL)
*ruid = team->real_uid;
if (euid != NULL)
*euid = team->effective_uid;
if (ssuid != NULL)
*ssuid = team->saved_set_uid;
return B_OK;
}
ssize_t
_kern_getgroups(int groupCount, gid_t* groupList)
{
return common_getgroups(groupCount, groupList, true);
}
status_t
_kern_setgroups(int groupCount, const gid_t* groupList)
{
return common_setgroups(groupCount, groupList, true);
}
status_t
_user_setresgid(gid_t rgid, gid_t egid, gid_t ssgid, bool setAllIfPrivileged)
{
return common_setresgid(rgid, egid, ssgid, setAllIfPrivileged, false);
}
status_t
_user_setresuid(uid_t ruid, uid_t euid, uid_t ssuid, bool setAllIfPrivileged)
{
return common_setresuid(ruid, euid, ssuid, setAllIfPrivileged, false);
}
status_t
_user_getresgid(gid_t *rgid, gid_t *egid, gid_t *ssgid)
{
Team* team = thread_get_current_thread()->team;
if (rgid != NULL) {
if (!IS_USER_ADDRESS(rgid)
|| user_memcpy(rgid, &team->real_gid, sizeof(uid_t)) != B_OK) {
return B_BAD_ADDRESS;
}
}
if (egid != NULL) {
if (!IS_USER_ADDRESS(egid)
|| user_memcpy(egid, &team->effective_gid, sizeof(uid_t)) != B_OK) {
return B_BAD_ADDRESS;
}
}
if (ssgid != NULL) {
if (!IS_USER_ADDRESS(ssgid)
|| user_memcpy(ssgid, &team->saved_set_gid, sizeof(uid_t)) != B_OK) {
return B_BAD_ADDRESS;
}
}
return B_OK;
}
status_t
_user_getresuid(uid_t *ruid, uid_t *euid, gid_t *ssuid)
{
Team* team = thread_get_current_thread()->team;
if (ruid != NULL) {
if (!IS_USER_ADDRESS(ruid)
|| user_memcpy(ruid, &team->real_uid, sizeof(uid_t)) != B_OK) {
return B_BAD_ADDRESS;
}
}
if (euid != NULL) {
if (!IS_USER_ADDRESS(euid)
|| user_memcpy(euid, &team->effective_uid, sizeof(uid_t)) != B_OK) {
return B_BAD_ADDRESS;
}
}
if (ssuid != NULL) {
if (!IS_USER_ADDRESS(ssuid)
|| user_memcpy(ssuid, &team->saved_set_uid, sizeof(uid_t)) != B_OK) {
return B_BAD_ADDRESS;
}
}
return B_OK;
}
ssize_t
_user_getgroups(int groupCount, gid_t* groupList)
{
return common_getgroups(groupCount, groupList, false);
}
ssize_t
_user_setgroups(int groupCount, const gid_t* groupList)
{
Team* team = thread_get_current_thread()->team;
if (!is_privileged(team))
return EPERM;
return common_setgroups(groupCount, groupList, false);
}