Compare commits

...

5 Commits

Author SHA1 Message Date
73a7d4f2a1
wind+libui: Run wind as a separate user
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-22 21:31:08 +01:00
efeab5699e
su: Allow disabled passwords 2023-11-22 21:31:08 +01:00
1005305d5a
su: Support supplementary groups 2023-11-22 21:31:08 +01:00
8a90db837b
kernel+libc: Add support for supplementary groups (2/2)
Adds system calls for setting and getting groups, along with libc wrappers.
2023-11-22 21:31:07 +01:00
3ad23eab21
kernel: Add support for supplementary groups (1/2)
Adds support for supplementary groups internally in the kernel.
No userspace support.
2023-11-22 18:49:40 +01:00
33 changed files with 325 additions and 88 deletions

View File

@ -1,4 +1,6 @@
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <os/ArgumentParser.h>
#include <pwd.h>
#include <signal.h>
@ -89,6 +91,30 @@ char* getpass()
return buf;
}
Result<void> set_supplementary_groups(const char* name)
{
Vector<gid_t> extra_groups;
setgrent();
group* grp;
while ((grp = getgrent()))
{
for (char** user = grp->gr_mem; *user; user++)
{
if (!strcmp(*user, name))
{
TRY(extra_groups.try_append(grp->gr_gid));
break;
}
}
}
endgrent();
if (setgroups(static_cast<int>(extra_groups.size()), extra_groups.data()) < 0) return err(errno);
return {};
}
Result<int> luna_main(int argc, char** argv)
{
StringView name;
@ -122,6 +148,12 @@ Result<int> luna_main(int argc, char** argv)
{
signal(SIGTTOU, SIG_IGN);
if (!strcmp(entry->pw_passwd, "!"))
{
fprintf(stderr, "%s: %s's password is disabled!\n", argv[0], entry->pw_name);
return 1;
}
char* pass = getpass();
if (!pass) return 1;
@ -134,6 +166,8 @@ Result<int> luna_main(int argc, char** argv)
memset(pass, 0, strlen(pass));
}
TRY(set_supplementary_groups(name.chars()));
setgid(entry->pw_gid);
setuid(entry->pw_uid);

View File

@ -1,3 +1,4 @@
root:!:0:
users:!:1:
users:!:1:selene
wind:!:2:selene
selene:!:1000:

View File

@ -1,2 +1,3 @@
root:toor:0:0:Administrator:/:/usr/bin/sh
wind:!:2:2:Window Manager:/:/usr/bin/init
selene:moon:1000:1000:User:/home/selene:/usr/bin/sh

View File

@ -9,7 +9,7 @@
_e(pivot_root) _e(sigreturn) _e(sigaction) _e(kill) _e(sigprocmask) _e(setpgid) _e(isatty) \
_e(getpgid) _e(socket) _e(bind) _e(connect) _e(listen) _e(accept) _e(poll) _e(msync) \
_e(truncate) _e(ftruncate) _e(utimensat) _e(setitimer) _e(pledge) _e(memstat) \
_e(setsid) _e(getsid)
_e(setsid) _e(getsid) _e(getgroups) _e(setgroups)
enum Syscalls
{

View File

@ -35,9 +35,9 @@ Result<u64> ScriptLoader::load(AddressSpace* space)
auto& interpreter_path = m_interpreter_cmdline[0];
auto* current = Scheduler::current();
auto interpreter =
TRY(VFS::resolve_path(interpreter_path.chars(), current->auth, current->current_directory, true));
if (!VFS::can_execute(interpreter, current->auth)) return err(EACCES);
auto interpreter = TRY(VFS::resolve_path(interpreter_path.chars(), current->auth, &current->extra_groups,
current->current_directory, true));
if (!VFS::can_execute(interpreter, current->auth, &current->extra_groups)) return err(EACCES);
auto loader = TRY(BinaryFormat::create_loader(interpreter, m_recursion_level + 1));
u64 entry = TRY(loader->load(space));

View File

@ -20,7 +20,7 @@ void InitRD::initialize()
static Result<void> vfs_create_dir_if_not_exists(const char* path, mode_t mode)
{
auto rc = VFS::create_directory(path, mode & (mode_t)~S_IFMT, Credentials {});
auto rc = VFS::create_directory(path, mode & (mode_t)~S_IFMT, Credentials {}, nullptr);
if (rc.has_error())
{
if (rc.error() == EEXIST) return {};
@ -37,7 +37,8 @@ Result<void> InitRD::populate_vfs()
{
if (entry.type == TarStream::EntryType::RegularFile)
{
auto file = TRY(VFS::create_file(entry.name.chars(), entry.mode & (mode_t)~S_IFMT, Credentials {}));
auto file =
TRY(VFS::create_file(entry.name.chars(), entry.mode & (mode_t)~S_IFMT, Credentials {}, nullptr));
file->write(entry.data(), 0, entry.size);
}
else if (entry.type == TarStream::EntryType::Directory)

View File

@ -17,8 +17,9 @@ namespace VFS
static constexpr int MAX_SYMLINKS = 8;
Result<SharedPtr<Inode>> resolve_path_impl(const char* path, Credentials auth, SharedPtr<Inode> current_inode,
bool follow_last_symlink, int& symlinks_followed)
Result<SharedPtr<Inode>> resolve_path_impl(const char* path, Credentials auth, const Vector<gid_t>* extra_groups,
SharedPtr<Inode> current_inode, bool follow_last_symlink,
int& symlinks_followed)
{
if (symlinks_followed >= MAX_SYMLINKS) return err(ELOOP);
@ -31,7 +32,7 @@ namespace VFS
const char* section;
while (parser.next().try_set_value(section))
{
if (!can_execute(current_inode, auth)) return err(EACCES);
if (!can_execute(current_inode, auth, extra_groups)) return err(EACCES);
current_inode = TRY(current_inode->find(section));
if (current_inode->type() == VFS::InodeType::Symlink && (follow_last_symlink || parser.has_next()))
@ -45,7 +46,8 @@ namespace VFS
symlink_root = parent_inode;
symlinks_followed++;
current_inode = TRY(resolve_path_impl(link.chars(), auth, symlink_root, true, symlinks_followed));
current_inode =
TRY(resolve_path_impl(link.chars(), auth, extra_groups, symlink_root, true, symlinks_followed));
symlinks_followed--;
}
@ -55,8 +57,8 @@ namespace VFS
return current_inode;
}
Result<SharedPtr<Inode>> resolve_path(const char* path, Credentials auth, SharedPtr<VFS::Inode> working_directory,
bool follow_last_symlink)
Result<SharedPtr<Inode>> resolve_path(const char* path, Credentials auth, const Vector<gid_t>* extra_groups,
SharedPtr<VFS::Inode> working_directory, bool follow_last_symlink)
{
SharedPtr<Inode> current_inode;
@ -66,17 +68,17 @@ namespace VFS
int symlinks_followed = 0;
return resolve_path_impl(path, auth, current_inode, follow_last_symlink, symlinks_followed);
return resolve_path_impl(path, auth, extra_groups, current_inode, follow_last_symlink, symlinks_followed);
}
Result<SharedPtr<Inode>> create_directory(const char* path, mode_t mode, Credentials auth,
SharedPtr<Inode> working_directory)
const Vector<gid_t>* extra_groups, SharedPtr<Inode> working_directory)
{
auto parent_path = TRY(PathParser::dirname(path));
auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory));
auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, extra_groups, working_directory));
if (!can_write(parent_inode, auth)) return err(EACCES);
if (!can_write(parent_inode, auth, extra_groups)) return err(EACCES);
auto child_name = TRY(PathParser::basename(path));
@ -86,13 +88,13 @@ namespace VFS
}
Result<SharedPtr<Inode>> create_file(const char* path, mode_t mode, Credentials auth,
SharedPtr<Inode> working_directory)
const Vector<gid_t>* extra_groups, SharedPtr<Inode> working_directory)
{
auto parent_path = TRY(PathParser::dirname(path));
auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory));
auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, extra_groups, working_directory));
if (!can_write(parent_inode, auth)) return err(EACCES);
if (!can_write(parent_inode, auth, extra_groups)) return err(EACCES);
auto child_name = TRY(PathParser::basename(path));
@ -133,7 +135,8 @@ namespace VFS
return {};
}
bool can_execute(SharedPtr<Inode> inode, Credentials auth)
// FIXME: Check all three permissions even if the UID or GID match.
bool can_execute(SharedPtr<Inode> inode, Credentials auth, const Vector<gid_t>* extra_groups)
{
if (auth.euid == 0) return true;
@ -142,10 +145,19 @@ namespace VFS
if (metadata.uid == auth.euid) { return metadata.mode & S_IXUSR; }
if (metadata.gid == auth.egid) { return metadata.mode & S_IXGRP; }
if (extra_groups)
{
for (gid_t group : *extra_groups)
{
if (metadata.gid == group) return metadata.mode & S_IXGRP;
}
}
return metadata.mode & S_IXOTH;
}
bool can_write(SharedPtr<Inode> inode, Credentials auth)
// FIXME: Check all three permissions even if the UID or GID match.
bool can_write(SharedPtr<Inode> inode, Credentials auth, const Vector<gid_t>* extra_groups)
{
if (auth.euid == 0) return true;
@ -154,10 +166,19 @@ namespace VFS
if (metadata.uid == auth.euid) { return metadata.mode & S_IWUSR; }
if (metadata.gid == auth.egid) { return metadata.mode & S_IWGRP; }
if (extra_groups)
{
for (gid_t group : *extra_groups)
{
if (metadata.gid == group) return metadata.mode & S_IWGRP;
}
}
return metadata.mode & S_IWOTH;
}
bool can_read(SharedPtr<Inode> inode, Credentials auth)
// FIXME: Check all three permissions even if the UID or GID match.
bool can_read(SharedPtr<Inode> inode, Credentials auth, const Vector<gid_t>* extra_groups)
{
if (auth.euid == 0) return true;
@ -166,6 +187,14 @@ namespace VFS
if (metadata.uid == auth.euid) { return metadata.mode & S_IRUSR; }
if (metadata.gid == auth.egid) { return metadata.mode & S_IRGRP; }
if (extra_groups)
{
for (gid_t group : *extra_groups)
{
if (metadata.gid == group) return metadata.mode & S_IRGRP;
}
}
return metadata.mode & S_IROTH;
}
@ -203,7 +232,8 @@ namespace VFS
auto new_root_parent = TRY(PathParser::dirname(new_root));
auto new_root_path = TRY(PathParser::basename(new_root));
auto new_root_parent_inode = TRY(VFS::resolve_path(new_root_parent.chars(), Credentials {}, working_directory));
auto new_root_parent_inode =
TRY(VFS::resolve_path(new_root_parent.chars(), Credentials {}, nullptr, working_directory));
auto new_root_inode = TRY(new_root_parent_inode->find(new_root_path.chars()));
if (new_root_inode->type() != VFS::InodeType::Directory) return err(ENOTDIR);
@ -215,7 +245,7 @@ namespace VFS
kdbgln("vfs: Pivoting root from / to %s, using %s as new root", put_old, new_root);
auto parent_inode = TRY(resolve_path(parent_path.chars(), Credentials {}, working_directory));
auto parent_inode = TRY(resolve_path(parent_path.chars(), Credentials {}, nullptr, working_directory));
auto inode = TRY(parent_inode->find(child.chars()));
if (inode->type() != VFS::InodeType::Directory) return err(ENOTDIR);
@ -236,7 +266,7 @@ namespace VFS
}
Result<void> mount(const char* path, SharedPtr<VFS::FileSystem> fs, Credentials auth,
SharedPtr<VFS::Inode> working_directory)
const Vector<gid_t>* extra_groups, SharedPtr<VFS::Inode> working_directory)
{
auto parent_path = TRY(PathParser::dirname(path));
auto child = TRY(PathParser::basename(path));
@ -245,7 +275,7 @@ namespace VFS
kdbgln("vfs: Mounting filesystem on target %s", path);
#endif
auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory));
auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, extra_groups, working_directory));
auto inode = TRY(parent_inode->find(child.chars()));
if (inode->type() != VFS::InodeType::Directory) return err(ENOTDIR);
@ -260,7 +290,8 @@ namespace VFS
return {};
}
Result<void> umount(const char* path, Credentials auth, SharedPtr<VFS::Inode> working_directory)
Result<void> umount(const char* path, Credentials auth, const Vector<gid_t>* extra_groups,
SharedPtr<VFS::Inode> working_directory)
{
auto parent_path = TRY(PathParser::dirname(path));
auto child = TRY(PathParser::basename(path));
@ -269,7 +300,7 @@ namespace VFS
kinfoln("vfs: Unmounting filesystem on target %s", path);
auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory));
auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, extra_groups, working_directory));
auto inode = TRY(parent_inode->find(child.chars()));
if (!inode->is_mountpoint()) return err(EINVAL);

