serenity/Kernel/Syscalls/profiling.cpp
Liav A 5e062414c1 Kernel: Add support for jails
Our implementation for Jails resembles much of how FreeBSD jails are
working - it's essentially only a matter of using a RefPtr in the
Process class to a Jail object. Then, when we iterate over all processes
in various cases, we could ensure if either the current process is in
jail and therefore should be restricted what is visible in terms of
PID isolation, and also to be able to expose metadata about Jails in
/sys/kernel/jails node (which does not reveal anything to a process
which is in jail).

A lifetime model for the Jail object is currently plain simple - there's
simpy no way to manually delete a Jail object once it was created. Such
feature should be carefully designed to allow safe destruction of a Jail
without the possibility of releasing a process which is in Jail from the
actual jail. Each process which is attached into a Jail cannot leave it
until the end of a Process (i.e. when finalizing a Process). All jails
are kept being referenced in the JailManagement. When a last attached
process is finalized, the Jail is automatically destroyed.
2022-11-05 18:00:58 -06:00

157 lines
5.1 KiB
C++

/*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Coredump.h>
#include <Kernel/PerformanceManager.h>
#include <Kernel/Process.h>
#include <Kernel/Scheduler.h>
#include <Kernel/Time/TimeManagement.h>
namespace Kernel {
bool g_profiling_all_threads;
PerformanceEventBuffer* g_global_perf_events;
u64 g_profiling_event_mask;
// NOTE: event_mask needs to be passed as a pointer as u64
// does not fit into a register on 32bit architectures.
ErrorOr<FlatPtr> Process::sys$profiling_enable(pid_t pid, Userspace<u64 const*> userspace_event_mask)
{
VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this);
TRY(require_no_promises());
auto const event_mask = TRY(copy_typed_from_user(userspace_event_mask));
return profiling_enable(pid, event_mask);
}
// NOTE: This second entrypoint exists to allow the kernel to invoke the syscall to enable boot profiling.
ErrorOr<FlatPtr> Process::profiling_enable(pid_t pid, u64 event_mask)
{
VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this);
if (pid == -1) {
auto credentials = this->credentials();
if (!credentials->is_superuser())
return EPERM;
ScopedCritical critical;
g_profiling_event_mask = PERF_EVENT_PROCESS_CREATE | PERF_EVENT_THREAD_CREATE | PERF_EVENT_MMAP;
if (g_global_perf_events) {
g_global_perf_events->clear();
} else {
g_global_perf_events = PerformanceEventBuffer::try_create_with_size(32 * MiB).leak_ptr();
if (!g_global_perf_events) {
g_profiling_event_mask = 0;
return ENOMEM;
}
}
SpinlockLocker lock(g_profiling_lock);
if (!TimeManagement::the().enable_profile_timer())
return ENOTSUP;
g_profiling_all_threads = true;
PerformanceManager::add_process_created_event(*Scheduler::colonel());
TRY(Process::for_each_in_same_jail([](auto& process) -> ErrorOr<void> {
PerformanceManager::add_process_created_event(process);
return {};
}));
g_profiling_event_mask = event_mask;
return 0;
}
auto process = Process::from_pid_in_same_jail(pid);
if (!process)
return ESRCH;
if (process->is_dead())
return ESRCH;
auto credentials = this->credentials();
auto profile_process_credentials = process->credentials();
if (!credentials->is_superuser() && profile_process_credentials->uid() != credentials->euid())
return EPERM;
SpinlockLocker lock(g_profiling_lock);
g_profiling_event_mask = PERF_EVENT_PROCESS_CREATE | PERF_EVENT_THREAD_CREATE | PERF_EVENT_MMAP;
process->set_profiling(true);
if (!process->create_perf_events_buffer_if_needed()) {
process->set_profiling(false);
return ENOMEM;
}
g_profiling_event_mask = event_mask;
if (!TimeManagement::the().enable_profile_timer()) {
process->set_profiling(false);
return ENOTSUP;
}
return 0;
}
ErrorOr<FlatPtr> Process::sys$profiling_disable(pid_t pid)
{
VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this);
TRY(require_no_promises());
if (pid == -1) {
auto credentials = this->credentials();
if (!credentials->is_superuser())
return EPERM;
ScopedCritical critical;
if (!TimeManagement::the().disable_profile_timer())
return ENOTSUP;
g_profiling_all_threads = false;
return 0;
}
auto process = Process::from_pid_in_same_jail(pid);
if (!process)
return ESRCH;
auto credentials = this->credentials();
auto profile_process_credentials = process->credentials();
if (!credentials->is_superuser() && profile_process_credentials->uid() != credentials->euid())
return EPERM;
SpinlockLocker lock(g_profiling_lock);
if (!process->is_profiling())
return EINVAL;
// FIXME: If we enabled the profile timer and it's not supported, how do we disable it now?
if (!TimeManagement::the().disable_profile_timer())
return ENOTSUP;
process->set_profiling(false);
return 0;
}
ErrorOr<FlatPtr> Process::sys$profiling_free_buffer(pid_t pid)
{
VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this);
TRY(require_no_promises());
if (pid == -1) {
auto credentials = this->credentials();
if (!credentials->is_superuser())
return EPERM;
OwnPtr<PerformanceEventBuffer> perf_events;
{
ScopedCritical critical;
perf_events = adopt_own_if_nonnull(g_global_perf_events);
g_global_perf_events = nullptr;
}
return 0;
}
auto process = Process::from_pid_in_same_jail(pid);
if (!process)
return ESRCH;
auto credentials = this->credentials();
auto profile_process_credentials = process->credentials();
if (!credentials->is_superuser() && profile_process_credentials->uid() != credentials->euid())
return EPERM;
SpinlockLocker lock(g_profiling_lock);
if (process->is_profiling())
return EINVAL;
process->delete_perf_events_buffer();
return 0;
}
}