98 lines
3.6 KiB
C++
98 lines
3.6 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// SPDX-FileCopyrightText: 2025 ai-ad4 and the FreePDFEditor contributors
|
|
//
|
|
// DocumentProcess.cpp — the sandboxed document process. Installs the seccomp
|
|
// filter, then serves Parse requests over the socketpair fd using a trivial
|
|
// length-prefixed binary protocol.
|
|
//
|
|
// Protocol (the "parse" operation counts the bytes it was handed):
|
|
// Request: [u64 request_id][u64 payload_len][payload_len bytes]
|
|
// Response: [u64 request_id][u64 byte_count][u8 ok]
|
|
//
|
|
// The spike's goal (§14 step 8) is to measure the cost of the process split
|
|
// and prove the sandbox works, not to exercise Cap'n Proto. An earlier
|
|
// version used Cap'n Proto two-party RPC over the socketpair; the server
|
|
// received and processed requests (the parse handler ran) but the responses
|
|
// never reached the client — a real integration issue recorded in the spike
|
|
// result doc for M2 to debug with the full event-loop integration. The raw
|
|
// protocol measures the channel cost without that blocker.
|
|
|
|
#include "DocumentProcess.h"
|
|
#include "Sandbox.h"
|
|
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <unistd.h>
|
|
|
|
namespace freepdfeditor::spike::e {
|
|
|
|
namespace {
|
|
|
|
// Read exactly n bytes from fd (handles partial reads). Returns false on EOF/error.
|
|
bool read_exact(int fd, void* buf, std::size_t n)
|
|
{
|
|
auto* p = static_cast<char*>(buf);
|
|
while (n > 0) {
|
|
ssize_t r = read(fd, p, n);
|
|
if (r <= 0) return false;
|
|
p += r; n -= std::size_t(r);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Write exactly n bytes to fd (handles partial writes). Returns false on error.
|
|
bool write_exact(int fd, const void* buf, std::size_t n)
|
|
{
|
|
const auto* p = static_cast<const char*>(buf);
|
|
while (n > 0) {
|
|
ssize_t w = write(fd, p, n);
|
|
if (w <= 0) return false;
|
|
p += w; n -= std::size_t(w);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int run_document_process(int fd)
|
|
{
|
|
// Allow disabling the sandbox via an env var for diagnosing RPC issues
|
|
// (the spike's gate requires the sandbox ON; this is a debug aid only).
|
|
const char* skip = std::getenv("FPE_SPIKE_E_NO_SANDBOX");
|
|
if (skip == nullptr || skip[0] == '\0') {
|
|
// Install the sandbox BEFORE any untrusted data arrives. After this,
|
|
// the process cannot open files, create sockets, fork, or exec.
|
|
if (install_sandbox() != 0) {
|
|
std::fprintf(stderr, "[doc] sandbox install failed\n");
|
|
return 1;
|
|
}
|
|
} else {
|
|
std::fprintf(stderr, "[doc] sandbox DISABLED (FPE_SPIKE_E_NO_SANDBOX set)\n");
|
|
}
|
|
|
|
// Serve requests until the UI process closes the channel (EOF on read).
|
|
for (;;) {
|
|
std::uint64_t request_id = 0, payload_len = 0;
|
|
if (!read_exact(fd, &request_id, sizeof(request_id))) break; // EOF
|
|
if (!read_exact(fd, &payload_len, sizeof(payload_len))) break;
|
|
// Per ADR-0004 the document process is untrusted: bound-check the
|
|
// length it sent. The UI also checks, but defence in depth.
|
|
if (payload_len > 16 * 1024 * 1024) break; // 16 MiB cap
|
|
std::vector<unsigned char> payload(payload_len);
|
|
if (!read_exact(fd, payload.data(), payload_len)) break;
|
|
|
|
// The "parse": count the bytes.
|
|
std::uint64_t byte_count = payload_len;
|
|
std::uint8_t ok = 1;
|
|
if (!write_exact(fd, &request_id, sizeof(request_id))) break;
|
|
if (!write_exact(fd, &byte_count, sizeof(byte_count))) break;
|
|
if (!write_exact(fd, &ok, sizeof(ok))) break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
} // namespace freepdfeditor::spike::e
|