glove/tests/control/guest_channel_transport_test.cpp
trvon a8137db76b
feat(control): neutral guest channels, quarantine recovery, capability sealing, local-service proxy
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.
2026-08-30 20:28:41 -06:00

236 lines
8.8 KiB
C++

#include "glove/control/guest_channel_transport.hpp"
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <string>
#include <string_view>
#include <thread>
namespace {
#define REQUIRE(cond) \
do { \
if (!(cond)) { \
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #cond, __FILE__, __LINE__); \
return 1; \
} \
} while (0)
using glove::control::guest_channel_deadline;
using glove::control::guest_channel_transport;
using glove::control::guest_channel_transport_error_code;
template<typename Value>
concept has_public_close = requires(Value& value) { value.close(); };
template<typename Value>
concept has_public_exchange = requires { &Value::exchange; };
static_assert(!has_public_close<guest_channel_transport>);
static_assert(!has_public_exchange<guest_channel_transport>);
class fd_pair {
public:
fd_pair() { valid_ = ::socketpair(AF_UNIX, SOCK_STREAM, 0, values_) == 0; }
fd_pair(const fd_pair&) = delete;
auto operator=(const fd_pair&) -> fd_pair& = delete;
~fd_pair() {
for (int& value : values_) {
if (value >= 0) {
static_cast<void>(::close(value));
}
}
}
[[nodiscard]] auto valid() const noexcept -> bool { return valid_; }
[[nodiscard]] auto get(std::size_t index) const noexcept -> int { return values_[index]; }
[[nodiscard]] auto release(std::size_t index) noexcept -> int {
const int value = values_[index];
values_[index] = -1;
return value;
}
private:
int values_[2]{-1, -1};
bool valid_ = false;
};
auto deadline_after(std::chrono::milliseconds duration) -> guest_channel_deadline {
return std::chrono::steady_clock::now() + duration;
}
auto write_bytes(int descriptor, const void* input, std::size_t size) -> bool {
const auto* bytes = static_cast<const std::byte*>(input);
std::size_t written = 0;
while (written < size) {
#if defined(MSG_NOSIGNAL)
const auto result = ::send(descriptor, bytes + written, size - written, MSG_NOSIGNAL);
#else
const auto result = ::write(descriptor, bytes + written, size - written);
#endif
if (result <= 0) {
return false;
}
written += static_cast<std::size_t>(result);
}
return true;
}
auto adopt(fd_pair& pair, std::size_t maximum = 1024U)
-> glove::control::guest_channel_transport_result<std::unique_ptr<guest_channel_transport>> {
return guest_channel_transport::adopt(pair.release(0), maximum, ::geteuid());
}
auto run() -> int {
{
fd_pair pair;
REQUIRE(pair.valid());
auto channel = adopt(pair);
REQUIRE(channel.has_value());
const std::string request = "request";
const auto length = htonl(static_cast<std::uint32_t>(request.size()));
REQUIRE(write_bytes(pair.get(1), &length, sizeof(length)));
REQUIRE(write_bytes(pair.get(1), request.data(), request.size()));
auto received = (*channel)->receive_frame(deadline_after(std::chrono::seconds{1}));
REQUIRE(received == request);
REQUIRE((*channel)->send_frame("response", deadline_after(std::chrono::seconds{1})));
}
// Identity is checked before a byte is consumed, and failed adoption closes ownership.
{
fd_pair pair;
REQUIRE(pair.valid());
auto rejected = guest_channel_transport::adopt(pair.release(0), 1024U, ::geteuid() + 1U);
REQUIRE(!rejected.has_value());
REQUIRE(rejected.error().code == guest_channel_transport_error_code::peer_identity);
char byte = 0;
REQUIRE(::read(pair.get(1), &byte, sizeof(byte)) == 0);
}
// Successful partial reads do not renew the single absolute deadline.
{
fd_pair pair;
REQUIRE(pair.valid());
auto channel = adopt(pair);
REQUIRE(channel.has_value());
const auto declared = htonl(8U);
std::jthread trickle{[descriptor = pair.get(1), declared] {
const auto* bytes = reinterpret_cast<const std::byte*>(&declared);
for (std::size_t index = 0; index < sizeof(declared); ++index) {
static_cast<void>(write_bytes(descriptor, bytes + index, 1U));
std::this_thread::sleep_for(std::chrono::milliseconds{30});
}
}};
const auto started = std::chrono::steady_clock::now();
auto received = (*channel)->receive_frame(deadline_after(std::chrono::milliseconds{55}));
const auto elapsed = std::chrono::steady_clock::now() - started;
REQUIRE(!received.has_value());
REQUIRE(received.error().code == guest_channel_transport_error_code::deadline_exceeded);
REQUIRE(elapsed < std::chrono::milliseconds{150});
}
// Cancellation interrupts partial header and body reads within a fixed bound.
for (const bool partial_body : {false, true}) {
fd_pair pair;
REQUIRE(pair.valid());
auto channel = adopt(pair);
REQUIRE(channel.has_value());
if (partial_body) {
const auto declared = htonl(64U);
REQUIRE(write_bytes(pair.get(1), &declared, sizeof(declared)));
REQUIRE(write_bytes(pair.get(1), "x", 1U));
} else {
const auto declared = htonl(64U);
REQUIRE(write_bytes(pair.get(1), &declared, 1U));
}
std::atomic<int> result{-1};
const auto started = std::chrono::steady_clock::now();
std::jthread reader{[&](std::stop_token stop) {
auto received =
(*channel)->receive_frame(deadline_after(std::chrono::seconds{5}), stop);
result.store(
received ? 0 : static_cast<int>(received.error().code), std::memory_order_release
);
}};
std::this_thread::sleep_for(std::chrono::milliseconds{20});
reader.request_stop();
reader.join();
REQUIRE(
result.load(std::memory_order_acquire) ==
static_cast<int>(guest_channel_transport_error_code::cancelled)
);
REQUIRE(std::chrono::steady_clock::now() - started < std::chrono::milliseconds{200});
}
// Response cancellation and peer disconnects have distinct typed failures.
{
fd_pair pair;
REQUIRE(pair.valid());
auto channel = adopt(pair);
REQUIRE(channel.has_value());
std::stop_source stopped;
stopped.request_stop();
auto cancelled = (*channel)->send_frame(
"response", deadline_after(std::chrono::seconds{1}), stopped.get_token()
);
REQUIRE(!cancelled.has_value());
REQUIRE(cancelled.error().code == guest_channel_transport_error_code::cancelled);
REQUIRE(::close(pair.release(1)) == 0);
auto disconnected =
(*channel)->send_frame("response", deadline_after(std::chrono::seconds{1}));
REQUIRE(!disconnected.has_value());
REQUIRE(disconnected.error().code == guest_channel_transport_error_code::disconnected);
}
// A peer that does not drain a response cannot stall beyond the deadline.
{
fd_pair pair;
REQUIRE(pair.valid());
int receive_buffer = 1024;
REQUIRE(
::setsockopt(
pair.get(1), SOL_SOCKET, SO_RCVBUF, &receive_buffer, sizeof(receive_buffer)
) == 0
);
auto channel = adopt(pair, 1024U * 1024U);
REQUIRE(channel.has_value());
const std::string response(1024U * 1024U, 'r');
const auto started = std::chrono::steady_clock::now();
auto sent = (*channel)->send_frame(response, deadline_after(std::chrono::milliseconds{40}));
REQUIRE(!sent.has_value());
REQUIRE(sent.error().code == guest_channel_transport_error_code::deadline_exceeded);
REQUIRE(std::chrono::steady_clock::now() - started < std::chrono::milliseconds{200});
}
// Destruction shuts down and closes the endpoint deterministically.
{
fd_pair pair;
REQUIRE(pair.valid());
auto channel = adopt(pair);
REQUIRE(channel.has_value());
const auto started = std::chrono::steady_clock::now();
channel->reset();
REQUIRE(std::chrono::steady_clock::now() - started < std::chrono::milliseconds{100});
char byte = 0;
REQUIRE(::read(pair.get(1), &byte, sizeof(byte)) == 0);
}
return 0;
}
} // namespace
auto main() -> int {
return run();
}