
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.
342 lines
14 KiB
C++
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());
|
|
}
|
|
|
|
}
|