Kernel/LibC: Implement {get,set,init}groups

This allows dropping /etc/group parsing from the kernel :D
This commit is contained in:
Bananymous 2025-08-10 18:19:31 +03:00
parent f41e254e35
commit af0bca74e4
9 changed files with 150 additions and 161 deletions

View File

@ -35,10 +35,8 @@ namespace Kernel
bool has_egid(gid_t) const;
BAN::ErrorOr<void> initialize_supplementary_groups();
private:
BAN::ErrorOr<BAN::String> find_username() const;
BAN::Span<const gid_t> groups() const { return m_supplementary.span(); }
BAN::ErrorOr<void> set_groups(BAN::Span<const gid_t> groups);
private:
uid_t m_ruid, m_euid, m_suid;

View File

@ -95,6 +95,9 @@ namespace Kernel
BAN::ErrorOr<long> sys_getppid() const { return m_parent; }
BAN::ErrorOr<long> sys_getpid() const { return pid(); }
BAN::ErrorOr<long> sys_getgroups(gid_t groups[], size_t count);
BAN::ErrorOr<long> sys_setgroups(const gid_t groups[], size_t count);
BAN::ErrorOr<long> open_inode(VirtualFileSystem::File&&, int flags);
BAN::ErrorOr<void> create_file_or_dir(int fd, const char* path, mode_t mode) const;

View File

@ -1,157 +1,9 @@
#include <kernel/Credentials.h>
#include <kernel/FS/VirtualFileSystem.h>
#include <ctype.h>
#include <fcntl.h>
namespace Kernel
{
static id_t parse_id(BAN::StringView line)
{
id_t id = 0;
for (char c : line)
{
if (!isdigit(c))
return -1;
id = (id * 10) + (c - '0');
}
return id;
};
BAN::ErrorOr<BAN::String> Credentials::find_username() const
{
auto inode = TRY(VirtualFileSystem::get().file_from_absolute_path(*this, "/etc/passwd"_sv, O_RDONLY)).inode;
BAN::String line;
off_t offset = 0;
uint8_t buffer[128];
while (offset < inode->size())
{
size_t nread = TRY(inode->read(offset, { buffer, sizeof(buffer) }));
bool line_done = false;
for (size_t i = 0; i < nread; i++)
{
if (buffer[i] == '\n')
{
TRY(line.append({ (const char*)buffer, i }));
line_done = true;
offset += i + 1;
break;
}
}
if (!line_done)
{
offset += nread;
TRY(line.append({ (const char*)buffer, nread }));
continue;
}
auto parts = TRY(line.sv().split(':', true));
if (parts.size() == 7 && m_euid == parse_id(parts[2]))
{
BAN::String result;
TRY(result.append(parts[0]));
return result;
}
line.clear();
}
auto parts = TRY(line.sv().split(':', true));
if (parts.size() == 7 && m_euid == parse_id(parts[2]))
{
BAN::String result;
TRY(result.append(parts[0]));
return result;
}
return BAN::Error::from_errno(EINVAL);
}
BAN::ErrorOr<void> Credentials::initialize_supplementary_groups()
{
m_supplementary.clear();
auto username = TRY(find_username());
auto file_or_error = VirtualFileSystem::get().file_from_absolute_path(*this, "/etc/group", O_RDONLY);
if (file_or_error.is_error())
{
if (file_or_error.error().get_error_code() == ENOENT)
return {};
return file_or_error.error();
}
auto inode = file_or_error.value().inode;
BAN::String line;
off_t offset = 0;
uint8_t buffer[128];
while (offset < inode->size())
{
size_t nread = TRY(inode->read(offset, { buffer, sizeof(buffer) }));
bool line_done = false;
for (size_t i = 0; i < nread; i++)
{
if (buffer[i] == '\n')
{
TRY(line.append({ (const char*)buffer, i }));
line_done = true;
offset += i + 1;
break;
}
}
if (!line_done)
{
offset += nread;
TRY(line.append({ (const char*)buffer, nread }));
continue;
}
auto parts = TRY(line.sv().split(':', true));
if (parts.size() != 4)
{
line.clear();
continue;
}
auto users = TRY(parts[3].split(','));
for (auto user : users)
{
if (user != username)
continue;
if (gid_t gid = parse_id(parts[2]); gid != -1)
{
TRY(m_supplementary.push_back(gid));
break;
}
}
line.clear();
}
auto parts = TRY(line.sv().split(':', true));
if (parts.size() == 4)
{
auto users = TRY(parts[3].split(','));
for (auto user : users)
{
if (user != username)
continue;
if (gid_t gid = parse_id(parts[2]); gid != -1)
{
TRY(m_supplementary.push_back(gid));
break;
}
}
}
return {};
}
bool Credentials::has_egid(gid_t gid) const
{
if (m_egid == gid)
@ -162,4 +14,13 @@ namespace Kernel
return false;
}
BAN::ErrorOr<void> Credentials::set_groups(BAN::Span<const gid_t> groups)
{
m_supplementary.clear();
TRY(m_supplementary.resize(groups.size()));
for (size_t i = 0; i < groups.size(); i++)
m_supplementary[i] = groups[i];
return {};
}
}

