Files

251 lines
7.5 KiB
C++
Raw Permalink Normal View History

2021-05-19 12:08:57 +02:00
#include "Runner.h"
#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
}
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);
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);
IsolationManager manager(inputId, outputId);
2024-02-19 09:44:46 +01:00
if (parser.isSet(dir))
manager.setRulesDir(parser.value(dir));
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;
}