2021-05-19 12:08:57 +02:00
|
|
|
#include "Runner.h"
|
|
|
|
|
|
2021-05-20 19:08:42 +02:00
|
|
|
#include <cassert>
|
2024-02-16 16:54:09 +01:00
|
|
|
#include <regex>
|
2021-05-25 14:24:44 +02:00
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
#include <sys/mount.h>
|
2021-08-14 17:13:41 +02:00
|
|
|
#include <signal.h>
|
2021-05-25 14:24:44 +02:00
|
|
|
#include <unistd.h>
|
2024-02-16 16:54:09 +01:00
|
|
|
#include <iostream>
|
2024-02-19 12:07:37 +01:00
|
|
|
#include <fstream>
|
2024-02-21 12:55:18 +01:00
|
|
|
#include <thread>
|
2024-05-17 11:39:44 +02:00
|
|
|
#include <chrono>
|
2024-02-21 12:55:18 +01:00
|
|
|
#include <atomic>
|
|
|
|
|
|
2024-05-17 11:39:44 +02:00
|
|
|
#define PIPE_READ 0
|
|
|
|
|
#define PIPE_WRITE 1
|
|
|
|
|
|
2024-02-21 12:55:18 +01:00
|
|
|
// the waiting thread.
|
2024-05-17 11:39:44 +02:00
|
|
|
void waitForChildren(pid_t pid, const int encFsWatchDogPipe)
|
2024-02-21 12:55:18 +01:00
|
|
|
{
|
|
|
|
|
static std::atomic_int childCount(0);
|
|
|
|
|
childCount.fetch_add(1);
|
|
|
|
|
// This small process waits until the child finished
|
|
|
|
|
// the 'wait' also cleans up the kernel process table and afterwards
|
|
|
|
|
// we simply exit.
|
|
|
|
|
int status;
|
|
|
|
|
waitpid(pid, &status, 0);
|
|
|
|
|
|
|
|
|
|
if (childCount.fetch_sub(1) == 1) {
|
|
|
|
|
const char *homedir = getenv("HOME");
|
|
|
|
|
assert (homedir); // we checked that in the main.cpp
|
|
|
|
|
try {
|
|
|
|
|
std::filesystem::path path(homedir);
|
|
|
|
|
path /= std::string(".iso-pipe");
|
|
|
|
|
std::filesystem::remove(path);
|
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
// realistically, the next line is irrelevant since nobody is listening.
|
|
|
|
|
fprintf(stderr, "failed to remove iso-pipe. Error: %s\n", e.what());
|
|
|
|
|
}
|
2024-05-17 11:39:44 +02:00
|
|
|
if (encFsWatchDogPipe)
|
|
|
|
|
write(encFsWatchDogPipe, "go!", 3); // the watchdog sends the kill signal to the encFS process.
|
2024-02-21 12:55:18 +01:00
|
|
|
exit(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-16 16:54:09 +01:00
|
|
|
|
2024-09-06 19:21:11 +02:00
|
|
|
/*
|
|
|
|
|
* The jailed app may use some shell features that detach and they will end up
|
|
|
|
|
* be reparented to the 'jailer' process.
|
|
|
|
|
* To avoid anyone becoming zombies we simply wait in a loop.
|
|
|
|
|
*/
|
|
|
|
|
void waitForChildren2()
|
|
|
|
|
{
|
|
|
|
|
while (true) {
|
|
|
|
|
wait(nullptr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 11:39:44 +02:00
|
|
|
void waitForEncFs(pid_t pid)
|
|
|
|
|
{
|
|
|
|
|
int status;
|
|
|
|
|
waitpid(pid, &status, 0);
|
|
|
|
|
// if the watched encfs process dies, typically because of an
|
|
|
|
|
// incorrect password, the starting of the jail should be cancelled.
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-20 19:08:42 +02:00
|
|
|
Runner::Runner(const Message &message, int errorFile)
|
2024-02-25 19:22:08 +01:00
|
|
|
: m_outputFD(errorFile),
|
2021-05-20 19:08:42 +02:00
|
|
|
m_message(message)
|
2021-05-19 12:08:57 +02:00
|
|
|
{
|
2024-05-17 11:39:44 +02:00
|
|
|
for (auto i = m_message.iBegin(); i.isValid(); i.next()) {
|
|
|
|
|
if (i.isJailId()) {
|
|
|
|
|
m_jailId = i.jailId();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-20 19:08:42 +02:00
|
|
|
}
|
2021-05-19 12:08:57 +02:00
|
|
|
|
2021-05-20 19:08:42 +02:00
|
|
|
void Runner::setOwnerUserId(uint32_t uid)
|
|
|
|
|
{
|
|
|
|
|
assert(uid > 0);
|
|
|
|
|
m_ownerUid = uid;
|
2021-05-19 12:08:57 +02:00
|
|
|
}
|
|
|
|
|
|
2024-02-20 22:15:20 +01:00
|
|
|
void Runner::setProcessName(char *name, int allocatedSize)
|
2021-05-25 14:24:44 +02:00
|
|
|
{
|
2024-02-20 22:15:20 +01:00
|
|
|
m_processName = name;
|
|
|
|
|
m_processNameSize = allocatedSize;
|
2021-05-25 14:24:44 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-19 12:08:57 +02:00
|
|
|
void Runner::run()
|
|
|
|
|
{
|
2021-05-20 19:08:42 +02:00
|
|
|
pid_t pid = fork();
|
2021-05-25 14:24:44 +02:00
|
|
|
if (pid == -1) {
|
|
|
|
|
fprintf(stderr, "Runner: Failed to fork\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-02-21 12:55:18 +01:00
|
|
|
if (pid) {// we are the parent (pid = child's pid)
|
2021-08-14 17:13:41 +02:00
|
|
|
// printf("Fork one done, created %d (I'm %d). going back to listening\n", pid, getpid());
|
2021-05-20 19:08:42 +02:00
|
|
|
return;
|
2021-08-14 17:13:41 +02:00
|
|
|
}
|
2024-02-21 12:55:18 +01:00
|
|
|
// remove SIGCHILD handling we inherited from parent.
|
|
|
|
|
struct sigaction act;
|
|
|
|
|
memset(&act, 0, sizeof(act));
|
|
|
|
|
sigaction(SIGCHLD, &act, 0);
|
2021-05-20 19:08:42 +02:00
|
|
|
|
2024-02-20 21:56:45 +01:00
|
|
|
// this fork doesn't communicate with the user-space app.
|
|
|
|
|
for (auto fd : m_pipes) {
|
|
|
|
|
close(fd); // cleanup
|
|
|
|
|
}
|
2021-08-14 17:13:41 +02:00
|
|
|
|
2024-05-17 11:39:44 +02:00
|
|
|
/*
|
|
|
|
|
* While still fully root, start the encfs base, if needed.
|
|
|
|
|
*/
|
|
|
|
|
int encFSWatchDog = 0;
|
|
|
|
|
for (auto i = m_message.iBegin(); i.isValid(); i.next()) {
|
|
|
|
|
if (i.isJailPwd()) {
|
|
|
|
|
// we decrypt the jail with the password given.
|
|
|
|
|
encFSWatchDog = runEncFs(i.stringPtr(), i.stringLength());
|
|
|
|
|
if (encFSWatchDog == 0) {
|
|
|
|
|
fprintf(stderr, "Failed to run encfs\n");
|
|
|
|
|
exit(18);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 14:54:32 +02:00
|
|
|
/*
|
|
|
|
|
* VPN support
|
|
|
|
|
*/
|
|
|
|
|
bool hasVpn = false;
|
|
|
|
|
for (auto i = m_message.iBegin(); i.isValid(); i.next()) {
|
|
|
|
|
if (i.isVpnConfig()) {
|
|
|
|
|
m_vpnBasePath = i.stringPtr();
|
|
|
|
|
hasVpn = true;
|
|
|
|
|
}
|
|
|
|
|
else if (i.isVpnPwdBool()) {
|
|
|
|
|
m_vpnHasPwdFile = i.boolData();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hasVpn) {
|
|
|
|
|
const std::string jailId = std::to_string(m_jailId);
|
|
|
|
|
m_netNsName = "iso-jail" + jailId;
|
|
|
|
|
// Clear and create fresh netns
|
|
|
|
|
system(("/usr/bin/ip netns delete " + m_netNsName + " >/dev/null 2>&1").c_str());
|
|
|
|
|
system(("/usr/bin/ip link delete veth-" + m_netNsName + " >/dev/null 2>&1").c_str());
|
|
|
|
|
system(("/usr/bin/ip netns add " + m_netNsName).c_str());
|
|
|
|
|
// add a loopback
|
|
|
|
|
system(("/usr/bin/ip netns exec " + m_netNsName + " ip link set dev lo up").c_str());
|
|
|
|
|
}
|
2024-05-17 11:39:44 +02:00
|
|
|
|
2021-08-14 17:13:41 +02:00
|
|
|
/*
|
|
|
|
|
* 'unshare' is the nicest way to run a program in a new namespace.
|
|
|
|
|
* PID 'unshare' means that the to-be-run application can not see which other
|
|
|
|
|
* processes are running outside of its own, app-local, namespace.
|
|
|
|
|
* IPC 'unshare' means that shared memory, pipes etc which running processes
|
|
|
|
|
* may make available are likewise namespaced away from the application.
|
|
|
|
|
* NEWNS 'unshare' means we fork the filesystem, allowing us to do mounts
|
|
|
|
|
* and umounts that change what the child process can see.
|
|
|
|
|
*/
|
2021-05-25 14:24:44 +02:00
|
|
|
if (-1 == unshare(CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS)) {
|
|
|
|
|
fprintf(stderr, "Runner: Failed to unshare\n");
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
signal(SIGINT, SIG_IGN);
|
|
|
|
|
signal(SIGTERM, SIG_IGN);
|
2021-08-14 17:13:41 +02:00
|
|
|
// we make a second fork of the process because that also forks the PID namespace.
|
|
|
|
|
// the child is (likely) getting PID 1
|
2021-05-25 14:24:44 +02:00
|
|
|
pid = fork();
|
|
|
|
|
if (pid == -1) {
|
|
|
|
|
fprintf(stderr, "Runner: Failed to fork\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-08-14 17:13:41 +02:00
|
|
|
|
2024-02-25 19:22:08 +01:00
|
|
|
if (pid) {// parent
|
|
|
|
|
char messageBuf[20];
|
|
|
|
|
snprintf(messageBuf, sizeof(messageBuf) - 1, "%d", pid);
|
|
|
|
|
sendUpstream(messageBuf);
|
|
|
|
|
exit(0); // Let the jailer do it's own thing from here on out.
|
|
|
|
|
}
|
|
|
|
|
close(m_outputFD);
|
2021-05-25 14:24:44 +02:00
|
|
|
|
|
|
|
|
// mounts, first propagation
|
2025-03-01 11:15:47 +01:00
|
|
|
if (mount("none", "/", nullptr, MS_REC | MS_PRIVATE, nullptr)) {
|
2021-05-25 14:24:44 +02:00
|
|
|
fprintf(stderr, "cannot change root filesystem propagation\n");
|
2021-05-20 19:08:42 +02:00
|
|
|
exit(1);
|
|
|
|
|
}
|
2021-05-25 14:24:44 +02:00
|
|
|
// get us a proc
|
2025-03-01 11:15:47 +01:00
|
|
|
if (mount("proc", "/proc", "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, nullptr)) {
|
2021-05-25 14:24:44 +02:00
|
|
|
fprintf(stderr, "Runner: Failed to mount proc\n");
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-17 01:03:43 +01:00
|
|
|
int newEnvCount = 0;
|
2021-08-14 22:42:26 +02:00
|
|
|
for (auto i = m_message.iBegin(); i.isValid(); i.next()) {
|
2025-03-01 11:01:00 +01:00
|
|
|
if (i.isNewEnvVar())
|
2024-02-17 01:03:43 +01:00
|
|
|
++newEnvCount;
|
2021-08-14 22:42:26 +02:00
|
|
|
else if (i.isUnmount()) {
|
2026-04-11 14:54:32 +02:00
|
|
|
|
2021-08-14 23:03:01 +02:00
|
|
|
auto md = i.mountData();
|
2024-02-17 21:14:03 +01:00
|
|
|
if (md.src.empty()) {
|
2024-02-21 11:21:37 +01:00
|
|
|
fprintf(stderr, "Runner: internal error\n");
|
2021-08-14 23:03:01 +02:00
|
|
|
exit(1); // internal error.
|
2024-02-17 21:14:03 +01:00
|
|
|
}
|
2021-08-14 23:03:01 +02:00
|
|
|
if (umount2(md.src.c_str(), UMOUNT_NOFOLLOW) == -1) {
|
2024-02-15 23:39:04 +01:00
|
|
|
if (!i.isTry()) {
|
2024-02-19 12:07:37 +01:00
|
|
|
perror("Runner: umount failed");
|
|
|
|
|
fprintf(stderr, " '%s'\n", md.src.c_str());
|
2024-02-15 23:39:04 +01:00
|
|
|
exit(1);
|
|
|
|
|
}
|
2021-08-14 23:03:01 +02:00
|
|
|
}
|
2021-08-14 22:42:26 +02:00
|
|
|
}
|
|
|
|
|
else if (i.isRemount()) {
|
|
|
|
|
auto md = i.mountData();
|
2024-02-17 21:14:03 +01:00
|
|
|
if (md.src.empty() || md.dst.empty()) {
|
2024-02-21 11:21:37 +01:00
|
|
|
fprintf(stderr, "Runner: internal error\n");
|
2021-08-14 22:42:26 +02:00
|
|
|
exit(1); // internal error.
|
2024-02-17 21:14:03 +01:00
|
|
|
}
|
2024-02-16 16:54:09 +01:00
|
|
|
|
2024-02-19 12:07:37 +01:00
|
|
|
struct stat fromDetails;
|
|
|
|
|
if (lstat(md.src.c_str(), &fromDetails)) {
|
|
|
|
|
if (i.isTry()) // source missing
|
|
|
|
|
continue;
|
|
|
|
|
perror("Runner: failed due to rbind from a non-existing source");
|
2024-02-16 16:54:09 +01:00
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
// TODO verify all parts of the source path are available to USER.
|
2024-02-19 12:07:37 +01:00
|
|
|
const bool sourceIsSock = (fromDetails.st_mode & S_IFSOCK) == S_IFSOCK;
|
|
|
|
|
const bool sourceIsDir = !sourceIsSock && (fromDetails.st_mode & S_IFDIR) == S_IFDIR;
|
|
|
|
|
// create dest-dir before mounting on it
|
|
|
|
|
std::filesystem::path dest(md.dst);
|
|
|
|
|
std::filesystem::path destDir(dest);
|
|
|
|
|
bool destIsDir = !dest.has_filename();
|
|
|
|
|
bool destExists = false;
|
|
|
|
|
struct stat toDetails;
|
|
|
|
|
if (lstat(md.dst.c_str(), &toDetails) == 0) {
|
|
|
|
|
destIsDir = (toDetails.st_mode & S_IFDIR) == S_IFDIR;
|
|
|
|
|
destExists = true;
|
|
|
|
|
}
|
|
|
|
|
if (!destIsDir) {
|
|
|
|
|
if (destExists && sourceIsDir) {
|
|
|
|
|
fprintf(stderr, "Runner: rbind failed, source is dir, dest is not\n");
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
if (!sourceIsDir)
|
|
|
|
|
destDir.remove_filename();
|
|
|
|
|
}
|
|
|
|
|
else if (!sourceIsDir) {
|
|
|
|
|
fprintf(stderr, "Runner: rbind failed, dest is dir, source is not");
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
mkdirs(destDir);
|
2026-04-11 14:54:32 +02:00
|
|
|
if (!sourceIsDir && !destIsDir && !destExists) {
|
2024-02-19 12:07:37 +01:00
|
|
|
// printf(" creating file to mount on %s\n", dest.string().c_str());
|
|
|
|
|
// then we need a simple file there.
|
|
|
|
|
std::ofstream file;
|
|
|
|
|
file.open(dest);
|
|
|
|
|
file.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned long mountOptions = MS_BIND;
|
|
|
|
|
if (sourceIsDir)
|
|
|
|
|
mountOptions |= MS_REC; // recursive
|
|
|
|
|
if (mount(md.src.c_str(), md.dst.c_str(), nullptr, mountOptions, nullptr)) {
|
2024-02-15 23:39:04 +01:00
|
|
|
if (!i.isTry()) {
|
2024-02-19 12:07:37 +01:00
|
|
|
perror("Runner: rbind failed");
|
|
|
|
|
fprintf(stderr, " %s -> %s\n", md.src.c_str(), md.dst.c_str());
|
2024-02-15 23:39:04 +01:00
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (i.isCreateTmp()) {
|
|
|
|
|
auto md = i.mountData();
|
2024-02-17 21:14:03 +01:00
|
|
|
if (md.dst.empty()) {
|
2024-02-21 11:21:37 +01:00
|
|
|
fprintf(stderr, "Runner: internal error\n");
|
2024-02-15 23:39:04 +01:00
|
|
|
exit(1); // internal error.
|
2024-02-17 21:14:03 +01:00
|
|
|
}
|
2024-02-17 18:11:48 +01:00
|
|
|
if (!i.isTry()) { // if it is a 'try' we won't create the dir first.
|
|
|
|
|
struct stat destDetails;
|
|
|
|
|
if (lstat(md.dst.c_str(), &destDetails)) {
|
2024-02-17 21:14:03 +01:00
|
|
|
if (mkdir(md.dst.c_str(), 0777)) {
|
2024-02-19 12:07:37 +01:00
|
|
|
perror("Runner: tmpfs's mkdir failed");
|
2024-02-17 18:11:48 +01:00
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if ((destDetails.st_mode & S_IFDIR) != S_IFDIR) { // is not dir
|
2024-02-19 12:07:37 +01:00
|
|
|
perror("Runner: failed due to tmpfs over a non-dir");
|
2024-02-17 18:11:48 +01:00
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2025-03-01 11:15:47 +01:00
|
|
|
if (mount("tmpfs", md.dst.c_str(), "tmpfs", MS_NOSUID|MS_NODEV, nullptr)) {
|
2024-02-15 23:39:04 +01:00
|
|
|
if (!i.isTry()) {
|
|
|
|
|
fprintf(stderr, "Runner: Failed to create tmpfs at %s\n", md.dst.c_str());
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
2021-08-14 22:42:26 +02:00
|
|
|
}
|
2024-02-19 12:07:37 +01:00
|
|
|
chown(md.dst.c_str(), m_ownerUid, getgid());
|
2021-08-14 22:42:26 +02:00
|
|
|
}
|
2024-02-16 16:54:09 +01:00
|
|
|
else if (i.isCopy()) {
|
|
|
|
|
auto cm = i.copyData();
|
|
|
|
|
if (cm.from.empty() || cm.to.empty()
|
|
|
|
|
|| cm.from.at(0) != '/' || cm.to.at(0) != '/') {
|
2024-02-26 10:49:40 +01:00
|
|
|
fprintf(stderr, "Runner: invalid copy request.\n");
|
2024-02-16 16:54:09 +01:00
|
|
|
exit(1);
|
|
|
|
|
}
|
2024-02-17 21:14:03 +01:00
|
|
|
// is destination a dir?
|
|
|
|
|
const std::filesystem::path to(cm.to);
|
2024-02-19 12:07:37 +01:00
|
|
|
if (!to.has_filename()) { // copy to dir requested
|
|
|
|
|
if (!i.isTry())
|
|
|
|
|
mkdirs(to);
|
2024-02-17 21:14:03 +01:00
|
|
|
struct stat destDetails;
|
|
|
|
|
if (lstat(cm.to.c_str(), &destDetails) == 0) {
|
|
|
|
|
if ((destDetails.st_mode & S_IFDIR) != S_IFDIR) {
|
2024-02-19 12:07:37 +01:00
|
|
|
if (i.isTry()) // lets not even call 'copy'
|
|
|
|
|
continue;
|
2024-02-17 21:14:03 +01:00
|
|
|
fprintf(stderr, "Runner: copy's target expected dir, but is not\n");
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-16 16:54:09 +01:00
|
|
|
if (!runCopy(cm.from, cm.to)) {
|
|
|
|
|
if (!i.isTry()) {
|
|
|
|
|
fprintf(stderr, "Runner: Failed to copy %s\n", cm.from.c_str());
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-20 19:14:25 +01:00
|
|
|
else if (i.isDBusMapping()) {
|
|
|
|
|
auto map = i.dbusMapping();
|
|
|
|
|
// a dbus mapping is provided by the default tool '/bin/xdg-dbus-proxy'
|
|
|
|
|
// we start one now.
|
|
|
|
|
|
|
|
|
|
if (map.systemBus) // ignore this one for now, not sure what to filter yet.
|
|
|
|
|
continue;
|
|
|
|
|
pid = fork();
|
|
|
|
|
if (pid == -1) {
|
|
|
|
|
fprintf(stderr, "Runner: Failed to fork\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (pid == 0) {// we are the child
|
|
|
|
|
if (setuid(m_ownerUid) != 0 || seteuid(m_ownerUid) != 0) {
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
2026-04-11 15:05:19 +02:00
|
|
|
char *arguments[9];
|
2024-02-20 19:14:25 +01:00
|
|
|
arguments[0] = const_cast<char*>("/bin/xdg-dbus-proxy");
|
|
|
|
|
arguments[1] = const_cast<char*>(map.from.c_str());
|
|
|
|
|
arguments[2] = const_cast<char*>(map.to.c_str());
|
|
|
|
|
arguments[3] = const_cast<char*>("--filter");
|
|
|
|
|
arguments[4] = const_cast<char*>("--call=org.freedesktop.Notifications=freedesktop/Notifications.*@org");
|
|
|
|
|
arguments[5] = const_cast<char*>("--call=org.freedesktop.Notifications=kde/Notifications.*@org");
|
|
|
|
|
arguments[6] = const_cast<char*>("--call=org.freedesktop.portal.Desktop=*@/org/freedesktop/portal/desktop/*");
|
2026-04-11 15:05:19 +02:00
|
|
|
arguments[7] = const_cast<char*>("--own=org.kde.*");
|
|
|
|
|
arguments[8] = 0;
|
2024-02-20 19:14:25 +01:00
|
|
|
execv(arguments[0], arguments);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-08-14 22:42:26 +02:00
|
|
|
}
|
2024-02-25 19:22:08 +01:00
|
|
|
|
2021-05-25 14:24:44 +02:00
|
|
|
// Prepare for normal users
|
2024-02-21 12:55:18 +01:00
|
|
|
const char *homedir = getenv("HOME");
|
|
|
|
|
assert (homedir); // we checked that in the main.cpp
|
2021-05-25 14:24:44 +02:00
|
|
|
if (chdir(homedir)) {
|
2021-05-20 19:08:42 +02:00
|
|
|
fprintf(stderr, "Runner: Failed to change to dir\n");
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
2026-04-11 14:54:32 +02:00
|
|
|
if (!m_netNsName.empty()) { // Start openVPN process
|
|
|
|
|
/*
|
|
|
|
|
* We start OpenVPN in the jail-ed namespaces for process-ip and the full mount setup.
|
|
|
|
|
* But openVPN will run in the global networking namespace.
|
|
|
|
|
* After it has made a connection we'll create the netns and move it there
|
|
|
|
|
* and indeed force the jail to use the new netns too.
|
|
|
|
|
*/
|
|
|
|
|
assert (!m_vpnBasePath.empty());
|
|
|
|
|
|
|
|
|
|
auto ovpn = m_vpnBasePath + ".ovpn";
|
|
|
|
|
auto sh = m_vpnBasePath + ".sh";
|
|
|
|
|
auto ac = m_vpnBasePath + "ac";
|
|
|
|
|
// the users process copied the config files to the target jail and
|
|
|
|
|
// gave them protection mask 0400.
|
|
|
|
|
// It's not really needed, but why not make at least the password
|
|
|
|
|
// file root jailed process can see them, but can't read them.
|
|
|
|
|
if (!m_vpnHasPwdFile && chown(ac.c_str(), 0, 0)) {
|
|
|
|
|
fprintf(stderr, "Runner: Failed to protect vpn ac\n");
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto pid = fork();
|
|
|
|
|
if (pid == -1) {
|
|
|
|
|
fprintf(stderr, "Runner: Failed to fork\n");
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
|
|
|
|
else if (pid == 0) { // child
|
|
|
|
|
char *arguments[] = {
|
|
|
|
|
const_cast<char*>("/usr/sbin/openvpn"),
|
|
|
|
|
const_cast<char*>("--config"), const_cast<char*>(ovpn.c_str()),
|
|
|
|
|
const_cast<char*>("--auth-user-pass"), const_cast<char*>(ac.c_str()),
|
|
|
|
|
// needed to run our scripts
|
|
|
|
|
const_cast<char*>("--script-security"), const_cast<char*>("2"),
|
|
|
|
|
|
|
|
|
|
// notice that /tmp is personal to this jail
|
|
|
|
|
const_cast<char*>("--log"), const_cast<char*>("/tmp/openvpn.log"),
|
|
|
|
|
const_cast<char*>("--up"), const_cast<char*>(sh.c_str()),
|
|
|
|
|
const_cast<char*>("--down"), const_cast<char*>(sh.c_str()),
|
|
|
|
|
nullptr
|
|
|
|
|
};
|
|
|
|
|
// continue life as openvpn
|
|
|
|
|
execv(arguments[0], arguments);
|
|
|
|
|
}
|
|
|
|
|
assert(pid > 0);
|
|
|
|
|
|
|
|
|
|
// poll for the generated script
|
|
|
|
|
for (int i = 0; i < 60; ++i) {
|
|
|
|
|
struct stat conf;
|
|
|
|
|
if (lstat("/tmp/vpnconf.sh", &conf) == 0)
|
|
|
|
|
break;
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
|
|
|
|
}
|
|
|
|
|
system("/tmp/vpnconf.sh"); // configure the network stack
|
|
|
|
|
|
|
|
|
|
std::string nsPath = "/var/run/netns/" + m_netNsName;
|
|
|
|
|
int nsFd = open(nsPath.c_str(), O_RDONLY);
|
|
|
|
|
if (nsFd == -1) {
|
|
|
|
|
perror("Runner: open netns");
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
if (setns(nsFd, CLONE_NEWNET) == -1) {
|
|
|
|
|
perror("Runner: setns");
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
close(nsFd);
|
|
|
|
|
}
|
|
|
|
|
// drop permissions
|
2021-08-14 17:13:41 +02:00
|
|
|
if (setuid(m_ownerUid) != 0 || seteuid(m_ownerUid) != 0) {
|
2021-05-25 14:24:44 +02:00
|
|
|
fprintf(stderr, "Runner: Failed to change UID\n");
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
2021-05-21 15:28:57 +02:00
|
|
|
|
2024-02-24 11:40:42 +01:00
|
|
|
// check if there is an init script and run that as a child process.
|
|
|
|
|
if (runInitScript()) {
|
2024-02-26 10:49:40 +01:00
|
|
|
fprintf(stderr, "Runner: init script failed\n");
|
2024-02-24 11:40:42 +01:00
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-15 22:05:28 +02:00
|
|
|
// no point keeping those open.
|
2024-02-17 21:51:47 +01:00
|
|
|
fclose(stdin);
|
|
|
|
|
fclose(stdout);
|
|
|
|
|
fclose(stderr);
|
2021-08-15 22:05:28 +02:00
|
|
|
|
2024-02-21 11:21:37 +01:00
|
|
|
// Keep this process waiting for more requests to start in this 'jail'.
|
|
|
|
|
// the real process is going to be the child.
|
|
|
|
|
pid = fork();
|
|
|
|
|
if (pid == -1) exit(1);
|
|
|
|
|
if (pid) { // parent
|
|
|
|
|
// printf("Fork 3 done, created %d (I'm %d). waiting for child to exit\n", pid, getpid());
|
2024-02-21 12:55:18 +01:00
|
|
|
renameThisProcess(m_processName, m_processNameSize, "jailer");
|
2024-05-17 11:39:44 +02:00
|
|
|
new std::thread(waitForChildren, pid, encFSWatchDog);
|
2024-09-06 19:21:11 +02:00
|
|
|
new std::thread(waitForChildren2);
|
2024-02-21 11:21:37 +01:00
|
|
|
|
|
|
|
|
// create the pipe that indicates this jail is occupied.
|
|
|
|
|
// the pipe can be used by the dispatcher to send us more things to run
|
|
|
|
|
// in this jail.
|
|
|
|
|
mkfifo(".iso-pipe", 0750);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Wait for commands from the dispatcher. We may be asked to run another
|
|
|
|
|
* app in the existing jail.
|
|
|
|
|
*
|
|
|
|
|
* This is a pipe, we open it for read. Read all there is and other side
|
|
|
|
|
* will close the pipe when they pushed through their message.
|
|
|
|
|
* We'll open it again for the next message.
|
|
|
|
|
*/
|
|
|
|
|
char buf[Message::MAX_SIZE + 1];
|
|
|
|
|
int msgSize = 0;
|
|
|
|
|
while (true) {
|
|
|
|
|
std::ifstream controlChannel;
|
|
|
|
|
controlChannel.open(".iso-pipe", std::ios_base::in);
|
|
|
|
|
if (!controlChannel.is_open()) {
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
|
|
|
|
while (controlChannel.is_open()) {
|
2024-02-21 12:55:18 +01:00
|
|
|
controlChannel.read(buf + msgSize, sizeof(buf) - msgSize);
|
2024-02-21 11:21:37 +01:00
|
|
|
auto read = controlChannel.gcount();
|
|
|
|
|
if (read == 0) // pipe is closed, lets process
|
|
|
|
|
break;
|
|
|
|
|
msgSize += read;
|
2025-03-01 11:01:00 +01:00
|
|
|
if (msgSize >= (int) sizeof(buf)) {// message too big. Ignore.
|
2024-02-21 12:55:18 +01:00
|
|
|
msgSize = 0;
|
2024-02-21 11:21:37 +01:00
|
|
|
break;
|
2024-02-21 12:55:18 +01:00
|
|
|
}
|
2024-02-21 11:21:37 +01:00
|
|
|
}
|
2024-02-21 12:55:18 +01:00
|
|
|
if (msgSize) {
|
|
|
|
|
pid = fork();
|
|
|
|
|
if (pid == -1) exit(1);
|
|
|
|
|
if (pid) { // I'm parent
|
|
|
|
|
// pid is child's pid, lets wait for them in a thread.
|
2024-05-17 11:39:44 +02:00
|
|
|
new std::thread(waitForChildren, pid, encFSWatchDog);
|
2024-02-21 12:55:18 +01:00
|
|
|
}
|
|
|
|
|
else { // run up to exec, below!
|
|
|
|
|
m_message = Message(buf, msgSize);
|
2024-04-22 20:38:42 +02:00
|
|
|
newEnvCount = 0;
|
|
|
|
|
for (auto i = m_message.iBegin(); i.isValid(); i.next()) {
|
|
|
|
|
if (i.isNewEnvVar())
|
|
|
|
|
++newEnvCount;
|
|
|
|
|
}
|
2024-02-21 12:55:18 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2024-02-21 11:21:37 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-16 18:51:46 +01:00
|
|
|
/*
|
|
|
|
|
* GUI applications want to run much better when they are started by bash,
|
|
|
|
|
* so why not?
|
|
|
|
|
*/
|
2024-02-17 01:03:43 +01:00
|
|
|
char *arguments[4];
|
|
|
|
|
arguments[0] = const_cast<char*>("/usr/bin/bash");
|
|
|
|
|
arguments[1] = const_cast<char*>("-c");
|
|
|
|
|
std::string args(m_message.path());
|
2024-02-16 18:51:46 +01:00
|
|
|
for (auto i = m_message.iBegin(); i.isValid(); i.next()) {
|
2026-04-11 14:54:32 +02:00
|
|
|
if (i.isArgument())
|
2024-02-19 22:45:32 +01:00
|
|
|
args += std::string(" '") + i.argument() + "'";
|
2021-05-21 15:28:57 +02:00
|
|
|
}
|
2024-02-16 18:51:46 +01:00
|
|
|
arguments[2] = const_cast<char*>(args.c_str());
|
|
|
|
|
arguments[3] = 0;
|
2024-02-17 01:03:43 +01:00
|
|
|
|
2024-04-22 20:38:42 +02:00
|
|
|
int count = 0;
|
2024-02-17 01:03:43 +01:00
|
|
|
while (environ[count])
|
|
|
|
|
++count;
|
2024-04-22 20:38:42 +02:00
|
|
|
count += newEnvCount;
|
2024-02-17 01:03:43 +01:00
|
|
|
|
|
|
|
|
auto envList = new char*[count + 1]; // yap, that's a memleak. :shrug:
|
|
|
|
|
memset(envList, 0, (count + 1) * sizeof(char*));
|
|
|
|
|
copyFilteredEnv(environ, envList);
|
|
|
|
|
assert(envList[count] == 0);
|
2026-04-11 14:54:32 +02:00
|
|
|
|
2024-02-17 01:03:43 +01:00
|
|
|
execve(arguments[0], arguments, envList);
|
2026-04-11 14:54:32 +02:00
|
|
|
exit(1);
|
2021-05-20 19:08:42 +02:00
|
|
|
}
|
|
|
|
|
|
2024-02-25 19:22:08 +01:00
|
|
|
void Runner::sendUpstream(const char *errorMessage)
|
2021-05-20 19:08:42 +02:00
|
|
|
{
|
2026-04-11 14:54:32 +02:00
|
|
|
char messageBuf[256];
|
|
|
|
|
int size = snprintf(messageBuf, sizeof(messageBuf) - 1,
|
|
|
|
|
"%u %s", m_jailId, errorMessage);
|
|
|
|
|
messageBuf[size] = '\0';
|
|
|
|
|
// notice that on Linux single pipe writes (up to PIPE_BUF, some 4K) are atomic
|
|
|
|
|
// which protects us from multiple clients messages interacting.
|
|
|
|
|
write(m_outputFD, messageBuf, size + 1);
|
2021-05-19 12:08:57 +02:00
|
|
|
}
|
2024-02-16 16:54:09 +01:00
|
|
|
|
2024-02-17 21:14:03 +01:00
|
|
|
bool Runner::runCopy(const std::string &from, const std::filesystem::path &to) const
|
2024-02-16 16:54:09 +01:00
|
|
|
{
|
|
|
|
|
// the 'from' may include a '*' wildcard, which makes all things a little harder...
|
|
|
|
|
auto index = from.find('*');
|
2024-02-17 21:14:03 +01:00
|
|
|
if (index == std::string::npos) { // straight-forward copy
|
|
|
|
|
std::filesystem::path fromFilePath(from);
|
|
|
|
|
auto fullTo = to;
|
|
|
|
|
if (!fullTo.has_filename()) // if the target is a directory, append the src filename
|
|
|
|
|
fullTo /= fromFilePath.filename();
|
|
|
|
|
return copySingle(fromFilePath, fullTo);
|
2024-02-16 16:54:09 +01:00
|
|
|
}
|
|
|
|
|
auto slashIndex = from.rfind(std::string("/"));
|
|
|
|
|
if (slashIndex > index) {
|
|
|
|
|
// TODO error out
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
assert(index != std::string::npos); // caller should have checked
|
|
|
|
|
std::filesystem::path path = from.substr(0, slashIndex);
|
|
|
|
|
std::string pattern = from.substr(slashIndex + 1);
|
|
|
|
|
pattern = pattern.replace(index - slashIndex, 1, ".*");
|
|
|
|
|
|
|
|
|
|
std::regex regEx(std::string("/") + pattern + "$");
|
|
|
|
|
for (const auto &entry : std::filesystem::directory_iterator(path)) {
|
|
|
|
|
if (std::regex_search(entry.path().string(), regEx)) {
|
|
|
|
|
std::filesystem::path source(entry.path());
|
|
|
|
|
bool ok = copySingle(source, to / source.filename());
|
|
|
|
|
if (!ok)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Runner::copySingle(const std::filesystem::path &from, const std::filesystem::path &to) const
|
|
|
|
|
{
|
2024-02-26 10:49:40 +01:00
|
|
|
// std::cout << from.string() << " -> " << to.string() << std::endl;
|
2024-02-16 16:54:09 +01:00
|
|
|
// TODO we copy as root, so this is dangerous!
|
|
|
|
|
// FIXME check permissions of each of the path elements for the target user.
|
|
|
|
|
|
2024-02-26 10:49:40 +01:00
|
|
|
try {
|
|
|
|
|
if (!std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing))
|
|
|
|
|
return false;
|
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
// std::cout << "Copy instruction failed with: " << e.what() << std::endl;
|
2024-02-17 21:14:03 +01:00
|
|
|
return false;
|
2024-02-26 10:49:40 +01:00
|
|
|
}
|
2024-02-16 16:54:09 +01:00
|
|
|
|
|
|
|
|
// stat and apply owner and permissions.
|
|
|
|
|
std::string fromStr(from.string());
|
|
|
|
|
struct stat source;
|
|
|
|
|
if (lstat(fromStr.c_str(), &source)) {
|
|
|
|
|
assert(false);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// st mode
|
|
|
|
|
std::string toStr(to.string());
|
|
|
|
|
chmod(toStr.c_str(), source.st_mode);
|
|
|
|
|
chown(toStr.c_str(), source.st_uid, source.st_gid);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-02-17 01:03:43 +01:00
|
|
|
|
|
|
|
|
void Runner::copyFilteredEnv(char **from, char **target)
|
|
|
|
|
{
|
|
|
|
|
while (*from) {
|
|
|
|
|
auto equals = strchrnul(*from, '=');
|
|
|
|
|
const int len = equals - *from;
|
|
|
|
|
if (len <= 0)
|
|
|
|
|
continue;
|
|
|
|
|
bool filtered = false;
|
|
|
|
|
for (auto i = m_message.iBegin(); i.isValid(); i.next()) {
|
|
|
|
|
if ((i.isNewEnvVar() || i.isEnvVarUnset())
|
|
|
|
|
&& i.stringLength() >= len
|
|
|
|
|
&& strncmp(i.stringPtr(), *from, len) == 0) {
|
|
|
|
|
filtered = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!filtered) {
|
|
|
|
|
*target = *from;
|
|
|
|
|
++target;
|
|
|
|
|
}
|
|
|
|
|
++from;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// finally, append the 'set' properties
|
|
|
|
|
for (auto i = m_message.iBegin(); i.isValid(); i.next()) {
|
|
|
|
|
if (i.isNewEnvVar()) {
|
|
|
|
|
*target = i.stringPtr();
|
|
|
|
|
++target;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-19 12:07:37 +01:00
|
|
|
|
|
|
|
|
void Runner::mkdirs(const std::filesystem::path &dir) const
|
|
|
|
|
{
|
|
|
|
|
std::filesystem::path partial;
|
|
|
|
|
bool userIdOk = false;
|
2026-04-11 14:50:53 +02:00
|
|
|
bool permissionsOk = false;
|
2024-02-19 12:07:37 +01:00
|
|
|
for (auto chunk = dir.begin(); chunk != dir.end(); ++chunk) {
|
|
|
|
|
if (partial.empty()) {
|
|
|
|
|
partial = *chunk;
|
|
|
|
|
userIdOk = false;
|
|
|
|
|
} else {
|
|
|
|
|
partial /= *chunk;
|
|
|
|
|
struct stat dirStat;
|
|
|
|
|
if (lstat(partial.string().c_str(), &dirStat) == 0) {
|
|
|
|
|
userIdOk = dirStat.st_uid == m_ownerUid;
|
2026-04-11 14:50:53 +02:00
|
|
|
permissionsOk = (dirStat.st_mode & 3) == 3;
|
|
|
|
|
if (!userIdOk && !permissionsOk && ((dirStat.st_mode & 0x30) == 0x30)) {
|
|
|
|
|
// it is readable to the group, lets check if that includes m_ownerId
|
|
|
|
|
// ok, we could iterate over all groups, but I'm lazy and we don't need
|
|
|
|
|
// that today, so this is simpler.
|
|
|
|
|
permissionsOk = dirStat.st_gid == getgid();
|
|
|
|
|
}
|
2024-02-19 12:07:37 +01:00
|
|
|
}
|
|
|
|
|
else { // does not exist, should we create it?
|
|
|
|
|
const std::string path = partial.string();
|
2026-04-11 14:50:53 +02:00
|
|
|
if (!userIdOk && !permissionsOk) {
|
2024-02-19 12:07:37 +01:00
|
|
|
fprintf(stderr, "Runner: mkdir in another user. Refusing [%s]\n", path.c_str());
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mkdir(path.c_str(), 0700)) {
|
|
|
|
|
perror("Runner: target-dir mkdir() failed");
|
|
|
|
|
fprintf(stderr, " '%s'\n", path.c_str());
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
chown(path.c_str(), m_ownerUid, getgid());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-20 22:15:20 +01:00
|
|
|
|
2024-02-24 11:40:42 +01:00
|
|
|
int Runner::runInitScript()
|
|
|
|
|
{
|
|
|
|
|
int length = 0;
|
|
|
|
|
const char *script = nullptr;
|
|
|
|
|
for (auto i = m_message.iBegin(); i.isValid(); i.next()) {
|
|
|
|
|
if (i.isInitSript()) {
|
|
|
|
|
length = i.stringLength();
|
|
|
|
|
script = i.stringPtr();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (length == 0)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
std::ofstream scriptFile;
|
|
|
|
|
constexpr const char *TmpFileName = "/tmp/iso-initscript.sh";
|
|
|
|
|
scriptFile.open(TmpFileName);
|
|
|
|
|
if (!scriptFile.is_open()) {
|
|
|
|
|
fprintf(stderr, "Failed to create init script in /tmp");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
scriptFile.write(script, length);
|
|
|
|
|
scriptFile.flush();
|
|
|
|
|
scriptFile.close();
|
|
|
|
|
|
|
|
|
|
pid_t pid = fork();
|
|
|
|
|
if (pid == -1) {
|
|
|
|
|
fprintf(stderr, "Runner: Failed to fork\n");
|
|
|
|
|
return 1; // if this one failed, the next on by calling method will too.
|
|
|
|
|
}
|
|
|
|
|
if (pid) {// we are the parent (pid = child's pid)
|
|
|
|
|
int status;
|
|
|
|
|
waitpid(pid, &status, 0);
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *arguments[3];
|
|
|
|
|
arguments[0] = const_cast<char*>("/usr/bin/bash");
|
|
|
|
|
arguments[1] = const_cast<char*>(TmpFileName);
|
|
|
|
|
arguments[2] = 0;
|
|
|
|
|
execv(arguments[0], arguments);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 11:39:44 +02:00
|
|
|
int Runner::runEncFs(const char *password, int strlen) const
|
|
|
|
|
{
|
|
|
|
|
int aStdinPipe[2];
|
|
|
|
|
if (pipe(aStdinPipe) < 0) {
|
|
|
|
|
perror("allocating pipe for encfs");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pid_t pid = fork();
|
|
|
|
|
if (pid == -1) {
|
|
|
|
|
fprintf(stderr, "Runner: Failed to fork\n");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
char buf[20];
|
|
|
|
|
snprintf(buf, sizeof(buf), "%d", m_jailId);
|
|
|
|
|
const std::string jailName(buf);
|
2025-03-01 11:01:00 +01:00
|
|
|
char path[2000];
|
|
|
|
|
char *curDir = getcwd(path, sizeof(path));
|
|
|
|
|
if (curDir == nullptr) {
|
|
|
|
|
fprintf(stderr, "Runner: runEncFs: path too long\n");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2024-05-17 11:39:44 +02:00
|
|
|
const std::string curDirStr(curDir);
|
|
|
|
|
std::string jaildir = curDirStr + "/" + jailName;
|
|
|
|
|
|
|
|
|
|
if (pid == 0) { // we are the child
|
|
|
|
|
// redirect stdin to our pipe
|
|
|
|
|
if (dup2(aStdinPipe[PIPE_READ], STDIN_FILENO) == -1) {
|
|
|
|
|
exit(errno);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// these are for use by parent only
|
|
|
|
|
close(aStdinPipe[PIPE_READ]);
|
|
|
|
|
close(aStdinPipe[PIPE_WRITE]);
|
|
|
|
|
// close(aStdoutPipe[PIPE_READ]);
|
|
|
|
|
// close(aStdoutPipe[PIPE_WRITE]);
|
|
|
|
|
close(STDOUT_FILENO);
|
|
|
|
|
close(STDERR_FILENO);
|
|
|
|
|
|
|
|
|
|
char *arguments[7];
|
|
|
|
|
arguments[0] = const_cast<char*>("/usr/bin/encfs");
|
|
|
|
|
arguments[1] = const_cast<char*>("-f"); // foreground. Don't fork.
|
|
|
|
|
arguments[2] = const_cast<char*>("--public");
|
|
|
|
|
arguments[3] = const_cast<char*>("--stdinpass");
|
|
|
|
|
std::string backend = curDirStr + "/." + jailName;
|
|
|
|
|
arguments[4] = const_cast<char*>(backend.c_str());
|
|
|
|
|
arguments[5] = const_cast<char*>(jaildir.c_str());
|
|
|
|
|
arguments[6] = 0;
|
|
|
|
|
|
|
|
|
|
execv(arguments[0], arguments);
|
|
|
|
|
}
|
|
|
|
|
else {// we are the parent (pid = child's pid)
|
|
|
|
|
close(aStdinPipe[PIPE_READ]);
|
|
|
|
|
|
|
|
|
|
// monitor the process, if it dies we should die too.
|
|
|
|
|
new std::thread(waitForEncFs, pid);
|
|
|
|
|
|
|
|
|
|
bool ok = false;
|
|
|
|
|
// next we wait for the target dir to actually become a mount.
|
|
|
|
|
dev_t oldDevice = 0;
|
|
|
|
|
for (int i = 0; i < 200; ++i) { // max 15 sec
|
|
|
|
|
struct stat targetDir;
|
|
|
|
|
if (stat(jaildir.c_str(), &targetDir)) {
|
|
|
|
|
printf("stat of encrypted dir failed '%s'\n", jaildir.c_str());
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i == 0) {
|
|
|
|
|
// On first loop, send the password to be 100%
|
|
|
|
|
// certain that the mount hasn't done anything yet.
|
|
|
|
|
// then remember the device this dir is on and if that
|
|
|
|
|
// changes then we know that the mount has succeeded.
|
|
|
|
|
oldDevice = targetDir.st_dev;
|
|
|
|
|
write(aStdinPipe[PIPE_WRITE], password, strlen);
|
|
|
|
|
write(aStdinPipe[PIPE_WRITE], "\n", 1);
|
|
|
|
|
}
|
|
|
|
|
else if (oldDevice != targetDir.st_dev) {
|
|
|
|
|
ok = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(75));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
close(aStdinPipe[PIPE_WRITE]);
|
|
|
|
|
if (!ok) {
|
|
|
|
|
printf("EncFS never did its thing. Wrong password?\n");
|
|
|
|
|
exit(2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We need to kill it later.
|
|
|
|
|
* Which means we need to have another process that actually has the rights
|
|
|
|
|
* to kill it we can talk to.
|
|
|
|
|
* So, a pipe and a simple process that will 'kill' the encfs process later,
|
2026-04-11 14:54:32 +02:00
|
|
|
* on command, it is!
|
2024-05-17 11:39:44 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const auto encFsPid = pid;
|
|
|
|
|
int encFsWatchdogPipe[2];
|
|
|
|
|
if (pipe(encFsWatchdogPipe) < 0) {
|
|
|
|
|
perror("allocating pipe for encfs");
|
|
|
|
|
kill(encFsPid, SIGTERM);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pid = fork();
|
|
|
|
|
if (pid == -1) {
|
|
|
|
|
fprintf(stderr, "Runner: Failed to fork\n");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
else if (pid == 0) {
|
|
|
|
|
close(encFsWatchdogPipe[PIPE_WRITE]);
|
|
|
|
|
renameThisProcess(m_processName, m_processNameSize, "watchdog-encfs");
|
|
|
|
|
|
|
|
|
|
char buf[10];
|
|
|
|
|
do {
|
|
|
|
|
auto size = read(encFsWatchdogPipe[PIPE_READ], &buf, sizeof(buf));
|
|
|
|
|
if (size < 0)
|
|
|
|
|
exit(0);
|
|
|
|
|
if (size >= 2) {
|
|
|
|
|
kill(encFsPid, SIGTERM);
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
|
|
|
|
} while(true);
|
|
|
|
|
}
|
|
|
|
|
close(encFsWatchdogPipe[PIPE_READ]);
|
|
|
|
|
|
|
|
|
|
return encFsWatchdogPipe[PIPE_WRITE];
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-20 22:15:20 +01:00
|
|
|
void renameThisProcess(char *nameBlob, int blobSize, const char *newName)
|
|
|
|
|
{
|
2026-04-11 14:54:32 +02:00
|
|
|
// Maybe more correct is to use prctl(PR_SET_NAME) ?
|
2024-02-20 22:15:20 +01:00
|
|
|
assert(nameBlob);
|
|
|
|
|
const auto newLength = strlen(newName);
|
2026-04-11 14:54:32 +02:00
|
|
|
if (blobSize > (std::int64_t) newLength) {
|
2024-02-20 22:15:20 +01:00
|
|
|
memcpy(nameBlob, newName, newLength);
|
|
|
|
|
for (int i = newLength; i < blobSize; ++i) {
|
|
|
|
|
nameBlob[i] = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|