107 lines
4.1 KiB
C++
107 lines
4.1 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors
|
|
//
|
|
// Spike E main (§14 step 8): fork into a UI process and a sandboxed document
|
|
// process, connect them over a socketpair, send N Parse requests, and measure
|
|
// the round-trip latency. This is the M0 spike that measures the cost of the
|
|
// §2.1 process split — the one thing the plan says is "genuinely painful to
|
|
// retrofit", so it must be de-risked at M0.
|
|
//
|
|
// The sandbox is installed in the child AFTER fork, before any untrusted data
|
|
// is processed. The parent (UI) does no untrusted parsing.
|
|
//
|
|
// Usage:
|
|
// spike_e_sandbox [n_requests=1000] [payload_bytes=1024]
|
|
//
|
|
// Exit 0 if all requests round-trip correctly (the channel works under the
|
|
// sandbox); 1 otherwise. The latency numbers go in the JSON report's notes.
|
|
|
|
#include "DocumentProcess.h"
|
|
#include "UIProcess.h"
|
|
#include "../common/SpikeRunner.h"
|
|
|
|
#include <cerrno>
|
|
#include <chrono>
|
|
#include <csignal>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <sys/socket.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
const std::size_t n_requests = (argc >= 2) ? std::size_t(std::atoll(argv[1])) : 1000;
|
|
const std::size_t payload_bytes = (argc >= 3) ? std::size_t(std::atoll(argv[2])) : 1024;
|
|
|
|
// Create a socketpair for the two processes to talk over. Set it
|
|
// non-blocking up front — Cap'n Proto's event loop needs non-blocking fds.
|
|
// We use a socketpair (not TCP) so the document process has no network
|
|
// address to reach and the sandbox can deny socket() outright; a TCP
|
|
// connection would let the sandboxed process talk to the network stack.
|
|
int fds[2];
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
|
|
std::fprintf(stderr, "socketpair failed: %s\n", std::strerror(errno));
|
|
return 2;
|
|
}
|
|
|
|
// Fork: child becomes the sandboxed document process; parent is the UI.
|
|
// The child inherits the socketpair fd; the sandbox (installed in the
|
|
// child after fork) denies socket/connect/open/fork/exec but allows the
|
|
// already-open fd the UI handed it.
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
std::fprintf(stderr, "fork failed: %s\n", std::strerror(errno));
|
|
return 2;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
// Child — document process. Close the parent's end, run on our end.
|
|
close(fds[0]);
|
|
int rc = freepdfeditor::spike::e::run_document_process(fds[1]);
|
|
close(fds[1]);
|
|
_exit(rc);
|
|
}
|
|
|
|
// Parent — UI process. Close the child's end, drive the channel, measure.
|
|
close(fds[1]);
|
|
auto t0 = std::chrono::steady_clock::now();
|
|
auto r = freepdfeditor::spike::e::run_ui_process(
|
|
fds[0], fds[1], n_requests, payload_bytes);
|
|
auto t1 = std::chrono::steady_clock::now();
|
|
close(fds[0]);
|
|
|
|
// Reap the child.
|
|
int status = 0;
|
|
waitpid(pid, &status, 0);
|
|
|
|
double wall_us = std::chrono::duration<double, std::micro>(t1 - t0).count();
|
|
|
|
freepdfeditor::spike::SpikeResult sr{};
|
|
sr.spike = "E";
|
|
sr.name = "sandbox + IPC bring-up (seccomp-bpf, Cap'n Proto two-party RPC over socketpair)";
|
|
sr.total = n_requests;
|
|
sr.passed = r.requests_acked;
|
|
sr.failed = n_requests - r.requests_acked;
|
|
sr.errored = r.ok ? 0 : 1;
|
|
sr.metric_name = "round_trip_success_rate";
|
|
sr.metric_value = n_requests ? double(r.requests_acked) / double(n_requests) : 0.0;
|
|
sr.target = 1.0;
|
|
sr.gate_met = r.ok;
|
|
char buf[512];
|
|
std::snprintf(buf, sizeof(buf),
|
|
"requests=%zu acked=%zu payload=%zuB "
|
|
"avg_latency=%.1fus min=%.1fus max=%.1fus wall=%.1fus "
|
|
"child_exit=%d; seccomp-bpf deny=open/socket/connect/fork/exec, "
|
|
"allow=memory/read/write/poll/epoll/ioctl/exit; "
|
|
"transport=Cap'n Proto two-party RPC (wrapSocketFd + TwoPartyVatNetwork)",
|
|
n_requests, r.requests_acked, payload_bytes,
|
|
r.avg_latency_us, r.min_latency_us, r.max_latency_us, wall_us,
|
|
WIFEXITED(status) ? WEXITSTATUS(status) : -1);
|
|
sr.notes = buf;
|
|
if (!r.ok) sr.notes += "; " + r.error;
|
|
return freepdfeditor::spike::emit_json_report(sr);
|
|
} |