View File

@ -101,7 +101,6 @@ namespace Kernel
BAN::ErrorOr<Process*> Process::create_userspace(const Credentials& credentials, BAN::StringView path, BAN::Span<BAN::StringView> arguments)
{
auto* process = create_process(credentials, 0);
TRY(process->m_credentials.initialize_supplementary_groups());
process->m_working_directory = VirtualFileSystem::get().root_file();
process->m_page_table = BAN::UniqPtr<PageTable>::adopt(MUST(PageTable::create_userspace()));
@ -2919,7 +2918,6 @@ namespace Kernel
m_credentials.set_euid(uid);
m_credentials.set_ruid(uid);
m_credentials.set_suid(uid);
TRY(m_credentials.initialize_supplementary_groups());
return 0;
}
@ -2928,7 +2926,6 @@ namespace Kernel
if (uid == m_credentials.ruid() || uid == m_credentials.suid())
{
m_credentials.set_euid(uid);
TRY(m_credentials.initialize_supplementary_groups());
return 0;
}
@ -2989,7 +2986,6 @@ namespace Kernel
if (uid == m_credentials.ruid() || uid == m_credentials.suid() || m_credentials.is_superuser())
{
m_credentials.set_euid(uid);
TRY(m_credentials.initialize_supplementary_groups());
return 0;
}
@ -3056,8 +3052,6 @@ namespace Kernel
if (euid != -1)
m_credentials.set_euid(euid);
TRY(m_credentials.initialize_supplementary_groups());
return 0;
}
@ -3205,6 +3199,28 @@ namespace Kernel
return BAN::Error::from_errno(error);
}
BAN::ErrorOr<long> Process::sys_getgroups(gid_t groups[], size_t count)
{
LockGuard _(m_process_lock);
TRY(validate_pointer_access(groups, count * sizeof(gid_t), true));
auto current = m_credentials.groups();
if (current.size() > count)
return BAN::Error::from_errno(EINVAL);
for (size_t i = 0; i < current.size(); i++)
groups[i] = current[i];
return current.size();
}
BAN::ErrorOr<long> Process::sys_setgroups(const gid_t groups[], size_t count)
{
LockGuard _(m_process_lock);
TRY(validate_pointer_access(groups, count * sizeof(gid_t), true));
if (!m_credentials.is_superuser())
return BAN::Error::from_errno(EPERM);
TRY(m_credentials.set_groups({ groups, count }));
return 0;
}
BAN::ErrorOr<BAN::String> Process::absolute_path_of(BAN::StringView path) const
{
LockGuard _(m_process_lock);

View File

@ -4,6 +4,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
static FILE* s_grent_fp = nullptr;
static group s_grent_struct;
@ -211,3 +212,106 @@ int getgrnam_r(const char* name, struct group* grp, char* buffer, size_t bufsize
fclose(fp);
return ret;
}
static gid_t* get_user_groups(const char* user)
{
FILE* fp = fopen("/etc/group", "r");
if (fp == nullptr)
return nullptr;
const long initial_len = sysconf(_SC_GETGR_R_SIZE_MAX);
size_t buffer_len = (initial_len == -1) ? 512 : initial_len;
char* buffer = static_cast<char*>(malloc(buffer_len));
if (buffer == nullptr)
{
fclose(fp);
return nullptr;
}
size_t group_count = 0;
gid_t* groups = static_cast<gid_t*>(malloc(sizeof(gid_t)));
if (groups == nullptr)
{
free(buffer);
fclose(fp);
return nullptr;
}
for (;;)
{
struct group result;
struct group* resultp;
int error = getgrent_impl(fp, &result, buffer, buffer_len, &resultp);
if (error == ERANGE)
{
const size_t new_buffer_len = buffer_len * 2;
char* new_buffer = static_cast<char*>(realloc(buffer, new_buffer_len));
if (new_buffer == nullptr)
{
error = ENOMEM;
break;
}
buffer = new_buffer;
buffer_len = new_buffer_len;
continue;
}
if (error != 0)
{
free(buffer);
free(groups);
fclose(fp);
return nullptr;
}
if (resultp == nullptr)
break;
bool contains = false;
for (size_t i = 0; result.gr_mem[i] && !contains; i++)
contains = (strcmp(result.gr_mem[i], user) == 0);
if (!contains)
continue;
gid_t* new_groups = static_cast<gid_t*>(realloc(groups, group_count * sizeof(gid_t)));
if (new_groups == nullptr)
{
free(buffer);
free(groups);
fclose(fp);
return nullptr;
}
groups = new_groups;
groups[group_count++] = result.gr_gid;
}
groups[group_count] = -1;
free(buffer);
fclose(fp);
return groups;
}
int initgroups(const char* user, gid_t group)
{
gid_t* groups = get_user_groups(user);
if (groups == nullptr)
return -1;
size_t group_count = 0;
while (groups[group_count] != -1)
group_count++;
groups[group_count] = group;
int result = setgroups(group_count + 1, groups);
free(groups);
return result;
}
int setgroups(size_t size, const gid_t list[])
{
return syscall(SYS_SETGROUPS, list, size);
}

View File

@ -14,7 +14,7 @@ __BEGIN_DECLS
struct group
{
char* gr_name; /* The name of the group. */
char* gr_passwd;/* The password of the group */
char* gr_passwd; /* The password of the group */
gid_t gr_gid; /* Numerical group ID. */
char** gr_mem; /* Pointer to a null-terminated array of character pointers to member names. */
};
@ -27,6 +27,9 @@ struct group* getgrnam(const char* name);
int getgrnam_r(const char* name, struct group* grp, char* buffer, size_t bufsize, struct group** result);
void setgrent(void);
int initgroups(const char* user, gid_t group);
int setgroups(size_t size, const gid_t list[]);
__END_DECLS
#endif

View File

@ -111,6 +111,8 @@ __BEGIN_DECLS
O(SYS_FLOCK, flock) \
O(SYS_GET_NPROCESSOR, get_nprocessor) \
O(SYS_FUTEX, futex) \
O(SYS_GETGROUPS, getgroups) \
O(SYS_SETGROUPS, setgroups) \
enum Syscall
{

View File

@ -673,8 +673,7 @@ error:
int getgroups(int gidsetsize, gid_t grouplist[])
{
dwarnln("FIXME: getgroups({}, {})", gidsetsize, grouplist);
return 0;
return syscall(SYS_GETGROUPS, grouplist, gidsetsize);
}
pid_t getppid(void)

View File

@ -3,6 +3,7 @@
#include <BAN/Vector.h>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
@ -94,6 +95,8 @@ int main(int argc, char** argv)
printf("Welcome back %s!\n", pwd->pw_name);
if (initgroups(name_buffer, pwd->pw_gid) == -1)
perror("initgroups");
if (setgid(pwd->pw_gid) == -1)
perror("setgid");
if (setuid(pwd->pw_uid) == -1)