// 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 #include #include #include #include #include #include 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(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(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 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