ladybird/Kernel/ProcessSpecificExposed.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

342 lines
14 KiB
C++

/*
* Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/JsonArraySerializer.h>
#include <AK/JsonObjectSerializer.h>
#include <AK/JsonValue.h>
#include <Kernel/FileSystem/Custody.h>
#include <Kernel/FileSystem/ProcFS.h>
#include <Kernel/InterruptDisabler.h>
#include <Kernel/KBufferBuilder.h>
#include <Kernel/Memory/AnonymousVMObject.h>
#include <Kernel/Memory/MemoryManager.h>
#include <Kernel/Process.h>
#include <Kernel/ProcessExposed.h>
#include <Kernel/TTY/TTY.h>
namespace Kernel {
ErrorOr<void> Process::procfs_get_thread_stack(ThreadID thread_id, KBufferBuilder& builder) const
{
auto array = TRY(JsonArraySerializer<>::try_create(builder));
auto thread = Thread::from_tid(thread_id);
if (!thread)
return ESRCH;
auto current_process_credentials = Process::current().credentials();
bool show_kernel_addresses = current_process_credentials->is_superuser();
bool kernel_address_added = false;
for (auto address : TRY(Processor::capture_stack_trace(*thread, 1024))) {
if (!show_kernel_addresses && !Memory::is_user_address(VirtualAddress { address })) {
if (kernel_address_added)
continue;
address = 0xdeadc0de;
kernel_address_added = true;
}
TRY(array.add(address));
}
TRY(array.finish());
return {};
}
ErrorOr<void> Process::traverse_stacks_directory(FileSystemID fsid, Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> callback) const
{
TRY(callback({ "."sv, { fsid, SegmentedProcFSIndex::build_segmented_index_for_main_property(pid(), SegmentedProcFSIndex::ProcessSubDirectory::Stacks, SegmentedProcFSIndex::MainProcessProperty::Reserved) }, 0 }));
TRY(callback({ ".."sv, { fsid, m_procfs_traits->component_index() }, 0 }));
return thread_list().with([&](auto& list) -> ErrorOr<void> {
for (auto const& thread : list) {
int tid = thread.tid().value();
InodeIdentifier identifier = { fsid, SegmentedProcFSIndex::build_segmented_index_for_thread_stack(pid(), thread.tid()) };
auto name = TRY(KString::number(tid));
TRY(callback({ name->view(), identifier, 0 }));
}
return {};
});
}
ErrorOr<NonnullLockRefPtr<Inode>> Process::lookup_stacks_directory(ProcFS const& procfs, StringView name) const
{
auto maybe_needle = name.to_uint();
if (!maybe_needle.has_value())
return ENOENT;
auto needle = maybe_needle.release_value();
ErrorOr<NonnullLockRefPtr<ProcFSProcessPropertyInode>> thread_stack_inode { ENOENT };
for_each_thread([&](Thread const& thread) {
int tid = thread.tid().value();
VERIFY(!(tid < 0));
if (needle == (unsigned)tid) {
thread_stack_inode = ProcFSProcessPropertyInode::try_create_for_thread_stack(procfs, thread.tid(), pid());
return IterationDecision::Break;
}
return IterationDecision::Continue;
});
if (thread_stack_inode.is_error())
return thread_stack_inode.release_error();
return thread_stack_inode.release_value();
}
ErrorOr<void> Process::traverse_children_directory(FileSystemID fsid, Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> callback) const
{
TRY(callback({ "."sv, { fsid, SegmentedProcFSIndex::build_segmented_index_for_sub_directory(pid(), SegmentedProcFSIndex::ProcessSubDirectory::Children) }, 0 }));
TRY(callback({ ".."sv, { fsid, m_procfs_traits->component_index() }, 0 }));
return Process::for_each_in_same_jail([&](Process& process) -> ErrorOr<void> {
if (process.ppid() == pid()) {
StringBuilder builder;
builder.appendff("{}", process.pid());
TRY(callback({ builder.string_view(), { fsid, SegmentedProcFSIndex::build_segmented_index_for_children(pid(), process.pid()) }, DT_LNK }));
}
return {};
});
}
ErrorOr<NonnullLockRefPtr<Inode>> Process::lookup_children_directory(ProcFS const& procfs, StringView name) const
{
auto maybe_pid = name.to_uint();
if (!maybe_pid.has_value())
return ENOENT;
auto child_process = Process::from_pid_in_same_jail(*maybe_pid);
if (!child_process || child_process->ppid() != pid())
return ENOENT;
return TRY(ProcFSProcessPropertyInode::try_create_for_child_process_link(procfs, *maybe_pid, pid()));
}
ErrorOr<size_t> Process::procfs_get_child_proccess_link(ProcessID child_pid, KBufferBuilder& builder) const
{
TRY(builder.appendff("../../{}", child_pid.value()));
return builder.length();
}
ErrorOr<size_t> Process::procfs_get_file_description_link(unsigned fd, KBufferBuilder& builder) const
{
auto file_description = TRY(open_file_description(fd));
// Note: These links are not guaranteed to point to actual VFS paths, just like in other kernels.
auto data = TRY(file_description->pseudo_path());
TRY(builder.append(data->view()));
return data->length();
}
ErrorOr<void> Process::traverse_file_descriptions_directory(FileSystemID fsid, Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> callback) const
{
TRY(callback({ "."sv, { fsid, m_procfs_traits->component_index() }, 0 }));
TRY(callback({ ".."sv, { fsid, m_procfs_traits->component_index() }, 0 }));
size_t count = 0;
fds().with_shared([&](auto& fds) {
fds.enumerate([&](auto& file_description_metadata) {
if (!file_description_metadata.is_valid()) {
count++;
return;
}
StringBuilder builder;
builder.appendff("{}", count);
// FIXME: Propagate errors from callback.
(void)callback({ builder.string_view(), { fsid, SegmentedProcFSIndex::build_segmented_index_for_file_description(pid(), count) }, DT_LNK });
count++;
});
});
return {};
}
ErrorOr<NonnullLockRefPtr<Inode>> Process::lookup_file_descriptions_directory(ProcFS const& procfs, StringView name) const
{
auto maybe_index = name.to_uint();
if (!maybe_index.has_value())
return ENOENT;
if (!m_fds.with_shared([&](auto& fds) { return fds.get_if_valid(*maybe_index); }))
return ENOENT;
return TRY(ProcFSProcessPropertyInode::try_create_for_file_description_link(procfs, *maybe_index, pid()));
}
ErrorOr<void> Process::procfs_get_pledge_stats(KBufferBuilder& builder) const
{
auto obj = TRY(JsonObjectSerializer<>::try_create(builder));
#define __ENUMERATE_PLEDGE_PROMISE(x) \
if (has_promised(Pledge::x)) { \
if (!promises_builder.is_empty()) \
TRY(promises_builder.try_append(' ')); \
TRY(promises_builder.try_append(#x##sv)); \
}
if (has_promises()) {
StringBuilder promises_builder;
ENUMERATE_PLEDGE_PROMISES
TRY(obj.add("promises"sv, promises_builder.string_view()));
}
#undef __ENUMERATE_PLEDGE_PROMISE
TRY(obj.finish());
return {};
}
ErrorOr<void> Process::procfs_get_unveil_stats(KBufferBuilder& builder) const
{
auto array = TRY(JsonArraySerializer<>::try_create(builder));
TRY(m_unveil_data.with([&](auto& unveil_data) -> ErrorOr<void> {
TRY(unveil_data.paths.for_each_node_in_tree_order([&](auto const& unveiled_path) -> ErrorOr<IterationDecision> {
if (!unveiled_path.was_explicitly_unveiled())
return IterationDecision::Continue;
auto obj = TRY(array.add_object());
TRY(obj.add("path"sv, unveiled_path.path()));
StringBuilder permissions_builder;
if (unveiled_path.permissions() & UnveilAccess::Read)
permissions_builder.append('r');
if (unveiled_path.permissions() & UnveilAccess::Write)
permissions_builder.append('w');
if (unveiled_path.permissions() & UnveilAccess::Execute)
permissions_builder.append('x');
if (unveiled_path.permissions() & UnveilAccess::CreateOrRemove)
permissions_builder.append('c');
if (unveiled_path.permissions() & UnveilAccess::Browse)
permissions_builder.append('b');
TRY(obj.add("permissions"sv, permissions_builder.string_view()));
TRY(obj.finish());
return IterationDecision::Continue;
}));
return {};
}));
TRY(array.finish());
return {};
}
ErrorOr<void> Process::procfs_get_perf_events(KBufferBuilder& builder) const
{
InterruptDisabler disabler;
if (!perf_events()) {
dbgln("ProcFS: No perf events for {}", pid());
return Error::from_errno(ENOBUFS);
}
return perf_events()->to_json(builder);
}
ErrorOr<void> Process::procfs_get_fds_stats(KBufferBuilder& builder) const
{
auto array = TRY(JsonArraySerializer<>::try_create(builder));
return fds().with_shared([&](auto& fds) -> ErrorOr<void> {
if (fds.open_count() == 0) {
TRY(array.finish());
return {};
}
size_t count = 0;
TRY(fds.try_enumerate([&](auto& file_description_metadata) -> ErrorOr<void> {
if (!file_description_metadata.is_valid()) {
count++;
return {};
}
bool cloexec = file_description_metadata.flags() & FD_CLOEXEC;
LockRefPtr<OpenFileDescription> description = file_description_metadata.description();
auto description_object = TRY(array.add_object());
TRY(description_object.add("fd"sv, count));
// TODO: Better OOM handling.
auto pseudo_path_or_error = description->pseudo_path();
TRY(description_object.add("absolute_path"sv, pseudo_path_or_error.is_error() ? "???"sv : pseudo_path_or_error.value()->view()));
TRY(description_object.add("seekable"sv, description->file().is_seekable()));
TRY(description_object.add("class"sv, description->file().class_name()));
TRY(description_object.add("offset"sv, description->offset()));
TRY(description_object.add("cloexec"sv, cloexec));
TRY(description_object.add("blocking"sv, description->is_blocking()));
TRY(description_object.add("can_read"sv, description->can_read()));
TRY(description_object.add("can_write"sv, description->can_write()));
Inode* inode = description->inode();
if (inode != nullptr) {
auto inode_object = TRY(description_object.add_object("inode"sv));
TRY(inode_object.add("fsid"sv, inode->fsid().value()));
TRY(inode_object.add("index"sv, inode->index().value()));
TRY(inode_object.finish());
}
TRY(description_object.finish());
count++;
return {};
}));
TRY(array.finish());
return {};
});
}
ErrorOr<void> Process::procfs_get_virtual_memory_stats(KBufferBuilder& builder) const
{
auto array = TRY(JsonArraySerializer<>::try_create(builder));
TRY(address_space().with([&](auto& space) -> ErrorOr<void> {
for (auto const& region : space->region_tree().regions()) {
auto current_process_credentials = Process::current().credentials();
if (!region.is_user() && !current_process_credentials->is_superuser())
continue;
auto region_object = TRY(array.add_object());
TRY(region_object.add("readable"sv, region.is_readable()));
TRY(region_object.add("writable"sv, region.is_writable()));
TRY(region_object.add("executable"sv, region.is_executable()));
TRY(region_object.add("stack"sv, region.is_stack()));
TRY(region_object.add("shared"sv, region.is_shared()));
TRY(region_object.add("syscall"sv, region.is_syscall_region()));
TRY(region_object.add("purgeable"sv, region.vmobject().is_anonymous()));
if (region.vmobject().is_anonymous()) {
TRY(region_object.add("volatile"sv, static_cast<Memory::AnonymousVMObject const&>(region.vmobject()).is_volatile()));
}
TRY(region_object.add("cacheable"sv, region.is_cacheable()));
TRY(region_object.add("address"sv, region.vaddr().get()));
TRY(region_object.add("size"sv, region.size()));
TRY(region_object.add("amount_resident"sv, region.amount_resident()));
TRY(region_object.add("amount_dirty"sv, region.amount_dirty()));
TRY(region_object.add("cow_pages"sv, region.cow_pages()));
TRY(region_object.add("name"sv, region.name()));
TRY(region_object.add("vmobject"sv, region.vmobject().class_name()));
StringBuilder pagemap_builder;
for (size_t i = 0; i < region.page_count(); ++i) {
auto page = region.physical_page(i);
if (!page)
pagemap_builder.append('N');
else if (page->is_shared_zero_page() || page->is_lazy_committed_page())
pagemap_builder.append('Z');
else
pagemap_builder.append('P');
}
TRY(region_object.add("pagemap"sv, pagemap_builder.string_view()));
TRY(region_object.finish());
}
return {};
}));
TRY(array.finish());
return {};
}
ErrorOr<void> Process::procfs_get_current_work_directory_link(KBufferBuilder& builder) const
{
return builder.append(TRY(const_cast<Process&>(*this).current_directory()->try_serialize_absolute_path())->view());
}
ErrorOr<void> Process::procfs_get_command_line(KBufferBuilder& builder) const
{
auto array = TRY(JsonArraySerializer<>::try_create(builder));
for (auto const& arg : arguments()) {
TRY(array.add(arg.view()));
}
TRY(array.finish());
return {};
}
mode_t Process::binary_link_required_mode() const
{
if (!executable())
return 0;
return m_procfs_traits->required_mode();
}
ErrorOr<void> Process::procfs_get_binary_link(KBufferBuilder& builder) const
{
auto custody = executable();
if (!custody)
return Error::from_errno(ENOEXEC);
return builder.append(TRY(custody->try_serialize_absolute_path())->view());
}
}