View File

@ -306,21 +306,23 @@ namespace VFS
virtual ~DeviceInode() = default;
};
Result<SharedPtr<Inode>> resolve_path(const char* path, Credentials auth,
Result<SharedPtr<Inode>> resolve_path(const char* path, Credentials auth, const Vector<gid_t>* extra_groups,
SharedPtr<VFS::Inode> working_directory = {},
bool follow_last_symlink = true);
Result<SharedPtr<Inode>> create_directory(const char* path, mode_t mode, Credentials auth,
const Vector<gid_t>* extra_groups,
SharedPtr<VFS::Inode> working_directory = {});
Result<SharedPtr<Inode>> create_file(const char* path, mode_t mode, Credentials auth,
const Vector<gid_t>* extra_groups,
SharedPtr<VFS::Inode> working_directory = {});
Result<void> validate_filename(StringView name);
bool can_execute(SharedPtr<Inode> inode, Credentials auth);
bool can_read(SharedPtr<Inode> inode, Credentials auth);
bool can_write(SharedPtr<Inode> inode, Credentials auth);
bool can_execute(SharedPtr<Inode> inode, Credentials auth, const Vector<gid_t>* extra_groups);
bool can_read(SharedPtr<Inode> inode, Credentials auth, const Vector<gid_t>* extra_groups);
bool can_write(SharedPtr<Inode> inode, Credentials auth, const Vector<gid_t>* extra_groups);
bool is_setuid(SharedPtr<Inode> inode);
bool is_setgid(SharedPtr<Inode> inode);
bool is_sticky(SharedPtr<Inode> inode);
@ -332,7 +334,8 @@ namespace VFS
Result<void> mount_root(SharedPtr<VFS::FileSystem> fs);
Result<void> pivot_root(const char* new_root, const char* put_old, SharedPtr<VFS::Inode> working_directory);
Result<void> mount(const char* path, SharedPtr<VFS::FileSystem> fs, Credentials auth,
SharedPtr<Inode> working_directory = {});
const Vector<gid_t>* extra_groups, SharedPtr<Inode> working_directory = {});
Result<void> umount(const char* path, Credentials auth, SharedPtr<Inode> working_directory = {});
Result<void> umount(const char* path, Credentials auth, const Vector<gid_t>* extra_groups,
SharedPtr<Inode> working_directory = {});
}

