From 290946ce80e8bf8e7a8e8a8593ea88f3465f5a3f Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Sun, 30 Mar 2008 00:08:13 +0000 Subject: [PATCH] * Implemented some basics for supplementary groups support: - The kernel stores the group IDs in the team structure. They are correctly inherited on fork() and load_image_etc(). - Implemented getgroups() for real, i.e. it retrieves the groups associated with the process. - Implemented setgroups(), initgroups() and (the BSDish) getgrouplist(). The latter two read the group information from the "group database" /etc/group (if existing). - Change the BIND port config, since we do have getgrouplist() now. * The set-uid feature was broken when the path to the executable was relative, since we used stat(), which, in the kernel, uses the kernel IO context. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@24669 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/posix/unistd.h | 7 +- headers/private/kernel/syscalls.h | 3 +- headers/private/kernel/thread_types.h | 2 + headers/private/kernel/usergroup.h | 3 +- src/kits/network/dns/config.h | 2 +- src/system/kernel/team.cpp | 7 + src/system/kernel/usergroup.cpp | 169 +++++++++----- src/system/libroot/posix/unistd/usergroup.cpp | 210 +++++++++++++++++- 8 files changed, 340 insertions(+), 63 deletions(-) diff --git a/headers/posix/unistd.h b/headers/posix/unistd.h index 8dbd14da9e..b13205b2d1 100644 --- a/headers/posix/unistd.h +++ b/headers/posix/unistd.h @@ -160,7 +160,6 @@ extern gid_t getegid(void); extern uid_t geteuid(void); extern gid_t getgid(void); 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); @@ -169,6 +168,12 @@ 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 int getgrouplist(const char* user, gid_t baseGroup, + gid_t* groupList, int* groupCount); +extern int getgroups(int groupCount, gid_t groupList[]); +extern int initgroups(const char* user, gid_t baseGroup); +extern int setgroups(int groupCount, const gid_t* groupList); + 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 550f7b7f09..2cd34294cf 100644 --- a/headers/private/kernel/syscalls.h +++ b/headers/private/kernel/syscalls.h @@ -116,11 +116,12 @@ extern status_t _kern_get_team_usage_info(team_id team, int32 who, team_usage_i // 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); +extern ssize_t _kern_getgroups(int groupCount, gid_t* groupList); +extern status_t _kern_setgroups(int groupCount, const gid_t* groupList); // signal functions extern status_t _kern_send_signal(pid_t tid, uint sig); diff --git a/headers/private/kernel/thread_types.h b/headers/private/kernel/thread_types.h index d689b74040..567b0d95e5 100644 --- a/headers/private/kernel/thread_types.h +++ b/headers/private/kernel/thread_types.h @@ -196,6 +196,8 @@ struct team { gid_t saved_set_gid; gid_t real_gid; gid_t effective_gid; + gid_t* supplementary_groups; + int supplementary_group_count; }; typedef int32 (*thread_entry_func)(thread_func, void *); diff --git a/headers/private/kernel/usergroup.h b/headers/private/kernel/usergroup.h index 997f66a13f..a166cc19cc 100644 --- a/headers/private/kernel/usergroup.h +++ b/headers/private/kernel/usergroup.h @@ -27,9 +27,10 @@ status_t update_set_id_user_and_group(struct team* team, const char* file); 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); +ssize_t _user_getgroups(int groupCount, gid_t* groupList); +ssize_t _user_setgroups(int groupCount, const gid_t* groupList); #ifdef __cplusplus } // extern "C" diff --git a/src/kits/network/dns/config.h b/src/kits/network/dns/config.h index f171bab3e6..28b3fbd0bc 100644 --- a/src/kits/network/dns/config.h +++ b/src/kits/network/dns/config.h @@ -11,7 +11,7 @@ /* #undef POSIX_GETGRNAM_R */ #define NEED_SETGROUPENT 1 -#define NEED_GETGROUPLIST 1 +/* #undef NEED_GETGROUPLIST */ /* define if prototype for getgrnam_r() is required */ #define NEED_GETGRNAM_R 1 diff --git a/src/system/kernel/team.cpp b/src/system/kernel/team.cpp index 553c8ab64a..606c9058d5 100644 --- a/src/system/kernel/team.cpp +++ b/src/system/kernel/team.cpp @@ -829,6 +829,9 @@ create_team_struct(const char *name, bool kernel) team->flags = 0; team->death_sem = -1; + team->supplementary_groups = NULL; + team->supplementary_group_count = 0; + team->dead_threads_kernel_time = 0; team->dead_threads_user_time = 0; @@ -913,6 +916,8 @@ delete_team_struct(struct team *team) while (job_control_entry* entry = team->dead_children->entries.RemoveHead()) delete entry; + malloc_referenced_release(team->supplementary_groups); + delete team->job_control_entry; // usually already NULL and transferred to the parent delete team->continued_children; @@ -1959,6 +1964,8 @@ team_init(kernel_args *args) sKernelTeam->saved_set_gid = 0; sKernelTeam->real_gid = 0; sKernelTeam->effective_gid = 0; + sKernelTeam->supplementary_groups = NULL; + sKernelTeam->supplementary_group_count = 0; insert_team_into_group(group, sKernelTeam); diff --git a/src/system/kernel/usergroup.cpp b/src/system/kernel/usergroup.cpp index 5bc77ce2c1..ab47d5e337 100644 --- a/src/system/kernel/usergroup.cpp +++ b/src/system/kernel/usergroup.cpp @@ -6,16 +6,19 @@ #include #include +#include #include -#include +#include +#include #include #include #include #include #include #include +#include #include @@ -137,6 +140,81 @@ common_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged, bool kernel) } +ssize_t +common_getgroups(int groupCount, gid_t* groupList, bool kernel) +{ + struct team* team = thread_get_current_thread()->team; + + InterruptsSpinLocker _(team_spinlock); + + const gid_t* groups = team->supplementary_groups; + int actualCount = team->supplementary_group_count; + + // follow the specification and return always at least one group + if (actualCount == 0) { + groups = &team->effective_gid; + actualCount = 1; + } + + // check for sufficient space + if (groupCount < actualCount) + return B_BAD_VALUE; + + // copy + if (kernel) { + memcpy(groupList, groups, actualCount); + } 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; + + gid_t* newGroups = NULL; + if (groupCount > 0) { + newGroups = (gid_t*)malloc_referenced(sizeof(gid_t) * groupCount); + if (newGroups == NULL) + return B_NO_MEMORY; + + if (kernel) { + memcpy(newGroups, groupList, sizeof(gid_t) * groupCount); + } else { + if (!IS_USER_ADDRESS(groupList) + || user_memcpy(newGroups, groupList, + sizeof(gid_t) * groupCount) != B_OK) { + free(newGroups); + return B_BAD_ADDRESS; + } + } + } + + InterruptsSpinLocker locker(team_spinlock); + + struct team* team = thread_get_current_thread()->team; + + gid_t* toFree = team->supplementary_groups; + team->supplementary_groups = newGroups; + team->supplementary_group_count = groupCount; + + locker.Unlock(); + + malloc_referenced_release(toFree); + + return B_OK; +} + + // #pragma mark - Kernel Private @@ -151,6 +229,10 @@ inherit_parent_user_and_group(struct team* team, struct team* parent) team->saved_set_gid = parent->saved_set_gid; team->real_gid = parent->real_gid; team->effective_gid = parent->effective_gid; + + malloc_referenced_acquire(parent->supplementary_groups); + team->supplementary_groups = parent->supplementary_groups; + team->supplementary_group_count = parent->supplementary_group_count; } @@ -158,8 +240,9 @@ status_t update_set_id_user_and_group(struct team* team, const char* file) { struct stat st; - if (stat(file, &st) < 0) - return errno; + status_t status = vfs_read_stat(-1, file, true, &st, false); + if (status != B_OK) + return status; InterruptsSpinLocker _(team_spinlock); @@ -195,19 +278,6 @@ _kern_getuid(bool effective) } -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) { @@ -222,6 +292,20 @@ _kern_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged) } +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); +} + + // #pragma mark - Syscalls @@ -243,42 +327,6 @@ _user_getuid(bool effective) } -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) { @@ -291,3 +339,20 @@ _user_setreuid(uid_t ruid, uid_t euid, bool setAllIfPrivileged) { return common_setreuid(ruid, euid, setAllIfPrivileged, false); } + + +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) +{ + if (!is_privileged(thread_get_current_thread()->team)) + return EPERM; + + return common_setgroups(groupCount, groupList, false); +} diff --git a/src/system/libroot/posix/unistd/usergroup.cpp b/src/system/libroot/posix/unistd/usergroup.cpp index 5964bf34cb..cf076ba65a 100644 --- a/src/system/libroot/posix/unistd/usergroup.cpp +++ b/src/system/libroot/posix/unistd/usergroup.cpp @@ -7,8 +7,12 @@ #include +#include #include +#include +#include #include +#include #include #include #include @@ -27,6 +31,134 @@ set_errno_if_necessary(const T& result) } +class FileLineReader { +public: + FileLineReader(int fd) + : fFD(fd), + fSize(0), + fOffset(0) + { + } + + char* NextLine() + { + char* eol; + if (fOffset >= fSize + || (eol = strchr(fBuffer + fOffset, '\n')) == NULL) { + _ReadBuffer(); + if (fOffset >= fSize) + return NULL; + + eol = strchr(fBuffer + fOffset, '\n'); + if (eol == NULL) + eol = fBuffer + fSize; + } + + char* result = fBuffer + fOffset; + *eol = '\0'; + fOffset = eol + 1 - fBuffer; + return result; + } + + char* NextNonEmptyLine() + { + while (char* line = NextLine()) { + while (*line != '\0' && isspace(*line)) + line++; + + if (*line != '\0' && *line != '#') + return line; + } + + return NULL; + } + +private: + void _ReadBuffer() + { + // catch special cases: full buffer or already done with the file + if (fSize == LINE_MAX || fFD < 0) + return; + + // move buffered bytes to the beginning of the buffer + int leftBytes = 0; + if (fOffset < fSize) { + leftBytes = fSize - fOffset; + memmove(fBuffer, fBuffer + fOffset, leftBytes); + } + + fOffset = 0; + fSize = leftBytes; + + // read + ssize_t bytesRead = read(fFD, fBuffer + leftBytes, + LINE_MAX - leftBytes); + if (bytesRead > 0) + fSize += bytesRead; + else + fFD = -1; + + // null-terminate + fBuffer[fSize] = '\0'; + } + +private: + int fFD; + char fBuffer[LINE_MAX + 1]; + int fSize; + int fOffset; +}; + + +class Tokenizer { +public: + Tokenizer(char* string) + : fString(string) + { + } + + char* NextToken(char separator) + { + if (fString == NULL) + return NULL; + + char* token = fString; + fString = strchr(fString, separator); + if (fString != NULL) { + *fString = '\0'; + fString++; + } + + return token; + } + + char* NextTrimmedToken(char separator) + { + char* token = NextToken(separator); + if (token == NULL) + return NULL; + + // skip spaces at the beginning + while (*token != '\0' && isspace(*token)) + token++; + + // cut off spaces at the end + char* end = token + strlen(token); + while (end != token && isspace(end[-1])) + end--; + *end = '\0'; + + return token; + } + +private: + char* fString; +}; + + +// #pragma mark - + + gid_t getegid(void) { @@ -55,13 +187,6 @@ getuid(void) } -int -getgroups(int groupSize, gid_t groupList[]) -{ - return set_errno_if_necessary(_kern_getgroups(groupSize, groupList)); -} - - int setgid(gid_t gid) { @@ -102,3 +227,74 @@ setreuid(uid_t ruid, uid_t euid) { return set_errno_if_necessary(_kern_setreuid(ruid, euid, false)); } + + +int +getgrouplist(const char* user, gid_t baseGroup, gid_t* groupList, + int* groupCount) +{ + int maxGroupCount = *groupCount; + *groupCount = 0; + + // read group file + int fd = open("/etc/group", O_RDONLY); + FileLineReader reader(fd); + + while (char* line = reader.NextNonEmptyLine()) { + Tokenizer lineTokenizer(line); + lineTokenizer.NextTrimmedToken(':'); // group name + lineTokenizer.NextTrimmedToken(':'); // group password + char* groupID = lineTokenizer.NextTrimmedToken(':'); + + if (groupID == NULL || !isdigit(*groupID)) + continue; + + gid_t gid = atol(groupID); + if (gid == baseGroup) + continue; + + while (char* groupUser = lineTokenizer.NextTrimmedToken(',')) { + if (*groupUser != '\0' && strcmp(groupUser, user) == 0) { + if (*groupCount < maxGroupCount) + groupList[*groupCount] = gid; + ++*groupCount; + } + } + } + + if (fd >= 0) + close(fd); + + // put in the base group + if (*groupCount < maxGroupCount) + groupList[*groupCount] = baseGroup; + ++*groupCount; + + return *groupCount <= maxGroupCount ? *groupCount : -1; +} + + +int +getgroups(int groupCount, gid_t groupList[]) +{ + return set_errno_if_necessary(_kern_getgroups(groupCount, groupList)); +} + + +int +initgroups(const char* user, gid_t baseGroup) +{ + gid_t groups[NGROUPS_MAX + 1]; + int groupCount = NGROUPS_MAX + 1; + if (getgrouplist(user, baseGroup, groups, &groupCount) < 0) + return -1; + + return setgroups(groupCount, groups); +} + + +int +setgroups(int groupCount, const gid_t* groupList) +{ + return set_errno_if_necessary(_kern_setgroups(groupCount, groupList)); +}