Compare commits

..

No commits in common. "ba3e32917e444ca14e0b665908a3c6571e8947a8" and "e2a77bb3da60b81ca83566afcd18f3edb10862fb" have entirely different histories.

5 changed files with 15 additions and 178 deletions

View File

@ -4,12 +4,9 @@
#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>
@ -20,8 +17,6 @@
#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)
@ -45,8 +40,6 @@ 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 {};
};
@ -72,12 +65,6 @@ 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
{
@ -227,15 +214,6 @@ 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)
@ -269,9 +247,9 @@ static Result<void> load_service(const os::Path& path)
return {};
}
static Result<void> load_services(StringView path)
static Result<void> load_services()
{
auto dir = TRY(os::Directory::open(path));
auto dir = TRY(os::Directory::open("/etc/init"));
auto services = TRY(dir->list_names(os::Directory::Filter::ParentAndBase));
sort(services.begin(), services.end(), String::compare);
@ -281,9 +259,9 @@ static Result<void> load_services(StringView path)
return {};
}
static Result<void> start_services(StringView path)
static Result<void> start_services()
{
TRY(load_services(path));
TRY(load_services());
for (auto& service : g_services)
{
do_log("[init] starting service %s\n", service.name.chars());
@ -323,7 +301,7 @@ static void mount_shmfs()
if (chmod("/dev/shm", 01777) < 0) exit(255);
}
Result<int> sysinit()
int main()
{
if (getpid() != 1)
{
@ -331,16 +309,12 @@ Result<int> sysinit()
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();
@ -356,11 +330,7 @@ Result<int> sysinit()
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");
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));
start_services();
while (1)
{
@ -395,66 +365,3 @@ Result<int> sysinit()
}
}
}
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();
}

View File

@ -74,11 +74,6 @@ 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
@ -112,8 +107,8 @@ Result<u64> sys_execve(Registers* regs, SyscallArgs args)
if (descriptor->flags & O_CLOEXEC) { descriptor = {}; }
}
if (is_setuid) current->auth.euid = current->auth.suid = inode->metadata().uid;
if (is_setgid) current->auth.egid = current->auth.sgid = inode->metadata().gid;
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;
current->name = path.chars();
@ -165,6 +160,8 @@ 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;
@ -174,7 +171,11 @@ Result<u64> sys_fork(Registers* regs, SyscallArgs)
memcpy(&thread->regs, regs, sizeof(*regs));
for (int i = 0; i < NSIG; i++) thread->signal_handlers[i] = current->signal_handlers[i];
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 };
}
thread->signal_mask = current->signal_mask;
thread->set_return(0);

View File

@ -13,7 +13,6 @@ set(SOURCES
src/Path.cpp
src/Mode.cpp
src/Prompt.cpp
src/Security.cpp
)
add_library(os ${SOURCES})

View File

@ -1,48 +0,0 @@
/**
* @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);
}
}

View File

@ -1,22 +0,0 @@
/**
* @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 {};
}
}