View File

@ -65,8 +65,8 @@ void oom_thread()
mark_critical(BinaryFormat::init(), "Failed to register initial binary formats");
mark_critical(FSRegistry::init(), "Failed to register initial file systems");
auto init =
mark_critical(VFS::resolve_path("/bin/preinit", Credentials {}), "Can't find init in the initial ramfs!");
auto init = mark_critical(VFS::resolve_path("/bin/preinit", Credentials {}, nullptr),
"Can't find init in the initial ramfs!");
auto init_thread = mark_critical(Scheduler::create_init_process(init, "/bin/preinit"),
"Failed to create PID 1 process for init");

View File

@ -52,14 +52,14 @@ Result<usize> UnixSocket::recv(u8* buf, usize length, int) const
return m_data.dequeue_data(buf, length);
}
static Result<void> bind_socket_to_fs(const char* path, Credentials auth, SharedPtr<VFS::Inode> working_directory,
SharedPtr<UnixSocket> socket)
static Result<void> bind_socket_to_fs(const char* path, Credentials auth, const Vector<gid_t>* extra_groups,
SharedPtr<VFS::Inode> working_directory, SharedPtr<UnixSocket> socket)
{
auto parent_path = TRY(PathParser::dirname(path));
auto parent_inode = TRY(VFS::resolve_path(parent_path.chars(), auth, working_directory));
auto parent_inode = TRY(VFS::resolve_path(parent_path.chars(), auth, extra_groups, working_directory));
if (!VFS::can_write(parent_inode, auth)) return err(EACCES);
if (!VFS::can_write(parent_inode, auth, extra_groups)) return err(EACCES);
auto child_name = TRY(PathParser::basename(path));
@ -91,7 +91,8 @@ Result<void> UnixSocket::bind(struct sockaddr* addr, socklen_t addrlen)
m_metadata.uid = current->auth.euid;
m_metadata.gid = current->auth.egid;
auto rc = bind_socket_to_fs(path.chars(), current->auth, current->current_directory, SharedPtr<Socket> { this });
auto rc = bind_socket_to_fs(path.chars(), current->auth, &current->extra_groups, current->current_directory,
SharedPtr<Socket> { this });
if (rc.has_error())
{
if (rc.error() == EEXIST) return err(EADDRINUSE);
@ -123,10 +124,11 @@ Result<void> UnixSocket::connect(Registers* regs, int flags, struct sockaddr* ad
auto* current = Scheduler::current();
auto inode = TRY(VFS::resolve_path(path.chars(), current->auth, current->current_directory));
auto inode =
TRY(VFS::resolve_path(path.chars(), current->auth, &current->extra_groups, current->current_directory));
if (inode->type() != VFS::InodeType::Socket)
return err(ENOTSOCK); // FIXME: POSIX doesn't say what error to return here?
if (!VFS::can_write(inode, current->auth)) return err(EACCES);
if (!VFS::can_write(inode, current->auth, &current->extra_groups)) return err(EACCES);
auto socket = (SharedPtr<UnixSocket>)inode;
if (socket->m_state != State::Listening) return err(ECONNREFUSED);

View File

@ -14,10 +14,10 @@ Result<u64> sys_chdir(Registers*, SyscallArgs args)
if (PathParser::is_absolute(path.view()))
{
SharedPtr<VFS::Inode> inode = TRY(VFS::resolve_path(path.chars(), current->auth));
SharedPtr<VFS::Inode> inode = TRY(VFS::resolve_path(path.chars(), current->auth, &current->extra_groups));
if (inode->type() != VFS::InodeType::Directory) return err(ENOTDIR);
if (!VFS::can_execute(inode, current->auth)) return err(EACCES);
if (!VFS::can_execute(inode, current->auth, &current->extra_groups)) return err(EACCES);
inode->add_handle();
if (current->current_directory) current->current_directory->remove_handle();
@ -29,10 +29,11 @@ Result<u64> sys_chdir(Registers*, SyscallArgs args)
}
else
{
SharedPtr<VFS::Inode> inode = TRY(VFS::resolve_path(path.chars(), current->auth, current->current_directory));
SharedPtr<VFS::Inode> inode =
TRY(VFS::resolve_path(path.chars(), current->auth, &current->extra_groups, current->current_directory));
if (inode->type() != VFS::InodeType::Directory) return err(ENOTDIR);
if (!VFS::can_execute(inode, current->auth)) return err(EACCES);
if (!VFS::can_execute(inode, current->auth, &current->extra_groups)) return err(EACCES);
auto old_wdir = current->current_directory_path.view();

View File

@ -68,9 +68,10 @@ Result<u64> sys_execve(Registers* regs, SyscallArgs args)
TRY(check_pledge(current, Promise::p_exec));
auto inode = TRY(VFS::resolve_path(path.chars(), current->auth, current->current_directory));
auto inode =
TRY(VFS::resolve_path(path.chars(), current->auth, &current->extra_groups, current->current_directory));
if (!VFS::can_execute(inode, current->auth)) return err(EACCES);
if (!VFS::can_execute(inode, current->auth, &current->extra_groups)) return err(EACCES);
#ifdef EXEC_DEBUG
kdbgln("exec: attempting to replace current image with %s", path.chars());
@ -159,6 +160,8 @@ Result<u64> sys_fork(Registers* regs, SyscallArgs)
TRY(check_pledge(current, Promise::p_proc));
auto extra_groups = TRY(current->extra_groups.shallow_copy());
auto guard = make_scope_guard([current] { MMU::switch_page_directory(current->self_directory()); });
memcpy(&current->regs, regs, sizeof(*regs));
@ -183,6 +186,7 @@ Result<u64> sys_fork(Registers* regs, SyscallArgs)
thread->controlling_terminal = current->controlling_terminal;
thread->pgid = current->pgid;
thread->sid = current->sid;
thread->extra_groups = move(extra_groups);
thread->virtual_clock.set_resolution(1'000'000);
thread->profiling_clock.set_resolution(1'000'000);

View File

@ -259,9 +259,10 @@ Result<u64> sys_truncate(Registers*, SyscallArgs args)
auto* current = Scheduler::current();
TRY(check_pledge(current, Promise::p_wpath));
auto inode = TRY(VFS::resolve_path(path.chars(), current->auth, current->current_directory));
auto inode =
TRY(VFS::resolve_path(path.chars(), current->auth, &current->extra_groups, current->current_directory));
if (!VFS::can_write(inode, current->auth)) return err(EACCES);
if (!VFS::can_write(inode, current->auth, &current->extra_groups)) return err(EACCES);
TRY(inode->truncate(length));
@ -308,8 +309,8 @@ Result<u64> sys_utimensat(Registers*, SyscallArgs args)
if (allow_write_access)
{
if (!VFS::can_write(inode, current->auth) && current->auth.euid != inode->metadata().uid &&
current->auth.euid != 0)
if (!VFS::can_write(inode, current->auth, &current->extra_groups) &&
current->auth.euid != inode->metadata().uid && current->auth.euid != 0)
return err(EACCES);
}
else if (current->auth.euid != inode->metadata().uid && current->auth.euid != 0)

View File

@ -237,3 +237,54 @@ Result<u64> sys_fchownat(Registers*, SyscallArgs args)
return 0;
}
Result<u64> sys_getgroups(Registers*, SyscallArgs args)
{
int ngroups = (int)args[0];
gid_t* grouplist = (gid_t*)args[1];
auto* current = Scheduler::current();
TRY(check_pledge(current, Promise::p_stdio));
if (!ngroups) return current->extra_groups.size();
if (ngroups < 0) return err(EINVAL);
if (static_cast<usize>(ngroups) < current->extra_groups.size()) return err(EINVAL);
if (!MemoryManager::copy_to_user(grouplist, current->extra_groups.data(),
current->extra_groups.size() * sizeof(gid_t)))
return err(EFAULT);
return current->extra_groups.size();
}
Result<u64> sys_setgroups(Registers*, SyscallArgs args)
{
int ngroups = (int)args[0];
const gid_t* grouplist = (const gid_t*)args[1];
auto* current = Scheduler::current();
TRY(check_pledge(current, Promise::p_id));
Credentials& auth = current->auth;
if (auth.euid != 0) return err(EPERM);
if (!ngroups)
{
current->extra_groups.clear();
return 0;
}
if (ngroups < 0 || ngroups > 32) return err(EINVAL);
TRY(current->extra_groups.try_reserve(ngroups));
current->extra_groups.mutate([&](gid_t* list, usize) -> usize {
if (MemoryManager::copy_from_user(grouplist, list, ngroups * sizeof(gid_t))) return ngroups;
return current->extra_groups.size();
});
return 0;
}

View File

@ -23,7 +23,7 @@ Result<u64> sys_unlinkat(Registers*, SyscallArgs args)
kinfoln("unlinkat: remove %s from directory %s, dirfd is %d", basename.chars(), dirname.chars(), dirfd);
auto inode = TRY(current->resolve_atfile(dirfd, dirname, false, false));
if (!VFS::can_write(inode, current->auth)) return err(EACCES);
if (!VFS::can_write(inode, current->auth, &current->extra_groups)) return err(EACCES);
auto child = TRY(inode->find(basename.chars()));
if (flags == AT_REMOVEDIR && child->type() != VFS::InodeType::Directory) return err(ENOTDIR);
@ -52,7 +52,7 @@ Result<u64> sys_symlinkat(Registers*, SyscallArgs args)
auto parent_inode = TRY(current->resolve_atfile(dirfd, parent, false, true));
if (!VFS::can_write(parent_inode, current->auth)) return err(EACCES);
if (!VFS::can_write(parent_inode, current->auth, &current->extra_groups)) return err(EACCES);
auto child_name = TRY(PathParser::basename(linkpath.view()));
@ -115,7 +115,7 @@ Result<u64> sys_linkat(Registers*, SyscallArgs args)
if (target->fs() != parent_inode->fs()) return err(EXDEV);
if (!VFS::can_write(parent_inode, current->auth)) return err(EACCES);
if (!VFS::can_write(parent_inode, current->auth, &current->extra_groups)) return err(EACCES);
auto child_name = TRY(PathParser::basename(newpath.view()));

View File

@ -13,8 +13,8 @@ Result<u64> sys_mkdir(Registers*, SyscallArgs args)
Thread* current = Scheduler::current();
TRY(check_pledge(current, Promise::p_cpath));
auto inode =
TRY(VFS::create_directory(path.chars(), mode & ~current->umask, current->auth, current->current_directory));
auto inode = TRY(VFS::create_directory(path.chars(), mode & ~current->umask, current->auth, &current->extra_groups,
current->current_directory));
auto metadata = inode->metadata();
metadata.uid = current->auth.euid;
metadata.gid = current->auth.egid;

View File

@ -19,7 +19,8 @@ Result<u64> sys_mount(Registers*, SyscallArgs args)
if (current->auth.euid != 0) return err(EPERM);
auto get_source = [current, &source]() -> Result<SharedPtr<Device>> {
auto inode = TRY(VFS::resolve_path(source.chars(), current->auth, current->current_directory));
auto inode =
TRY(VFS::resolve_path(source.chars(), current->auth, &current->extra_groups, current->current_directory));
if (inode->type() != VFS::InodeType::BlockDevice) return err(ENOTBLK);
dev_t device_id = inode->metadata().devid;
return TRY(DeviceRegistry::fetch_special_device(luna_dev_major(device_id), luna_dev_minor(device_id)));
@ -40,7 +41,7 @@ Result<u64> sys_mount(Registers*, SyscallArgs args)
fs = TRY(factory(device));
}
TRY(VFS::mount(target.chars(), fs, current->auth, current->current_directory));
TRY(VFS::mount(target.chars(), fs, current->auth, &current->extra_groups, current->current_directory));
return 0;
}
@ -53,7 +54,7 @@ Result<u64> sys_umount(Registers*, SyscallArgs args)
TRY(check_pledge(current, Promise::p_mount));
if (current->auth.euid != 0) return err(EPERM);
TRY(VFS::umount(target.chars(), current->auth, current->current_directory));
TRY(VFS::umount(target.chars(), current->auth, &current->extra_groups, current->current_directory));
return 0;
}

View File

@ -44,7 +44,8 @@ Result<u64> sys_openat(Registers*, SyscallArgs args)
{
if (error == ENOENT && (flags & O_CREAT) && !path.is_empty())
{
inode = TRY(VFS::create_file(path.chars(), mode & ~current->umask, current->auth, parent_inode));
inode = TRY(VFS::create_file(path.chars(), mode & ~current->umask, current->auth, &current->extra_groups,
parent_inode));
// FIXME: Pass these in create_file().
auto metadata = inode->metadata();
metadata.uid = current->auth.euid;
@ -58,8 +59,8 @@ Result<u64> sys_openat(Registers*, SyscallArgs args)
return err(EEXIST);
else
{
if ((flags & O_RDONLY) && !VFS::can_read(inode, current->auth)) return err(EACCES);
if ((flags & O_WRONLY) && !VFS::can_write(inode, current->auth)) return err(EACCES);
if ((flags & O_RDONLY) && !VFS::can_read(inode, current->auth, &current->extra_groups)) return err(EACCES);
if ((flags & O_WRONLY) && !VFS::can_write(inode, current->auth, &current->extra_groups)) return err(EACCES);
}
inode = TRY(inode->open());

View File

@ -81,9 +81,9 @@ Result<u64> sys_faccessat(Registers*, SyscallArgs args)
auto inode = TRY(current->resolve_atfile(dirfd, path, false, true));
if ((amode & R_OK) && !VFS::can_read(inode, creds)) return err(EACCES);
if ((amode & W_OK) && !VFS::can_write(inode, creds)) return err(EACCES);
if ((amode & X_OK) && !VFS::can_execute(inode, creds)) return err(EACCES);
if ((amode & R_OK) && !VFS::can_read(inode, creds, &current->extra_groups)) return err(EACCES);
if ((amode & W_OK) && !VFS::can_write(inode, creds, &current->extra_groups)) return err(EACCES);
if ((amode & X_OK) && !VFS::can_execute(inode, creds, &current->extra_groups)) return err(EACCES);
// Either all checks succeeded, or amode == F_OK and the file exists, since resolve_atfile() would have failed
// otherwise.

View File

@ -156,6 +156,7 @@ namespace Scheduler
thread->pgid = 1;
thread->cmdline = name;
thread->auth = Credentials { .uid = 0, .euid = 0, .suid = 0, .gid = 0, .egid = 0, .sgid = 0 };
thread->extra_groups = {};
Vector<String> args;
auto name_string = TRY(String::from_cstring(name));

View File

@ -64,10 +64,11 @@ Result<SharedPtr<VFS::Inode>> Thread::resolve_atfile(int dirfd, const String& pa
if (parent_inode) *parent_inode = this->current_directory;
if (PathParser::is_absolute(path.view()))
return VFS::resolve_path(path.chars(), this->auth, {}, follow_last_symlink);
return VFS::resolve_path(path.chars(), this->auth, &this->extra_groups, {}, follow_last_symlink);
if (dirfd == AT_FDCWD)
return VFS::resolve_path(path.chars(), this->auth, this->current_directory, follow_last_symlink);
return VFS::resolve_path(path.chars(), this->auth, &this->extra_groups, this->current_directory,
follow_last_symlink);
auto descriptor = TRY(resolve_fd(dirfd));
@ -75,7 +76,7 @@ Result<SharedPtr<VFS::Inode>> Thread::resolve_atfile(int dirfd, const String& pa
if (path.is_empty() && allow_empty_path) return descriptor->inode();
return VFS::resolve_path(path.chars(), this->auth, descriptor->inode(), follow_last_symlink);
return VFS::resolve_path(path.chars(), this->auth, &this->extra_groups, descriptor->inode(), follow_last_symlink);
}
[[noreturn]] void Thread::exit_and_signal_parent(int _status)

View File

@ -54,6 +54,7 @@ struct Thread : public LinkedListNode<Thread>
pid_t sid { 0 };
Credentials auth;
Vector<gid_t> extra_groups;
u64 user_ticks_self = 0;
u64 kernel_ticks_self = 0;

View File

@ -33,6 +33,9 @@ extern "C"
/* End group file parsing. */
void endgrent(void);
/* Set this process's list of supplementary groups. */
int setgroups(int size, const gid_t* list);
#ifdef __cplusplus
}
#endif

View File

@ -205,6 +205,9 @@ extern "C"
/* Restrict system operations. */
int pledge(const char* promises, const char* execpromises);
/* Get this process's list of supplementary groups. */
int getgroups(int size, gid_t* list);
#ifdef __cplusplus
}
#endif

View File

@ -5,6 +5,8 @@
#include <luna/Vector.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>
static struct group grp;
static FILE* f { nullptr };
@ -112,4 +114,10 @@ extern "C"
f = nullptr;
}
}
int setgroups(int size, const gid_t* list)
{
long rc = syscall(SYS_setgroups, size, list);
__errno_return(rc, int);
}
}

