You've already forked isolationRunner
c0f579ff6d
This allows a jail to have a VPN config associated and as a result we start a new net namespace, completely isolating the jails networking. We then start an openVPN client to route between the main network and the jails' network. The main limitation here is that we don't setup DNS, which basically means that the VPN will route DNS calls to the other side, but since we don't remount resolv.conf this depends on the vpn provider actually mapping the nameserver we use. For people that use a nameserver like 192.168.100.1, this most of the time works just fine. Improvement is possible.
594 lines
15 KiB
C++
594 lines
15 KiB
C++
#include "Message.h"
|
|
|
|
#include <string.h>
|
|
#include <stdexcept>
|
|
#include <cassert>
|
|
|
|
#include <QDebug>
|
|
|
|
enum FieldType {
|
|
JailId = 1,
|
|
ExecutablePath = 10,
|
|
Argument,
|
|
InitScript,
|
|
JailPassword, // The jaildir is encfs encrypted. Decrypt password.
|
|
|
|
IsTry = 20, // allow next command to fail
|
|
RBindMountSource, // mount from an existing directory.
|
|
RBindMountDest,
|
|
UnMountDir, // unmount (remove) an existing mountpoint.
|
|
CreateTmpFs, // with the full path as arg
|
|
|
|
CopyFrom = 30,
|
|
CopyTo,
|
|
|
|
EnvironUnset = 35, // an env-var we want to filter
|
|
EnvironSet, // override env-var value.
|
|
|
|
DBusProxyFrom = 40, // start the xdg-dbus-proxy between from and to
|
|
DBusProxyTo,
|
|
DBusProxySystemFrom, // start the dbus proxy with system rules
|
|
DBusProxySystemTo,
|
|
|
|
VpnConfig = 50, // base path to vpn files (inside jail)
|
|
VpnHasPassFile = 51, // has a password file in the jail
|
|
};
|
|
|
|
// returns the length of the string, or throw if no closing zero found
|
|
static int stringLength(const char *from, const char *end)
|
|
{
|
|
auto at = from;
|
|
while (at <= end) {
|
|
if (*at == 0)
|
|
return at - from;
|
|
++at;
|
|
}
|
|
throw std::runtime_error("unterminated string found");
|
|
}
|
|
|
|
Message::Message(int size)
|
|
: m_reservedSize(size)
|
|
{
|
|
assert(size > 0);
|
|
m_buf = std::shared_ptr<char>(new char[size], std::default_delete<char[]>());
|
|
m_writePtr = m_buf.get();
|
|
memset(m_writePtr, 0, m_reservedSize);
|
|
}
|
|
|
|
Message::Message(char *buffer, int bufferSize)
|
|
: m_reservedSize(bufferSize)
|
|
{
|
|
m_buf = std::shared_ptr<char>(new char[bufferSize], std::default_delete<char[]>());
|
|
memcpy(m_buf.get(), buffer, bufferSize);
|
|
buffer = m_buf.get();
|
|
|
|
const char *end = buffer + bufferSize;
|
|
while (buffer < end) {
|
|
if (*buffer == ExecutablePath) {
|
|
m_path = buffer + 1;
|
|
}
|
|
else if (*buffer == Argument) {
|
|
break;
|
|
}
|
|
const int len = ::stringLength(buffer + 1, end);
|
|
if (buffer + len + 2 > end) {
|
|
throw std::runtime_error("buffer too small");
|
|
}
|
|
buffer += len + 2;
|
|
}
|
|
}
|
|
|
|
char *Message::path() const
|
|
{
|
|
return m_path;
|
|
}
|
|
|
|
void Message::setPath(const std::string &path)
|
|
{
|
|
m_path = m_writePtr + 1;
|
|
addString(ExecutablePath, path);
|
|
}
|
|
|
|
void Message::addArgument(const char *string)
|
|
{
|
|
addString(Argument, string);
|
|
}
|
|
|
|
void Message::printFields() const
|
|
{
|
|
#ifndef NDEBUG
|
|
qDebug().nospace() << "Message[" << path() << "] " << size() << " bytes";
|
|
|
|
const char *buffer = m_buf.get();
|
|
const char *end = buffer + m_reservedSize;
|
|
while (buffer < end) {
|
|
std::string prefix;
|
|
const char type = *buffer;
|
|
if (type == Argument) {
|
|
prefix = "argument:";
|
|
}
|
|
else if (type == RBindMountSource) {
|
|
prefix = "rbind-src: ";
|
|
}
|
|
else if (type == RBindMountDest) {
|
|
prefix = "rbind-dst: ";
|
|
}
|
|
else if (type == UnMountDir) {
|
|
prefix = "umountDir: ";
|
|
}
|
|
else if (type == CreateTmpFs) {
|
|
prefix = "tmpFs-create: ";
|
|
}
|
|
else if (type == CopyFrom) {
|
|
prefix = "copyFrom: ";
|
|
}
|
|
else if (type == CopyTo) {
|
|
prefix = "copyTo: ";
|
|
}
|
|
else if (type == EnvironSet) {
|
|
prefix = "New Env: ";
|
|
}
|
|
else if (type == EnvironUnset) {
|
|
prefix = "Filter Env: ";
|
|
}
|
|
else if (type == DBusProxyFrom) {
|
|
prefix = "dbus-from: ";
|
|
}
|
|
else if (type == DBusProxySystemFrom) {
|
|
prefix = "dbus-system-from: ";
|
|
}
|
|
else if (type == DBusProxyTo) {
|
|
prefix = "dbus-to: ";
|
|
}
|
|
else if (type == DBusProxySystemTo) {
|
|
prefix = "dbus-system-to: ";
|
|
}
|
|
else if (type == VpnConfig) {
|
|
prefix = "vpn-config: ";
|
|
}
|
|
else if (type == JailId) {
|
|
// This only works if the source and destination have the same endian-ness
|
|
// as the basic design places them in the same exe just forked into two
|
|
// processes, this is not a concern.
|
|
uint32_t data = buffer[4];
|
|
data <<= 8;
|
|
data += buffer[3];
|
|
data <<= 8;
|
|
data += buffer[2];
|
|
data <<= 8;
|
|
data += buffer[1];
|
|
buffer += 5;
|
|
qDebug() << "=> JailId:" << data;
|
|
continue;
|
|
}
|
|
else if (type == VpnHasPassFile) { // bool
|
|
qDebug() << "=> hasPassFile:" << (buffer[1] != 0);
|
|
buffer += 2;
|
|
continue;
|
|
}
|
|
else if (type == 0) {
|
|
qDebug() << Qt::endl;
|
|
return;
|
|
}
|
|
const int len = ::stringLength(buffer + 1, end);
|
|
if (buffer + len + 2 > end) {
|
|
qWarning() << "buffer overrun detected, str len:" << len;
|
|
return;
|
|
}
|
|
if (type == IsTry) {
|
|
qWarning() << " try";
|
|
buffer += 1;
|
|
continue;
|
|
}
|
|
else if (type != ExecutablePath) {
|
|
qWarning() << " " << prefix.c_str() << QString::fromLocal8Bit(buffer + 1, len);
|
|
}
|
|
buffer += len + 2;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
char *Message::begin() const
|
|
{
|
|
return m_buf.get();
|
|
}
|
|
|
|
int Message::size() const
|
|
{
|
|
if (m_writePtr)
|
|
return m_writePtr - m_buf.get();
|
|
return m_reservedSize;
|
|
}
|
|
|
|
void Message::addUmountPoint(const std::string &dir)
|
|
{
|
|
addString(UnMountDir, dir);
|
|
}
|
|
|
|
void Message::addRemount(const std::string &source, const std::string &destination)
|
|
{
|
|
addString(RBindMountSource, source); // always first
|
|
addString(RBindMountDest, destination); // make sure this is last as it 'executes' the whole thing.
|
|
}
|
|
|
|
void Message::setTry(bool isTry)
|
|
{
|
|
if (isTry)
|
|
addTag(IsTry); // always first
|
|
}
|
|
|
|
void Message::setJailId(uint32_t jailId)
|
|
{
|
|
assert(m_writePtr);
|
|
if (m_buf.get() + m_reservedSize < m_writePtr + 5) {
|
|
throw std::runtime_error("jailId does not fit buffer");
|
|
}
|
|
// This only works if the source and destination have the same endian-ness
|
|
// as the basic design places them in the same exe just forked into two
|
|
// processes, this is not a concern.
|
|
m_writePtr[0] = JailId;
|
|
m_writePtr[1] = jailId & 0xFF;
|
|
m_writePtr[2] = (jailId >> 8) & 0xFF;
|
|
m_writePtr[3] = (jailId >> 16) & 0xFF;
|
|
m_writePtr[4] = (jailId >> 24) & 0xFF;
|
|
m_writePtr += 5;
|
|
}
|
|
|
|
void Message::addMountTmpDir(const std::string &dir)
|
|
{
|
|
addString(CreateTmpFs, dir);
|
|
}
|
|
|
|
void Message::addCopy(const std::string &from, const std::string &to)
|
|
{
|
|
addString(CopyFrom, from);
|
|
addString(CopyTo, to);
|
|
}
|
|
|
|
void Message::addInitSript(const std::string &text)
|
|
{
|
|
addString(InitScript, text);
|
|
}
|
|
|
|
void Message::addEnvToUnset(const std::string &propertyName)
|
|
{
|
|
assert(propertyName.find('=') == std::string::npos);
|
|
addString(EnvironUnset, propertyName);
|
|
}
|
|
|
|
void Message::addEnvToSet(const std::string &envVar)
|
|
{
|
|
assert(envVar.find('=') != std::string::npos);
|
|
addString(EnvironSet, envVar);
|
|
}
|
|
|
|
void Message::setJailPassword(const std::string &pwd)
|
|
{
|
|
addString(JailPassword, pwd);
|
|
}
|
|
|
|
void Message::addDBusProxy(DBusType type, const std::string &from, const std::string &to)
|
|
{
|
|
addString(type == UserSessionBus ? DBusProxyFrom : DBusProxySystemFrom, from);
|
|
addString(type == UserSessionBus ? DBusProxyTo : DBusProxySystemTo, to);
|
|
}
|
|
|
|
void Message::setVpnConfig(const std::string &configPath)
|
|
{
|
|
addString(VpnConfig, configPath);
|
|
}
|
|
|
|
void Message::setHasVpnPassFile(bool yes)
|
|
{
|
|
assert(m_writePtr);
|
|
if (m_buf.get() + m_reservedSize < m_writePtr + 2)
|
|
throw std::runtime_error("Bool does not fit buffer");
|
|
m_writePtr[0] = VpnHasPassFile;
|
|
m_writePtr[1] = yes ? 1 : 0;
|
|
m_writePtr += 2;
|
|
}
|
|
|
|
void Message::addTag(char type)
|
|
{
|
|
assert(m_writePtr);
|
|
if (m_buf.get() + m_reservedSize < m_writePtr + 1) {
|
|
throw std::runtime_error("Tag does not fit buffer");
|
|
}
|
|
*m_writePtr = type;
|
|
++m_writePtr;
|
|
}
|
|
|
|
void Message::addString(char type, const std::string &string)
|
|
{
|
|
assert(m_writePtr);
|
|
if (m_buf.get() + m_reservedSize < m_writePtr + string.size() + 2) {
|
|
// won't fit.
|
|
throw std::runtime_error("String does not fit buffer");
|
|
}
|
|
m_writePtr[0] = type;
|
|
memcpy(++m_writePtr, string.c_str(), string.size());
|
|
m_writePtr += string.size();
|
|
m_writePtr[0] = 0; // trailing zero
|
|
++m_writePtr;
|
|
}
|
|
|
|
|
|
// ///////////////////////////////////////////////////////////////
|
|
|
|
Message::Iterator::Iterator(const Message * const message)
|
|
: m_parent(message),
|
|
m_cur(message->begin()),
|
|
m_recordSize(-1)
|
|
{
|
|
next();
|
|
}
|
|
|
|
bool Message::Iterator::isArgument() const
|
|
{
|
|
assert(isValid());
|
|
return m_cur[0] == Argument;
|
|
}
|
|
|
|
bool Message::Iterator::isNewEnvVar() const
|
|
{
|
|
assert(isValid());
|
|
return m_cur[0] == EnvironSet;
|
|
}
|
|
|
|
bool Message::Iterator::isEnvVarUnset() const
|
|
{
|
|
assert(isValid());
|
|
return m_cur[0] == EnvironUnset;
|
|
}
|
|
|
|
bool Message::Iterator::isUnmount() const
|
|
{
|
|
assert(isValid());
|
|
return m_cur[0] == UnMountDir;
|
|
}
|
|
|
|
bool Message::Iterator::isRemount() const
|
|
{
|
|
assert(isValid());
|
|
return m_cur[0] == RBindMountSource;
|
|
}
|
|
|
|
bool Message::Iterator::isCreateTmp() const
|
|
{
|
|
assert(isValid());
|
|
return m_cur[0] == CreateTmpFs;
|
|
}
|
|
|
|
bool Message::Iterator::isCopy() const
|
|
{
|
|
assert(isValid());
|
|
return m_cur[0] == CopyFrom;
|
|
}
|
|
|
|
bool Message::Iterator::isJailId() const
|
|
{
|
|
assert(isValid());
|
|
return m_cur[0] == JailId;
|
|
}
|
|
|
|
bool Message::Iterator::isInitSript() const
|
|
{
|
|
assert(isValid());
|
|
return m_cur[0] == InitScript;
|
|
}
|
|
|
|
bool Message::Iterator::isJailPwd() const
|
|
{
|
|
assert(isValid());
|
|
return m_cur[0] == JailPassword;
|
|
}
|
|
|
|
bool Message::Iterator::isVpnConfig() const
|
|
{
|
|
assert(isValid());
|
|
return m_cur[0] == VpnConfig;
|
|
}
|
|
|
|
bool Message::Iterator::isVpnPwdBool() const
|
|
{
|
|
assert(isValid());
|
|
return m_cur[0] == VpnHasPassFile;
|
|
}
|
|
|
|
bool Message::Iterator::isValid() const
|
|
{
|
|
assert(m_parent);
|
|
assert(m_cur);
|
|
assert(m_cur >= m_parent->begin());
|
|
if (m_cur < m_parent->begin() + m_parent->size()) {
|
|
return m_cur[0] != 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Message::Iterator::isDBusMapping() const
|
|
{
|
|
return m_cur[0] == DBusProxyFrom || m_cur[0] == DBusProxySystemFrom;
|
|
}
|
|
|
|
bool Message::Iterator::boolData() const
|
|
{
|
|
return m_cur[0] != 0;
|
|
}
|
|
|
|
CopyMessage Message::Iterator::copyData() const
|
|
{
|
|
assert(m_parent);
|
|
CopyMessage rc;
|
|
if (m_cur[0] != CopyFrom)
|
|
throw std::runtime_error("Not copy data");
|
|
|
|
const char *end = m_parent->m_buf.get() + m_parent->m_reservedSize;
|
|
auto length = ::stringLength(m_cur + 1, end);
|
|
rc.from = std::string(m_cur + 1, length);
|
|
auto length2 = ::stringLength(m_cur + 1 + length + 2, end);
|
|
rc.to = std::string(m_cur + 1 + length + 2, length2);
|
|
return rc;
|
|
}
|
|
|
|
DBusMapping Message::Iterator::dbusMapping() const
|
|
{
|
|
assert(m_parent);
|
|
DBusMapping rc;
|
|
if (m_cur[0] == DBusProxyFrom)
|
|
rc.systemBus = false;
|
|
else if (m_cur[0] == DBusProxySystemFrom)
|
|
rc.systemBus = true;
|
|
else
|
|
throw std::runtime_error("Not dbus-mapping data");
|
|
|
|
const char *end = m_parent->m_buf.get() + m_parent->m_reservedSize;
|
|
auto length = ::stringLength(m_cur + 1, end);
|
|
rc.from = std::string(m_cur + 1, length);
|
|
auto length2 = ::stringLength(m_cur + 1 + length + 2, end);
|
|
rc.to = std::string(m_cur + 1 + length + 2, length2);
|
|
return rc;
|
|
}
|
|
|
|
uint32_t Message::Iterator::jailId() const
|
|
{
|
|
assert(m_parent);
|
|
const char *end = m_parent->m_buf.get() + m_parent->m_reservedSize;
|
|
if (m_cur + 4 > end)
|
|
throw std::runtime_error("Not enough space in message");
|
|
assert(isJailId()); // check if it actually is a jailid
|
|
uint32_t data = m_cur[4];
|
|
data <<= 8;
|
|
data += m_cur[3];
|
|
data <<= 8;
|
|
data += m_cur[2];
|
|
data <<= 8;
|
|
data += m_cur[1];
|
|
return data;
|
|
}
|
|
|
|
char *Message::Iterator::stringPtr() const
|
|
{
|
|
return m_cur + 1;
|
|
}
|
|
|
|
int Message::Iterator::stringLength() const
|
|
{
|
|
assert(isValid());
|
|
// not valid for one of the commands that have more than one string.
|
|
assert(!isRemount() && !isCopy());
|
|
return m_recordSize - 2;
|
|
}
|
|
|
|
MountMessage Message::Iterator::mountData() const
|
|
{
|
|
assert(m_parent);
|
|
MountMessage rc;
|
|
if (m_cur[0] == RBindMountSource)
|
|
rc.type = MountMessage::Remount;
|
|
else if (m_cur[0] == UnMountDir)
|
|
rc.type = MountMessage::Umount;
|
|
else if (m_cur[0] == CreateTmpFs)
|
|
rc.type = MountMessage::CreateTmpFs;
|
|
else
|
|
throw std::runtime_error("Not mount data");
|
|
|
|
const char *end = m_parent->m_buf.get() + m_parent->m_reservedSize;
|
|
int index = 0;
|
|
while (true) {
|
|
char t = m_cur[index];
|
|
const char *str = m_cur + index + 1;
|
|
const int strSize = ::stringLength(m_cur + index + 1, end);
|
|
index += strSize + 2;
|
|
checkAvail(index);
|
|
if (t == RBindMountSource) {
|
|
rc.src = std::string(str, strSize);
|
|
}
|
|
else if (t == RBindMountDest) {
|
|
rc.dst = std::string(str, strSize);
|
|
break; // last in the sequence
|
|
}
|
|
else if (rc.type == MountMessage::Umount) {
|
|
rc.src = std::string(str, strSize);
|
|
// only the src field is expected
|
|
break;
|
|
}
|
|
else if (rc.type == MountMessage::CreateTmpFs) {
|
|
rc.dst = std::string(str, strSize);
|
|
// only the dst field is expected
|
|
break;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
const char *Message::Iterator::argument() const
|
|
{
|
|
assert(m_parent);
|
|
assert(isArgument());
|
|
assert(m_recordSize != -1); // we called 'next'
|
|
return m_cur + 1;
|
|
}
|
|
|
|
bool Message::Iterator::next()
|
|
{
|
|
assert(m_cur);
|
|
if (m_recordSize != -1) {
|
|
m_cur = m_cur + m_recordSize;
|
|
if (!isValid())
|
|
return false;
|
|
}
|
|
|
|
m_isTry = *m_cur == IsTry;
|
|
if (m_isTry)
|
|
m_cur += 1;
|
|
|
|
m_recordSize = 0;
|
|
checkAvail(2);
|
|
// find the string-size(s)
|
|
const char *end = m_parent->m_buf.get() + m_parent->m_reservedSize;
|
|
switch (*m_cur) {
|
|
case ExecutablePath: // fall through
|
|
case Argument: // fall through
|
|
case UnMountDir: // fall through
|
|
case EnvironSet: // fall through
|
|
case EnvironUnset: // fall through
|
|
case InitScript: // fall through
|
|
case JailPassword: // fall through
|
|
case VpnConfig: // fall through
|
|
case CreateTmpFs:
|
|
m_recordSize = ::stringLength(m_cur + 1, end) + 2;
|
|
break;
|
|
case DBusProxyFrom: // fall through
|
|
case DBusProxySystemFrom: // fall through
|
|
case RBindMountSource: // fall-through
|
|
case CopyFrom:
|
|
// these take 2 args
|
|
m_recordSize = ::stringLength(m_cur + 1, end) + 2;
|
|
m_recordSize += ::stringLength(m_cur + m_recordSize + 1, end) + 2;
|
|
break;
|
|
case JailId:
|
|
m_recordSize += 5;
|
|
break;
|
|
case VpnHasPassFile: // bool
|
|
m_recordSize += 2;
|
|
break;
|
|
case DBusProxyTo: // fall through
|
|
case DBusProxySystemTo: // fall through
|
|
case RBindMountDest:
|
|
assert(false);
|
|
default:
|
|
assert(false);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Message::Iterator::checkAvail(int bytes) const
|
|
{
|
|
if (bytes < 0 || bytes > MAX_SIZE)
|
|
throw std::runtime_error("Serialization failure: impossible size");
|
|
if (m_parent->begin() + m_parent->size() < m_cur + bytes)
|
|
throw std::runtime_error("Message::Iterator: out of bounds");
|
|
}
|