mirror of
https://github.com/SerenityOS/serenity.git
synced 2026-11-07 12:38:38 -10:00
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:
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -184,3 +184,4 @@ foreach(name IN LISTS FUZZER_TARGETS)
|
||||
endforeach()
|
||||
|
||||
add_subdirectory(pkg)
|
||||
add_subdirectory(runc)
|
||||
@@ -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)
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user