Introduce the framed guest_channel_transport with shared deadlines and std::jthread ownership, metadata-only retired-schema quarantine with fail-closed integrity, stateless frozen-catalog validators with capability pinned to zero, and the generic local-service proxy with descriptor-pinned endpoints, alias-only guest sockets, audit-gated delivery, and a Linux managed runtime capability derived only from a registry-bound operational factory. Sage vocabulary is confined to src/adapters/sage.
1598 lines
66 KiB
C++
1598 lines
66 KiB
C++
#include "glove/audit/sink.hpp"
|
|
#include "glove/container/digest.hpp"
|
|
#include "glove/container/receipt_producer.hpp"
|
|
#include "glove/control/guest_channel_transport.hpp"
|
|
#include "glove/control/local_service_proxy.hpp"
|
|
#include "glove/control/receipt_audit_protocol.hpp"
|
|
#include "glove/control/session_registry.hpp"
|
|
#include "glove/supervisor/path_alias.hpp"
|
|
#include "glove/supervisor/session_plan.hpp"
|
|
|
|
#include "adapters/sage/guest_channel.hpp"
|
|
#include "cgroup_v2.hpp"
|
|
#include "linux_session_executor.hpp"
|
|
#include "linux_session_preparation.hpp"
|
|
|
|
#include <fcntl.h>
|
|
#include <sched.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/un.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <new>
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <system_error>
|
|
#include <thread>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#if defined(__has_feature)
|
|
# if __has_feature(thread_sanitizer)
|
|
# define GLOVE_LOCAL_PROXY_TSAN 1
|
|
# endif
|
|
#endif
|
|
#ifndef GLOVE_LOCAL_PROXY_TSAN
|
|
# define GLOVE_LOCAL_PROXY_TSAN 0
|
|
#endif
|
|
|
|
namespace allocation_fault {
|
|
std::atomic<std::int64_t> remaining{-1};
|
|
}
|
|
|
|
#if !GLOVE_LOCAL_PROXY_TSAN
|
|
void* operator new(std::size_t size) {
|
|
const auto before = allocation_fault::remaining.load();
|
|
if (before == 0) {
|
|
allocation_fault::remaining.store(-1);
|
|
throw std::bad_alloc{};
|
|
}
|
|
if (before > 0) {
|
|
allocation_fault::remaining.fetch_sub(1);
|
|
}
|
|
if (void* allocation = std::malloc(size); allocation != nullptr) {
|
|
return allocation;
|
|
}
|
|
throw std::bad_alloc{};
|
|
}
|
|
|
|
void operator delete(void* allocation) noexcept {
|
|
std::free(allocation);
|
|
}
|
|
|
|
void operator delete(void* allocation, [[maybe_unused]] std::size_t size) noexcept {
|
|
std::free(allocation);
|
|
}
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
#define REQUIRE(condition) \
|
|
do { \
|
|
if (!(condition)) { \
|
|
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #condition, __FILE__, __LINE__); \
|
|
return 1; \
|
|
} \
|
|
} while (false)
|
|
|
|
class unique_fd {
|
|
public:
|
|
explicit unique_fd(int descriptor = -1) noexcept : descriptor_{descriptor} {}
|
|
|
|
unique_fd(const unique_fd&) = delete;
|
|
auto operator=(const unique_fd&) -> unique_fd& = delete;
|
|
|
|
unique_fd(unique_fd&& other) noexcept : descriptor_{std::exchange(other.descriptor_, -1)} {}
|
|
|
|
auto operator=(unique_fd&& other) noexcept -> unique_fd& {
|
|
if (this != &other) {
|
|
if (descriptor_ >= 0) {
|
|
static_cast<void>(::close(descriptor_));
|
|
}
|
|
descriptor_ = std::exchange(other.descriptor_, -1);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
~unique_fd() {
|
|
if (descriptor_ >= 0) {
|
|
static_cast<void>(::close(descriptor_));
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] auto get() const noexcept -> int { return descriptor_; }
|
|
|
|
private:
|
|
int descriptor_ = -1;
|
|
};
|
|
|
|
class temporary_directory {
|
|
public:
|
|
temporary_directory() {
|
|
std::string pattern = "/tmp/glove-local-service-test-XXXXXX";
|
|
if (char* created = ::mkdtemp(pattern.data()); created != nullptr) {
|
|
root_ = created;
|
|
}
|
|
}
|
|
|
|
temporary_directory(const temporary_directory&) = delete;
|
|
auto operator=(const temporary_directory&) -> temporary_directory& = delete;
|
|
|
|
~temporary_directory() {
|
|
std::error_code ignored;
|
|
std::filesystem::remove_all(root_, ignored);
|
|
}
|
|
|
|
[[nodiscard]] auto root() const -> const std::filesystem::path& { return root_; }
|
|
|
|
private:
|
|
std::filesystem::path root_;
|
|
};
|
|
|
|
class rejecting_sink final : public glove::audit::sink {
|
|
public:
|
|
auto record(const glove::audit::event&) -> std::expected<void, std::string> override {
|
|
return std::unexpected(std::string{"audit unavailable"});
|
|
}
|
|
};
|
|
|
|
class fail_terminal_sink final : public glove::audit::sink {
|
|
public:
|
|
auto record(const glove::audit::event& event) -> std::expected<void, std::string> override {
|
|
const std::scoped_lock lock{mutex_};
|
|
if (!events_.empty()) {
|
|
return std::unexpected(std::string{"terminal audit unavailable"});
|
|
}
|
|
events_.push_back(event);
|
|
return {};
|
|
}
|
|
|
|
auto take() -> std::vector<glove::audit::event> {
|
|
const std::scoped_lock lock{mutex_};
|
|
return events_;
|
|
}
|
|
|
|
private:
|
|
std::mutex mutex_;
|
|
std::vector<glove::audit::event> events_;
|
|
};
|
|
|
|
class delayed_sink final : public glove::audit::sink {
|
|
public:
|
|
explicit delayed_sink(std::chrono::milliseconds delay) : delay_{delay} {}
|
|
|
|
auto record(const glove::audit::event& event) -> std::expected<void, std::string> override {
|
|
{
|
|
const std::scoped_lock lock{mutex_};
|
|
events_.push_back(event);
|
|
}
|
|
std::this_thread::sleep_for(delay_);
|
|
return {};
|
|
}
|
|
|
|
auto take() -> std::vector<glove::audit::event> {
|
|
const std::scoped_lock lock{mutex_};
|
|
return events_;
|
|
}
|
|
|
|
private:
|
|
std::chrono::milliseconds delay_;
|
|
std::mutex mutex_;
|
|
std::vector<glove::audit::event> events_;
|
|
};
|
|
|
|
auto make_owner_directory(const std::filesystem::path& path) -> bool {
|
|
return std::filesystem::create_directory(path) && ::chmod(path.c_str(), 0700) == 0;
|
|
}
|
|
|
|
auto prerequisite_name(glove::container::linux_detail::linux_session_prerequisite prerequisite)
|
|
-> std::string_view {
|
|
using glove::container::linux_detail::linux_session_prerequisite;
|
|
switch (prerequisite) {
|
|
case linux_session_prerequisite::available:
|
|
return "available";
|
|
case linux_session_prerequisite::user_namespace_unavailable:
|
|
return "user namespace unavailable";
|
|
case linux_session_prerequisite::cgroup_v2_unavailable:
|
|
return "cgroup v2 unavailable";
|
|
case linux_session_prerequisite::cgroup_controllers_unavailable:
|
|
return "cgroup controllers unavailable";
|
|
case linux_session_prerequisite::cgroup_delegation_unavailable:
|
|
return "cgroup delegation unavailable";
|
|
}
|
|
return "unknown prerequisite state";
|
|
}
|
|
|
|
auto write_owner_file(const std::filesystem::path& path, std::string_view contents) -> bool {
|
|
std::ofstream output{path, std::ios::binary};
|
|
output.write(contents.data(), static_cast<std::streamsize>(contents.size()));
|
|
output.close();
|
|
return output.good() && ::chmod(path.c_str(), 0600) == 0;
|
|
}
|
|
|
|
auto address_for(const std::filesystem::path& path) -> sockaddr_un {
|
|
sockaddr_un address{};
|
|
address.sun_family = AF_UNIX;
|
|
const auto value = path.string();
|
|
std::memcpy(address.sun_path, value.c_str(), value.size() + 1U);
|
|
return address;
|
|
}
|
|
|
|
auto make_listener(const std::filesystem::path& path) -> unique_fd {
|
|
unique_fd listener{::socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)};
|
|
if (listener.get() < 0 || path.string().size() >= sizeof(sockaddr_un::sun_path)) {
|
|
return unique_fd{};
|
|
}
|
|
const auto address = address_for(path);
|
|
if (::bind(listener.get(), reinterpret_cast<const sockaddr*>(&address), sizeof(address)) != 0 ||
|
|
::chmod(path.c_str(), 0600) != 0 || ::listen(listener.get(), 8) != 0) {
|
|
return unique_fd{};
|
|
}
|
|
return listener;
|
|
}
|
|
|
|
auto launch_template() -> glove::supervisor::runtime_launch_template {
|
|
return {
|
|
.runtime_discovery = {},
|
|
.executable_path = "/usr/bin/true",
|
|
.executable_search_paths = {},
|
|
.arguments = {},
|
|
.environment = {"PATH=/usr/bin:/bin"},
|
|
.read_only_paths = {},
|
|
};
|
|
}
|
|
|
|
auto validator_for(const std::filesystem::path& source)
|
|
-> glove::supervisor::result<glove::supervisor::session_plan_validator> {
|
|
using namespace glove::supervisor;
|
|
auto paths = path_alias_registry::build({
|
|
path_alias_policy{
|
|
.alias = "workspace",
|
|
.host_path = std::filesystem::canonical(source).string(),
|
|
.target_path = "/workspace",
|
|
.max_ttl_secs = 120,
|
|
.access = {
|
|
path_access_policy{
|
|
.access = path_access::ephemeral_write,
|
|
.materialization = path_materialization::copy,
|
|
.create_policy = path_create_policy::empty_directory,
|
|
.cleanup_policy = path_cleanup_policy::remove,
|
|
.max_bytes = 2'097'152,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
if (!paths) {
|
|
return std::unexpected(paths.error());
|
|
}
|
|
const auto digest = runtime_launch_template_digest(launch_template());
|
|
if (!digest) {
|
|
return std::unexpected(digest.error());
|
|
}
|
|
return session_plan_validator::build(
|
|
session_plan_policy{
|
|
.revision = 1,
|
|
.max_plan_ttl_ms = 120'000,
|
|
.runtime_templates =
|
|
{
|
|
runtime_template_policy{
|
|
.runtime_template_id = "pi-safe",
|
|
.runtime_id = "pi",
|
|
.adapter_command_digest = *digest,
|
|
.backend = sandbox_backend::linux_production,
|
|
.allowed_path_aliases = {"workspace"},
|
|
.allowed_projection_destinations = {},
|
|
.launch = launch_template(),
|
|
.adoption = std::nullopt,
|
|
},
|
|
},
|
|
.library_projection_destinations = {},
|
|
.resource_profiles =
|
|
{
|
|
resource_limits{
|
|
.cpu_time_ms = 1'000,
|
|
.memory_bytes = 67'108'864,
|
|
.pids = 16,
|
|
.wall_time_ms = 2'000,
|
|
.disk_bytes = 2'097'152,
|
|
.terminal_output_bytes = 1'048'576,
|
|
},
|
|
},
|
|
.egress_policy_ids = {"no-network"},
|
|
.tool_policy_ids = {"sage-readonly"},
|
|
.secret_handles = {},
|
|
.egress_policies = {},
|
|
.secret_mounts = {},
|
|
},
|
|
std::move(*paths)
|
|
);
|
|
}
|
|
|
|
auto path_grant_for(const std::filesystem::path& source, std::string alias = "service-source")
|
|
-> std::expected<glove::supervisor::resolved_path_grant, std::string> {
|
|
using namespace glove::supervisor;
|
|
auto paths = path_alias_registry::build({
|
|
path_alias_policy{
|
|
.alias = alias,
|
|
.host_path = source.string(),
|
|
.target_path = "/workspace",
|
|
.max_ttl_secs = 120,
|
|
.access = {
|
|
path_access_policy{
|
|
.access = path_access::read,
|
|
.materialization = path_materialization::bind,
|
|
.create_policy = path_create_policy::never,
|
|
.cleanup_policy = path_cleanup_policy::retain,
|
|
.max_bytes = 0,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
if (!paths) {
|
|
return std::unexpected(paths.error());
|
|
}
|
|
return paths->resolve({
|
|
.alias = std::move(alias),
|
|
.access = path_access::read,
|
|
.ttl_secs = 60,
|
|
.max_bytes = 0,
|
|
});
|
|
}
|
|
|
|
auto connect_mount(int directory_fd, std::string_view alias) -> int {
|
|
const auto path =
|
|
std::string{"/proc/self/fd/"} + std::to_string(directory_fd) + "/" + std::string{alias};
|
|
if (path.size() >= sizeof(sockaddr_un::sun_path)) {
|
|
return -1;
|
|
}
|
|
const auto address = address_for(path);
|
|
const int descriptor = ::socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
|
if (descriptor < 0 ||
|
|
::connect(descriptor, reinterpret_cast<const sockaddr*>(&address), sizeof(address)) != 0) {
|
|
if (descriptor >= 0) {
|
|
static_cast<void>(::close(descriptor));
|
|
}
|
|
return -1;
|
|
}
|
|
return descriptor;
|
|
}
|
|
|
|
auto descriptor_path(int descriptor) -> std::filesystem::path {
|
|
std::string link = "/proc/self/fd/" + std::to_string(descriptor);
|
|
std::vector<char> path(4096U, '\0');
|
|
const auto length = ::readlink(link.c_str(), path.data(), path.size() - 1U);
|
|
return length > 0
|
|
? std::filesystem::path{std::string{path.data(), static_cast<std::size_t>(length)}}
|
|
: std::filesystem::path{};
|
|
}
|
|
|
|
auto descriptor_count() -> std::size_t {
|
|
std::error_code error;
|
|
std::size_t count = 0;
|
|
for ([[maybe_unused]] const auto& entry :
|
|
std::filesystem::directory_iterator{"/proc/self/fd", error}) {
|
|
++count;
|
|
}
|
|
return error ? 0U : count;
|
|
}
|
|
|
|
[[maybe_unused]] auto directory_entry_count(const std::filesystem::path& path) -> std::size_t {
|
|
std::error_code error;
|
|
std::size_t count = 0;
|
|
for ([[maybe_unused]] const auto& entry : std::filesystem::directory_iterator{path, error}) {
|
|
++count;
|
|
}
|
|
return error ? std::numeric_limits<std::size_t>::max() : count;
|
|
}
|
|
|
|
auto preparation_inputs(std::uint64_t now_ms) -> glove::control::session_start_inputs {
|
|
const glove::supervisor::resource_limits limits{
|
|
.cpu_time_ms = 10'000,
|
|
.memory_bytes = std::uint64_t{128} * 1024U * 1024U,
|
|
.pids = 16,
|
|
.wall_time_ms = 5'000,
|
|
.disk_bytes = std::uint64_t{16} * 1024U * 1024U,
|
|
.terminal_output_bytes = std::uint64_t{1024} * 1024U,
|
|
};
|
|
return {
|
|
.session =
|
|
{
|
|
.schema_version = 1,
|
|
.session_id = "proxy-preparation",
|
|
.controller_plan_digest = std::string(64U, 'a'),
|
|
.plan_content_digest = std::string(64U, 'b'),
|
|
.state = glove::control::session_state::preparing,
|
|
.policy_revision = 1,
|
|
.expires_at_ms = now_ms + 60'000U,
|
|
.created_at_ms = now_ms - 1U,
|
|
},
|
|
.launch =
|
|
{
|
|
.validation = {.schema_version = 1, .policy_revision = 1},
|
|
.runtime_id = "copilot",
|
|
.runtime_template_id = "copilot-safe",
|
|
.adapter_command_digest = std::string(64U, 'c'),
|
|
.backend = glove::supervisor::sandbox_backend::linux_production,
|
|
.argv = {"/usr/bin/true", "--version"},
|
|
.environment = {"PATH=/usr/bin:/bin", "TERM=xterm-256color"},
|
|
.read_only_paths = {},
|
|
.limits = limits,
|
|
.expires_at_ms = now_ms + 60'000U,
|
|
.requires_direct_write_approval = false,
|
|
.egress_policy_id = "",
|
|
.egress_targets = {},
|
|
.secret_mounts = {},
|
|
.adoption = std::nullopt,
|
|
.refinement = std::nullopt,
|
|
},
|
|
.path_grants = {},
|
|
.library_projections = {},
|
|
.adoption = std::nullopt,
|
|
.authorization_id = "approval-proxy-preparation",
|
|
.authorization_expires_at_ms = now_ms + 30'000U,
|
|
};
|
|
}
|
|
|
|
auto options_for(
|
|
const std::filesystem::path& runtime,
|
|
const std::filesystem::path& endpoint,
|
|
std::shared_ptr<glove::audit::sink> audit,
|
|
std::shared_ptr<const glove::control::guest_channel_adapter_binding> adapter,
|
|
std::uint64_t timeout_ms = 1'000,
|
|
std::uint32_t max_concurrency = 2
|
|
) -> glove::control::linux_detail::local_service_proxy_options {
|
|
return {
|
|
.runtime_root = runtime,
|
|
.io_timeout_ms = timeout_ms,
|
|
.max_concurrency = max_concurrency,
|
|
.endpoints =
|
|
{
|
|
glove::control::linux_detail::local_service_endpoint{
|
|
.alias = "sage-observe",
|
|
.socket_path = endpoint,
|
|
.runtime_ids = {"copilot", "pi"},
|
|
},
|
|
},
|
|
.audit = std::move(audit),
|
|
.guest_channel_adapter = std::move(adapter),
|
|
};
|
|
}
|
|
|
|
auto exchange_once(
|
|
int listener, std::string expected_request, std::string response, std::atomic_bool& exact
|
|
) -> std::jthread {
|
|
return std::jthread{
|
|
[listener, expected = std::move(expected_request), response = std::move(response), &exact] {
|
|
const int accepted = ::accept4(listener, nullptr, nullptr, SOCK_CLOEXEC);
|
|
if (accepted < 0) {
|
|
return;
|
|
}
|
|
auto transport = glove::control::guest_channel_transport::adopt(
|
|
accepted, 16U * 1024U, static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
if (!transport) {
|
|
return;
|
|
}
|
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{2};
|
|
auto request = (*transport)->receive_frame(deadline);
|
|
if (!request) {
|
|
return;
|
|
}
|
|
exact = *request == expected;
|
|
static_cast<void>((*transport)->send_frame(response, deadline));
|
|
}
|
|
};
|
|
}
|
|
|
|
auto run(bool privileged_only) -> int {
|
|
using glove::control::guest_channel_transport;
|
|
using glove::control::linux_detail::local_service_proxy_factory;
|
|
|
|
if (privileged_only) {
|
|
const auto prerequisite =
|
|
glove::container::linux_detail::probe_linux_session_prerequisite();
|
|
if (!prerequisite) {
|
|
std::fprintf(
|
|
stderr,
|
|
"Linux capability prerequisite probe failed: %s\n",
|
|
prerequisite.error().c_str()
|
|
);
|
|
return 1;
|
|
}
|
|
if (*prerequisite !=
|
|
glove::container::linux_detail::linux_session_prerequisite::available) {
|
|
const auto reason = prerequisite_name(*prerequisite);
|
|
std::fprintf(
|
|
stderr,
|
|
"SKIP concrete Linux capability sealing: %.*s\n",
|
|
static_cast<int>(reason.size()),
|
|
reason.data()
|
|
);
|
|
return 77;
|
|
}
|
|
}
|
|
|
|
temporary_directory temporary;
|
|
REQUIRE(!temporary.root().empty());
|
|
const auto source = temporary.root() / "source";
|
|
const auto runtime = temporary.root() / "runtime";
|
|
const auto upstream = temporary.root() / "upstream";
|
|
REQUIRE(make_owner_directory(source));
|
|
REQUIRE(make_owner_directory(runtime));
|
|
REQUIRE(make_owner_directory(upstream));
|
|
const auto endpoint = upstream / "sage.sock";
|
|
auto listener = make_listener(endpoint);
|
|
REQUIRE(listener.get() >= 0);
|
|
|
|
auto validator = validator_for(source);
|
|
REQUIRE(validator.has_value());
|
|
auto shared_validator =
|
|
std::make_shared<const glove::supervisor::session_plan_validator>(std::move(*validator));
|
|
auto adapter = glove::adapters::sage::resolve_guest_channel_adapter(
|
|
"sage-observation", "sage.glove-observation.v1"
|
|
);
|
|
REQUIRE(adapter.has_value());
|
|
const auto catalog = (*adapter)->channels;
|
|
auto registry = glove::control::session_registry::open_or_create(
|
|
temporary.root() / "sessions.journal",
|
|
shared_validator,
|
|
{},
|
|
glove::control::default_session_registry_bytes,
|
|
catalog
|
|
);
|
|
REQUIRE(registry.has_value());
|
|
auto shared_registry = std::shared_ptr<glove::control::session_registry>{std::move(*registry)};
|
|
auto audit = glove::audit::make_memory_sink();
|
|
auto factory = local_service_proxy_factory::create(
|
|
options_for(runtime, endpoint, audit, *adapter), shared_registry
|
|
);
|
|
REQUIRE(factory.has_value());
|
|
REQUIRE((*factory)->operational());
|
|
REQUIRE(::chmod(runtime.c_str(), 0755) == 0);
|
|
REQUIRE(!(*factory)->operational());
|
|
REQUIRE(::chmod(runtime.c_str(), 0700) == 0);
|
|
REQUIRE((*factory)->operational());
|
|
REQUIRE((*factory)->manages_runtime("pi"));
|
|
REQUIRE(!(*factory)->manages_runtime("codex"));
|
|
|
|
auto direct_parent_grant = path_grant_for(upstream, "direct-parent");
|
|
REQUIRE(direct_parent_grant.has_value());
|
|
auto direct_parent_result = (*factory)->validate_path_grants(
|
|
std::span<const glove::supervisor::resolved_path_grant>{&*direct_parent_grant, 1U}
|
|
);
|
|
REQUIRE(!direct_parent_result.has_value());
|
|
REQUIRE(direct_parent_result.error().find(upstream.string()) == std::string::npos);
|
|
|
|
auto grandparent_grant = path_grant_for(temporary.root(), "grandparent");
|
|
REQUIRE(grandparent_grant.has_value());
|
|
REQUIRE(
|
|
!(*factory)
|
|
->validate_path_grants(
|
|
std::span<const glove::supervisor::resolved_path_grant>{&*grandparent_grant, 1U}
|
|
)
|
|
.has_value()
|
|
);
|
|
|
|
auto unrelated_grant = path_grant_for(source, "unrelated");
|
|
REQUIRE(unrelated_grant.has_value());
|
|
REQUIRE((*factory)
|
|
->validate_path_grants(
|
|
std::span<const glove::supervisor::resolved_path_grant>{&*unrelated_grant, 1U}
|
|
)
|
|
.has_value());
|
|
REQUIRE(!path_grant_for(endpoint, "exact-socket").has_value());
|
|
|
|
const auto real_endpoint_root = temporary.root() / "real-endpoint-root";
|
|
const auto real_endpoint_parent = real_endpoint_root / "child";
|
|
REQUIRE(make_owner_directory(real_endpoint_root));
|
|
REQUIRE(make_owner_directory(real_endpoint_parent));
|
|
const auto aliased_endpoint = real_endpoint_parent / "aliased.sock";
|
|
auto aliased_listener = make_listener(aliased_endpoint);
|
|
REQUIRE(aliased_listener.get() >= 0);
|
|
const auto endpoint_root_alias = temporary.root() / "endpoint-root-alias";
|
|
REQUIRE(::symlink(real_endpoint_root.c_str(), endpoint_root_alias.c_str()) == 0);
|
|
auto alias_factory = local_service_proxy_factory::create(
|
|
options_for(runtime, endpoint_root_alias / "child/aliased.sock", audit, *adapter),
|
|
shared_registry
|
|
);
|
|
REQUIRE(alias_factory.has_value());
|
|
auto aliased_parent_grant = path_grant_for(real_endpoint_parent, "aliased-parent");
|
|
REQUIRE(aliased_parent_grant.has_value());
|
|
REQUIRE(
|
|
!(*alias_factory)
|
|
->validate_path_grants(
|
|
std::span<const glove::supervisor::resolved_path_grant>{&*aliased_parent_grant, 1U}
|
|
)
|
|
.has_value()
|
|
);
|
|
|
|
auto equivalent_adapter = glove::adapters::sage::resolve_guest_channel_adapter(
|
|
"sage-observation", "sage.glove-observation.v1"
|
|
);
|
|
REQUIRE(equivalent_adapter.has_value());
|
|
REQUIRE(!local_service_proxy_factory::create(
|
|
options_for(runtime, endpoint, audit, *equivalent_adapter), shared_registry
|
|
)
|
|
.has_value());
|
|
|
|
auto mismatched_adapter = std::make_shared<const glove::control::guest_channel_adapter_binding>(
|
|
glove::control::guest_channel_adapter_binding{
|
|
.adapter_id = "sage-observation",
|
|
.channel_schema_id = "sage.unregistered.v1",
|
|
.runtime_ids = {"pi"},
|
|
.channels = catalog,
|
|
}
|
|
);
|
|
REQUIRE(!local_service_proxy_factory::create(
|
|
options_for(runtime, endpoint, audit, std::move(mismatched_adapter)),
|
|
shared_registry
|
|
)
|
|
.has_value());
|
|
|
|
auto generic_registry_result = glove::control::session_registry::open_or_create(
|
|
temporary.root() / "generic-sessions.journal", shared_validator
|
|
);
|
|
REQUIRE(generic_registry_result.has_value());
|
|
auto generic_registry =
|
|
std::shared_ptr<glove::control::session_registry>{std::move(*generic_registry_result)};
|
|
auto generic_factory = local_service_proxy_factory::create(
|
|
options_for(runtime, endpoint, audit, {}), generic_registry
|
|
);
|
|
REQUIRE(generic_factory.has_value());
|
|
REQUIRE((*generic_factory)->operational());
|
|
auto generic_session = (*generic_factory)->prepare_session("generic-session", "pi");
|
|
REQUIRE(generic_session.has_value());
|
|
auto generic_mount = (*generic_session)->mount();
|
|
REQUIRE(generic_mount.has_value());
|
|
REQUIRE(::close(generic_mount->descriptor_fd) == 0);
|
|
generic_mount->descriptor_fd = -1;
|
|
|
|
auto session = (*factory)->prepare_session("session-1", "pi");
|
|
REQUIRE(session.has_value());
|
|
REQUIRE(!(*factory)->prepare_session("session-2", "codex").has_value());
|
|
auto mount = (*session)->mount();
|
|
REQUIRE(mount.has_value());
|
|
REQUIRE(mount->alias == "local-services");
|
|
REQUIRE(mount->target_path == "/run/glove-services/local");
|
|
REQUIRE(mount->directory && !mount->writable);
|
|
REQUIRE(mount->service_proxy_manifest_digest.has_value());
|
|
REQUIRE((::fcntl(mount->descriptor_fd, F_GETFD) & FD_CLOEXEC) != 0);
|
|
unique_fd mount_fd{mount->descriptor_fd};
|
|
mount->descriptor_fd = -1;
|
|
|
|
const std::string request{"opaque\0request", 14U};
|
|
const std::string response{"opaque\0response", 15U};
|
|
std::atomic_bool exact{false};
|
|
auto upstream_exchange = exchange_once(listener.get(), request, response, exact);
|
|
auto client = guest_channel_transport::adopt(
|
|
connect_mount(mount_fd.get(), "sage-observe"),
|
|
16U * 1024U,
|
|
static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
REQUIRE(client.has_value());
|
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{2};
|
|
REQUIRE((*client)->send_frame(request, deadline).has_value());
|
|
auto received = (*client)->receive_frame(deadline);
|
|
REQUIRE(received.has_value());
|
|
REQUIRE(*received == response);
|
|
upstream_exchange.join();
|
|
REQUIRE(exact.load());
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{50});
|
|
auto events = audit->take();
|
|
REQUIRE(events.size() == 2U);
|
|
REQUIRE(events.front().what == glove::audit::action::local_service);
|
|
REQUIRE(events.front().tool_name == "session-1:sage-observe");
|
|
REQUIRE(events.front().arguments_json.empty());
|
|
REQUIRE(events.front().status == glove::mcp::tool_call_status::transport_error);
|
|
REQUIRE(events.front().error_message == "delivery_pending");
|
|
REQUIRE(events.back().status == glove::mcp::tool_call_status::ok);
|
|
REQUIRE(events.back().error_message == "delivered");
|
|
|
|
std::jthread disconnect_upstream{[listener = listener.get()] {
|
|
const int accepted = ::accept4(listener, nullptr, nullptr, SOCK_CLOEXEC);
|
|
auto transport = guest_channel_transport::adopt(
|
|
accepted, 16U * 1024U, static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
if (!transport) {
|
|
return;
|
|
}
|
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{1};
|
|
auto request = (*transport)->receive_frame(deadline);
|
|
if (!request) {
|
|
return;
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{50});
|
|
static_cast<void>((*transport)->send_frame("disconnect-response", deadline));
|
|
}};
|
|
auto disconnect_client = guest_channel_transport::adopt(
|
|
connect_mount(mount_fd.get(), "sage-observe"),
|
|
16U * 1024U,
|
|
static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
REQUIRE(disconnect_client.has_value());
|
|
const auto disconnect_deadline = std::chrono::steady_clock::now() + std::chrono::seconds{1};
|
|
REQUIRE((*disconnect_client)->send_frame("disconnect", disconnect_deadline).has_value());
|
|
disconnect_client->reset();
|
|
disconnect_upstream.join();
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{20});
|
|
auto disconnect_events = audit->take();
|
|
REQUIRE(disconnect_events.size() == 2U);
|
|
REQUIRE(disconnect_events.front().error_message == "delivery_pending");
|
|
REQUIRE(disconnect_events.back().error_message == "delivery_failed");
|
|
REQUIRE(std::ranges::none_of(disconnect_events, [](const auto& event) {
|
|
return event.status == glove::mcp::tool_call_status::ok;
|
|
}));
|
|
|
|
auto terminal_failure_audit = std::make_shared<fail_terminal_sink>();
|
|
auto terminal_failure_factory = local_service_proxy_factory::create(
|
|
options_for(runtime, endpoint, terminal_failure_audit, *adapter), shared_registry
|
|
);
|
|
REQUIRE(terminal_failure_factory.has_value());
|
|
auto terminal_failure_session =
|
|
(*terminal_failure_factory)->prepare_session("terminal-audit-failure", "pi");
|
|
REQUIRE(terminal_failure_session.has_value());
|
|
auto terminal_failure_mount = (*terminal_failure_session)->mount();
|
|
REQUIRE(terminal_failure_mount.has_value());
|
|
unique_fd terminal_failure_mount_fd{terminal_failure_mount->descriptor_fd};
|
|
terminal_failure_mount->descriptor_fd = -1;
|
|
std::atomic_bool terminal_failure_exact{false};
|
|
auto terminal_failure_upstream = exchange_once(
|
|
listener.get(), "terminal-failure", "terminal-response", terminal_failure_exact
|
|
);
|
|
auto terminal_failure_client = guest_channel_transport::adopt(
|
|
connect_mount(terminal_failure_mount_fd.get(), "sage-observe"),
|
|
16U * 1024U,
|
|
static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
REQUIRE(terminal_failure_client.has_value());
|
|
const auto terminal_failure_deadline =
|
|
std::chrono::steady_clock::now() + std::chrono::seconds{1};
|
|
REQUIRE((*terminal_failure_client)
|
|
->send_frame("terminal-failure", terminal_failure_deadline)
|
|
.has_value());
|
|
auto terminal_response = (*terminal_failure_client)->receive_frame(terminal_failure_deadline);
|
|
REQUIRE(terminal_response.has_value());
|
|
REQUIRE(*terminal_response == "terminal-response");
|
|
terminal_failure_upstream.join();
|
|
auto terminal_failure_events = terminal_failure_audit->take();
|
|
REQUIRE(terminal_failure_events.size() == 1U);
|
|
REQUIRE(terminal_failure_events.front().error_message == "delivery_pending");
|
|
REQUIRE(
|
|
terminal_failure_events.front().status == glove::mcp::tool_call_status::transport_error
|
|
);
|
|
|
|
auto timeout_audit = glove::audit::make_memory_sink();
|
|
auto timeout_factory = local_service_proxy_factory::create(
|
|
options_for(runtime, endpoint, timeout_audit, *adapter, 100, 1), shared_registry
|
|
);
|
|
REQUIRE(timeout_factory.has_value());
|
|
auto timeout_session = (*timeout_factory)->prepare_session("timeout", "pi");
|
|
REQUIRE(timeout_session.has_value());
|
|
auto timeout_identity_mount = (*timeout_session)->mount();
|
|
REQUIRE(timeout_identity_mount.has_value());
|
|
REQUIRE(
|
|
timeout_identity_mount->service_proxy_manifest_digest !=
|
|
mount->service_proxy_manifest_digest
|
|
);
|
|
REQUIRE(::close(timeout_identity_mount->descriptor_fd) == 0);
|
|
timeout_identity_mount->descriptor_fd = -1;
|
|
auto timeout_mount = (*timeout_session)->mount();
|
|
REQUIRE(timeout_mount.has_value());
|
|
unique_fd timeout_mount_fd{timeout_mount->descriptor_fd};
|
|
timeout_mount->descriptor_fd = -1;
|
|
std::jthread slow_upstream{[listener = listener.get()] {
|
|
const int accepted = ::accept4(listener, nullptr, nullptr, SOCK_CLOEXEC);
|
|
auto transport = guest_channel_transport::adopt(
|
|
accepted, 16U * 1024U, static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
if (!transport) {
|
|
return;
|
|
}
|
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{1};
|
|
if ((*transport)->receive_frame(deadline)) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{250});
|
|
}
|
|
}};
|
|
auto timeout_client = guest_channel_transport::adopt(
|
|
connect_mount(timeout_mount_fd.get(), "sage-observe"),
|
|
16U * 1024U,
|
|
static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
REQUIRE(timeout_client.has_value());
|
|
const auto timeout_started = std::chrono::steady_clock::now();
|
|
const auto timeout_deadline = timeout_started + std::chrono::seconds{1};
|
|
REQUIRE((*timeout_client)->send_frame("trickle-safe", timeout_deadline).has_value());
|
|
REQUIRE(!(*timeout_client)->receive_frame(timeout_deadline).has_value());
|
|
REQUIRE(std::chrono::steady_clock::now() - timeout_started < std::chrono::milliseconds{500});
|
|
slow_upstream.join();
|
|
auto timeout_events = timeout_audit->take();
|
|
REQUIRE(timeout_events.size() <= 1U);
|
|
REQUIRE(std::ranges::none_of(timeout_events, [](const auto& event) {
|
|
return event.status == glove::mcp::tool_call_status::ok;
|
|
}));
|
|
if (!timeout_events.empty()) {
|
|
REQUIRE(timeout_events.front().arguments_json.empty());
|
|
REQUIRE(timeout_events.front().error_message == "delivery_failed");
|
|
}
|
|
|
|
auto slow_audit = std::make_shared<delayed_sink>(std::chrono::milliseconds{150});
|
|
auto slow_audit_factory = local_service_proxy_factory::create(
|
|
options_for(runtime, endpoint, slow_audit, *adapter, 100, 1), shared_registry
|
|
);
|
|
REQUIRE(slow_audit_factory.has_value());
|
|
auto slow_audit_session = (*slow_audit_factory)->prepare_session("audit-deadline", "pi");
|
|
REQUIRE(slow_audit_session.has_value());
|
|
auto slow_audit_mount = (*slow_audit_session)->mount();
|
|
REQUIRE(slow_audit_mount.has_value());
|
|
unique_fd slow_audit_mount_fd{slow_audit_mount->descriptor_fd};
|
|
slow_audit_mount->descriptor_fd = -1;
|
|
std::atomic_bool slow_audit_exact{false};
|
|
auto slow_audit_upstream =
|
|
exchange_once(listener.get(), "audit-deadline", "late-response", slow_audit_exact);
|
|
auto slow_audit_client = guest_channel_transport::adopt(
|
|
connect_mount(slow_audit_mount_fd.get(), "sage-observe"),
|
|
16U * 1024U,
|
|
static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
REQUIRE(slow_audit_client.has_value());
|
|
const auto slow_audit_deadline = std::chrono::steady_clock::now() + std::chrono::seconds{1};
|
|
REQUIRE((*slow_audit_client)->send_frame("audit-deadline", slow_audit_deadline).has_value());
|
|
REQUIRE(!(*slow_audit_client)->receive_frame(slow_audit_deadline).has_value());
|
|
slow_audit_upstream.join();
|
|
auto slow_audit_events = slow_audit->take();
|
|
REQUIRE(slow_audit_events.size() == 1U);
|
|
REQUIRE(slow_audit_events.front().error_message == "delivery_pending");
|
|
REQUIRE(slow_audit_events.front().status == glove::mcp::tool_call_status::transport_error);
|
|
|
|
auto bounded_audit = glove::audit::make_memory_sink();
|
|
auto bounded_factory = local_service_proxy_factory::create(
|
|
options_for(runtime, endpoint, bounded_audit, *adapter, 1'000, 1), shared_registry
|
|
);
|
|
REQUIRE(bounded_factory.has_value());
|
|
auto bounded_session = (*bounded_factory)->prepare_session("bounded", "pi");
|
|
REQUIRE(bounded_session.has_value());
|
|
auto bounded_mount = (*bounded_session)->mount();
|
|
REQUIRE(bounded_mount.has_value());
|
|
unique_fd bounded_mount_fd{bounded_mount->descriptor_fd};
|
|
bounded_mount->descriptor_fd = -1;
|
|
std::atomic_int active{0};
|
|
std::atomic_int maximum{0};
|
|
std::jthread bounded_upstream{[listener = listener.get(), &active, &maximum] {
|
|
std::vector<std::thread> handlers;
|
|
for (int request_index = 0; request_index < 2; ++request_index) {
|
|
const int accepted = ::accept4(listener, nullptr, nullptr, SOCK_CLOEXEC);
|
|
if (accepted < 0) {
|
|
return;
|
|
}
|
|
handlers.emplace_back([accepted, &active, &maximum] {
|
|
auto transport = guest_channel_transport::adopt(
|
|
accepted, 16U * 1024U, static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
if (!transport) {
|
|
return;
|
|
}
|
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{2};
|
|
auto request = (*transport)->receive_frame(deadline);
|
|
if (!request) {
|
|
return;
|
|
}
|
|
const int current = active.fetch_add(1) + 1;
|
|
maximum.store(std::max(maximum.load(), current));
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{100});
|
|
active.fetch_sub(1);
|
|
static_cast<void>((*transport)->send_frame(*request, deadline));
|
|
});
|
|
}
|
|
for (auto& handler : handlers) {
|
|
handler.join();
|
|
}
|
|
}};
|
|
std::atomic_int bounded_successes{0};
|
|
auto bounded_client = [&](std::string payload) {
|
|
auto transport = guest_channel_transport::adopt(
|
|
connect_mount(bounded_mount_fd.get(), "sage-observe"),
|
|
16U * 1024U,
|
|
static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
if (!transport) {
|
|
return;
|
|
}
|
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{2};
|
|
if ((*transport)->send_frame(payload, deadline)) {
|
|
auto response = (*transport)->receive_frame(deadline);
|
|
if (response && *response == payload) {
|
|
bounded_successes.fetch_add(1);
|
|
}
|
|
}
|
|
};
|
|
std::jthread first_bounded{bounded_client, "one"};
|
|
std::jthread second_bounded{bounded_client, "two"};
|
|
first_bounded.join();
|
|
second_bounded.join();
|
|
bounded_upstream.join();
|
|
REQUIRE(bounded_successes.load() == 2);
|
|
REQUIRE(maximum.load() == 1);
|
|
|
|
const auto later_endpoint = upstream / "later.sock";
|
|
auto later_listener = make_listener(later_endpoint);
|
|
REQUIRE(later_listener.get() >= 0);
|
|
|
|
auto non_pi_options = options_for(runtime, endpoint, audit, *adapter, 1'000, 1);
|
|
non_pi_options.endpoints = {
|
|
{
|
|
.alias = "codex-only",
|
|
.socket_path = endpoint,
|
|
.runtime_ids = {"codex"},
|
|
},
|
|
{
|
|
.alias = "opencode-only",
|
|
.socket_path = later_endpoint,
|
|
.runtime_ids = {"opencode"},
|
|
},
|
|
};
|
|
auto non_pi_factory =
|
|
local_service_proxy_factory::create(std::move(non_pi_options), shared_registry);
|
|
REQUIRE(non_pi_factory.has_value());
|
|
REQUIRE((*non_pi_factory)->prepare_session("codex-session", "codex").has_value());
|
|
REQUIRE((*non_pi_factory)->prepare_session("opencode-session", "opencode").has_value());
|
|
|
|
auto fair_options = options_for(runtime, endpoint, audit, *adapter, 2'000, 1);
|
|
fair_options.endpoints = {
|
|
{
|
|
.alias = "a-hot",
|
|
.socket_path = endpoint,
|
|
.runtime_ids = {"pi"},
|
|
},
|
|
{
|
|
.alias = "z-later",
|
|
.socket_path = later_endpoint,
|
|
.runtime_ids = {"pi"},
|
|
},
|
|
};
|
|
auto fair_factory =
|
|
local_service_proxy_factory::create(std::move(fair_options), shared_registry);
|
|
REQUIRE(fair_factory.has_value());
|
|
auto fair_session = (*fair_factory)->prepare_session("fair", "pi");
|
|
REQUIRE(fair_session.has_value());
|
|
auto fair_mount = (*fair_session)->mount();
|
|
REQUIRE(fair_mount.has_value());
|
|
unique_fd fair_mount_fd{fair_mount->descriptor_fd};
|
|
fair_mount->descriptor_fd = -1;
|
|
std::jthread hot_upstream{[listener = listener.get()] {
|
|
for (int index = 0; index < 4; ++index) {
|
|
const int accepted = ::accept4(listener, nullptr, nullptr, SOCK_CLOEXEC);
|
|
auto transport = guest_channel_transport::adopt(
|
|
accepted, 16U * 1024U, static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
if (!transport) {
|
|
return;
|
|
}
|
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds{2};
|
|
auto request = (*transport)->receive_frame(deadline);
|
|
if (!request) {
|
|
return;
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{75});
|
|
static_cast<void>((*transport)->send_frame(*request, deadline));
|
|
}
|
|
}};
|
|
std::atomic_bool later_exact{false};
|
|
auto later_upstream =
|
|
exchange_once(later_listener.get(), "later-request", "later-response", later_exact);
|
|
std::vector<std::unique_ptr<guest_channel_transport>> hot_clients;
|
|
hot_clients.reserve(4U);
|
|
for (int index = 0; index < 4; ++index) {
|
|
auto hot_client = guest_channel_transport::adopt(
|
|
connect_mount(fair_mount_fd.get(), "a-hot"),
|
|
16U * 1024U,
|
|
static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
REQUIRE(hot_client.has_value());
|
|
const auto client_deadline = std::chrono::steady_clock::now() + std::chrono::seconds{2};
|
|
REQUIRE(
|
|
(*hot_client)->send_frame("hot-" + std::to_string(index), client_deadline).has_value()
|
|
);
|
|
hot_clients.push_back(std::move(*hot_client));
|
|
}
|
|
auto later_client = guest_channel_transport::adopt(
|
|
connect_mount(fair_mount_fd.get(), "z-later"),
|
|
16U * 1024U,
|
|
static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
REQUIRE(later_client.has_value());
|
|
const auto later_deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds{250};
|
|
REQUIRE((*later_client)->send_frame("later-request", later_deadline).has_value());
|
|
auto later_response = (*later_client)->receive_frame(later_deadline);
|
|
REQUIRE(later_response.has_value());
|
|
REQUIRE(*later_response == "later-response");
|
|
later_upstream.join();
|
|
REQUIRE(later_exact.load());
|
|
for (auto& hot_client : hot_clients) {
|
|
const auto hot_deadline = std::chrono::steady_clock::now() + std::chrono::seconds{2};
|
|
REQUIRE(hot_client->receive_frame(hot_deadline).has_value());
|
|
}
|
|
hot_upstream.join();
|
|
|
|
if (privileged_only) {
|
|
const auto materializations = temporary.root() / "materializations";
|
|
REQUIRE(make_owner_directory(materializations));
|
|
auto preparer_result = glove::control::linux_detail::linux_session_preparer::create(
|
|
materializations.string(), audit, *factory
|
|
);
|
|
if (!preparer_result) {
|
|
std::fprintf(
|
|
stderr,
|
|
"Linux capability setup failed: %s\n",
|
|
preparer_result.error().c_str()
|
|
);
|
|
return 1;
|
|
}
|
|
auto preparer = std::make_shared<glove::control::linux_detail::linux_session_preparer>(
|
|
std::move(*preparer_result)
|
|
);
|
|
const auto now_ms =
|
|
static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::system_clock::now().time_since_epoch()
|
|
)
|
|
.count());
|
|
{
|
|
auto pi_only_options = options_for(runtime, endpoint, audit, *adapter);
|
|
pi_only_options.endpoints.front().runtime_ids = {"pi"};
|
|
auto pi_only_factory =
|
|
local_service_proxy_factory::create(std::move(pi_only_options), shared_registry);
|
|
REQUIRE(pi_only_factory.has_value());
|
|
auto pi_only_preparer = glove::control::linux_detail::linux_session_preparer::create(
|
|
materializations.string(), audit, *pi_only_factory
|
|
);
|
|
REQUIRE(pi_only_preparer.has_value());
|
|
auto denied_grant = path_grant_for(upstream, "non-allowlisted-runtime-parent");
|
|
REQUIRE(denied_grant.has_value());
|
|
auto denied_inputs = preparation_inputs(now_ms);
|
|
denied_inputs.session.session_id = "proxy-path-non-allowlisted-runtime";
|
|
denied_inputs.authorization_id = "approval-proxy-path-non-allowlisted-runtime";
|
|
denied_inputs.path_grants.push_back(std::move(*denied_grant));
|
|
REQUIRE(!pi_only_preparer->prepare(std::move(denied_inputs), now_ms).has_value());
|
|
REQUIRE(std::filesystem::is_empty(materializations));
|
|
|
|
auto blocked_inputs = preparation_inputs(now_ms);
|
|
blocked_inputs.session.session_id = "proxy-path-conflict";
|
|
blocked_inputs.authorization_id = "approval-proxy-path-conflict";
|
|
blocked_inputs.path_grants.push_back(std::move(*direct_parent_grant));
|
|
auto blocked = preparer->prepare(std::move(blocked_inputs), now_ms);
|
|
REQUIRE(!blocked.has_value());
|
|
REQUIRE(blocked.error().find(upstream.string()) == std::string::npos);
|
|
REQUIRE(std::filesystem::is_empty(materializations));
|
|
|
|
auto prepared = preparer->prepare(preparation_inputs(now_ms), now_ms);
|
|
REQUIRE(prepared.has_value());
|
|
REQUIRE(prepared->local_service_proxy != nullptr);
|
|
REQUIRE(
|
|
std::ranges::find(
|
|
prepared->profile.environment,
|
|
"GLOVE_LOCAL_SERVICE_DIR=/run/glove-services/local"
|
|
) != prepared->profile.environment.end()
|
|
);
|
|
const auto mounts = prepared->lifecycle->mounts();
|
|
const auto service_mount = std::ranges::find(
|
|
mounts,
|
|
std::string{"/run/glove-services/local"},
|
|
&glove::supervisor::linux_detail::session_mount::target_path
|
|
);
|
|
REQUIRE(service_mount != mounts.end());
|
|
REQUIRE(service_mount->alias == "local-services");
|
|
REQUIRE(service_mount->directory && !service_mount->writable);
|
|
REQUIRE(
|
|
service_mount->service_proxy_manifest_digest ==
|
|
prepared->binding.service_proxy_manifest_digest
|
|
);
|
|
}
|
|
#if !GLOVE_LOCAL_PROXY_TSAN
|
|
unique_fd reused_install_descriptor;
|
|
{
|
|
auto plain_preparer_result =
|
|
glove::control::linux_detail::linux_session_preparer::create(
|
|
materializations.string(), audit
|
|
);
|
|
REQUIRE(plain_preparer_result.has_value());
|
|
auto plain_prepared =
|
|
plain_preparer_result->prepare(preparation_inputs(now_ms + 1U), now_ms + 1U);
|
|
REQUIRE(plain_prepared.has_value());
|
|
auto install_session = (*factory)->prepare_session("install-allocation-fault", "pi");
|
|
REQUIRE(install_session.has_value());
|
|
auto install_mount = (*install_session)->mount();
|
|
REQUIRE(install_mount.has_value());
|
|
const int transferred_descriptor = install_mount->descriptor_fd;
|
|
const auto before_install = descriptor_count() - 1U;
|
|
allocation_fault::remaining.store(0);
|
|
auto installed =
|
|
plain_prepared->lifecycle->install_service_mount(std::move(*install_mount));
|
|
allocation_fault::remaining.store(-1);
|
|
REQUIRE(!installed.has_value());
|
|
REQUIRE(::fcntl(transferred_descriptor, F_GETFD) == -1);
|
|
REQUIRE(errno == EBADF);
|
|
const auto after_install = descriptor_count();
|
|
if (after_install != before_install) {
|
|
std::fprintf(
|
|
stderr,
|
|
"install allocation descriptor drift: before=%zu after=%zu\n",
|
|
before_install,
|
|
after_install
|
|
);
|
|
}
|
|
REQUIRE(after_install == before_install);
|
|
reused_install_descriptor = unique_fd{::open("/dev/null", O_RDONLY | O_CLOEXEC)};
|
|
REQUIRE(reused_install_descriptor.get() == transferred_descriptor);
|
|
}
|
|
REQUIRE(::fcntl(reused_install_descriptor.get(), F_GETFD) >= 0);
|
|
#endif
|
|
|
|
auto runtime_result = glove::control::linux_detail::linux_session_runtime::create(
|
|
shared_registry, preparer, {}
|
|
);
|
|
REQUIRE(runtime_result.has_value());
|
|
auto concrete_runtime =
|
|
std::shared_ptr<glove::control::linux_detail::linux_session_runtime>{
|
|
std::move(*runtime_result)
|
|
};
|
|
REQUIRE(::chmod(endpoint.c_str(), 0660) == 0);
|
|
auto stale_endpoint_capability = (*factory)->try_seal(concrete_runtime);
|
|
REQUIRE(!stale_endpoint_capability.has_value());
|
|
REQUIRE(::chmod(endpoint.c_str(), 0600) == 0);
|
|
REQUIRE(::chmod(runtime.c_str(), 0755) == 0);
|
|
auto stale_root_capability = (*factory)->try_seal(concrete_runtime);
|
|
REQUIRE(!stale_root_capability.has_value());
|
|
REQUIRE(::chmod(runtime.c_str(), 0700) == 0);
|
|
|
|
auto sealed_result = (*factory)->try_seal(concrete_runtime);
|
|
REQUIRE(sealed_result.has_value());
|
|
REQUIRE(sealed_result->has_value());
|
|
auto sealed = std::move(**sealed_result);
|
|
REQUIRE(sealed->operational_for(concrete_runtime.get(), shared_registry.get()));
|
|
#if !GLOVE_LOCAL_PROXY_TSAN
|
|
allocation_fault::remaining.store(0);
|
|
auto failed_capability = (*factory)->try_seal(concrete_runtime);
|
|
allocation_fault::remaining.store(-1);
|
|
REQUIRE(!failed_capability.has_value());
|
|
#endif
|
|
|
|
auto lifetime_registry_result = glove::control::session_registry::open_or_create(
|
|
temporary.root() / "lifetime-sessions.journal",
|
|
shared_validator,
|
|
{},
|
|
glove::control::default_session_registry_bytes,
|
|
catalog
|
|
);
|
|
REQUIRE(lifetime_registry_result.has_value());
|
|
auto lifetime_registry =
|
|
std::shared_ptr<glove::control::session_registry>{std::move(*lifetime_registry_result)};
|
|
auto registry_mismatch_runtime_result =
|
|
glove::control::linux_detail::linux_session_runtime::create(
|
|
lifetime_registry, preparer, {}
|
|
);
|
|
REQUIRE(registry_mismatch_runtime_result.has_value());
|
|
auto registry_mismatch_runtime =
|
|
std::shared_ptr<glove::control::linux_detail::linux_session_runtime>{
|
|
std::move(*registry_mismatch_runtime_result)
|
|
};
|
|
auto registry_mismatch_capability =
|
|
(*factory)->try_seal(std::move(registry_mismatch_runtime));
|
|
REQUIRE(!registry_mismatch_capability.has_value());
|
|
|
|
auto lifetime_factory_result = local_service_proxy_factory::create(
|
|
options_for(runtime, endpoint, audit, *adapter), lifetime_registry
|
|
);
|
|
REQUIRE(lifetime_factory_result.has_value());
|
|
auto lifetime_factory = std::move(*lifetime_factory_result);
|
|
auto lifetime_preparer_result =
|
|
glove::control::linux_detail::linux_session_preparer::create(
|
|
materializations.string(), audit, lifetime_factory
|
|
);
|
|
REQUIRE(lifetime_preparer_result.has_value());
|
|
auto lifetime_preparer =
|
|
std::make_shared<glove::control::linux_detail::linux_session_preparer>(
|
|
std::move(*lifetime_preparer_result)
|
|
);
|
|
auto lifetime_runtime_result = glove::control::linux_detail::linux_session_runtime::create(
|
|
lifetime_registry, lifetime_preparer, {}
|
|
);
|
|
REQUIRE(lifetime_runtime_result.has_value());
|
|
auto lifetime_runtime =
|
|
std::shared_ptr<glove::control::linux_detail::linux_session_runtime>{
|
|
std::move(*lifetime_runtime_result)
|
|
};
|
|
auto lifetime_capability_result = lifetime_factory->try_seal(lifetime_runtime);
|
|
REQUIRE(lifetime_capability_result.has_value());
|
|
REQUIRE(lifetime_capability_result->has_value());
|
|
auto lifetime_capability = std::move(**lifetime_capability_result);
|
|
const auto* lifetime_runtime_identity = lifetime_runtime.get();
|
|
const auto* lifetime_registry_identity = lifetime_registry.get();
|
|
std::weak_ptr<glove::control::session_registry> weak_lifetime_registry = lifetime_registry;
|
|
std::weak_ptr<glove::control::linux_detail::linux_session_preparer> weak_lifetime_preparer =
|
|
lifetime_preparer;
|
|
std::weak_ptr<local_service_proxy_factory> weak_lifetime_factory = lifetime_factory;
|
|
std::weak_ptr<glove::control::linux_detail::linux_session_runtime> weak_lifetime_runtime =
|
|
lifetime_runtime;
|
|
lifetime_runtime.reset();
|
|
lifetime_preparer.reset();
|
|
lifetime_factory.reset();
|
|
lifetime_registry.reset();
|
|
REQUIRE(lifetime_capability->operational_for(
|
|
lifetime_runtime_identity, lifetime_registry_identity
|
|
));
|
|
auto lifetime_sessions = lifetime_runtime_identity->list();
|
|
REQUIRE(lifetime_sessions.has_value());
|
|
REQUIRE(lifetime_sessions->empty());
|
|
REQUIRE(!weak_lifetime_runtime.expired());
|
|
REQUIRE(!weak_lifetime_preparer.expired());
|
|
REQUIRE(!weak_lifetime_factory.expired());
|
|
REQUIRE(!weak_lifetime_registry.expired());
|
|
lifetime_capability.reset();
|
|
REQUIRE(weak_lifetime_runtime.expired());
|
|
REQUIRE(weak_lifetime_preparer.expired());
|
|
REQUIRE(weak_lifetime_factory.expired());
|
|
REQUIRE(weak_lifetime_registry.expired());
|
|
|
|
auto generic_options = options_for(runtime, endpoint, audit, {}, 1'000, 1);
|
|
auto generic_pi_factory =
|
|
local_service_proxy_factory::create(std::move(generic_options), shared_registry);
|
|
REQUIRE(generic_pi_factory.has_value());
|
|
auto generic_preparer_result = glove::control::linux_detail::linux_session_preparer::create(
|
|
materializations.string(), audit, *generic_pi_factory
|
|
);
|
|
REQUIRE(generic_preparer_result.has_value());
|
|
auto generic_preparer =
|
|
std::make_shared<glove::control::linux_detail::linux_session_preparer>(
|
|
std::move(*generic_preparer_result)
|
|
);
|
|
auto generic_runtime = glove::control::linux_detail::linux_session_runtime::create(
|
|
shared_registry, generic_preparer, {}
|
|
);
|
|
REQUIRE(generic_runtime.has_value());
|
|
auto generic_runtime_shared =
|
|
std::shared_ptr<glove::control::linux_detail::linux_session_runtime>{
|
|
std::move(*generic_runtime)
|
|
};
|
|
auto generic_capability = (*generic_pi_factory)->try_seal(generic_runtime_shared);
|
|
REQUIRE(generic_capability.has_value());
|
|
REQUIRE(!generic_capability->has_value());
|
|
|
|
auto non_pi_preparer_result = glove::control::linux_detail::linux_session_preparer::create(
|
|
materializations.string(), audit, *non_pi_factory
|
|
);
|
|
REQUIRE(non_pi_preparer_result.has_value());
|
|
auto non_pi_preparer =
|
|
std::make_shared<glove::control::linux_detail::linux_session_preparer>(
|
|
std::move(*non_pi_preparer_result)
|
|
);
|
|
auto non_pi_runtime = glove::control::linux_detail::linux_session_runtime::create(
|
|
shared_registry, non_pi_preparer, {}
|
|
);
|
|
REQUIRE(non_pi_runtime.has_value());
|
|
auto non_pi_runtime_shared =
|
|
std::shared_ptr<glove::control::linux_detail::linux_session_runtime>{
|
|
std::move(*non_pi_runtime)
|
|
};
|
|
REQUIRE((*non_pi_factory)->manages_runtime("codex"));
|
|
REQUIRE((*non_pi_factory)->manages_runtime("opencode"));
|
|
auto non_pi_capability = (*non_pi_factory)->try_seal(non_pi_runtime_shared);
|
|
REQUIRE(non_pi_capability.has_value());
|
|
REQUIRE(!non_pi_capability->has_value());
|
|
|
|
auto mismatched_factory = local_service_proxy_factory::create(
|
|
options_for(runtime, endpoint, audit, *adapter), shared_registry
|
|
);
|
|
REQUIRE(mismatched_factory.has_value());
|
|
auto mismatched_capability = (*mismatched_factory)->try_seal(concrete_runtime);
|
|
REQUIRE(!mismatched_capability.has_value());
|
|
|
|
constexpr std::string_view bootstrap_secret =
|
|
"1111111111111111111111111111111111111111111111111111111111111111";
|
|
const auto key_path = temporary.root() / "capability.key";
|
|
REQUIRE(write_owner_file(
|
|
key_path, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
|
|
));
|
|
auto producer = glove::container::receipt_audit_producer::initialize({
|
|
.key_path = key_path,
|
|
.journal_path = temporary.root() / "capability-receipts.journal",
|
|
});
|
|
REQUIRE(producer.has_value());
|
|
REQUIRE((*producer)->acknowledge_bootstrap((*producer)->anchor()).has_value());
|
|
auto protocol = glove::control::receipt_audit_protocol::create(
|
|
bootstrap_secret,
|
|
*producer,
|
|
shared_validator,
|
|
shared_registry,
|
|
concrete_runtime,
|
|
{},
|
|
materializations.string(),
|
|
sealed
|
|
);
|
|
REQUIRE(protocol.has_value());
|
|
const auto request_frame =
|
|
std::string{"{\"jsonrpc\":\"2.0\",\"id\":\"capability\",\"method\":\"capabilities\","
|
|
"\"params\":{\"schema_version\":1,\"bootstrap_secret\":\""} +
|
|
std::string{bootstrap_secret} + "\",\"deadline_ms\":2000,\"payload\":null}}";
|
|
auto capability = (*protocol)->handle_frame(request_frame, 1'000);
|
|
REQUIRE(capability.has_value());
|
|
REQUIRE(
|
|
capability->find("\"observation_intent_channel_schema_version\":1") != std::string::npos
|
|
);
|
|
|
|
auto runtime_mismatch_protocol = glove::control::receipt_audit_protocol::create(
|
|
bootstrap_secret,
|
|
*producer,
|
|
shared_validator,
|
|
shared_registry,
|
|
{},
|
|
{},
|
|
materializations.string(),
|
|
sealed
|
|
);
|
|
REQUIRE(runtime_mismatch_protocol.has_value());
|
|
auto runtime_mismatch = (*runtime_mismatch_protocol)->handle_frame(request_frame, 1'000);
|
|
REQUIRE(runtime_mismatch.has_value());
|
|
REQUIRE(
|
|
runtime_mismatch->find("\"observation_intent_channel_schema_version\":0") !=
|
|
std::string::npos
|
|
);
|
|
|
|
auto other_registry_result = glove::control::session_registry::open_or_create(
|
|
temporary.root() / "other-capability-sessions.journal",
|
|
shared_validator,
|
|
{},
|
|
glove::control::default_session_registry_bytes,
|
|
catalog
|
|
);
|
|
REQUIRE(other_registry_result.has_value());
|
|
auto other_registry =
|
|
std::shared_ptr<glove::control::session_registry>{std::move(*other_registry_result)};
|
|
auto registry_mismatch_protocol = glove::control::receipt_audit_protocol::create(
|
|
bootstrap_secret,
|
|
*producer,
|
|
shared_validator,
|
|
other_registry,
|
|
concrete_runtime,
|
|
{},
|
|
materializations.string(),
|
|
sealed
|
|
);
|
|
REQUIRE(registry_mismatch_protocol.has_value());
|
|
auto registry_mismatch = (*registry_mismatch_protocol)->handle_frame(request_frame, 1'000);
|
|
REQUIRE(registry_mismatch.has_value());
|
|
REQUIRE(
|
|
registry_mismatch->find("\"observation_intent_channel_schema_version\":0") !=
|
|
std::string::npos
|
|
);
|
|
|
|
REQUIRE(::chmod(endpoint.c_str(), 0660) == 0);
|
|
REQUIRE(!sealed->operational_for(concrete_runtime.get(), shared_registry.get()));
|
|
auto drifted_capability = (*protocol)->handle_frame(request_frame, 1'000);
|
|
REQUIRE(drifted_capability.has_value());
|
|
REQUIRE(
|
|
drifted_capability->find("\"observation_intent_channel_schema_version\":0") !=
|
|
std::string::npos
|
|
);
|
|
REQUIRE(::chmod(endpoint.c_str(), 0600) == 0);
|
|
return 0;
|
|
}
|
|
|
|
REQUIRE(::chmod(endpoint.c_str(), 0660) == 0);
|
|
REQUIRE(!(*factory)->operational());
|
|
auto drift_error = (*factory)->prepare_session("mode-drift", "pi");
|
|
REQUIRE(!drift_error.has_value());
|
|
REQUIRE(drift_error.error().find(endpoint.string()) == std::string::npos);
|
|
REQUIRE(drift_error.error().find("secret") == std::string::npos);
|
|
REQUIRE(::chmod(endpoint.c_str(), 0600) == 0);
|
|
REQUIRE((*factory)->operational());
|
|
REQUIRE(::chmod(upstream.c_str(), 0755) == 0);
|
|
REQUIRE(!(*factory)->operational());
|
|
REQUIRE(::chmod(upstream.c_str(), 0700) == 0);
|
|
REQUIRE((*factory)->operational());
|
|
if (::geteuid() == 0) {
|
|
REQUIRE(::chown(endpoint.c_str(), 1, 1) == 0);
|
|
REQUIRE(!(*factory)->operational());
|
|
REQUIRE(::chown(endpoint.c_str(), 0, 0) == 0);
|
|
REQUIRE((*factory)->operational());
|
|
}
|
|
const auto extra_link = upstream / "extra-link.sock";
|
|
REQUIRE(::link(endpoint.c_str(), extra_link.c_str()) == 0);
|
|
REQUIRE(!(*factory)->operational());
|
|
REQUIRE(::unlink(extra_link.c_str()) == 0);
|
|
REQUIRE((*factory)->operational());
|
|
|
|
const auto session_directory = descriptor_path(mount_fd.get());
|
|
REQUIRE(!session_directory.empty());
|
|
const auto guest_socket = session_directory / "sage-observe";
|
|
REQUIRE(::unlink(guest_socket.c_str()) == 0);
|
|
std::ofstream{guest_socket} << "replacement";
|
|
REQUIRE(std::filesystem::is_regular_file(guest_socket));
|
|
client->reset();
|
|
session->reset();
|
|
mount_fd = unique_fd{};
|
|
REQUIRE(std::filesystem::is_regular_file(guest_socket));
|
|
std::filesystem::remove(guest_socket);
|
|
std::filesystem::remove(session_directory);
|
|
|
|
const auto descriptors_before = descriptor_count();
|
|
auto leak_session = (*factory)->prepare_session("fd-check", "pi");
|
|
REQUIRE(leak_session.has_value());
|
|
auto leak_mount = (*leak_session)->mount();
|
|
REQUIRE(leak_mount.has_value());
|
|
REQUIRE(::close(leak_mount->descriptor_fd) == 0);
|
|
leak_mount->descriptor_fd = -1;
|
|
|
|
#if !GLOVE_LOCAL_PROXY_TSAN
|
|
const auto mount_fault_descriptors = descriptor_count();
|
|
bool observed_mount_allocation_failure = false;
|
|
bool observed_mount_success = false;
|
|
for (std::int64_t fail_after = 0; fail_after < 8; ++fail_after) {
|
|
allocation_fault::remaining.store(fail_after);
|
|
try {
|
|
auto mount_attempt = (*leak_session)->mount();
|
|
allocation_fault::remaining.store(-1);
|
|
if (mount_attempt) {
|
|
REQUIRE(::close(mount_attempt->descriptor_fd) == 0);
|
|
mount_attempt->descriptor_fd = -1;
|
|
observed_mount_success = true;
|
|
} else {
|
|
observed_mount_allocation_failure = true;
|
|
}
|
|
} catch (...) {
|
|
allocation_fault::remaining.store(-1);
|
|
REQUIRE(false);
|
|
}
|
|
REQUIRE(descriptor_count() == mount_fault_descriptors);
|
|
}
|
|
REQUIRE(observed_mount_allocation_failure);
|
|
REQUIRE(observed_mount_success);
|
|
#endif
|
|
|
|
leak_session->reset();
|
|
REQUIRE(descriptor_count() == descriptors_before);
|
|
|
|
#if !GLOVE_LOCAL_PROXY_TSAN
|
|
const auto fault_entries = directory_entry_count(runtime);
|
|
const auto fault_descriptors = descriptor_count();
|
|
bool observed_late_allocation_failure = false;
|
|
bool observed_success_after_failures = false;
|
|
for (std::int64_t fail_after = 0; fail_after < 128; ++fail_after) {
|
|
const auto fault_session_id = "allocation-fault-" + std::to_string(fail_after);
|
|
bool failed = false;
|
|
allocation_fault::remaining.store(fail_after);
|
|
try {
|
|
auto attempt = (*factory)->prepare_session(fault_session_id, "pi");
|
|
allocation_fault::remaining.store(-1);
|
|
failed = !attempt.has_value();
|
|
if (attempt) {
|
|
attempt->reset();
|
|
observed_success_after_failures = true;
|
|
}
|
|
} catch (...) {
|
|
allocation_fault::remaining.store(-1);
|
|
std::fprintf(
|
|
stderr,
|
|
"allocation fault escaped expected at %lld\n",
|
|
static_cast<long long>(fail_after)
|
|
);
|
|
REQUIRE(false);
|
|
}
|
|
allocation_fault::remaining.store(-1);
|
|
observed_late_allocation_failure =
|
|
observed_late_allocation_failure || (failed && fail_after >= 10);
|
|
REQUIRE(directory_entry_count(runtime) == fault_entries);
|
|
REQUIRE(descriptor_count() == fault_descriptors);
|
|
if (observed_late_allocation_failure && observed_success_after_failures) {
|
|
break;
|
|
}
|
|
}
|
|
REQUIRE(observed_late_allocation_failure);
|
|
REQUIRE(observed_success_after_failures);
|
|
#endif
|
|
|
|
auto cancelled_session = (*factory)->prepare_session("cancelled", "pi");
|
|
REQUIRE(cancelled_session.has_value());
|
|
auto cancelled_mount = (*cancelled_session)->mount();
|
|
REQUIRE(cancelled_mount.has_value());
|
|
unique_fd cancelled_mount_fd{cancelled_mount->descriptor_fd};
|
|
cancelled_mount->descriptor_fd = -1;
|
|
unique_fd partial_client{connect_mount(cancelled_mount_fd.get(), "sage-observe")};
|
|
REQUIRE(partial_client.get() >= 0);
|
|
constexpr std::array<unsigned char, 2> partial_prefix{0, 0};
|
|
REQUIRE(
|
|
::write(partial_client.get(), partial_prefix.data(), partial_prefix.size()) ==
|
|
static_cast<ssize_t>(partial_prefix.size())
|
|
);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{20});
|
|
const auto cancel_started = std::chrono::steady_clock::now();
|
|
cancelled_session->reset();
|
|
REQUIRE(std::chrono::steady_clock::now() - cancel_started < std::chrono::milliseconds{500});
|
|
|
|
const auto symlink_parent = temporary.root() / "symlink-parent";
|
|
REQUIRE(make_owner_directory(symlink_parent));
|
|
const auto symlink_endpoint = symlink_parent / "service.sock";
|
|
REQUIRE(::symlink(endpoint.c_str(), symlink_endpoint.c_str()) == 0);
|
|
REQUIRE(!local_service_proxy_factory::create(
|
|
options_for(runtime, symlink_endpoint, audit, *adapter), shared_registry
|
|
)
|
|
.has_value());
|
|
|
|
const auto failed_registry_path = temporary.root() / "failed-sessions.journal";
|
|
auto failed_registry = glove::control::session_registry::open_or_create(
|
|
failed_registry_path,
|
|
shared_validator,
|
|
{},
|
|
glove::control::default_session_registry_bytes,
|
|
catalog
|
|
);
|
|
REQUIRE(failed_registry.has_value());
|
|
auto failed_registry_shared =
|
|
std::shared_ptr<glove::control::session_registry>{std::move(*failed_registry)};
|
|
auto failing_audit = std::make_shared<rejecting_sink>();
|
|
auto failing_factory = local_service_proxy_factory::create(
|
|
options_for(runtime, endpoint, failing_audit, *adapter, 250), failed_registry_shared
|
|
);
|
|
REQUIRE(failing_factory.has_value());
|
|
auto failing_session = (*failing_factory)->prepare_session("audit-failure", "pi");
|
|
REQUIRE(failing_session.has_value());
|
|
auto failing_mount = (*failing_session)->mount();
|
|
REQUIRE(failing_mount.has_value());
|
|
unique_fd failing_mount_fd{failing_mount->descriptor_fd};
|
|
failing_mount->descriptor_fd = -1;
|
|
std::atomic_bool failing_exact{false};
|
|
auto failing_exchange =
|
|
exchange_once(listener.get(), "secret-payload", "secret-response", failing_exact);
|
|
auto failing_client = guest_channel_transport::adopt(
|
|
connect_mount(failing_mount_fd.get(), "sage-observe"),
|
|
16U * 1024U,
|
|
static_cast<std::uint32_t>(::geteuid())
|
|
);
|
|
REQUIRE(failing_client.has_value());
|
|
const auto failing_deadline = std::chrono::steady_clock::now() + std::chrono::seconds{1};
|
|
REQUIRE((*failing_client)->send_frame("secret-payload", failing_deadline).has_value());
|
|
REQUIRE(!(*failing_client)->receive_frame(failing_deadline).has_value());
|
|
failing_exchange.join();
|
|
REQUIRE(failing_exact.load());
|
|
std::weak_ptr<glove::control::session_registry> retained_registry = failed_registry_shared;
|
|
failed_registry_shared.reset();
|
|
REQUIRE(!retained_registry.expired());
|
|
REQUIRE((*failing_factory)->operational());
|
|
failing_client->reset();
|
|
failing_session->reset();
|
|
failing_factory->reset();
|
|
REQUIRE(retained_registry.expired());
|
|
|
|
listener = unique_fd{};
|
|
REQUIRE(::unlink(endpoint.c_str()) == 0);
|
|
auto replacement_listener = make_listener(endpoint);
|
|
REQUIRE(replacement_listener.get() >= 0);
|
|
REQUIRE(!(*factory)->operational());
|
|
REQUIRE(!(*factory)->prepare_session("replaced", "pi").has_value());
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
auto main(int argc, char** argv) -> int {
|
|
if (argc == 1) {
|
|
return run(false);
|
|
}
|
|
if (argc == 2 && std::string_view{argv[1]} == "--privileged") {
|
|
return run(true);
|
|
}
|
|
return 1;
|
|
}
|