libreoffice-online/kit/ForKit.cpp

761 lines
23 KiB
C++
Raw Normal View History

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* A very simple, single threaded helper to efficiently pre-init and
* spawn lots of kits as children.
*/
#include <config.h>
#ifndef __FreeBSD__
#include <sys/capability.h>
#endif
#include <sys/types.h>
#include <sys/wait.h>
#include <sysexits.h>
#include <atomic>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <thread>
#include <chrono>
#include <Poco/Path.h>
#include <Common.hpp>
#include "Kit.hpp"
#include "SetupKitEnvironment.hpp"
#include <Log.hpp>
#include <Unit.hpp>
#include <Util.hpp>
#include <WebSocketHandler.hpp>
#if !MOBILEAPP
#include <Admin.hpp>
#endif
#include <common/FileUtil.hpp>
wsd: faster jail setup via bind-mount loolmount now works and supports mounting and unmounting, plus numerous improvements, refactoring, logging, etc.. When enabled, binding improves the jail setup time by anywhere from 2x to orders of magnitude (in docker, f.e.). A new config entry mount_jail_tree controls whether mounting is used or the old method of linking/copying of jail contents. It is set to true by default and falls back to linking/copying. A test mount is done when the setting is enabled, and if mounting fails, it's disabled to avoid noise. Temporarily disabled for unit-tests until we can cleanup lingering mounts after Jenkins aborts our build job. In a future patch we will have mount/jail cleanup as part of make. The network/system files in /etc that need frequent refreshing are now updated in systemplate to make their most recent version available in the jails. These files can change during the course of loolwsd lifetime, and are unlikely to be updated in systemplate after installation at all. We link to them in the systemplate/etc directory, and if that fails, we copy them before forking each kit instance to have the latest. This reworks the approach used to bind-mount the jails and the templates such that the total is now down to only three mounts: systemplate, lo, tmp. As now systemplate and lotemplate are shared, they must be mounted as readonly, this means that user/ must now be moved into tmp/user/ which is writable. The mount-points must be recursive, because we mount lo/ within the mount-point of systemplate (which is the root of the jail). But because we (re)bind recursively, and because both systemplate and lotemplate are mounted for each jails, we need to make them unbindable, so they wouldn't multiply the mount-points for each jails (an explosive growth!) Contrarywise, we don't want the mount-points to be shared, because we don't expect to add/remove mounts after a jail is created. The random temp directory is now created and set correctly, plus many logging and other improvements. Change-Id: Iae3fda5e876cf47d2cae6669a87b5b826a8748df Reviewed-on: https://gerrit.libreoffice.org/c/online/+/92829 Tested-by: Jenkins Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com> Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
2020-04-09 08:02:58 -05:00
#include <common/JailUtil.hpp>
#include <common/Seccomp.hpp>
#include <common/SigUtil.hpp>
#include <common/security.h>
#include <common/ConfigUtil.hpp>
#ifndef KIT_IN_PROCESS
static bool NoCapsForKit = false;
static bool NoSeccomp = false;
#if ENABLE_DEBUG
static bool SingleKit = false;
#endif
#else
static const bool NoCapsForKit = true; // NoCaps for in-process kit.
static const bool NoSeccomp = true; // NoSeccomp for in-process kit.
#endif
static std::string UserInterface;
static bool DisplayVersion = false;
static std::string UnitTestLibrary;
static std::string LogLevel;
static std::atomic<unsigned> ForkCounter(0);
/// The [child pid -> jail path] map.
static std::map<pid_t, std::string> childJails;
/// The jails that need cleaning up. This should be small.
static std::vector<std::string> cleanupJailPaths;
#ifndef KIT_IN_PROCESS
int ClientPortNumber = DEFAULT_CLIENT_PORT_NUMBER;
std::string MasterLocation;
#endif
extern "C" { void dump_forkit_state(void); /* easy for gdb */ }
void dump_forkit_state()
{
std::ostringstream oss;
oss << "Forkit: " << ForkCounter << " forks\n"
<< " loglevel: " << LogLevel << "\n"
<< " unit test: " << UnitTestLibrary << "\n"
#ifndef KIT_IN_PROCESS
<< " NoCapsForKit: " << NoCapsForKit << "\n"
<< " NoSeccomp: " << NoSeccomp << "\n"
# if ENABLE_DEBUG
<< " SingleKit: " << SingleKit << "\n"
# endif
#endif
<< " ClientPortNumber: " << ClientPortNumber << "\n"
<< " MasterLocation: " << MasterLocation
<< "\n";
const std::string msg = oss.str();
fprintf(stderr, "%s", msg.c_str());
LOG_TRC(msg);
}
class ServerWSHandler;
// We have a single thread and a single connection so we won't bother with
// access synchronization
std::shared_ptr<ServerWSHandler> WSHandler;
class ServerWSHandler final : public WebSocketHandler
{
std::string _socketName;
public:
ServerWSHandler(const std::string& socketName) :
WebSocketHandler(/* isClient = */ true, /* isMasking */ false),
_socketName(socketName)
{
}
protected:
void handleMessage(const std::vector<char>& data) override
{
std::string message(data.data(), data.size());
#if !MOBILEAPP
if (UnitKit::get().filterKitMessage(this, message))
return;
#endif
StringVector tokens = Util::tokenize(message);
Log::StreamLogger logger = Log::debug();
if (logger.enabled())
{
logger << _socketName << ": recv [";
for (const auto& token : tokens)
{
logger << tokens.getParam(token) << ' ';
}
LOG_END(logger, true);
}
// Note: Syntax or parsing errors here are unexpected and fatal.
if (SigUtil::getTerminationFlag())
{
LOG_DBG("Termination flag set: skip message processing");
}
else if (tokens.size() == 2 && tokens.equals(0, "spawn"))
{
const int count = std::stoi(tokens[1]);
if (count > 0)
{
LOG_INF("Setting to spawn " << tokens[1] << " child" << (count == 1 ? "" : "ren") << " per request.");
ForkCounter = count;
}
else
{
LOG_WRN("Cannot spawn " << tokens[1] << " children as requested.");
}
}
else if (tokens.size() == 2 && tokens.equals(0, "setloglevel"))
{
// Set environment variable so that new children will also set their log levels accordingly.
setenv("LOOL_LOGLEVEL", tokens[1].c_str(), 1);
Log::logger().setLevel(tokens[1]);
}
else if (tokens.size() == 3 && tokens.equals(0, "setconfig"))
{
// Currently only rlimit entries are supported.
if (!Rlimit::handleSetrlimitCommand(tokens))
{
LOG_ERR("Unknown setconfig command: " << message);
}
}
else if (tokens.equals(0, "exit"))
{
LOG_INF("Setting TerminationFlag due to 'exit' command from parent.");
SigUtil::setTerminationFlag();
}
else
{
LOG_ERR("Bad or unknown token [" << tokens[0] << ']');
}
}
void onDisconnect() override
{
#if !MOBILEAPP
LOG_ERR("ForKit connection lost without exit arriving from wsd. Setting TerminationFlag");
SigUtil::setTerminationFlag();
#endif
}
};
#ifndef KIT_IN_PROCESS
#ifndef __FreeBSD__
static bool haveCapability(cap_value_t capability)
{
cap_t caps = cap_get_proc();
if (caps == nullptr)
{
LOG_SFL("cap_get_proc() failed");
return false;
}
char *cap_name = cap_to_name(capability);
cap_flag_value_t value;
if (cap_get_flag(caps, capability, CAP_EFFECTIVE, &value) == -1)
{
if (cap_name)
{
LOG_SFL("cap_get_flag failed for " << cap_name);
cap_free(cap_name);
}
else
{
LOG_SFL("cap_get_flag failed for capability " << capability);
}
return false;
}
if (value != CAP_SET)
{
if (cap_name)
{
LOG_ERR("Capability " << cap_name << " is not set for the loolforkit program.");
cap_free(cap_name);
}
else
{
LOG_ERR("Capability " << capability << " is not set for the loolforkit program.");
}
return false;
}
if (cap_name)
{
LOG_INF("Have capability " << cap_name);
cap_free(cap_name);
}
else
{
LOG_INF("Have capability " << capability);
}
return true;
}
static bool haveCorrectCapabilities()
{
bool result = true;
// Do check them all, don't shortcut with &&
if (!haveCapability(CAP_SYS_CHROOT))
result = false;
if (!haveCapability(CAP_MKNOD))
result = false;
2016-10-12 07:15:39 -05:00
if (!haveCapability(CAP_FOWNER))
result = false;
if (!haveCapability(CAP_CHOWN))
result = false;
return result;
}
#else
static bool haveCorrectCapabilities()
{
// chroot() can only be called by root
return getuid() == 0;
}
#endif // __FreeBSD__
#endif
/// Check if some previously forked kids have died.
static void cleanupChildren()
{
pid_t exitedChildPid;
int status = 0;
int segFaultCount = 0;
// Reap quickly without doing slow cleanup so WSD can spawn more rapidly.
while ((exitedChildPid = waitpid(-1, &status, WUNTRACED | WNOHANG)) > 0)
{
const auto it = childJails.find(exitedChildPid);
if (it != childJails.end())
{
LOG_INF("Child " << exitedChildPid << " has exited, will remove its jail [" << it->second << "].");
cleanupJailPaths.emplace_back(it->second);
childJails.erase(it);
if (childJails.empty() && !SigUtil::getTerminationFlag())
{
// We ran out of kits and we aren't terminating.
LOG_WRN("No live Kits exist, and we are not terminating yet.");
}
if (WIFSIGNALED(status) && (WTERMSIG(status) == SIGSEGV || WTERMSIG(status) == SIGBUS))
{
++segFaultCount;
}
}
else
{
LOG_ERR("Unknown child " << exitedChildPid << " has exited");
}
}
if (segFaultCount)
{
#ifdef KIT_IN_PROCESS
#if !MOBILEAPP
Admin::instance().addSegFaultCount(segFaultCount);
#endif
#else
if (WSHandler)
{
std::stringstream stream;
stream << "segfaultcount " << segFaultCount << '\n';
int ret = WSHandler->sendMessage(stream.str());
if (ret == -1)
{
LOG_WRN("Could not send 'segfaultcount' message through websocket");
}
else
{
LOG_WRN("Successfully sent 'segfaultcount' message " << stream.str());
}
}
#endif
}
// Now delete the jails.
auto i = cleanupJailPaths.size();
while (i-- > 0)
{
const std::string path = cleanupJailPaths[i];
JailUtil::removeJail(path);
const FileUtil::Stat st(path);
if (st.good() && st.isDirectory())
LOG_DBG("Could not remove jail path [" << path << "]. Will retry later.");
else
cleanupJailPaths.erase(cleanupJailPaths.begin() + i);
}
}
static int createLibreOfficeKit(const std::string& childRoot,
const std::string& sysTemplate,
const std::string& loTemplate,
const std::string& loSubPath,
bool queryVersion = false)
{
// Generate a jail ID to be used for in the jail path.
const std::string jailId = Util::rng::getFilename(16);
wsd: faster jail setup via bind-mount loolmount now works and supports mounting and unmounting, plus numerous improvements, refactoring, logging, etc.. When enabled, binding improves the jail setup time by anywhere from 2x to orders of magnitude (in docker, f.e.). A new config entry mount_jail_tree controls whether mounting is used or the old method of linking/copying of jail contents. It is set to true by default and falls back to linking/copying. A test mount is done when the setting is enabled, and if mounting fails, it's disabled to avoid noise. Temporarily disabled for unit-tests until we can cleanup lingering mounts after Jenkins aborts our build job. In a future patch we will have mount/jail cleanup as part of make. The network/system files in /etc that need frequent refreshing are now updated in systemplate to make their most recent version available in the jails. These files can change during the course of loolwsd lifetime, and are unlikely to be updated in systemplate after installation at all. We link to them in the systemplate/etc directory, and if that fails, we copy them before forking each kit instance to have the latest. This reworks the approach used to bind-mount the jails and the templates such that the total is now down to only three mounts: systemplate, lo, tmp. As now systemplate and lotemplate are shared, they must be mounted as readonly, this means that user/ must now be moved into tmp/user/ which is writable. The mount-points must be recursive, because we mount lo/ within the mount-point of systemplate (which is the root of the jail). But because we (re)bind recursively, and because both systemplate and lotemplate are mounted for each jails, we need to make them unbindable, so they wouldn't multiply the mount-points for each jails (an explosive growth!) Contrarywise, we don't want the mount-points to be shared, because we don't expect to add/remove mounts after a jail is created. The random temp directory is now created and set correctly, plus many logging and other improvements. Change-Id: Iae3fda5e876cf47d2cae6669a87b5b826a8748df Reviewed-on: https://gerrit.libreoffice.org/c/online/+/92829 Tested-by: Jenkins Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com> Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
2020-04-09 08:02:58 -05:00
// Update the dynamic files as necessary.
JailUtil::SysTemplate::updateDynamicFiles(sysTemplate);
// Used to label the spare kit instances
static size_t spareKitId = 0;
++spareKitId;
LOG_DBG("Forking a loolkit process with jailId: " << jailId << " as spare loolkit #"
<< spareKitId << '.');
const pid_t pid = fork();
if (!pid)
{
2016-10-13 05:10:09 -05:00
// Child
// Close the pipe from loolwsd
close(0);
#ifndef KIT_IN_PROCESS
2016-04-09 12:26:33 -05:00
UnitKit::get().postFork();
#endif
2016-04-09 12:26:33 -05:00
if (std::getenv("SLEEPKITFORDEBUGGER"))
{
const size_t delaySecs = std::stoul(std::getenv("SLEEPKITFORDEBUGGER"));
if (delaySecs > 0)
{
std::cerr << "Kit: Sleeping " << delaySecs
<< " seconds to give you time to attach debugger to process "
<< getpid() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(delaySecs));
}
}
lokit_main(childRoot, jailId, sysTemplate, loTemplate, loSubPath, NoCapsForKit, NoSeccomp,
queryVersion, DisplayVersion, spareKitId);
}
else
{
// Parent
if (pid < 0)
{
LOG_SYS("Fork failed");
}
else
{
LOG_INF("Forked kit [" << pid << ']');
childJails[pid] = childRoot + jailId;
}
2016-04-13 09:10:02 -05:00
#ifndef KIT_IN_PROCESS
2016-04-13 09:10:02 -05:00
UnitKit::get().launchedKit(pid);
#endif
}
2016-04-08 05:09:06 -05:00
return pid;
}
void forkLibreOfficeKit(const std::string& childRoot,
const std::string& sysTemplate,
const std::string& loTemplate,
const std::string& loSubPath,
int limit)
{
// Cleanup first, to reduce disk load.
cleanupChildren();
#ifndef KIT_IN_PROCESS
(void) limit;
#else
if (limit > 0)
ForkCounter = limit;
#endif
if (ForkCounter > 0)
{
// Create as many as requested.
const size_t count = ForkCounter;
LOG_INF("Spawning " << count << " new child" << (count == 1 ? "." : "ren."));
const size_t retry = count * 2;
for (size_t i = 0; ForkCounter > 0 && i < retry; ++i)
{
if (ForkCounter-- <= 0
|| createLibreOfficeKit(childRoot, sysTemplate, loTemplate, loSubPath) < 0)
{
LOG_ERR("Failed to create a kit process.");
++ForkCounter;
}
}
}
}
#ifndef KIT_IN_PROCESS
static void printArgumentHelp()
{
std::cout << "Usage: loolforkit [OPTION]..." << std::endl;
std::cout << " Single-threaded process that spawns lok instances" << std::endl;
std::cout << " Note: Running this standalone is not possible. It is spawned by loolwsd" << std::endl;
std::cout << " and is controlled via a pipe." << std::endl;
std::cout << "" << std::endl;
}
int main(int argc, char** argv)
{
/*WARNING: PRIVILEGED CODE CHECKING START */
/*WARNING*/ // early check for avoiding the security check for username 'lool'
/*WARNING*/ // (deliberately only this, not moving the entire parameter parsing here)
/*WARNING*/ bool checkLoolUser = true;
/*WARNING*/ std::string disableLoolUserChecking("--disable-lool-user-checking");
/*WARNING*/ for (int i = 1; checkLoolUser && (i < argc); ++i)
/*WARNING*/ {
/*WARNING*/ if (disableLoolUserChecking == argv[i])
/*WARNING*/ checkLoolUser = false;
/*WARNING*/ }
/*WARNING*/ if (!hasCorrectUID("loolforkit"))
/*WARNING*/ {
/*WARNING*/ // don't allow if any capability is set (unless root; who runs this
/*WARNING*/ // as root or runs this in a container and provides --disable-lool-user-checking knows what they
/*WARNING*/ // are doing)
/*WARNING*/ if (hasUID("root"))
/*WARNING*/ {
/*WARNING*/ // This is fine, the 'root' can do anything anyway
/*WARNING*/ }
/*WARNING*/ else if (isInContainer())
/*WARNING*/ {
/*WARNING*/ // This is fine, we are confined in the container anyway
/*WARNING*/ }
/*WARNING*/ else if (hasAnyCapability())
/*WARNING*/ {
/*WARNING*/ if (!checkLoolUser)
/*WARNING*/ LOG_FTL("Security: --disable-lool-user-checking failed, loolforkit has some capabilities set.");
/*WARNING*/ LOG_FTL("Aborting.");
/*WARNING*/ return EX_SOFTWARE;
/*WARNING*/ }
/*WARNING*/ // even without the capabilities, don't run unless the user really knows
/*WARNING*/ // what they are doing, and provided a --disable-lool-user-checking
/*WARNING*/ if (checkLoolUser)
/*WARNING*/ {
/*WARNING*/ LOG_FTL("Aborting.");
/*WARNING*/ return EX_SOFTWARE;
/*WARNING*/ }
/*WARNING*/ LOG_ERR("Security: Check for the 'lool' username overridden on the command line.");
/*WARNING*/ }
/*WARNING: PRIVILEGED CODE CHECKING END */
// Continue in privileged mode, but only if:
// * the user is 'lool' (privileged user)
// * the user is 'root', and --disable-lool-user-checking was provided
// Alternatively allow running in non-privileged mode (with --nocaps), if:
// * the user is a non-priviled user, the binary is not privileged
// either (no caps set), and --disable-lool-user-checking was provided
if (std::getenv("SLEEPFORDEBUGGER"))
{
const size_t delaySecs = std::stoul(std::getenv("SLEEPFORDEBUGGER"));
if (delaySecs > 0)
{
std::cerr << "Forkit: Sleeping " << delaySecs
<< " seconds to give you time to attach debugger to process "
<< getpid() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(delaySecs));
}
}
#ifndef FUZZER
SigUtil::setFatalSignals("forkit startup of " LOOLWSD_VERSION " " LOOLWSD_VERSION_HASH);
SigUtil::setTerminationSignals();
#endif
2017-03-30 15:55:17 -05:00
Util::setThreadName("forkit");
wsd: faster jail setup via bind-mount loolmount now works and supports mounting and unmounting, plus numerous improvements, refactoring, logging, etc.. When enabled, binding improves the jail setup time by anywhere from 2x to orders of magnitude (in docker, f.e.). A new config entry mount_jail_tree controls whether mounting is used or the old method of linking/copying of jail contents. It is set to true by default and falls back to linking/copying. A test mount is done when the setting is enabled, and if mounting fails, it's disabled to avoid noise. Temporarily disabled for unit-tests until we can cleanup lingering mounts after Jenkins aborts our build job. In a future patch we will have mount/jail cleanup as part of make. The network/system files in /etc that need frequent refreshing are now updated in systemplate to make their most recent version available in the jails. These files can change during the course of loolwsd lifetime, and are unlikely to be updated in systemplate after installation at all. We link to them in the systemplate/etc directory, and if that fails, we copy them before forking each kit instance to have the latest. This reworks the approach used to bind-mount the jails and the templates such that the total is now down to only three mounts: systemplate, lo, tmp. As now systemplate and lotemplate are shared, they must be mounted as readonly, this means that user/ must now be moved into tmp/user/ which is writable. The mount-points must be recursive, because we mount lo/ within the mount-point of systemplate (which is the root of the jail). But because we (re)bind recursively, and because both systemplate and lotemplate are mounted for each jails, we need to make them unbindable, so they wouldn't multiply the mount-points for each jails (an explosive growth!) Contrarywise, we don't want the mount-points to be shared, because we don't expect to add/remove mounts after a jail is created. The random temp directory is now created and set correctly, plus many logging and other improvements. Change-Id: Iae3fda5e876cf47d2cae6669a87b5b826a8748df Reviewed-on: https://gerrit.libreoffice.org/c/online/+/92829 Tested-by: Jenkins Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com> Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
2020-04-09 08:02:58 -05:00
Util::setApplicationPath(Poco::Path(argv[0]).parent().toString());
2017-03-30 15:55:17 -05:00
// Initialization
2016-10-12 03:47:26 -05:00
const bool logToFile = std::getenv("LOOL_LOGFILE");
const char* logFilename = std::getenv("LOOL_LOGFILENAME");
const char* logLevel = std::getenv("LOOL_LOGLEVEL");
const char* logColor = std::getenv("LOOL_LOGCOLOR");
std::map<std::string, std::string> logProperties;
if (logToFile && logFilename)
{
logProperties["path"] = std::string(logFilename);
}
Log::initialize("frk", "trace", logColor != nullptr, logToFile, logProperties);
LogLevel = logLevel ? logLevel : "trace";
if (LogLevel != "trace")
{
LOG_INF("Setting log-level to [trace] and delaying setting to configured [" << LogLevel << "] until after Forkit initialization.");
}
std::string childRoot;
std::string loSubPath;
std::string sysTemplate;
std::string loTemplate;
for (int i = 0; i < argc; ++i)
{
char *cmd = argv[i];
char *eq;
if (std::strstr(cmd, "--losubpath=") == cmd)
{
eq = std::strchr(cmd, '=');
loSubPath = std::string(eq+1);
}
else if (std::strstr(cmd, "--systemplate=") == cmd)
{
eq = std::strchr(cmd, '=');
sysTemplate = std::string(eq+1);
}
else if (std::strstr(cmd, "--lotemplate=") == cmd)
{
eq = std::strchr(cmd, '=');
loTemplate = std::string(eq+1);
}
else if (std::strstr(cmd, "--childroot=") == cmd)
{
eq = std::strchr(cmd, '=');
childRoot = std::string(eq+1);
}
else if (std::strstr(cmd, "--clientport=") == cmd)
{
eq = std::strchr(cmd, '=');
ClientPortNumber = std::stoll(std::string(eq+1));
}
else if (std::strstr(cmd, "--masterport=") == cmd)
{
eq = std::strchr(cmd, '=');
MasterLocation = std::string(eq+1);
}
else if (std::strstr(cmd, "--version") == cmd)
{
std::string version, hash;
Util::getVersionInfo(version, hash);
std::cout << "loolforkit version details: " << version << " - " << hash << std::endl;
DisplayVersion = true;
}
else if (std::strstr(cmd, "--rlimits") == cmd)
{
eq = std::strchr(cmd, '=');
const std::string rlimits = std::string(eq+1);
StringVector tokens = Util::tokenize(rlimits, ';');
for (const auto& cmdLimit : tokens)
{
const std::pair<std::string, std::string> pair = Util::split(tokens.getParam(cmdLimit), ':');
StringVector tokensLimit;
tokensLimit.push_back("setconfig");
tokensLimit.push_back(pair.first);
tokensLimit.push_back(pair.second);
if (!Rlimit::handleSetrlimitCommand(tokensLimit))
{
LOG_ERR("Unknown rlimits command: " << tokens.getParam(cmdLimit));
}
}
}
#if ENABLE_DEBUG
// this process has various privileges - don't run arbitrary code.
else if (std::strstr(cmd, "--unitlib=") == cmd)
{
eq = std::strchr(cmd, '=');
UnitTestLibrary = std::string(eq+1);
}
else if (std::strstr(cmd, "--singlekit") == cmd)
{
SingleKit = true;
}
#endif
// we are running in a lower-privilege mode - with no chroot
else if (std::strstr(cmd, "--nocaps") == cmd)
{
LOG_ERR("Security: Running without the capability to enter a chroot jail is ill advised.");
NoCapsForKit = true;
}
// we are running without seccomp protection
else if (std::strstr(cmd, "--noseccomp") == cmd)
{
LOG_ERR("Security: Running without the ability to filter system calls is ill advised.");
NoSeccomp = true;
}
else if (std::strstr(cmd, "--ui") == cmd)
{
eq = std::strchr(cmd, '=');
UserInterface = std::string(eq+1);
}
}
if (loSubPath.empty() || sysTemplate.empty() ||
loTemplate.empty() || childRoot.empty())
{
printArgumentHelp();
return EX_USAGE;
}
if (!UnitBase::init(UnitBase::UnitType::Kit,
UnitTestLibrary))
{
LOG_FTL("Failed to load kit unit test library");
return EX_USAGE;
}
setupKitEnvironment(UserInterface);
if (!std::getenv("LD_BIND_NOW")) // must be set by parent.
LOG_INF("Note: LD_BIND_NOW is not set.");
if (!NoCapsForKit && !haveCorrectCapabilities())
{
LOG_FTL("Capabilities are not set for the loolforkit program.");
LOG_FTL("Please make sure that the current partition was *not* mounted with the 'nosuid' option.");
LOG_FTL("If you are on SLES11, please set 'file_caps=1' as kernel boot option.");
return EX_SOFTWARE;
}
// Initialize LoKit
if (!globalPreinit(loTemplate))
{
LOG_FTL("Failed to preinit lokit.");
Log::shutdown();
std::_Exit(EX_SOFTWARE);
}
if (Util::getProcessThreadCount() != 1)
LOG_ERR("forkit has more than a single thread after pre-init");
// Link the network and system files in sysTemplate, if possible.
wsd: faster jail setup via bind-mount loolmount now works and supports mounting and unmounting, plus numerous improvements, refactoring, logging, etc.. When enabled, binding improves the jail setup time by anywhere from 2x to orders of magnitude (in docker, f.e.). A new config entry mount_jail_tree controls whether mounting is used or the old method of linking/copying of jail contents. It is set to true by default and falls back to linking/copying. A test mount is done when the setting is enabled, and if mounting fails, it's disabled to avoid noise. Temporarily disabled for unit-tests until we can cleanup lingering mounts after Jenkins aborts our build job. In a future patch we will have mount/jail cleanup as part of make. The network/system files in /etc that need frequent refreshing are now updated in systemplate to make their most recent version available in the jails. These files can change during the course of loolwsd lifetime, and are unlikely to be updated in systemplate after installation at all. We link to them in the systemplate/etc directory, and if that fails, we copy them before forking each kit instance to have the latest. This reworks the approach used to bind-mount the jails and the templates such that the total is now down to only three mounts: systemplate, lo, tmp. As now systemplate and lotemplate are shared, they must be mounted as readonly, this means that user/ must now be moved into tmp/user/ which is writable. The mount-points must be recursive, because we mount lo/ within the mount-point of systemplate (which is the root of the jail). But because we (re)bind recursively, and because both systemplate and lotemplate are mounted for each jails, we need to make them unbindable, so they wouldn't multiply the mount-points for each jails (an explosive growth!) Contrarywise, we don't want the mount-points to be shared, because we don't expect to add/remove mounts after a jail is created. The random temp directory is now created and set correctly, plus many logging and other improvements. Change-Id: Iae3fda5e876cf47d2cae6669a87b5b826a8748df Reviewed-on: https://gerrit.libreoffice.org/c/online/+/92829 Tested-by: Jenkins Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com> Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
2020-04-09 08:02:58 -05:00
JailUtil::SysTemplate::setupDynamicFiles(sysTemplate);
// Make dev/[u]random point to the writable devices in tmp/dev/.
JailUtil::SysTemplate::setupRandomDeviceLinks(sysTemplate);
#if !MOBILEAPP
// Parse the configuration.
const auto conf = std::getenv("LOOL_CONFIG");
config::initialize(std::string(conf ? conf : std::string()));
#endif
LOG_INF("Preinit stage OK.");
// We must have at least one child, more are created dynamically.
// Ask this first child to send version information to master process and trace startup.
::setenv("LOOL_TRACE_STARTUP", "1", 1);
const pid_t forKitPid
= createLibreOfficeKit(childRoot, sysTemplate, loTemplate, loSubPath, true);
if (forKitPid < 0)
{
LOG_FTL("Failed to create a kit process.");
Log::shutdown();
std::_Exit(EX_SOFTWARE);
}
// No need to trace subsequent children.
::unsetenv("LOOL_TRACE_STARTUP");
if (LogLevel != "trace")
{
LOG_INF("Forkit initialization complete: setting log-level to [" << LogLevel << "] as configured.");
Log::logger().setLevel(LogLevel);
}
SocketPoll mainPoll(Util::getThreadName());
mainPoll.runOnClientThread(); // We will do the polling on this thread.
WSHandler = std::make_shared<ServerWSHandler>("forkit_ws");
#if !MOBILEAPP
mainPoll.insertNewUnixSocket(MasterLocation, FORKIT_URI, WSHandler);
#endif
SigUtil::setUserSignals();
LOG_INF("ForKit process is ready.");
while (!SigUtil::getTerminationFlag())
{
2016-04-09 12:26:33 -05:00
UnitKit::get().invokeForKitTest();
mainPoll.poll(std::chrono::microseconds(POLL_TIMEOUT_MICRO_S));
SigUtil::checkDumpGlobalState(dump_forkit_state);
#if ENABLE_DEBUG
if (!SingleKit)
#endif
forkLibreOfficeKit(childRoot, sysTemplate, loTemplate, loSubPath);
}
int returnValue = EX_OK;
UnitKit::get().returnValue(returnValue);
#if 0
int status = 0;
waitpid(forKitPid, &status, WUNTRACED);
#endif
LOG_INF("ForKit process finished.");
Log::shutdown();
std::_Exit(returnValue);
}
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */