/* Static, unprivileged command relay for the protected manager's executable * namespace. Never executes a program locally or interprets shell text. * The separately credentialed runtime owner receives argv/environment and * exactly stdin/stdout/stderr descriptors. No manager identity key is opened. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern char **environ; #define SOCKET_PATH "/run/archipelago-managed/runtime.sock" #define MAX_PACKET 65536U #define MAGIC 0x41524352U #ifndef RUNTIME_RESULT_MS #define RUNTIME_RESULT_MS 1810000U #endif static unsigned char packet[MAX_PACKET]; static size_t used; static int control_fd = -1; static _Noreturn void fail(void) { /* Standard streams are delegated aliases and may be full blocking pipes. * Revoke first and never wait for stdio diagnostics/flushing on failure. */ if (control_fd >= 0) close(control_fd); explicit_bzero(packet, sizeof(packet)); _exit(125); } static uint64_t now_ms(void) { struct timespec now; if (clock_gettime(CLOCK_BOOTTIME, &now) || now.tv_sec < 0 || now.tv_nsec < 0 || now.tv_nsec >= 1000000000L || (uint64_t)now.tv_sec > UINT64_MAX / 1000U) fail(); return (uint64_t)now.tv_sec * 1000U + (uint64_t)now.tv_nsec / 1000000U; } static void number(uint32_t value) { if (used > MAX_PACKET - 4) fail(); value = htonl(value); memcpy(packet + used, &value, 4); used += 4; } static void string(const char *value) { size_t size = strnlen(value, MAX_PACKET); if (size == MAX_PACKET || used > MAX_PACKET - 4 || size > MAX_PACKET - used - 4) fail(); number((uint32_t)size); memcpy(packet + used, value, size); used += size; } static void ready(int fd, short events, uint64_t end) { for (;;) { uint64_t now = now_ms(); if (now >= end) fail(); uint64_t left = end - now; struct pollfd wait = {.fd = fd, .events = events}; int result = poll(&wait, 1, (int)(left > 1000U ? 1000U : left)); if (now_ms() >= end) fail(); if (result < 0 && errno == EINTR) continue; if (result == 0) continue; if (result < 0 || !(wait.revents & events) || (wait.revents & (POLLERR | POLLNVAL))) fail(); return; } } static void send_rest(int fd, const unsigned char *bytes, size_t size, uint64_t end) { while (size) { ready(fd, POLLOUT, end); ssize_t count = send(fd, bytes, size, MSG_DONTWAIT | MSG_NOSIGNAL); if (count < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) continue; if (count <= 0) fail(); bytes += count; size -= (size_t)count; } } static void private_endpoint(void) { struct stat meta; const char *parents[] = {"/", "/run", "/run/archipelago-managed"}; for (size_t i = 0; i < sizeof(parents) / sizeof(parents[0]); i++) { if (lstat(parents[i], &meta) || !S_ISDIR(meta.st_mode) || meta.st_uid != 0 || (meta.st_mode & 0022)) fail(); } if (lstat(SOCKET_PATH, &meta) || !S_ISSOCK(meta.st_mode) || meta.st_uid != 0 || (meta.st_mode & 0007)) fail(); } int main(int argc, char **argv) { if (argc < 1 || !argv || !argv[0]) fail(); if (getuid() == 0 || geteuid() != getuid() || getegid() != getgid() || prctl(PR_SET_DUMPABLE, 0, 0, 0, 0)) fail(); for (int fd = 0; fd <= 2; fd++) if (fcntl(fd, F_GETFD) < 0) fail(); signal(SIGPIPE, SIG_IGN); private_endpoint(); const char *name = strrchr(argv[0], '/'); name = name ? name + 1 : argv[0]; int start = 0; if (!strcmp(name, "archipelago-runtime-proxy")) { if (argc < 3 || strcmp(argv[1], "--")) fail(); start = 2; } if (argc - start < 1 || argc - start > 256) fail(); size_t envc = 0; while (environ[envc]) { if (++envc > 128) fail(); } char cwd[4096]; if (!getcwd(cwd, sizeof(cwd))) fail(); number(MAGIC); number(1); number((uint32_t)(argc - start)); number((uint32_t)envc); string(cwd); for (int i = start; i < argc; i++) string(argv[i]); for (size_t i = 0; i < envc; i++) string(environ[i]); uint64_t admission_end = now_ms() + 2000U; int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); if (fd < 0) fail(); control_fd = fd; struct sockaddr_un address = {.sun_family = AF_UNIX}; memcpy(address.sun_path, SOCKET_PATH, sizeof(SOCKET_PATH)); if (connect(fd, (struct sockaddr *)&address, sizeof(address))) { if (errno != EINPROGRESS) fail(); ready(fd, POLLOUT, admission_end); int error = 0; socklen_t length = sizeof(error); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &length) || error) fail(); } if (now_ms() >= admission_end) fail(); struct ucred peer; socklen_t peer_size = sizeof(peer); if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &peer, &peer_size) || peer_size != sizeof(peer) || peer.uid != 0 || peer.gid != 0 || peer.pid <= 1) fail(); uint32_t size = htonl((uint32_t)used); struct iovec io[] = {{&size, sizeof(size)}, {packet, used}}; union { struct cmsghdr alignment; char bytes[CMSG_SPACE(3 * sizeof(int))]; } control; memset(&control, 0, sizeof(control)); struct msghdr message = {.msg_iov = io, .msg_iovlen = 2, .msg_control = control.bytes, .msg_controllen = sizeof(control.bytes)}; struct cmsghdr *header = CMSG_FIRSTHDR(&message); header->cmsg_level = SOL_SOCKET; header->cmsg_type = SCM_RIGHTS; header->cmsg_len = CMSG_LEN(3 * sizeof(int)); int streams[] = {0, 1, 2}; memcpy(CMSG_DATA(header), streams, sizeof(streams)); ssize_t count; for (;;) { ready(fd, POLLOUT, admission_end); count = sendmsg(fd, &message, MSG_DONTWAIT | MSG_NOSIGNAL); if (count < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) continue; break; } if (count <= 0 || (size_t)count > used + 4) fail(); if ((size_t)count < 4) { send_rest(fd, ((unsigned char *)&size) + count, 4 - (size_t)count, admission_end); send_rest(fd, packet, used, admission_end); } else { send_rest(fd, packet + ((size_t)count - 4), used - ((size_t)count - 4), admission_end); } explicit_bzero(packet, sizeof(packet)); if (now_ms() >= admission_end) fail(); /* The owner cancels on connection loss. This prototype client budget is * separate from future interactive-session/lifecycle policy. Output travels * through delegated FDs, never an unbounded broker-owned capture buffer. */ uint32_t result[3]; size_t received = 0; uint64_t result_end = now_ms() + RUNTIME_RESULT_MS; while (received < sizeof(result)) { ready(fd, POLLIN, result_end); ssize_t n = recv(fd, ((unsigned char *)result) + received, sizeof(result) - received, MSG_DONTWAIT); if (n < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) continue; if (n <= 0) fail(); received += (size_t)n; } if (now_ms() >= result_end) fail(); close(fd); control_fd = -1; if (ntohl(result[0]) != MAGIC) fail(); uint32_t kind = ntohl(result[1]), value = ntohl(result[2]); if (kind == 0 && value <= 255) return (int)value; if (kind == 1 && value > 0 && value < NSIG && value != SIGSTOP && value != SIGCONT && value != SIGTSTP && value != SIGTTIN && value != SIGTTOU) { if (value != SIGKILL && signal((int)value, SIG_DFL) == SIG_ERR) fail(); sigset_t unblocked; if (sigemptyset(&unblocked) || sigaddset(&unblocked, (int)value) || sigprocmask(SIG_UNBLOCK, &unblocked, NULL) || raise((int)value)) fail(); } fail(); }