2021-05-19 12:08:57 +02:00
|
|
|
#include "Runner.h"
|
2024-02-19 12:52:17 +01:00
|
|
|
#include "IsolationManager.h"
|
2021-05-18 23:10:33 +02:00
|
|
|
|
|
|
|
|
#include <unistd.h>
|
2024-02-21 11:21:37 +01:00
|
|
|
#include <sys/stat.h>
|
2021-05-25 14:24:44 +02:00
|
|
|
#include <sys/wait.h>
|
2021-08-14 17:13:41 +02:00
|
|
|
#include <signal.h>
|
2021-05-25 14:24:44 +02:00
|
|
|
|
2024-02-19 09:44:46 +01:00
|
|
|
#include <QCommandLineParser>
|
2024-05-17 11:39:44 +02:00
|
|
|
#include <QApplication>
|
2021-05-18 23:10:33 +02:00
|
|
|
|
2021-08-14 22:00:42 +02:00
|
|
|
// #define DEBUG_MESSAGE
|
|
|
|
|
|
2021-08-14 17:13:41 +02:00
|
|
|
static void sigchld_hdl (int sig)
|
2021-05-25 14:24:44 +02:00
|
|
|
{
|
2021-08-14 17:13:41 +02:00
|
|
|
// Wait for all dead processes.
|
|
|
|
|
// We use a non-blocking call to be sure this signal handler will never block.
|
|
|
|
|
while (waitpid(-1, NULL, WNOHANG) > 0) { }
|
2021-05-25 14:24:44 +02:00
|
|
|
}
|
2021-05-19 15:07:10 +02:00
|
|
|
|
2024-02-21 11:21:37 +01:00
|
|
|
static FILE* findJail(std::deque<std::string> &jailCells, const std::string &pipeFilename)
|
|
|
|
|
{
|
|
|
|
|
for (auto i = jailCells.begin(); i != jailCells.end(); ++i) {
|
|
|
|
|
if (*i == pipeFilename) {
|
|
|
|
|
struct stat fileData;
|
|
|
|
|
if (lstat(pipeFilename.c_str(), &fileData)) {
|
|
|
|
|
jailCells.erase(i);
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
return fopen(pipeFilename.c_str(), "w");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string pipeFilename(uint32_t ownerUid, const Message &message)
|
|
|
|
|
{
|
|
|
|
|
// create the pipe-file
|
|
|
|
|
for (auto i = message.iBegin(); i.isValid(); i.next()) {
|
|
|
|
|
if (i.isJailId()) {
|
|
|
|
|
uint32_t jailId = i.jailId();
|
|
|
|
|
char path[50];
|
|
|
|
|
auto home = getenv("HOME");
|
|
|
|
|
assert(home);
|
|
|
|
|
snprintf(path, sizeof(path), "%s/.local/jails/%u/.iso-pipe", home, jailId);
|
|
|
|
|
return std::string(path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return std::string();
|
|
|
|
|
}
|
2021-08-14 17:13:41 +02:00
|
|
|
|
2021-05-18 23:10:33 +02:00
|
|
|
/*
|
|
|
|
|
The runner is the priviledged part that starts processes in their own
|
|
|
|
|
settings.
|
|
|
|
|
*/
|
2024-02-21 11:21:37 +01:00
|
|
|
static void mainRunner(char **argv, int inputId, int outputId)
|
2021-05-18 23:10:33 +02:00
|
|
|
{
|
2024-02-21 11:21:37 +01:00
|
|
|
// check basic correctness.
|
|
|
|
|
if (getenv("HOME") == nullptr) {
|
|
|
|
|
fprintf(stderr, "Runner: missing HOME env var\n");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-20 19:08:42 +02:00
|
|
|
const uint32_t ownerUid = getuid(); // the real user, not root.
|
2021-05-25 14:24:44 +02:00
|
|
|
|
2024-02-21 11:21:37 +01:00
|
|
|
std::deque<std::string> occupiedJailcells;
|
|
|
|
|
|
2021-08-14 17:13:41 +02:00
|
|
|
/* Create a handler that takes all forked-off processes and gets
|
|
|
|
|
notified when they finish after which we call the sigchld_handler
|
|
|
|
|
method.
|
|
|
|
|
This method will 'wait' on all children, which is required for the
|
|
|
|
|
kernel to cleanup after the children after they finish.
|
|
|
|
|
*/
|
|
|
|
|
struct sigaction act;
|
|
|
|
|
memset (&act, 0, sizeof(act));
|
|
|
|
|
act.sa_handler = sigchld_hdl;
|
2024-02-21 12:55:18 +01:00
|
|
|
if (sigaction(SIGCHLD, &act, 0)) {
|
2021-08-14 17:13:41 +02:00
|
|
|
fprintf(stderr, "Failed to install sigaction handler for SIGCHLD\n");
|
2021-05-25 14:24:44 +02:00
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-17 21:14:03 +01:00
|
|
|
if (setuid(geteuid())) { // become fully root
|
|
|
|
|
fprintf(stderr, "Failed to setuid(%d)\n", geteuid());
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
2024-02-20 22:15:20 +01:00
|
|
|
|
|
|
|
|
const int origProcessNameSize = strlen(*argv);
|
|
|
|
|
renameThisProcess(*argv, origProcessNameSize, "dispatcher");
|
|
|
|
|
|
2021-08-14 17:13:41 +02:00
|
|
|
/*
|
|
|
|
|
* Loop forever and wait for messages via our pipe, from the mainListener(),
|
|
|
|
|
* then we run those with the settings provided in the message.
|
|
|
|
|
* This keeps the amount of logic that runs under privs to the bare minimum,
|
|
|
|
|
* notice that the Runner will call exec() as the original (non-priviledged) user.
|
|
|
|
|
*/
|
2021-08-14 22:00:42 +02:00
|
|
|
char buf[Message::MAX_SIZE + 2];
|
2021-08-14 17:13:41 +02:00
|
|
|
uint32_t offset = 0;
|
2021-05-19 12:08:57 +02:00
|
|
|
while (true) {
|
2021-08-14 17:13:41 +02:00
|
|
|
ssize_t amount = read(inputId, buf + offset, sizeof(buf) - offset);
|
|
|
|
|
if (amount == -1) {
|
|
|
|
|
if (errno == EINTR) // try again.
|
|
|
|
|
continue;
|
|
|
|
|
fprintf(stderr, "Failed to read from pipe %d\n", errno);
|
|
|
|
|
exit(0);
|
|
|
|
|
}
|
|
|
|
|
if (amount == 0) // pipe is closed, we should shutdown
|
|
|
|
|
return;
|
|
|
|
|
offset += amount;
|
|
|
|
|
if (offset < 2)
|
|
|
|
|
continue;
|
2021-08-14 22:00:42 +02:00
|
|
|
uint32_t messageSize = static_cast<uint8_t>(buf[1]);
|
|
|
|
|
messageSize = messageSize << 8;
|
|
|
|
|
messageSize += static_cast<uint8_t>(buf[0]);
|
2026-03-27 10:39:15 +01:00
|
|
|
if (messageSize > Message::MAX_SIZE || messageSize == 0) {
|
|
|
|
|
fprintf(stderr, "Runner: invalid message size %u\n", messageSize);
|
|
|
|
|
offset = 0; // discard
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (offset < messageSize + 2)
|
2021-08-14 17:13:41 +02:00
|
|
|
continue;
|
|
|
|
|
|
2021-05-19 12:08:57 +02:00
|
|
|
try {
|
2021-08-14 17:13:41 +02:00
|
|
|
Message message(buf + 2, offset - 2);
|
2021-08-14 22:00:42 +02:00
|
|
|
#ifdef DEBUG_MESSAGE
|
2024-02-17 21:14:03 +01:00
|
|
|
message.printFields();
|
2021-08-14 22:00:42 +02:00
|
|
|
#endif
|
2024-02-21 11:21:37 +01:00
|
|
|
|
|
|
|
|
auto pipeFileName = pipeFilename(ownerUid, message);
|
|
|
|
|
auto pipe = findJail(occupiedJailcells, pipeFileName);
|
|
|
|
|
bool sendSuccess = false;
|
|
|
|
|
if (pipe) {
|
|
|
|
|
size_t amount = fwrite(buf + 2, 1, offset - 2, pipe);
|
|
|
|
|
if (amount == offset - 2)
|
|
|
|
|
sendSuccess = true;
|
|
|
|
|
fclose(pipe);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!sendSuccess) {
|
|
|
|
|
occupiedJailcells.push_back(pipeFileName);
|
|
|
|
|
Runner runner(message, outputId);
|
|
|
|
|
runner.setProcessName(*argv, origProcessNameSize);
|
|
|
|
|
runner.setOwnerUserId(ownerUid);
|
|
|
|
|
runner.addPipe(inputId);
|
|
|
|
|
runner.run();
|
|
|
|
|
}
|
2021-05-19 12:08:57 +02:00
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
auto len = strlen(e.what());
|
|
|
|
|
write(outputId, e.what(), len + 1);
|
|
|
|
|
}
|
2021-08-14 17:13:41 +02:00
|
|
|
offset = 0;
|
2021-05-19 12:08:57 +02:00
|
|
|
}
|
2021-05-18 23:10:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
The listener listens on dbus and does the filesystem stuff and defers
|
|
|
|
|
to the runner to actually start an app
|
|
|
|
|
*/
|
2021-05-20 12:43:04 +02:00
|
|
|
static void mainListener(int x, char **y, int inputId, int outputId)
|
2021-05-18 23:10:33 +02:00
|
|
|
{
|
|
|
|
|
// drop priviledges
|
2021-05-20 12:43:04 +02:00
|
|
|
const int targetUid = getuid(); // the real user, not root.
|
|
|
|
|
if (setuid(targetUid) != 0) { // this is needed for DBUS to connect.
|
|
|
|
|
fprintf(stderr, "Failed to change to user %d\n", targetUid);
|
2021-05-18 23:10:33 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2021-05-20 12:43:04 +02:00
|
|
|
if (seteuid(targetUid) != 0) {
|
|
|
|
|
fprintf(stderr, "Failed to change to e-user %d\n", targetUid);
|
2021-05-18 23:10:33 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 11:39:44 +02:00
|
|
|
QApplication app(x, y);
|
2024-02-19 12:52:17 +01:00
|
|
|
app.setApplicationName("isolation-runner");
|
2024-05-17 11:39:44 +02:00
|
|
|
app.setQuitOnLastWindowClosed(false); // avoid magic behavior
|
|
|
|
|
|
2024-02-26 11:11:25 +01:00
|
|
|
std::srand(std::time(0));
|
2024-02-19 09:44:46 +01:00
|
|
|
QCommandLineParser parser;
|
|
|
|
|
parser.addHelpOption(); // allows users to get an overview of options
|
|
|
|
|
QCommandLineOption dir(QStringList() << "rulesdir" << "d", "Directory where the rules are", "PATH");
|
|
|
|
|
parser.addOption(dir);
|
|
|
|
|
parser.process(app);
|
2024-02-19 12:52:17 +01:00
|
|
|
IsolationManager manager(inputId, outputId);
|
2024-02-19 09:44:46 +01:00
|
|
|
if (parser.isSet(dir))
|
|
|
|
|
manager.setRulesDir(parser.value(dir));
|
2024-02-19 09:23:40 +01:00
|
|
|
app.exec();
|
2021-05-18 23:10:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main(int x, char **y)
|
|
|
|
|
{
|
2021-05-20 12:43:04 +02:00
|
|
|
if (geteuid() != 0) {
|
|
|
|
|
fprintf(stderr, "Should be installed suid root\n");
|
2021-05-18 23:10:33 +02:00
|
|
|
return 1;
|
|
|
|
|
}
|
2021-05-20 12:43:04 +02:00
|
|
|
if (getuid() == 0) {
|
|
|
|
|
fprintf(stderr, "Do not run as root\n");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2024-05-20 22:02:28 +02:00
|
|
|
const char *homedir = getenv("HOME");
|
|
|
|
|
if (homedir == nullptr) {
|
|
|
|
|
fprintf(stderr, "Env var HOME missing\n");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (chdir(homedir) != 0) {
|
|
|
|
|
fprintf(stderr, "Env var HOME invalid\n");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (chdir(".local/jails") != 0) {
|
|
|
|
|
// if fail, create and then try to enter it again.
|
|
|
|
|
if (mkdir(".local", 0700) == 0)
|
|
|
|
|
chown(".local", getuid(), getgid());
|
|
|
|
|
if (mkdir(".local/jails", 0700) == 0)
|
|
|
|
|
chown(".local/jails", getuid(), getgid());
|
|
|
|
|
if (chdir(".local/jails") != 0) {
|
|
|
|
|
fprintf(stderr, "Failed to find jails dir\n");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-20 12:43:04 +02:00
|
|
|
if (getgid() == 0 || getegid() == 0) {
|
|
|
|
|
fprintf(stderr, "Warn: group is root, likely setup problem\n");
|
|
|
|
|
}
|
2021-05-18 23:10:33 +02:00
|
|
|
|
|
|
|
|
int pUp[2]; // from runner to listener
|
|
|
|
|
if (pipe(pUp) == -1) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
int pDown[2]; // from listener to runner
|
|
|
|
|
if (pipe(pDown) == -1) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pid_t pid = fork();
|
2024-02-21 11:21:37 +01:00
|
|
|
if (pid < 0) { // fork failed
|
2021-05-18 23:10:33 +02:00
|
|
|
return 1;
|
|
|
|
|
}
|
2024-02-21 11:21:37 +01:00
|
|
|
if (pid) { // parent
|
2021-05-18 23:10:33 +02:00
|
|
|
close(pUp[1]);
|
|
|
|
|
close(pDown[0]);
|
|
|
|
|
mainListener(x, y, pUp[0], pDown[1]);
|
|
|
|
|
close(pUp[0]);
|
|
|
|
|
close(pDown[1]);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
close(pUp[0]);
|
|
|
|
|
close(pDown[1]);
|
2024-02-21 11:21:37 +01:00
|
|
|
mainRunner(y, pDown[0], pUp[1]);
|
2021-05-18 23:10:33 +02:00
|
|
|
close(pUp[1]);
|
|
|
|
|
close(pDown[0]);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|