Compare commits
4 Commits
e2a77bb3da
...
ba3e32917e
Author | SHA1 | Date | |
---|---|---|---|
ba3e32917e | |||
cfb60fad25 | |||
9954fc1658 | |||
a98df9e743 |
105
apps/init.cpp
105
apps/init.cpp
@ -4,9 +4,12 @@
|
||||
#include <luna/Sort.h>
|
||||
#include <luna/String.h>
|
||||
#include <luna/Vector.h>
|
||||
#include <os/ArgumentParser.h>
|
||||
#include <os/Directory.h>
|
||||
#include <os/File.h>
|
||||
#include <os/Process.h>
|
||||
#include <os/Security.h>
|
||||
#include <pwd.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -17,6 +20,8 @@
|
||||
#include <sys/sysmacros.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static bool g_is_system = false;
|
||||
|
||||
FILE* g_init_log = nullptr;
|
||||
|
||||
// Request a successful exit from the system (for tests)
|
||||
@ -40,6 +45,8 @@ struct Service
|
||||
String standard_output;
|
||||
String standard_error;
|
||||
String standard_input;
|
||||
Option<uid_t> user {};
|
||||
Option<gid_t> group {};
|
||||
bool wait { false };
|
||||
Option<pid_t> pid {};
|
||||
};
|
||||
@ -65,6 +72,12 @@ static Result<void> service_child(const Service& service, SharedPtr<os::File> ou
|
||||
if (error) dup2(error->fd(), STDERR_FILENO);
|
||||
if (input) dup2(input->fd(), STDIN_FILENO);
|
||||
|
||||
if (service.user.has_value())
|
||||
{
|
||||
setgid(service.group.value());
|
||||
setuid(service.user.value());
|
||||
}
|
||||
|
||||
if (service.environment.is_empty()) { TRY(os::Process::exec(args[0].view(), args.slice(), false)); }
|
||||
else
|
||||
{
|
||||
@ -214,6 +227,15 @@ static Result<void> load_service(const os::Path& path)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (g_is_system && parts[0].view() == "User")
|
||||
{
|
||||
auto* pw = getpwnam(parts[1].chars());
|
||||
if (!pw) continue;
|
||||
service.user = pw->pw_uid;
|
||||
service.group = pw->pw_gid;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parts[0].view() == "Wait")
|
||||
{
|
||||
if (parts[1].view() == "true" || parts[1].view().to_uint().value_or(0) == 1)
|
||||
@ -247,9 +269,9 @@ static Result<void> load_service(const os::Path& path)
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<void> load_services()
|
||||
static Result<void> load_services(StringView path)
|
||||
{
|
||||
auto dir = TRY(os::Directory::open("/etc/init"));
|
||||
auto dir = TRY(os::Directory::open(path));
|
||||
|
||||
auto services = TRY(dir->list_names(os::Directory::Filter::ParentAndBase));
|
||||
sort(services.begin(), services.end(), String::compare);
|
||||
@ -259,9 +281,9 @@ static Result<void> load_services()
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<void> start_services()
|
||||
static Result<void> start_services(StringView path)
|
||||
{
|
||||
TRY(load_services());
|
||||
TRY(load_services(path));
|
||||
for (auto& service : g_services)
|
||||
{
|
||||
do_log("[init] starting service %s\n", service.name.chars());
|
||||
@ -301,7 +323,7 @@ static void mount_shmfs()
|
||||
if (chmod("/dev/shm", 01777) < 0) exit(255);
|
||||
}
|
||||
|
||||
int main()
|
||||
Result<int> sysinit()
|
||||
{
|
||||
if (getpid() != 1)
|
||||
{
|
||||
@ -309,12 +331,16 @@ int main()
|
||||
return 1;
|
||||
}
|
||||
|
||||
g_is_system = true;
|
||||
|
||||
// Before this point, we don't even have an stdin, stdout and stderr. Set it up now so that child processes (and us)
|
||||
// can print stuff.
|
||||
stdin = fopen("/dev/console", "r");
|
||||
stdout = fopen("/dev/console", "w");
|
||||
stderr = fopen("/dev/console", "w");
|
||||
|
||||
TRY(os::Security::pledge("stdio rpath wpath cpath fattr host mount proc exec signal", nullptr));
|
||||
|
||||
mount_tmpfs();
|
||||
mount_shmfs();
|
||||
|
||||
@ -330,7 +356,11 @@ int main()
|
||||
if (signal(SIGTERM, sigterm_handler) == SIG_ERR) do_log("[init] failed to register handler for SIGTERM\n");
|
||||
if (signal(SIGQUIT, sigquit_handler) == SIG_ERR) do_log("[init] failed to register handler for SIGQUIT\n");
|
||||
|
||||
start_services();
|
||||
TRY(os::Security::pledge("stdio rpath wpath cpath proc exec", nullptr));
|
||||
|
||||
start_services("/etc/init");
|
||||
|
||||
TRY(os::Security::pledge("stdio rpath wpath proc exec", nullptr));
|
||||
|
||||
while (1)
|
||||
{
|
||||
@ -365,3 +395,66 @@ int main()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<int> user_init()
|
||||
{
|
||||
setpgid(0, 0);
|
||||
|
||||
g_init_log = fopen("/dev/uart0", "w");
|
||||
check(g_init_log);
|
||||
setlinebuf(g_init_log);
|
||||
fcntl(fileno(g_init_log), F_SETFD, FD_CLOEXEC);
|
||||
|
||||
TRY(os::Security::pledge("stdio rpath wpath cpath proc exec", nullptr));
|
||||
|
||||
start_services("/etc/user");
|
||||
|
||||
TRY(os::Security::pledge("stdio rpath wpath proc exec", nullptr));
|
||||
|
||||
while (1)
|
||||
{
|
||||
int status;
|
||||
auto rc = os::Process::wait(os::Process::ANY_CHILD, &status);
|
||||
if (rc.has_error()) continue;
|
||||
|
||||
pid_t child = rc.release_value();
|
||||
|
||||
for (auto& service : g_services)
|
||||
{
|
||||
if (service.pid.has_value() && service.pid.value() == child)
|
||||
{
|
||||
if (WIFEXITED(status))
|
||||
{
|
||||
do_log("[init] service %s exited with status %d\n", service.name.chars(), WEXITSTATUS(status));
|
||||
}
|
||||
else
|
||||
{
|
||||
do_log("[init] service %s was terminated by signal %d\n", service.name.chars(), WTERMSIG(status));
|
||||
}
|
||||
|
||||
if (service.restart)
|
||||
{
|
||||
do_log("[init] restarting service %s\n", service.name.chars());
|
||||
|
||||
start_service(service);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<int> luna_main(int argc, char** argv)
|
||||
{
|
||||
bool user;
|
||||
|
||||
os::ArgumentParser parser;
|
||||
parser.add_description("The init system for Luna.");
|
||||
parser.add_system_program_info("init"_sv);
|
||||
parser.add_switch_argument(user, 'u', "user"_sv, "initialize a user session instead of the system");
|
||||
parser.parse(argc, argv);
|
||||
|
||||
if (user) return user_init();
|
||||
return sysinit();
|
||||
}
|
||||
|
@ -74,6 +74,11 @@ Result<u64> sys_execve(Registers* regs, SyscallArgs args)
|
||||
kdbgln("exec: attempting to replace current image with %s", path.chars());
|
||||
#endif
|
||||
|
||||
bool is_setuid = VFS::is_setuid(inode);
|
||||
bool is_setgid = VFS::is_setgid(inode);
|
||||
bool is_secure_environment = is_setgid || is_setuid;
|
||||
if (is_secure_environment && current->execpromises >= 0) return err(EACCES);
|
||||
|
||||
auto loader = TRY(BinaryFormat::create_loader(inode));
|
||||
|
||||
#ifdef EXEC_DEBUG
|
||||
@ -107,8 +112,8 @@ Result<u64> sys_execve(Registers* regs, SyscallArgs args)
|
||||
if (descriptor->flags & O_CLOEXEC) { descriptor = {}; }
|
||||
}
|
||||
|
||||
if (VFS::is_setuid(inode)) current->auth.euid = current->auth.suid = inode->metadata().uid;
|
||||
if (VFS::is_setgid(inode)) current->auth.egid = current->auth.sgid = inode->metadata().gid;
|
||||
if (is_setuid) current->auth.euid = current->auth.suid = inode->metadata().uid;
|
||||
if (is_setgid) current->auth.egid = current->auth.sgid = inode->metadata().gid;
|
||||
|
||||
current->name = path.chars();
|
||||
|
||||
@ -160,8 +165,6 @@ Result<u64> sys_fork(Registers* regs, SyscallArgs)
|
||||
thread->current_directory_path = move(current_directory_path);
|
||||
thread->umask = current->umask;
|
||||
thread->parent = current;
|
||||
// TODO: Should promises be inherited across fork()? We're assuming yes, as they're already reset on exec (unless
|
||||
// execpromises has been set). Couldn't find any suitable documentation from OpenBSD about this.
|
||||
thread->promises = current->promises;
|
||||
thread->execpromises = current->execpromises;
|
||||
|
||||
@ -171,11 +174,7 @@ Result<u64> sys_fork(Registers* regs, SyscallArgs)
|
||||
|
||||
memcpy(&thread->regs, regs, sizeof(*regs));
|
||||
|
||||
for (int i = 0; i < NSIG; i++)
|
||||
{
|
||||
auto sighandler = current->signal_handlers[i].sa_handler;
|
||||
thread->signal_handlers[i] = { .sa_handler = sighandler, .sa_mask = 0, .sa_flags = 0 };
|
||||
}
|
||||
for (int i = 0; i < NSIG; i++) thread->signal_handlers[i] = current->signal_handlers[i];
|
||||
thread->signal_mask = current->signal_mask;
|
||||
|
||||
thread->set_return(0);
|
||||
|
@ -13,6 +13,7 @@ set(SOURCES
|
||||
src/Path.cpp
|
||||
src/Mode.cpp
|
||||
src/Prompt.cpp
|
||||
src/Security.cpp
|
||||
)
|
||||
|
||||
add_library(os ${SOURCES})
|
||||
|
48
libos/include/os/Security.h
Normal file
48
libos/include/os/Security.h
Normal file
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @file Security.h
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Functions to restrict process operations.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <luna/Result.h>
|
||||
|
||||
namespace os
|
||||
{
|
||||
namespace Security
|
||||
{
|
||||
/**
|
||||
* @brief Restrict system operations.
|
||||
*
|
||||
* The pledge() call, borrowed from OpenBSD, is a simple function to sandbox a process effectively.
|
||||
*
|
||||
* Syscalls are divided into a number of categories ("promises"), the following are the ones implemented on
|
||||
* Luna: stdio rpath wpath cpath fattr chown unix tty proc exec prot_exec id mount signal host error
|
||||
*
|
||||
* The way pledge() works is: the process "tells" the kernel which subset of functions it will use, and if it
|
||||
* suddenly uses something it has not promised (probably because the process was hacked, using ROP or something
|
||||
* else) the kernel kills the process immediately with an uncatchable SIGABRT. Alternatively, if the process has
|
||||
* pledged the "error" promise, the call will fail with ENOSYS.
|
||||
*
|
||||
* Pledges are not inherited across exec, although one may specify another set of promises to apply on the next
|
||||
* execve() call. Thus, pledge() is not a way to restrict untrusted programs (unless the "exec" pledge is
|
||||
* removed), but more of a way to protect trusted local programs from vulnerabilities.
|
||||
*
|
||||
* One may call pledge() several times, but only to remove promises, not to add them.
|
||||
*
|
||||
* A typical call to pledge would look like this:
|
||||
*
|
||||
* TRY(os::Security::pledge("stdio rpath wpath unix proc", nullptr));
|
||||
*
|
||||
* @param promises The promises to apply immediately, separated by spaces. If empty, the process may only call
|
||||
* _exit(2). If NULL, the promises are not changed.
|
||||
* @param execpromises The promises to apply on the next call to execve(2), separated by spaces. If empty, the
|
||||
* process may only call _exit(2). If NULL, the execpromises are not changed.
|
||||
* @return Result<void> Whether the operation succeded.
|
||||
*/
|
||||
Result<void> pledge(const char* promises, const char* execpromises);
|
||||
}
|
||||
}
|
22
libos/src/Security.cpp
Normal file
22
libos/src/Security.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @file Security.cpp
|
||||
* @author apio (cloudapio.eu)
|
||||
* @brief Functions to restrict process operations.
|
||||
*
|
||||
* @copyright Copyright (c) 2023, the Luna authors.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <os/Security.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace os::Security
|
||||
{
|
||||
Result<void> pledge(const char* promises, const char* execpromises)
|
||||
{
|
||||
int rc = ::pledge(promises, execpromises);
|
||||
if (rc < 0) return err(errno);
|
||||
return {};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user