diff --git a/headers/posix/unistd.h b/headers/posix/unistd.h index 7b63ce00ec..8dbd14da9e 100644 --- a/headers/posix/unistd.h +++ b/headers/posix/unistd.h @@ -159,13 +159,15 @@ extern pid_t setpgrp(void); extern gid_t getegid(void); extern uid_t geteuid(void); extern gid_t getgid(void); -extern int getgroups(int groupSize, gid_t groupList[]); extern uid_t getuid(void); +extern int getgroups(int groupSize, gid_t groupList[]); extern int setgid(gid_t gid); extern int setuid(uid_t uid); extern int setegid(gid_t gid); extern int seteuid(uid_t uid); +extern int setregid(gid_t rgid, gid_t egid); +extern int setreuid(uid_t ruid, uid_t euid); extern char *getlogin(void); extern int getlogin_r(char *name, size_t nameSize); diff --git a/headers/private/kernel/syscalls.h b/headers/private/kernel/syscalls.h index 71d5375683..550f7b7f09 100644 --- a/headers/private/kernel/syscalls.h +++ b/headers/private/kernel/syscalls.h @@ -113,6 +113,15 @@ extern status_t _kern_get_team_info(team_id id, team_info *info); extern status_t _kern_get_next_team_info(int32 *cookie, team_info *info); extern status_t _kern_get_team_usage_info(team_id team, int32 who, team_usage_info *info, size_t size); +// user/group functions +extern gid_t _kern_getgid(bool effective); +extern uid_t _kern_getuid(bool effective); +extern ssize_t _kern_getgroups(int groupSize, gid_t* groupList); +extern status_t _kern_setregid(gid_t rgid, gid_t egid, + bool setAllIfPrivileged); +extern status_t _kern_setreuid(uid_t ruid, uid_t euid, + bool setAllIfPrivileged); + // signal functions extern status_t _kern_send_signal(pid_t tid, uint sig); extern status_t _kern_sigprocmask(int how, const sigset_t *set, diff --git a/headers/private/kernel/thread_types.h b/headers/private/kernel/thread_types.h index 78c02652cc..d689b74040 100644 --- a/headers/private/kernel/thread_types.h +++ b/headers/private/kernel/thread_types.h @@ -189,6 +189,13 @@ struct team { bigtime_t dead_threads_kernel_time; bigtime_t dead_threads_user_time; + + uid_t saved_set_uid; + uid_t real_uid; + uid_t effective_uid; + gid_t saved_set_gid; + gid_t real_gid; + gid_t effective_gid; }; typedef int32 (*thread_entry_func)(thread_func, void *); diff --git a/headers/private/kernel/usergroup.h b/headers/private/kernel/usergroup.h new file mode 100644 index 0000000000..997f66a13f --- /dev/null +++ b/headers/private/kernel/usergroup.h @@ -0,0 +1,39 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef _KERNEL_USERGROUP_H +#define _KERNEL_USERGROUP_H + +#include + +#include + + +struct team; + + +#ifdef __cplusplus +extern "C" { +#endif + +// kernel private functions + +void inherit_parent_user_and_group(struct team* team, + struct team* parent); +status_t update_set_id_user_and_group(struct team* team, const char* file); + +// syscalls + +gid_t _user_getgid(bool effective); +uid_t _user_getuid(bool effective); +ssize_t _user_getgroups(int groupSize, gid_t* groupList); +status_t _user_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged); +status_t _user_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged); + +#ifdef __cplusplus +} // extern "C" +#endif + + +#endif // _KERNEL_USERGROUP_H diff --git a/src/system/kernel/Jamfile b/src/system/kernel/Jamfile index 13fe475fa6..7af0be79fe 100644 --- a/src/system/kernel/Jamfile +++ b/src/system/kernel/Jamfile @@ -42,6 +42,7 @@ KernelMergeObject kernel_core.o : team.cpp thread.cpp timer.c + usergroup.cpp wait_for_objects.cpp : $(TARGET_KERNEL_PIC_CCFLAGS) diff --git a/src/system/kernel/lib/Jamfile b/src/system/kernel/lib/Jamfile index e2e0f87b91..b37f0ecd6b 100644 --- a/src/system/kernel/lib/Jamfile +++ b/src/system/kernel/lib/Jamfile @@ -80,7 +80,7 @@ KernelMergeObject kernel_posix.o : read.c sync.c truncate.c - usergroup.c + usergroup.cpp write.c # string memchr.c diff --git a/src/system/kernel/syscalls.cpp b/src/system/kernel/syscalls.cpp index 4046ef13dd..a9afb5941e 100644 --- a/src/system/kernel/syscalls.cpp +++ b/src/system/kernel/syscalls.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include diff --git a/src/system/kernel/team.cpp b/src/system/kernel/team.cpp index e30acf4b82..553c8ab64a 100644 --- a/src/system/kernel/team.cpp +++ b/src/system/kernel/team.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -1171,6 +1172,11 @@ load_image_etc(int32 argCount, char * const *args, int32 envCount, team->loading_info = &loadingInfo; } + // Inherit the parent's user/group, but also check the executable's + // set-user/group-id permission + inherit_parent_user_and_group(team, parent); + update_set_id_user_and_group(team, args[0]); + state = disable_interrupts(); GRAB_TEAM_LOCK(); @@ -1377,6 +1383,10 @@ exec_team(const char *path, int32 argCount, char * const *args, atomic_or(&team->flags, TEAM_FLAG_EXEC_DONE); + // Update user/group according to the executable's set-user/group-id + // permission. + update_set_id_user_and_group(team, path); + status = team_create_thread_start(teamArgs); // this one usually doesn't return... @@ -1449,6 +1459,9 @@ fork_team(void) strlcpy(team->args, parentTeam->args, sizeof(team->args)); + // Inherit the parent's user/group. + inherit_parent_user_and_group(team, parentTeam); + state = disable_interrupts(); GRAB_TEAM_LOCK(); @@ -1940,6 +1953,13 @@ team_init(kernel_args *args) strcpy(sKernelTeam->args, sKernelTeam->name); sKernelTeam->state = TEAM_STATE_NORMAL; + sKernelTeam->saved_set_uid = 0; + sKernelTeam->real_uid = 0; + sKernelTeam->effective_uid = 0; + sKernelTeam->saved_set_gid = 0; + sKernelTeam->real_gid = 0; + sKernelTeam->effective_gid = 0; + insert_team_into_group(group, sKernelTeam); sKernelTeam->io_context = vfs_new_io_context(NULL); diff --git a/src/system/kernel/usergroup.cpp b/src/system/kernel/usergroup.cpp new file mode 100644 index 0000000000..5bc77ce2c1 --- /dev/null +++ b/src/system/kernel/usergroup.cpp @@ -0,0 +1,293 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + + +// #pragma mark - Implementation Private + + +static bool +is_privileged(struct team* team) +{ + // currently only the root user is privileged + return team->effective_uid == 0; +} + + +static status_t +common_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged, bool kernel) +{ + struct team* team = thread_get_current_thread()->team; + + InterruptsSpinLocker _(team_spinlock); + + bool privileged = kernel || is_privileged(team); + + // real gid + if (rgid == (gid_t)-1) { + rgid = team->real_gid; + } else { + if (setAllIfPrivileged) { + // setgid() semantics: If privileged set both, real, effective and + // saved set-gid, otherwise set the effective gid. + if (privileged) { + team->saved_set_gid = rgid; + team->real_gid = rgid; + team->effective_gid = rgid; + return B_OK; + } + + // not privileged -- set only the effective gid + egid = rgid; + rgid = team->real_gid; + } else { + // setregid() semantics: set the real gid, if allowed to + if (!privileged && rgid != team->real_gid) + return EPERM; + } + } + + // effective gid + 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; + } + } + + // Getting here means all checks were successful -- set the gids. + team->real_gid = rgid; + team->effective_gid = egid; + + return B_OK; +} + + +static status_t +common_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged, bool kernel) +{ + struct team* team = thread_get_current_thread()->team; + + InterruptsSpinLocker _(team_spinlock); + + bool privileged = kernel || is_privileged(team); + + // real uid + if (ruid == (uid_t)-1) { + ruid = team->real_uid; + } else { + if (setAllIfPrivileged) { + // setuid() semantics: If privileged set both, real, effective and + // saved set-uid, otherwise set the effective uid. + if (privileged) { + team->saved_set_uid = ruid; + team->real_uid = ruid; + team->effective_uid = ruid; + return B_OK; + } + + // not privileged -- set only the effective uid + euid = ruid; + ruid = team->real_uid; + } else { + // setreuid() semantics: set the real uid, if allowed to + // Note: We allow setting the real uid to the effective uid. This + // is unspecified by the specs, but is common practice. + if (!privileged && ruid != team->real_uid + && ruid != team->effective_uid) { + return EPERM; + } + } + } + + // effective uid + 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; + } + } + + // Getting here means all checks were successful -- set the uids. + team->real_uid = ruid; + team->effective_uid = euid; + + return B_OK; +} + + +// #pragma mark - Kernel Private + + +void +inherit_parent_user_and_group(struct team* team, struct team* parent) +{ + InterruptsSpinLocker _(team_spinlock); + + 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; +} + + +status_t +update_set_id_user_and_group(struct team* team, const char* file) +{ + struct stat st; + if (stat(file, &st) < 0) + return errno; + + InterruptsSpinLocker _(team_spinlock); + + 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; +} + + +gid_t +_kern_getgid(bool effective) +{ + struct team* team = thread_get_current_thread()->team; + + return effective ? team->effective_gid : team->real_gid; +} + + +uid_t +_kern_getuid(bool effective) +{ + struct team* team = thread_get_current_thread()->team; + + return effective ? team->effective_uid : team->real_uid; +} + + +ssize_t +_kern_getgroups(int groupSize, gid_t* groupList) +{ + // TODO: Implement proper supplementary group support! + // For now only return the effective group. + + if (groupSize > 0) + groupList[0] = getegid(); + + return 1; +} + + +status_t +_kern_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged) +{ + return common_setregid(rgid, egid, setAllIfPrivileged, true); +} + + +status_t +_kern_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged) +{ + return common_setreuid(ruid, euid, setAllIfPrivileged, true); +} + + +// #pragma mark - Syscalls + + +gid_t +_user_getgid(bool effective) +{ + struct team* team = thread_get_current_thread()->team; + + return effective ? team->effective_gid : team->real_gid; +} + + +uid_t +_user_getuid(bool effective) +{ + struct team* team = thread_get_current_thread()->team; + + return effective ? team->effective_uid : team->real_uid; +} + + +ssize_t +_user_getgroups(int groupSize, gid_t* userGroupList) +{ + gid_t* groupList = NULL; + + if (groupSize < 0) + return B_BAD_VALUE; + if (groupSize > NGROUPS_MAX + 1) + groupSize = NGROUPS_MAX + 1; + + if (groupSize > 0) { + if (userGroupList == NULL || !IS_USER_ADDRESS(userGroupList)) + return B_BAD_VALUE; + + groupList = new(nothrow) gid_t[groupSize]; + if (groupList == NULL) + return B_NO_MEMORY; + } + + ArrayDeleter _(groupList); + + ssize_t result = _kern_getgroups(groupSize, groupList); + if (result < 0) + return result; + + if (groupSize > 0) { + if (user_memcpy(userGroupList, groupList, sizeof(gid_t) * result) + != B_OK) { + return B_BAD_ADDRESS; + } + } + + return result; +} + + +status_t +_user_setregid(gid_t rgid, gid_t egid, bool setAllIfPrivileged) +{ + return common_setregid(rgid, egid, setAllIfPrivileged, false); +} + + +status_t +_user_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged) +{ + return common_setreuid(ruid, euid, setAllIfPrivileged, false); +} diff --git a/src/system/libroot/posix/unistd/Jamfile b/src/system/libroot/posix/unistd/Jamfile index cb74474b4a..a208288feb 100644 --- a/src/system/libroot/posix/unistd/Jamfile +++ b/src/system/libroot/posix/unistd/Jamfile @@ -35,7 +35,7 @@ MergeObject posix_unistd.o : truncate.c ttyname.c ualarm.c - usergroup.c + usergroup.cpp usleep.c write.c ; diff --git a/src/system/libroot/posix/unistd/usergroup.c b/src/system/libroot/posix/unistd/usergroup.c deleted file mode 100644 index 4d3dd86511..0000000000 --- a/src/system/libroot/posix/unistd/usergroup.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2002-2006, Axel Dörfler, axeld@pinc-software.de. All rights reserved. - * Distributed under the terms of the MIT License. - */ - - -#include - -#include -#include -#include -#include -#include - - -// ToDo: implement the user/group functions for real! - - -gid_t -getegid(void) -{ - return 0; -} - - -uid_t -geteuid(void) -{ - return 0; -} - - -gid_t -getgid(void) -{ - return 0; -} - - -int -getgroups(int groupSize, gid_t groupList[]) -{ - return 0; -} - - -uid_t -getuid(void) -{ - return 0; -} - - -int -setgid(gid_t gid) -{ - if (gid == 0) - return 0; - - return EPERM; -} - - -int -setuid(uid_t uid) -{ - if (uid == 0) - return 0; - - return EPERM; -} - - -int -setegid(gid_t gid) -{ - if (gid == 0) - return 0; - - return EPERM; -} - - -int -seteuid(uid_t uid) -{ - if (uid == 0) - return 0; - - return EPERM; -} - diff --git a/src/system/libroot/posix/unistd/usergroup.cpp b/src/system/libroot/posix/unistd/usergroup.cpp new file mode 100644 index 0000000000..5964bf34cb --- /dev/null +++ b/src/system/libroot/posix/unistd/usergroup.cpp @@ -0,0 +1,104 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. All rights reserved. + * Copyright 2002-2006, Axel Dörfler, axeld@pinc-software.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ + + +#include + +#include +#include +#include +#include +#include + + +template +static inline T +set_errno_if_necessary(const T& result) +{ + if (result < 0) { + errno = result; + return -1; + } + + return result; +} + + +gid_t +getegid(void) +{ + return _kern_getgid(true); +} + + +uid_t +geteuid(void) +{ + return _kern_getuid(true); +} + + +gid_t +getgid(void) +{ + return _kern_getgid(false); +} + + +uid_t +getuid(void) +{ + return _kern_getuid(false); +} + + +int +getgroups(int groupSize, gid_t groupList[]) +{ + return set_errno_if_necessary(_kern_getgroups(groupSize, groupList)); +} + + +int +setgid(gid_t gid) +{ + return set_errno_if_necessary(_kern_setregid(gid, (gid_t)-1, true)); +} + + +int +setuid(uid_t uid) +{ + return set_errno_if_necessary(_kern_setreuid(uid, (uid_t)-1, true)); +} + + +int +setegid(gid_t gid) +{ + return set_errno_if_necessary(_kern_setregid((gid_t)-1, gid, false)); +} + + +int +seteuid(uid_t uid) +{ + return set_errno_if_necessary(_kern_setreuid((uid_t)-1, uid, false)); +} + + +int +setregid(gid_t rgid, gid_t egid) +{ + return set_errno_if_necessary(_kern_setregid(rgid, egid, false)); +} + + +int +setreuid(uid_t ruid, uid_t euid) +{ + return set_errno_if_necessary(_kern_setreuid(ruid, euid, false)); +}