Userland+Base: Introduce userspace implementation for running containers

Together with a first JSON file for bringing up a fully functional
BuggieBox container, we allow users to take advantage of the kernel
unsharing features that were introduced in earlier commits.
This commit is contained in:
Liav A. authored and Tim Schumacher committed 2024-07-21 11:44:23 +02:00
1 parent aa01f52592
commit fdcfa23e01
9 files changed
+709

No files matched your search

@@ -0,0 +1,130 @@
{
"jail": true,
"pid-isolation": true,
"command": "/bin/sh",
"hostname": null,
"layout": [
{
"type": "mount",
"source": null,
"target": "/",
"fs_type": "RAMFS"
},
{
"type": "directory",
"target": "/tmp"
},
{
"type": "directory",
"target": "/bin"
},
{
"type": "directory",
"target": "/usr/"
},
{
"type": "directory",
"target": "/usr/lib/"
},
{
"type": "copy_custom",
"source": "/bin/BuggieBox",
"target": "/bin/BuggieBox"
},
{
"type": "copy_original",
"sources": [
"/usr/lib/Loader.so",
"/usr/lib/libc.so",
"/usr/lib/libgcc_s.so",
"/usr/lib/libsystem.so",
"/usr/lib/libshell.so.serenity",
"/usr/lib/libshell.so",
"/usr/lib/libarchive.so.serenity",
"/usr/lib/libarchive.so",
"/usr/lib/libelf.so.serenity",
"/usr/lib/libelf.so",
"/usr/lib/libgfx.so.serenity",
"/usr/lib/libgfx.so",
"/usr/lib/libline.so.serenity",
"/usr/lib/libline.so",
"/usr/lib/libregex.so.serenity",
"/usr/lib/libregex.so",
"/usr/lib/libaudio.so.serenity",
"/usr/lib/libaudio.so",
"/usr/lib/libcompress.so.serenity",
"/usr/lib/libcompress.so",
"/usr/lib/libfilesystem.so.serenity",
"/usr/lib/libfilesystem.so",
"/usr/lib/libcrypto.so.serenity",
"/usr/lib/libcrypto.so",
"/usr/lib/libcrypt.so.serenity",
"/usr/lib/libcrypt.so",
"/usr/lib/libcore.so",
"/usr/lib/libcore.so.serenity",
"/usr/lib/libcoreminimal.so",
"/usr/lib/libcoreminimal.so.serenity",
"/usr/lib/libunicode.so.serenity",
"/usr/lib/libunicode.so",
"/usr/lib/libsyntax.so.serenity",
"/usr/lib/libsyntax.so",
"/usr/lib/libcodecomprehension.so.serenity",
"/usr/lib/libcodecomprehension.so",
"/usr/lib/libriff.so.serenity",
"/usr/lib/libriff.so",
"/usr/lib/libipc.so.serenity",
"/usr/lib/libipc.so",
"/usr/lib/libthreading.so.serenity",
"/usr/lib/libthreading.so",
"/usr/lib/libtextcodec.so.serenity",
"/usr/lib/libtextcodec.so",
"/usr/lib/liburl.so.serenity",
"/usr/lib/liburl.so"
]
},
{
"type": "directory",
"target": "/etc/"
},
{
"type": "copy_original",
"sources": [
"/etc/passwd"
]
},
{
"type": "symlink",
"path": "/bin/sh",
"target": "/bin/BuggieBox"
},
{
"type": "symlink",
"path": "/bin/ls",
"target": "/bin/BuggieBox"
},
{
"type": "directory",
"target": "/lib/"
},
{
"type": "directory",
"target": "/proc/"
},
{
"type": "mount",
"source": null,
"target": "/proc",
"fs_type": "ProcFS"
},
{
"type": "directory",
"target": "/sys/"
},
{
"type": "mount",
"source": null,
"target": "/sys",
"fs_type": "SysFS"
}
]
}
+1
View File
@@ -203,6 +203,7 @@ echo "done"
printf "installing shortcuts... "
ln -sf /bin/PackageManager mnt/bin/pkg
ln -sf /bin/RunContainer mnt/bin/runc
ln -sf Shell mnt/bin/sh
ln -sf test mnt/bin/[
ln -sf less mnt/bin/more
+1
View File
@@ -184,3 +184,4 @@ foreach(name IN LISTS FUZZER_TARGETS)
endforeach()
add_subdirectory(pkg)
add_subdirectory(runc)
+14
View File
@@ -0,0 +1,14 @@
serenity_component(
RunContainer
RECOMMENDED
TARGETS RunContainer
)
set(SOURCES
LayoutParsing.cpp
VFSRootContextLayout.cpp
main.cpp
)
serenity_app(RunContainer ICON app-assistant)
target_link_libraries(RunContainer PRIVATE LibCore LibMain LibFileSystem)
+181
View File
@@ -0,0 +1,181 @@
/*
* Copyright (c) 2024, Liav A. <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "LayoutParsing.h"
namespace LayoutParsing {
struct JSONPropertyHandler {
ErrorOr<bool> (*probe)(JsonObject const& object) = nullptr;
ErrorOr<void> (*handle)(VFSRootContextLayout& layout, JsonObject const& object) = nullptr;
};
static ErrorOr<bool> mount_object_probe(JsonObject const& object)
{
VERIFY(object.has_string("type"sv));
auto type = object.get_byte_string("type"sv).value();
if (type != "mount"sv)
return false;
if (!object.has_null("source"sv) && !object.has_string("source"sv))
return Error::from_string_view("Object source property not found"sv);
if (!object.has_string("target"sv))
return Error::from_string_view("Object mount property not found"sv);
if (!object.has_string("fs_type"sv))
return Error::from_string_view("Object fs_type property not found"sv);
return true;
}
static ErrorOr<void> mount_object_handle(VFSRootContextLayout& layout, JsonObject const& object)
{
VERIFY(object.has_string("target"sv));
VERIFY(object.has_string("source"sv) || object.has_null("source"sv));
VERIFY(object.has_string("fs_type"sv));
auto target = object.get_byte_string("target"sv).value();
auto fs_type = object.get_byte_string("fs_type"sv).value();
auto source = object.get_byte_string("source"sv);
StringView actual_source = "none"sv;
if (source.has_value())
actual_source = source.value();
TRY(layout.mount_new_filesystem(fs_type, actual_source, target, 0));
return {};
}
static ErrorOr<bool> directory_object_probe(JsonObject const& object)
{
VERIFY(object.has_string("type"sv));
auto type = object.get_byte_string("type"sv).value();
if (type != "directory"sv)
return false;
if (!object.has_string("target"sv))
return Error::from_string_view("Object (directory type) target property not found"sv);
return true;
}
static ErrorOr<void> directory_object_handle(VFSRootContextLayout& layout, JsonObject const& object)
{
VERIFY(object.has_string("target"sv));
auto target = object.get_byte_string("target"sv).value();
TRY(layout.mkdir(target));
return {};
}
static ErrorOr<bool> copy_custom_object_probe(JsonObject const& object)
{
VERIFY(object.has_string("type"sv));
auto type = object.get_byte_string("type"sv).value();
if (type != "copy_custom"sv)
return false;
if (!object.has_string("source"sv))
return Error::from_string_view("Object (copy_custom type) source property not found"sv);
if (!object.has_string("target"sv))
return Error::from_string_view("Object (copy_custom type) target property not found"sv);
return true;
}
static ErrorOr<void> copy_custom_object_handle(VFSRootContextLayout& layout, JsonObject const& object)
{
VERIFY(object.has_string("source"sv));
VERIFY(object.has_string("target"sv));
auto source = object.get_byte_string("source"sv).value();
auto target = object.get_byte_string("target"sv).value();
TRY(layout.copy_to_custom_location(source, target));
return {};
}
static ErrorOr<bool> copy_original_object_probe(JsonObject const& object)
{
VERIFY(object.has_string("type"sv));
auto type = object.get_byte_string("type"sv).value();
if (type != "copy_original"sv)
return false;
if (!object.has_array("sources"sv))
return Error::from_string_view("Object (copy_original type) sources array property not found"sv);
return true;
}
static ErrorOr<void> copy_original_object_handle(VFSRootContextLayout& layout, JsonObject const& object)
{
VERIFY(object.has_array("sources"sv));
auto sources = object.get_array("sources"sv).value();
for (size_t index = 0; index < sources.size(); index++) {
auto& path = sources[index];
if (!path.is_string())
return Error::from_string_view("Object (copy_original type) sources array property invalid"sv);
TRY(layout.copy_as_original(path.as_string()));
}
return {};
}
static ErrorOr<bool> symlink_object_probe(JsonObject const& object)
{
VERIFY(object.has_string("type"sv));
auto type = object.get_byte_string("type"sv).value();
if (type != "symlink"sv)
return false;
if (!object.has_string("path"sv))
return Error::from_string_view("Object (symlink) path property not found"sv);
if (!object.has_string("target"sv))
return Error::from_string_view("Object (symlink) target property not found"sv);
return true;
}
static ErrorOr<void> symlink_object_handle(VFSRootContextLayout& layout, JsonObject const& object)
{
VERIFY(object.has_string("path"sv));
VERIFY(object.has_string("target"sv));
auto path = object.get_byte_string("path"sv).value();
auto target = object.get_byte_string("target"sv).value();
TRY(layout.symlink(path, target));
return {};
}
static constexpr JSONPropertyHandler s_handlers[] = {
{ mount_object_probe, mount_object_handle },
{ directory_object_probe, directory_object_handle },
{ copy_custom_object_probe, copy_custom_object_handle },
{ copy_original_object_probe, copy_original_object_handle },
{ symlink_object_probe, symlink_object_handle },
};
static ErrorOr<void> handle_property(VFSRootContextLayout& layout, JsonObject const& object)
{
for (auto& handler : s_handlers) {
auto found_handler = TRY(handler.probe(object));
if (!found_handler)
continue;
return handler.handle(layout, object);
}
auto type = object.get_byte_string("type"sv).value();
dbgln("WARNING: Unknown object type - {}, it might affect layout creation severely", type);
return {};
}
ErrorOr<void> handle_creation_sequence(VFSRootContextLayout& layout, JsonArray const& layout_creation_sequence)
{
for (size_t index = 0; index < layout_creation_sequence.size(); index++) {
auto& object = layout_creation_sequence[index];
if (!object.is_object())
return Error::from_string_view("Invalid layout JSON object"sv);
if (!object.as_object().has_string("type"sv))
return Error::from_string_view("Invalid layout JSON object - no type being specified"sv);
TRY(handle_property(layout, object.as_object()));
}
return {};
}
}
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2024, Liav A. <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <AK/Types.h>
#include "VFSRootContextLayout.h"
namespace LayoutParsing {
ErrorOr<void> handle_creation_sequence(VFSRootContextLayout&, JsonArray const& layout_creation_sequence);
}
@@ -0,0 +1,121 @@
/*
* Copyright (c) 2024, Liav A. <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/LexicalPath.h>
#include <AK/String.h>
#include <LibCore/System.h>
#include <LibFileSystem/FileSystem.h>
#include "VFSRootContextLayout.h"
static bool is_source_none(StringView source)
{
return source == "none"sv;
}
static ErrorOr<int> get_source_fd(StringView source)
{
if (is_source_none(source))
return -1;
auto fd_or_error = Core::System::open(source, O_RDWR);
if (fd_or_error.is_error())
fd_or_error = Core::System::open(source, O_RDONLY);
return fd_or_error;
}
ErrorOr<void> VFSRootContextLayout::mount_new_filesystem(StringView fstype, StringView source, StringView target_path, int flags)
{
auto source_fd = TRY(get_source_fd(source));
auto actual_path = TRY(generate_path_with_relation_to_preparation_environment_path(target_path));
auto target_path_string = TRY(String::from_utf8(target_path));
auto fstype_string = TRY(String::from_utf8(fstype));
TRY(Core::System::mount({}, source_fd, actual_path, fstype, flags));
auto mount = Mount { Mount::Type::RegularMount, {}, source_fd, target_path_string, fstype_string };
TRY(m_mounts.try_append(mount));
return {};
}
ErrorOr<void> VFSRootContextLayout::chown(StringView path, uid_t uid, gid_t gid)
{
auto actual_path = TRY(generate_path_with_relation_to_preparation_environment_path(path));
return Core::System::chown(actual_path, uid, gid);
}
ErrorOr<void> VFSRootContextLayout::chmod(StringView path, mode_t mode)
{
auto actual_path = TRY(generate_path_with_relation_to_preparation_environment_path(path));
return Core::System::chmod(actual_path, mode);
}
ErrorOr<void> VFSRootContextLayout::symlink(StringView path, StringView target_path)
{
auto actual_path = TRY(generate_path_with_relation_to_preparation_environment_path(path));
return Core::System::symlink(target_path, actual_path);
}
ErrorOr<void> VFSRootContextLayout::copy_as_original(StringView source_path)
{
return copy_to_custom_location(source_path, source_path);
}
ErrorOr<void> VFSRootContextLayout::copy_to_custom_location(StringView source_path, StringView target_path)
{
auto actual_path = TRY(generate_path_with_relation_to_preparation_environment_path(target_path));
TRY(FileSystem::copy_file_or_directory(
actual_path, source_path,
FileSystem::RecursionMode::Disallowed,
FileSystem::LinkMode::Disallowed,
FileSystem::AddDuplicateFileMarker::No,
FileSystem::PreserveMode::Ownership | FileSystem::PreserveMode::Permissions));
return {};
}
ErrorOr<void> VFSRootContextLayout::mkdir(StringView target_path)
{
auto actual_path = TRY(generate_path_with_relation_to_preparation_environment_path(target_path));
TRY(Core::System::mkdir(actual_path.bytes_as_string_view(), 0700));
return {};
}
ErrorOr<String> VFSRootContextLayout::generate_path_with_relation_to_preparation_environment_path(StringView target_path) const
{
VERIFY(LexicalPath(target_path).is_canonical());
auto path = LexicalPath::join(m_preparation_environment_path.bytes_as_string_view(), target_path);
return String::from_byte_string(path.string());
}
VFSRootContextLayout::VFSRootContextLayout(String preparation_environment_path, unsigned target_vfs_root_context_id)
: m_preparation_environment_path(move(preparation_environment_path))
, m_target_vfs_root_context_id(target_vfs_root_context_id)
{
}
ErrorOr<NonnullOwnPtr<VFSRootContextLayout>> VFSRootContextLayout::create(StringView preparation_environment_path, unsigned target_vfs_root_context_id)
{
auto path = TRY(String::from_utf8(preparation_environment_path));
return adopt_nonnull_own_or_enomem(new (nothrow) VFSRootContextLayout(move(path), target_vfs_root_context_id));
}
ErrorOr<void> VFSRootContextLayout::apply_mounts_on_vfs_root_context_id()
{
for (auto& mount : m_mounts) {
auto path_on_preparation_environment = TRY(generate_path_with_relation_to_preparation_environment_path(mount.path));
TRY(Core::System::copy_mount({},
m_target_vfs_root_context_id,
path_on_preparation_environment.bytes_as_string_view(),
mount.path, 0));
}
for (auto& mount : m_mounts.in_reverse()) {
auto path_on_preparation_environment = TRY(generate_path_with_relation_to_preparation_environment_path(mount.path));
TRY(Core::System::umount({}, path_on_preparation_environment.bytes_as_string_view()));
}
m_mounts.clear();
return {};
}
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2024, Liav A. <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/NonnullOwnPtr.h>
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Types.h>
#include <AK/Vector.h>
#include <unistd.h>
class VFSRootContextLayout {
public:
struct Mount {
enum class Type {
BindMount,
RegularMount,
};
Type type;
Optional<unsigned> source_vfs_root_context_id;
Optional<unsigned> source_fd;
String path;
String fstype;
};
ErrorOr<void> mount_new_filesystem(StringView fstype, StringView source, StringView target_path, int flags);
// ErrorOr<void> bind_mount(int source_vfs_root_context_id, StringView source_path, StringView target_path, int flags);
ErrorOr<void> chown(StringView path, uid_t uid, gid_t gid);
ErrorOr<void> chmod(StringView target_path, mode_t);
ErrorOr<void> symlink(StringView path, StringView target_path);
ErrorOr<void> copy_as_original(StringView source_path);
ErrorOr<void> copy_to_custom_location(StringView source_path, StringView target_path);
ErrorOr<void> mkdir(StringView target_path);
static ErrorOr<NonnullOwnPtr<VFSRootContextLayout>> create(StringView preparation_environment_path, unsigned target_vfs_root_context_id);
ErrorOr<void> apply_mounts_on_vfs_root_context_id();
private:
VFSRootContextLayout(String preparation_environment_path, unsigned target_vfs_root_context_id);
ErrorOr<String> generate_path_with_relation_to_preparation_environment_path(StringView target_path) const;
String m_preparation_environment_path;
unsigned const m_target_vfs_root_context_id { 0 };
Vector<Mount> m_mounts;
};
+189
View File
@@ -0,0 +1,189 @@
/*
* Copyright (c) 2024, Liav A. <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <AK/String.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/File.h>
#include <LibCore/System.h>
#include <LibMain/Main.h>
#include <sys/prctl.h>
#include <unistd.h>
#include "LayoutParsing.h"
#include "VFSRootContextLayout.h"
static ErrorOr<unsigned> create_vfs_root_context_unshare()
{
return TRY(Core::System::unshare_create(Kernel::UnshareType::VFSRootContext, 0));
}
static ErrorOr<void> create_custom_vfs_root_context_layout(JsonArray const& layout_creation_sequence)
{
auto vfs_root_context_index = TRY(create_vfs_root_context_unshare());
char pattern[] = "/tmp/container_root_XXXXXX";
auto temp_directory_path = TRY(Core::System::mkdtemp(pattern));
auto vfs_root_context_layout = TRY(VFSRootContextLayout::create(temp_directory_path.bytes_as_string_view(), vfs_root_context_index));
TRY(LayoutParsing::handle_creation_sequence(*vfs_root_context_layout, layout_creation_sequence));
TRY(vfs_root_context_layout->apply_mounts_on_vfs_root_context_id());
TRY(Core::System::rmdir(temp_directory_path));
TRY(Core::System::unshare_attach(Kernel::UnshareType::VFSRootContext, vfs_root_context_index));
// Finally, apply changes to current working directory.
(void)chdir("/");
return {};
}
static ErrorOr<void> create_and_attach_scoped_process_list_unshare()
{
auto scoped_process_list_index = TRY(Core::System::unshare_create(Kernel::UnshareType::ScopedProcessList, 0));
TRY(Core::System::unshare_attach(Kernel::UnshareType::ScopedProcessList, scoped_process_list_index));
return {};
}
static ErrorOr<void> create_and_attach_hostname_context(StringView hostname)
{
auto hostname_context_index = TRY(Core::System::unshare_create(Kernel::UnshareType::HostnameContext, 0));
TRY(Core::System::unshare_attach(Kernel::UnshareType::HostnameContext, hostname_context_index));
TRY(Core::System::sethostname(hostname));
return {};
}
static ErrorOr<void> extract_values_from_file(Core::File& file, String& command, JsonArray& new_vfs_root_context_layout_creation_sequence, bool& pid_isolation, Optional<String>& hostname_context_name, bool& enforce_jail)
{
auto file_contents = TRY(file.read_until_eof());
auto json = TRY(JsonValue::from_string(file_contents));
auto configuration_object = json.as_object();
if (!configuration_object.has_bool("jail"sv))
return Error::from_string_literal("JSON configuration invalid: Jail enforcement flag is not specified");
if (!configuration_object.has_bool("pid-isolation"sv))
return Error::from_string_literal("JSON configuration invalid: PID isolation flag is not specified");
if (!configuration_object.has_string("command"sv))
return Error::from_string_literal("JSON configuration invalid: Command is not specified");
if (!configuration_object.has_array("layout"sv))
return Error::from_string_literal("JSON configuration invalid: VFS root context layout is not specified");
if (configuration_object.has_null("hostname"sv) && configuration_object.has_string("hostname"sv))
return Error::from_string_literal("JSON configuration invalid: Can't have hostname as null and string during configuration");
if (!configuration_object.has_null("hostname"sv) && !configuration_object.has_string("hostname"sv))
return Error::from_string_literal("JSON configuration invalid: Hostname is not specified");
new_vfs_root_context_layout_creation_sequence = move(configuration_object.get("layout"sv).value().as_array());
pid_isolation = configuration_object.get("pid-isolation"sv).value().as_bool();
enforce_jail = configuration_object.get("jail"sv).value().as_bool();
auto possible_hostname = configuration_object.get_byte_string("hostname"sv);
if (possible_hostname.has_value())
hostname_context_name = TRY(String::from_byte_string(possible_hostname.value()));
command = TRY(String::from_byte_string(configuration_object.get("command"sv).value().as_string()));
return {};
}
static ErrorOr<void> deploy_container_based_on_config_file(StringView config_file_path)
{
TRY(Core::System::pledge("stdio rpath wpath cpath proc mount unshare exec fattr chown"));
auto file = TRY(Core::File::open(config_file_path, Core::File::OpenMode::Read));
bool pid_isolation = false;
bool enforce_jail = false;
String command;
Optional<String> hostname_context_name {};
JsonArray new_vfs_root_context_layout_creation_sequence {};
TRY(extract_values_from_file(*file, command, new_vfs_root_context_layout_creation_sequence, pid_isolation, hostname_context_name, enforce_jail));
// NOTE: First we gather all information, then we start deploying.
// To ensure proper functionality, we do this in the following sequence:
// - Create PID isolation and attach the scoped process list
// - Creating a VFS root context
// - Populating the VFS root context with the desired layout
// - Attach to the VFS root context
// - Attach to the hostname context
// - Enforce jail restrictions
if (pid_isolation)
TRY(create_and_attach_scoped_process_list_unshare());
// NOTE: To be able to properly create the desired layout of the container,
// we first mount a new filesystem instance in a temporary location, then we
// populate its directories and mount subsequent mounts if so desired.
// Then we copy all mounts (from the root directory of the VFS root context)
// up to all of its subsequent mounts, completing the filesystem skeleton of the
// container.
if (!new_vfs_root_context_layout_creation_sequence.is_empty())
TRY(create_custom_vfs_root_context_layout(new_vfs_root_context_layout_creation_sequence));
// Remove the fattr & chown pledges
TRY(Core::System::pledge("stdio rpath wpath cpath proc mount unshare exec"));
if (hostname_context_name.has_value())
TRY(create_and_attach_hostname_context(hostname_context_name.value().bytes_as_string_view()));
// Remove the unshare pledge
TRY(Core::System::pledge("stdio rpath wpath cpath proc mount exec"));
if (enforce_jail)
TRY(Core::System::enter_jail_mode());
// Remove the proc pledge
TRY(Core::System::pledge("stdio rpath wpath cpath mount exec"));
// FIXME: Find a better way to convert between String and Vector<StringView>
auto splitted_command = command.bytes_as_string_view().split_view(' ');
TRY(Core::System::exec_command(splitted_command, false));
return {};
}
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
bool pid_isolation = false;
bool enforce_jail = false;
bool preserve_env = false;
Vector<StringView> command;
StringView config_file;
Core::ArgsParser args_parser;
args_parser.set_stop_on_first_non_option(true);
args_parser.add_option(pid_isolation, "Create new process list", "pid-isolation", 'p');
args_parser.add_option(config_file, "Use JSON-based configruation file", "configuration", 'f', "");
args_parser.add_option(enforce_jail, "Enforce jail restrictions on container", "enforce-jail", 'j');
args_parser.add_option(preserve_env, "Preserve user environment when running command", "preserve-env", 'E');
args_parser.add_positional_argument(command, "Command to run at elevated privilege level", "command", Core::ArgsParser::Required::No);
args_parser.parse(arguments);
if (!config_file.is_null() && !config_file.is_empty()) {
TRY(deploy_container_based_on_config_file(config_file));
return 0;
}
TRY(Core::System::pledge("stdio rpath wpath cpath proc unshare exec"));
if (command.is_empty())
return Error::from_string_view("Can't create a container with no specified command."sv);
if (!(pid_isolation || enforce_jail))
return Error::from_string_view("Can't create a container with no attributes (jail/pid-isolation)."sv);
if (pid_isolation)
TRY(create_and_attach_scoped_process_list_unshare());
// Remove the unshare pledge
TRY(Core::System::pledge("stdio rpath wpath cpath proc exec"));
if (enforce_jail)
TRY(Core::System::enter_jail_mode());
// Remove the proc pledge
TRY(Core::System::pledge("stdio rpath wpath cpath exec"));
TRY(Core::System::exec_command(command, preserve_env));
return 0;
}