View File

@ -545,4 +545,10 @@ extern "C"
long rc = syscall(SYS_setsid);
__errno_return(rc, pid_t);
}
int getgroups(int size, gid_t* list)
{
long rc = syscall(SYS_getgroups, size, list);
__errno_return(rc, int);
}
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <luna/Alloc.h>
#include <luna/CString.h>
#include <luna/Common.h>
#include <luna/Result.h>
#include <luna/Slice.h>
#include <luna/Types.h>
@ -245,6 +246,12 @@ template <typename T> class Vector
return other;
}
template <typename Callback> void mutate(Callback callback)
{
usize size = callback(m_data, m_capacity);
m_size = min(m_capacity, size);
}
private:
T* m_data { nullptr };
usize m_capacity { 0 };
@ -252,7 +259,7 @@ template <typename T> class Vector
Result<void> resize(usize new_capacity)
{
if (new_capacity < m_capacity) return {};
if (new_capacity <= m_capacity) return {};
const usize new_byte_capacity = new_capacity * sizeof(T);

View File

@ -19,7 +19,7 @@ namespace os::SharedMemory
{
Result<u8*> create(StringView path, usize size)
{
int fd = shm_open(path.chars(), O_RDWR | O_CREAT | O_EXCL, 0600);
int fd = shm_open(path.chars(), O_RDWR | O_CREAT | O_EXCL, 0660);
if (fd < 0)
{
int olderr = errno;
@ -57,7 +57,7 @@ namespace os::SharedMemory
Result<u8*> adopt(StringView path, usize size, bool delete_fs)
{
int fd = shm_open(path.chars(), O_RDWR, 0600);
int fd = shm_open(path.chars(), O_RDWR, 0660);
if (delete_fs) shm_unlink(path.chars());
if (fd < 0) return err(errno);

View File

@ -19,6 +19,7 @@ namespace ui
{
IPC_ENUM_SERVER(ui),
CREATE_WINDOW_ID,
REMOVE_SHM_ID,
SET_WINDOW_TITLE_ID,
INVALIDATE_ID,
CLOSE_WINDOW_ID,
@ -41,6 +42,13 @@ namespace ui
WindowType type;
};
struct RemoveSharedMemoryRequest
{
static constexpr u8 ID = REMOVE_SHM_ID;
int window;
};
struct SetWindowTitleRequest
{
static constexpr u8 ID = SET_WINDOW_TITLE_ID;

View File

@ -8,6 +8,7 @@
*/
#include <luna/String.h>
#include <os/File.h>
#include <os/SharedMemory.h>
#include <sys/mman.h>
#include <ui/App.h>
@ -27,13 +28,17 @@ namespace ui
auto path = COPY_IPC_STRING(response.shm_path);
u32* pixels = (u32*)TRY(os::SharedMemory::adopt(path.view(), rect.height * rect.width * 4));
u32* pixels = (u32*)TRY(os::SharedMemory::adopt(path.view(), rect.height * rect.width * 4, false));
window->m_canvas = ui::Canvas { rect.width, rect.height, rect.width, (u8*)pixels };
window->m_id = response.window;
Window* p = window.ptr();
ui::RemoveSharedMemoryRequest shm_request;
shm_request.window = response.window;
os::IPC::send_async(App::the().client(), shm_request);
App::the().register_window(move(window), {});
return p;

View File

@ -5,6 +5,7 @@
#include <luna/String.h>
#include <os/File.h>
#include <os/SharedMemory.h>
#include <sys/mman.h>
#include <time.h>
#define TRY_OR_IPC_ERROR(expr) \
@ -94,10 +95,23 @@ static Result<void> handle_create_window_message(Client& client)
ui::CreateWindowResponse response;
response.window = id;
SET_IPC_STRING(response.shm_path, shm_path.chars());
window->shm_path = move(shm_path);
os::IPC::send_async(client.conn, response);
return {};
}
static Result<void> handle_remove_shm_message(Client& client)
{
ui::RemoveSharedMemoryRequest request;
READ_MESSAGE(request);
CHECK_WINDOW_ID(request);
shm_unlink(client.windows[request.window]->shm_path.chars());
return {};
}
static Result<void> handle_set_window_title_message(Client& client)
{
ui::SetWindowTitleRequest request;
@ -162,6 +176,7 @@ namespace wind
switch (id)
{
case ui::CREATE_WINDOW_ID: return handle_create_window_message(client);
case ui::REMOVE_SHM_ID: return handle_remove_shm_message(client);
case ui::SET_WINDOW_TITLE_ID: return handle_set_window_title_message(client);
case ui::INVALIDATE_ID: return handle_invalidate_message(client);
case ui::CLOSE_WINDOW_ID: return handle_close_window_message(client);

View File

@ -16,6 +16,7 @@ struct Window : public LinkedListNode<Window>
ui::Rect contents;
u32* pixels;
String name;
String shm_path;
bool dirty { false };
Client* client;
int id;

View File

@ -6,6 +6,7 @@
#include "Screen.h"
#include "Window.h"
#include <errno.h>
#include <grp.h>
#include <moon/Keyboard.h>
#include <os/ArgumentParser.h>
#include <os/File.h>
@ -17,6 +18,7 @@
#include <string.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
@ -52,6 +54,31 @@ static void debug(const Vector<OwnedPtr<Client>>& clients)
os::println("--- wind: END DEBUG OUTPUT ---");
}
Result<void> set_supplementary_groups(const char* name)
{
Vector<gid_t> extra_groups;
setgrent();
group* grp;
while ((grp = getgrent()))
{
for (char** user = grp->gr_mem; *user; user++)
{
if (!strcmp(*user, name))
{
os::println("Adding supplementary group: %d", grp->gr_gid);
TRY(extra_groups.try_append(grp->gr_gid));
break;
}
}
}
endgrent();
if (setgroups(static_cast<int>(extra_groups.size()), extra_groups.data()) < 0) return err(errno);
return {};
}
Result<int> luna_main(int argc, char** argv)
{
srand((unsigned)time(NULL));
@ -95,25 +122,44 @@ Result<int> luna_main(int argc, char** argv)
close(fd);
}
setegid(2);
seteuid(2);
if (setsid() < 0) perror("setsid");
mode_t mask = umask(0002);
auto server = TRY(os::LocalServer::create(socket_path, false));
TRY(server->listen(20));
umask(mask);
seteuid(0);
clearenv();
pid_t child = TRY(os::Process::fork());
if (!child)
{
if (!user.is_empty())
{
auto* pwd = getpwnam(user.chars());
if (pwd)
{
TRY(set_supplementary_groups(user.chars()));
setgid(pwd->pw_gid);
setuid(pwd->pw_uid);
}
}
if (setsid() < 0) perror("setsid");
auto server = TRY(os::LocalServer::create(socket_path, false));
TRY(server->listen(20));
StringView args[] = { "/usr/bin/init"_sv, "--user"_sv };
TRY(os::Process::spawn("/usr/bin/init"_sv, Slice<StringView> { args, 2 }, false));
TRY(os::Process::exec("/usr/bin/init"_sv, Slice<StringView> { args, 2 }, false));
}
umask(0002);
setuid(2);
setgid(2);
ui::Color background = ui::Color::from_rgb(0x10, 0x10, 0x10);