[Libreoffice-commits] online.git: android/lib common/Common.hpp common/FileUtil.cpp common/FileUtil.hpp common/JailUtil.cpp common/JailUtil.hpp common/Log.hpp common/security.h common/Session.cpp common/SigUtil.cpp common/Util.cpp common/Util.hpp configure.ac debian/loolwsd.postinst.in docker/Ubuntu kit/ForKit.cpp kit/Kit.cpp loolwsd.spec.in loolwsd-systemplate-setup loolwsd.xml.in Makefile.am net/Socket.cpp net/Socket.hpp test/run_unit.sh.in tools/mount.cpp wsd/LOOLWSD.cpp wsd/Storage.hpp
Ashod Nakashian (via logerrit)
logerrit at kemper.freedesktop.org
Wed Jul 1 03:43:05 UTC 2020
Makefile.am | 4
android/lib/src/main/cpp/CMakeLists.txt.in | 1
common/Common.hpp | 2
common/FileUtil.cpp | 52 +++-
common/FileUtil.hpp | 43 +++
common/JailUtil.cpp | 364 +++++++++++++++++++++++++++++
common/JailUtil.hpp | 70 +++++
common/Log.hpp | 1
common/Session.cpp | 1
common/SigUtil.cpp | 1
common/Util.cpp | 26 +-
common/Util.hpp | 23 +
common/security.h | 1
configure.ac | 2
debian/loolwsd.postinst.in | 1
docker/Ubuntu | 1
kit/ForKit.cpp | 21 +
kit/Kit.cpp | 199 ++++++---------
loolwsd-systemplate-setup | 5
loolwsd.spec.in | 2
loolwsd.xml.in | 1
net/Socket.cpp | 1
net/Socket.hpp | 1
test/run_unit.sh.in | 2
tools/mount.cpp | 124 +++++++++
wsd/LOOLWSD.cpp | 89 +++----
wsd/Storage.hpp | 4
27 files changed, 836 insertions(+), 206 deletions(-)
New commits:
commit 5c9988f2e345ca82e7bb5f5e9bf66a30b82a0446
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
AuthorDate: Thu Apr 9 09:02:58 2020 -0400
Commit: Ashod Nakashian <ashnakash at gmail.com>
CommitDate: Wed Jul 1 05:42:43 2020 +0200
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 at gmail.com>
Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
diff --git a/Makefile.am b/Makefile.am
index 171a07e36..d3f18e6a3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -16,6 +16,7 @@ export ENABLE_DEBUG
bin_PROGRAMS = \
loolforkit \
+ loolmount \
loolconvert loolconfig
if ENABLE_LIBFUZZER
@@ -87,6 +88,7 @@ AM_ETAGSFLAGS = --c++-kinds=+p --fields=+iaS --extra=+q -R --totals=yes --exclud
AM_CTAGSFLAGS = $(AM_ETAGSFLAGS)
shared_sources = common/FileUtil.cpp \
+ common/JailUtil.cpp \
common/Log.cpp \
common/Protocol.cpp \
common/StringVector.cpp \
@@ -131,7 +133,6 @@ noinst_PROGRAMS = clientnb \
lokitclient \
loolmap \
loolstress \
- loolmount \
loolsocketdump
if ENABLE_LIBFUZZER
@@ -239,6 +240,7 @@ shared_headers = common/Common.hpp \
common/Crypto.hpp \
common/JsonUtil.hpp \
common/FileUtil.hpp \
+ common/JailUtil.hpp \
common/Log.hpp \
common/LOOLWebSocket.hpp \
common/Protocol.hpp \
diff --git a/android/lib/src/main/cpp/CMakeLists.txt.in b/android/lib/src/main/cpp/CMakeLists.txt.in
index 7b1dc9202..56b5a8a52 100644
--- a/android/lib/src/main/cpp/CMakeLists.txt.in
+++ b/android/lib/src/main/cpp/CMakeLists.txt.in
@@ -4,6 +4,7 @@ cmake_minimum_required(VERSION 3.4.1)
add_library(androidapp SHARED
androidapp.cpp
../../../../../common/FileUtil.cpp
+ ../../../../../common/JailUtil.cpp
../../../../../common/Log.cpp
../../../../../common/MessageQueue.cpp
../../../../../common/Protocol.cpp
diff --git a/common/Common.hpp b/common/Common.hpp
index b12cc67b5..d2cc933d6 100644
--- a/common/Common.hpp
+++ b/common/Common.hpp
@@ -32,7 +32,7 @@ constexpr long READ_BUFFER_SIZE = 64 * 1024;
/// or as intentionally flooding the server.
constexpr int MAX_MESSAGE_SIZE = 2 * 1024 * READ_BUFFER_SIZE;
-constexpr const char JAILED_DOCUMENT_ROOT[] = "/user/docs/";
+constexpr const char JAILED_DOCUMENT_ROOT[] = "/tmp/user/docs/";
constexpr const char CHILD_URI[] = "/loolws/child?";
constexpr const char NEW_CHILD_URI[] = "/loolws/newchild";
constexpr const char LO_JAIL_SUBPATH[] = "lo";
diff --git a/common/FileUtil.cpp b/common/FileUtil.cpp
index 4d8ac0a56..ac980b5f3 100644
--- a/common/FileUtil.cpp
+++ b/common/FileUtil.cpp
@@ -11,8 +11,8 @@
#include "FileUtil.hpp"
+#include <dirent.h>
#include <ftw.h>
-#include <sys/stat.h>
#ifdef __linux
#include <sys/vfs.h>
#elif defined IOS
@@ -39,6 +39,9 @@ namespace filesystem = ::std::filesystem;
# include <Poco/TemporaryFile.h>
#endif
+#include <Poco/File.h>
+#include <Poco/Path.h>
+
#include "Log.hpp"
#include "Util.hpp"
#include "Unit.hpp"
@@ -196,6 +199,8 @@ namespace FileUtil
void removeFile(const std::string& path, const bool recursive)
{
+ LOG_DBG("Removing [" << path << "] " << (recursive ? "recursively." : "only."));
+
// Amazingly filesystem::remove_all silently fails to work on some
// systems. No real need to be using experimental API here either.
#if 0 // HAVE_STD_FILESYSTEM
@@ -211,10 +216,12 @@ namespace FileUtil
try
{
struct stat sb;
+ errno = 0;
if (!recursive || stat(path.c_str(), &sb) == -1 || S_ISREG(sb.st_mode))
{
- // Non-recursive directories, and files.
- Poco::File(path).remove(recursive);
+ // Non-recursive directories and files that exist.
+ if (errno != ENOENT)
+ Poco::File(path).remove(recursive);
}
else
{
@@ -225,12 +232,12 @@ namespace FileUtil
catch (const std::exception&e)
{
// Already removed or we don't care about failures.
- LOG_DBG("Exception removing " << path << ' ' << recursive << " : " << e.what());
+ LOG_DBG("Failed to remove [" << path << "] " << (recursive ? "recursively: " : "only: ")
+ << e.what());
}
#endif
}
-
} // namespace FileUtil
namespace
@@ -385,6 +392,41 @@ namespace FileUtil
return AnonymizeUserData ? Util::anonymize(username, AnonymizationSalt) : username;
}
+ bool isEmptyDirectory(const char* path)
+ {
+ DIR* dir = opendir(path);
+ if (dir == nullptr)
+ return errno != EACCES; // Assume it's not empty when EACCES.
+
+ int count = 0;
+ while (readdir(dir) && ++count < 3)
+ ;
+
+ closedir(dir);
+ return count <= 2; // Discounting . and ..
+ }
+
+ bool linkOrCopyFile(const char* source, const char* target)
+ {
+ if (link(source, target) == -1)
+ {
+ LOG_INF("link(\"" << source << "\", \"" << target << "\") failed: " << strerror(errno)
+ << ". Will copy.");
+ try
+ {
+ Poco::File(source).copyTo(target);
+ }
+ catch (const std::exception& exc)
+ {
+ LOG_ERR("Copying of [" << source << "] to [" << target
+ << "] failed: " << exc.what());
+ return false;
+ }
+ }
+
+ return true;
+ }
+
} // namespace FileUtil
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/common/FileUtil.hpp b/common/FileUtil.hpp
index a64ff9758..1ef60a843 100644
--- a/common/FileUtil.hpp
+++ b/common/FileUtil.hpp
@@ -9,9 +9,10 @@
#pragma once
+#include <cerrno>
#include <string>
+#include <sys/stat.h>
-#include <Poco/File.h>
#include <Poco/Path.h>
namespace FileUtil
@@ -65,6 +66,10 @@ namespace FileUtil
removeFile(path.toString(), recursive);
}
+ /// Returns true iff the directory is empty (or doesn't exist).
+ bool isEmptyDirectory(const char* path);
+ inline bool isEmptyDirectory(const std::string& path) { return isEmptyDirectory(path.c_str()); }
+
/// Copy a file from @fromPath to @toPath, throws on failure.
void copyFileTo(const std::string &fromPath, const std::string &toPath);
@@ -81,6 +86,42 @@ namespace FileUtil
return getTempFilePath(srcDir, srcFilename, std::string());
}
+ /// Link source to target, and copy if linking fails.
+ bool linkOrCopyFile(const char* source, const char* target);
+
+ /// File/Directory stat helper.
+ class Stat
+ {
+ public:
+ /// Stat the given path. Symbolic links are stats when @link is true.
+ Stat(const std::string& file, bool link = false)
+ : _path(file)
+ , _res(link ? lstat(file.c_str(), &_sb) : stat(file.c_str(), &_sb))
+ , _errno(errno)
+ {
+ }
+
+ bool good() const { return _res == 0; }
+ bool bad() const { return !good(); }
+ bool erno() const { return _errno; }
+ const struct ::stat& sb() const { return _sb; }
+
+ const std::string path() const { return _path; }
+
+ bool isDirectory() const { return S_ISDIR(_sb.st_mode); }
+ bool isFile() const { return S_ISREG(_sb.st_mode); }
+ bool isLink() const { return S_ISLNK(_sb.st_mode); }
+
+ /// Returns true iff the path exists, regarlesss of access permission.
+ bool exists() const { return good() || (_errno != ENOENT && _errno != ENOTDIR); }
+
+ private:
+ const std::string _path;
+ struct ::stat _sb;
+ const int _res;
+ const int _errno;
+ };
+
} // end namespace FileUtil
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/common/JailUtil.cpp b/common/JailUtil.cpp
new file mode 100644
index 000000000..95927c476
--- /dev/null
+++ b/common/JailUtil.cpp
@@ -0,0 +1,364 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * 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/.
+ */
+
+#include <config.h>
+
+#include "FileUtil.hpp"
+#include "JailUtil.hpp"
+
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#ifdef __linux
+#include <sys/sysmacros.h>
+#endif
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+
+#include "Log.hpp"
+
+namespace JailUtil
+{
+bool loolmount(const std::string& arg, std::string source, std::string target)
+{
+ source = Util::trim(source, '/');
+ target = Util::trim(target, '/');
+ const std::string cmd = Poco::Path(Util::getApplicationPath(), "loolmount").toString() + ' '
+ + arg + ' ' + source + ' ' + target;
+ LOG_TRC("Executing loolmount command: " << cmd);
+ return !system(cmd.c_str());
+}
+
+bool bind(const std::string& source, const std::string& target)
+{
+ Poco::File(target).createDirectory();
+ const bool res = loolmount("-b", source, target);
+ if (res)
+ LOG_TRC("Bind-mounted [" << source << "] -> [" << target << "].");
+ else
+ LOG_ERR("Failed to bind-mount [" << source << "] -> [" << target << "].");
+ return res;
+}
+
+bool remountReadonly(const std::string& source, const std::string& target)
+{
+ Poco::File(target).createDirectory();
+ const bool res = loolmount("-r", source, target);
+ if (res)
+ LOG_TRC("Mounted [" << source << "] -> [" << target << "].");
+ else
+ LOG_ERR("Failed to mount [" << source << "] -> [" << target << "].");
+ return res;
+}
+
+bool unmount(const std::string& target)
+{
+ LOG_DBG("Unmounting [" << target << "].");
+ const bool res = loolmount("-u", "", target);
+ if (res)
+ LOG_TRC("Unmounted [" << target << "] successfully.");
+ else
+ LOG_ERR("Failed to unmount [" << target << "].");
+ return res;
+}
+
+bool safeRemoveDir(const std::string& path)
+{
+ unmount(path);
+
+ static const bool bind = std::getenv("LOOL_BIND_MOUNT");
+
+ // We must be empty if we had mounted.
+ if (bind && !FileUtil::isEmptyDirectory(path))
+ {
+ LOG_WRN("Path [" << path << "] is not empty. Will not remove it.");
+ return false;
+ }
+
+ // Recursively remove if link/copied.
+ FileUtil::removeFile(path, !bind);
+ return true;
+}
+
+void removeJail(const std::string& path)
+{
+ LOG_INF("Removing jail [" << path << "].");
+
+ // Unmount the tmp directory. Don't care if we fail.
+ const std::string tmpPath = Poco::Path(path, "tmp").toString();
+ FileUtil::removeFile(tmpPath, true); // Delete tmp contents with prejeduce.
+ unmount(tmpPath);
+
+ // Unmount the loTemplate directory.
+ unmount(Poco::Path(path, "lo").toString());
+
+ // Unmount the jail (sysTemplate).
+ safeRemoveDir(path);
+}
+
+/// This cleans up the jails directories.
+/// Note that we assume the templates are mounted
+/// and we unmount first. This is critical, because
+/// otherwise when mounting is disabled we may
+/// inadvertently delete the contents of the mount-points.
+void cleanupJails(const std::string& root)
+{
+ LOG_INF("Cleaning up childroot directory [" << root << "].");
+
+ FileUtil::Stat stRoot(root);
+ if (!stRoot.exists() || !stRoot.isDirectory())
+ {
+ LOG_TRC("Directory [" << root << "] is not a directory or doesn't exist.");
+ return;
+ }
+
+ if (FileUtil::Stat(root + "/lo").exists())
+ {
+ // This is a jail.
+ removeJail(root);
+ }
+ else
+ {
+ // Not a jail, recurse. UnitTest creates sub-directories.
+ LOG_TRC("Directory [" << root << "] is not a jail, recursing.");
+
+ std::vector<std::string> jails;
+ Poco::File(root).list(jails);
+ for (const auto& jail : jails)
+ {
+ const Poco::Path path(root, jail);
+ if (jail == "tmp") // Delete tmp with prejeduce.
+ FileUtil::removeFile(path.toString(), true);
+ else
+ cleanupJails(path.toString());
+ }
+ }
+
+ // Remove empty directories.
+ if (FileUtil::isEmptyDirectory(root))
+ safeRemoveDir(root);
+ else
+ LOG_WRN("Jails root directory [" << root << "] is not empty. Will not remove it.");
+}
+
+void setupJails(bool bindMount, const std::string& jailRoot, const std::string& sysTemplate)
+{
+ // Start with a clean slate.
+ cleanupJails(jailRoot);
+ Poco::File(jailRoot).createDirectories();
+
+ unsetenv("LOOL_BIND_MOUNT"); // Clear to avoid surprises.
+ if (bindMount)
+ {
+ // Test mounting to verify it actually works,
+ // as it might not function in some systems.
+ const std::string target = Poco::Path(jailRoot, "lool_test_mount").toString();
+ if (bind(sysTemplate, target))
+ {
+ safeRemoveDir(target);
+ setenv("LOOL_BIND_MOUNT", "1", 1);
+ LOG_INF("Enabling Bind-Mounting of jail contents for better performance per "
+ "mount_jail_tree config in loolwsd.xml.");
+ }
+ else
+ LOG_ERR("Bind-Mounting fails and will be disabled for this run. To disable permanently "
+ "set mount_jail_tree config entry in loolwsd.xml to false.");
+ }
+ else
+ LOG_INF("Disabling Bind-Mounting of jail contents per "
+ "mount_jail_tree config in loolwsd.xml.");
+}
+
+void symlinkPathToJail(const std::string& sysTemplate, const std::string& loTemplate,
+ const std::string& loSubPath)
+{
+ std::string symlinkTarget;
+ for (int i = 0; i < Poco::Path(loTemplate).depth(); i++)
+ symlinkTarget += "../";
+ symlinkTarget += loSubPath;
+
+ const Poco::Path symlinkSourcePath(sysTemplate + '/' + loTemplate);
+ const std::string symlinkSource = symlinkSourcePath.toString();
+ Poco::File(symlinkSourcePath.parent()).createDirectories();
+
+ LOG_DBG("Linking symbolically [" << symlinkSource << "] to [" << symlinkTarget << "].");
+
+ const FileUtil::Stat stLink(symlinkSource, true); // The file is a link.
+ if (stLink.exists())
+ {
+ if (!stLink.isLink())
+ LOG_WRN("Link [" << symlinkSource << "] already exists but isn't a link.");
+ else
+ LOG_TRC("Link [" << symlinkSource << "] already exists, skipping linking.");
+
+ return;
+ }
+
+ if (symlink(symlinkTarget.c_str(), symlinkSource.c_str()) == -1)
+ LOG_SYS("Failed to symlink(\"" << symlinkTarget << "\", \"" << symlinkSource << "\")");
+}
+
+// This is the second stage of setting up /dev/[u]random
+// in the jails. Here we create the random devices in
+// /tmp/dev/ in the jail chroot. See setupRandomDeviceLinks().
+void setupJailDevNodes(const std::string& root)
+{
+ // Create the urandom and random devices
+ Poco::File(Poco::Path(root, "/dev")).createDirectory();
+ if (!Poco::File(root + "/dev/random").exists())
+ {
+ LOG_DBG("Making /dev/random node in [" << root << "/dev].");
+ if (mknod((root + "/dev/random").c_str(),
+ S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH,
+ makedev(1, 8))
+ != 0)
+ {
+ LOG_SYS("mknod(" << root << "/dev/random) failed. Mount must not use nodev flag.");
+ }
+ }
+
+ if (!Poco::File(root + "/dev/urandom").exists())
+ {
+ LOG_DBG("Making /dev/urandom node in [" << root << "/dev].");
+ if (mknod((root + "/dev/urandom").c_str(),
+ S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH,
+ makedev(1, 9))
+ != 0)
+ {
+ LOG_SYS("mknod(" << root << "/dev/urandom) failed. Mount must not use nodev flag.");
+ }
+ }
+}
+
+namespace SysTemplate
+{
+/// The network and other system files we need to keep up-to-date in jails.
+/// These must be up-to-date, as they can change during
+/// the long lifetime of our process. Also, it's unlikely
+/// that systemplate will get re-generated after installation.
+static const auto DynamicFilePaths = { "/etc/passwd", "/etc/group", "/etc/host.conf",
+ "/etc/hosts", "/etc/nsswitch.conf", "/etc/resolv.conf" };
+
+/// Copy by default for KIT_IN_PROCESS.
+static bool LinkDynamicFiles = false;
+
+void setupDynamicFiles(const std::string& sysTemplate)
+{
+ LOG_INF("Setting up dynamic files in sysTemplate.");
+
+ const std::string etcSysTemplatePath = Poco::Path(sysTemplate, "etc").toString();
+ LinkDynamicFiles = true;
+ for (const auto& srcFilename : DynamicFilePaths)
+ {
+ const Poco::File srcFilePath(srcFilename);
+ if (!srcFilePath.exists())
+ continue;
+
+ // Remove the file to create a symlink.
+ const Poco::Path dstFilePath(sysTemplate, srcFilename);
+ if (LinkDynamicFiles)
+ {
+ LOG_INF("Linking [" << srcFilename << "] -> [" << dstFilePath.toString() << "].");
+ FileUtil::removeFile(dstFilePath);
+
+ // Link or copy.
+ if (link(srcFilename, dstFilePath.toString().c_str()) != -1)
+ continue;
+
+ // Failed to link a file. Disable linking and copy instead.
+ LOG_WRN("Failed to link [" << srcFilename << "] -> [" << dstFilePath.toString() << "] ("
+ << strerror(errno) << "). Will copy.");
+ LinkDynamicFiles = false;
+ }
+
+ // Linking fails, just copy.
+ LOG_INF("Copying [" << srcFilename << "] -> [" << dstFilePath.toString() << "].");
+ srcFilePath.copyTo(etcSysTemplatePath);
+ }
+}
+
+void updateDynamicFiles(const std::string& sysTemplate)
+{
+ if (!LinkDynamicFiles)
+ {
+ LOG_INF("Updating dynamic files in sysTemplate.");
+
+ const std::string etcSysTemplatePath = Poco::Path(sysTemplate, "etc").toString();
+ for (const auto& srcFilename : DynamicFilePaths)
+ {
+ const Poco::File srcFilePath(srcFilename);
+ if (!srcFilePath.exists())
+ continue;
+
+ const Poco::Path dstFilePath(sysTemplate, srcFilename);
+ LOG_DBG("Copying [" << srcFilename << "] -> [" << dstFilePath.toString() << "].");
+ srcFilePath.copyTo(etcSysTemplatePath);
+ }
+ }
+}
+
+void setupLoSymlink(const std::string& sysTemplate, const std::string& loTemplate,
+ const std::string& loSubPath)
+{
+ symlinkPathToJail(sysTemplate, loTemplate, loSubPath);
+
+ // Font paths can end up as realpaths so match that too.
+ char* resolved = realpath(loTemplate.c_str(), nullptr);
+ if (resolved)
+ {
+ if (strcmp(loTemplate.c_str(), resolved) != 0)
+ symlinkPathToJail(sysTemplate, std::string(resolved), loSubPath);
+ free(resolved);
+ }
+}
+
+void setupRandomDeviceLink(const std::string& sysTemplate, const std::string& name)
+{
+ const std::string path = sysTemplate + "/dev/";
+ Poco::File(path).createDirectories();
+
+ const std::string linkpath = path + name;
+ const std::string target = "../tmp/dev/" + name;
+ LOG_DBG("Linking symbolically [" << linkpath << "] to [" << target << "].");
+
+ const FileUtil::Stat stLink(linkpath, true); // The file is a link.
+ if (stLink.exists())
+ {
+ if (!stLink.isLink())
+ LOG_WRN("Random device link [" << linkpath << "] exists but isn't a link.");
+ else
+ LOG_TRC("Random device link [" << linkpath << "] already exists.");
+
+ return;
+ }
+
+ if (symlink(target.c_str(), linkpath.c_str()) == -1)
+ LOG_SYS("Failed to symlink(\"" << target << "\", \"" << linkpath << "\")");
+}
+
+// The random devices are setup in two stages.
+// This is the first stage, where we create symbolic links
+// in sysTemplate/dev/[u]random pointing to ../tmp/dev/[u]random
+// when we setup sysTemplate in forkit.
+// In the second stage, during jail creation, we create the dev
+// nodes in /tmp/dev/[u]random inside the jail chroot.
+void setupRandomDeviceLinks(const std::string& sysTemplate)
+{
+ setupRandomDeviceLink(sysTemplate, "random");
+ setupRandomDeviceLink(sysTemplate, "urandom");
+}
+
+} // namespace SysTemplate
+
+} // namespace JailUtil
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/common/JailUtil.hpp b/common/JailUtil.hpp
new file mode 100644
index 000000000..d783b53d3
--- /dev/null
+++ b/common/JailUtil.hpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * 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/.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <Poco/File.h>
+#include <Poco/Path.h>
+
+namespace JailUtil
+{
+/// Bind mount a jail directory.
+bool bind(const std::string& source, const std::string& target);
+
+/// Remount a bound mount point as readonly.
+bool remountReadonly(const std::string& source, const std::string& target);
+
+/// Unmount a bind-mounted jail directory.
+bool unmount(const std::string& target);
+
+/// Remove the jail directory and all its contents.
+void removeJail(const std::string& path);
+
+/// Remove all the jails given their paths.
+inline void removeJails(const std::vector<std::string>& jails)
+{
+ for (const auto& path : jails)
+ {
+ removeJail(path);
+ }
+}
+
+/// Remove all jails.
+void cleanupJails(const std::string& jailRoot);
+
+/// Setup the jails.
+void setupJails(bool bindMount, const std::string& jailRoot, const std::string& sysTemplate);
+
+/// Setup /dev/random and /dev/urandom in the given jail path.
+void setupJailDevNodes(const std::string& root);
+
+namespace SysTemplate
+{
+/// Create a symlink inside the jailPath so that the absolute pathname loTemplate, when
+/// interpreted inside a chroot at jailPath, points to loSubPath (relative to the chroot).
+void setupLoSymlink(const std::string& sysTemplate, const std::string& loTemplate,
+ const std::string& loSubPath);
+
+/// Setup links for /dev/random and /dev/urandom in systemplate.
+void setupRandomDeviceLinks(const std::string& root);
+
+/// Setup of the dynamic files within the sysTemplate by either
+/// copying or linking. See updateJail_DynamicFilesInSysTemplate.
+void setupDynamicFiles(const std::string& sysTemplate);
+
+/// Update the dynamic files within the sysTemplate before each child fork.
+void updateDynamicFiles(const std::string& sysTemplate);
+
+} // namespace SysTemplate
+
+} // end namespace JailUtil
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/common/Log.hpp b/common/Log.hpp
index 604c66ac7..b2bb43860 100644
--- a/common/Log.hpp
+++ b/common/Log.hpp
@@ -12,6 +12,7 @@
#include <sys/syscall.h>
#include <unistd.h>
+#include <cerrno>
#include <cstddef>
#include <functional>
#include <iostream>
diff --git a/common/Session.cpp b/common/Session.cpp
index 204573e0f..cdccea9d6 100644
--- a/common/Session.cpp
+++ b/common/Session.cpp
@@ -11,7 +11,6 @@
#include "Session.hpp"
-#include <sys/stat.h>
#include <sys/types.h>
#include <ftw.h>
#include <utime.h>
diff --git a/common/SigUtil.cpp b/common/SigUtil.cpp
index dff88b8a6..2bc3d5aa7 100644
--- a/common/SigUtil.cpp
+++ b/common/SigUtil.cpp
@@ -16,7 +16,6 @@
#endif
#include <csignal>
#include <sys/poll.h>
-#include <sys/stat.h>
#include <sys/uio.h>
#include <unistd.h>
diff --git a/common/Util.cpp b/common/Util.cpp
index ee1aa19b1..16681cc56 100644
--- a/common/Util.cpp
+++ b/common/Util.cpp
@@ -166,14 +166,15 @@ namespace Util
return tmp;
}
- std::string createRandomTmpDir()
+ std::string createRandomTmpDir(std::string root)
{
- std::string defaultTmp = getDefaultTmpDir();
- std::string newTmp =
- defaultTmp + "/lool-" + rng::getFilename(16);
- if (::mkdir(newTmp.c_str(), S_IRWXU) < 0) {
- LOG_ERR("Failed to create random temp directory");
- return defaultTmp;
+ if (root.empty())
+ root = getDefaultTmpDir();
+ const std::string newTmp = root + "/lool-" + rng::getFilename(16);
+ if (::mkdir(newTmp.c_str(), S_IRWXU) < 0)
+ {
+ LOG_SYS("Failed to create random temp directory [" << newTmp << "]");
+ return root;
}
return newTmp;
}
@@ -966,6 +967,17 @@ namespace Util
return result;
}
+ static std::string ApplicationPath;
+ void setApplicationPath(const std::string& path)
+ {
+ ApplicationPath = Poco::Path(path).absolute().toString();
+ }
+
+ std::string getApplicationPath()
+ {
+ return ApplicationPath;
+ }
+
#if !MOBILEAPP
// If OS is not mobile, it must be Linux.
std::string getLinuxVersion(){
diff --git a/common/Util.hpp b/common/Util.hpp
index f6ca50825..ba70cba47 100644
--- a/common/Util.hpp
+++ b/common/Util.hpp
@@ -60,8 +60,9 @@ namespace Util
std::string getFilename(const size_t length);
}
- /// Create randomized temporary directory
- std::string createRandomTmpDir();
+ /// Create randomized temporary directory in the root provided.
+ /// If root is empty, the current temp directory is used.
+ std::string createRandomTmpDir(std::string root = std::string());
#if !MOBILEAPP
/// Get number of threads in this process or -1 on error
@@ -319,6 +320,21 @@ namespace Util
return s;
}
+ inline std::string& trim(std::string& s, const char ch)
+ {
+ const size_t last = s.find_last_not_of(ch);
+ if (last != std::string::npos)
+ {
+ s = s.substr(0, last + 1);
+ }
+ else
+ {
+ s.clear();
+ }
+
+ return s;
+ }
+
/// Trim spaces from both left and right. Just spaces.
inline std::string& trim(std::string& s)
{
@@ -1104,6 +1120,9 @@ int main(int argc, char**argv)
return result;
}
+ void setApplicationPath(const std::string& path);
+ std::string getApplicationPath();
+
/**
* Converts vector of strings to map. Strings should have formed like this: key + delimiter + value.
* In case of a misformed string or zero length vector, passes that item and warns the developer.
diff --git a/common/security.h b/common/security.h
index 0fd0a691b..0bd794d2b 100644
--- a/common/security.h
+++ b/common/security.h
@@ -12,7 +12,6 @@
#pragma once
-#include <sys/mount.h>
#include <sys/types.h>
#include <pwd.h>
diff --git a/configure.ac b/configure.ac
index 7a8177e91..740dc2e3c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1140,7 +1140,7 @@ if test "$enable_androidapp" = "yes"; then
$srcdir/android/lib/src/main/cpp/CMakeLists.txt:android/lib/src/main/cpp/CMakeLists.txt.in])
fi
-AC_CONFIG_FILES([test/run_unit.sh],[chmod +x test/run_unit.sh])
+AC_CONFIG_FILES([test/run_unit.sh:test/run_unit.sh.in],[chmod +x test/run_unit.sh])
AC_OUTPUT
diff --git a/debian/loolwsd.postinst.in b/debian/loolwsd.postinst.in
index 41198e75a..ac38e2ab1 100644
--- a/debian/loolwsd.postinst.in
+++ b/debian/loolwsd.postinst.in
@@ -5,6 +5,7 @@ set -e
case "$1" in
configure)
setcap cap_fowner,cap_mknod,cap_sys_chroot=ep /usr/bin/loolforkit || true
+ setcap cap_sys_admin=ep /usr/bin/loolmount || true
adduser --quiet --system --group --home /opt/lool lool
mkdir -p /var/cache/loolwsd && chown lool: /var/cache/loolwsd
diff --git a/docker/Ubuntu b/docker/Ubuntu
index 4d00a16bb..a41d38bba 100644
--- a/docker/Ubuntu
+++ b/docker/Ubuntu
@@ -29,6 +29,7 @@ COPY /scripts/run-lool.sh /
# set up LibreOffice Online (normally done by postinstall script of package)
# Fix permissions
RUN setcap cap_fowner,cap_mknod,cap_sys_chroot=ep /usr/bin/loolforkit && \
+ setcap cap_sys_admin=ep /usr/bin/loolmount && \
adduser --quiet --system --group --home /opt/lool lool && \
mkdir -p /var/cache/loolwsd && chown lool: /var/cache/loolwsd && \
rm -rf /var/cache/loolwsd/* && \
diff --git a/kit/ForKit.cpp b/kit/ForKit.cpp
index b69e2593b..d1ecb1cdd 100644
--- a/kit/ForKit.cpp
+++ b/kit/ForKit.cpp
@@ -14,7 +14,6 @@
#include <config.h>
#include <sys/capability.h>
-#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sysexits.h>
@@ -40,6 +39,7 @@
#endif
#include <common/FileUtil.hpp>
+#include <common/JailUtil.hpp>
#include <common/Seccomp.hpp>
#include <common/SigUtil.hpp>
#include <security.h>
@@ -280,11 +280,7 @@ static void cleanupChildren()
}
// Now delete the jails.
- for (const auto& path : jails)
- {
- LOG_INF("Removing jail [" << path << "].");
- FileUtil::removeFile(path, true);
- }
+ JailUtil::removeJails(jails);
}
static int createLibreOfficeKit(const std::string& childRoot,
@@ -296,6 +292,9 @@ static int createLibreOfficeKit(const std::string& childRoot,
// Generate a jail ID to be used for in the jail path.
const std::string jailId = Util::rng::getFilename(16);
+ // Update the dynamic files as necessary.
+ JailUtil::SysTemplate::updateDynamicFiles(sysTemplate);
+
// Used to label the spare kit instances
static size_t spareKitId = 0;
++spareKitId;
@@ -433,6 +432,7 @@ int main(int argc, char** argv)
#endif
Util::setThreadName("forkit");
+ Util::setApplicationPath(Poco::Path(argv[0]).parent().toString());
// Initialization
const bool logToFile = std::getenv("LOOL_LOGFILE");
@@ -587,6 +587,15 @@ int main(int argc, char** argv)
if (Util::getProcessThreadCount() != 1)
LOG_ERR("Error: forkit has more than a single thread after pre-init");
+ // Link the network and system files in sysTemplate.
+ JailUtil::SysTemplate::setupDynamicFiles(sysTemplate);
+
+ // Make the real lo path in the chroot point to the chroot lo/.
+ JailUtil::SysTemplate::setupLoSymlink(sysTemplate, loTemplate, loSubPath);
+
+ // Make dev/[u]random point to the writable devices in tmp/dev/.
+ JailUtil::SysTemplate::setupRandomDeviceLinks(sysTemplate);
+
LOG_INF("Preinit stage OK.");
// We must have at least one child, more are created dynamically.
diff --git a/kit/Kit.cpp b/kit/Kit.cpp
index b50ae65a4..94241417a 100644
--- a/kit/Kit.cpp
+++ b/kit/Kit.cpp
@@ -54,6 +54,7 @@
#include <Common.hpp>
#include <MobileApp.hpp>
#include <FileUtil.hpp>
+#include <common/JailUtil.hpp>
#include "KitHelper.hpp"
#include "Kit.hpp"
#include <Protocol.hpp>
@@ -123,7 +124,11 @@ static LokHookFunction2* initFunction = nullptr;
namespace
{
#ifndef BUILDING_TESTS
- enum class LinkOrCopyType { All, LO, NoUsr };
+ enum class LinkOrCopyType
+ {
+ All,
+ LO
+ };
LinkOrCopyType linkOrCopyType;
std::string sourceForLinkOrCopy;
Path destinationForLinkOrCopy;
@@ -137,15 +142,13 @@ namespace
{
switch (type)
{
- case LinkOrCopyType::NoUsr:
- return "non-user";
- case LinkOrCopyType::LO:
- return "LibreOffice";
- case LinkOrCopyType::All:
- return "all";
- default:
- assert(!"Unknown LinkOrCopyType.");
- return "unknown";
+ case LinkOrCopyType::LO:
+ return "LibreOffice";
+ case LinkOrCopyType::All:
+ return "all";
+ default:
+ assert(!"Unknown LinkOrCopyType.");
+ return "unknown";
}
}
@@ -153,9 +156,6 @@ namespace
{
switch (linkOrCopyType)
{
- case LinkOrCopyType::NoUsr:
- // bind mounted.
- return strcmp(path,"usr") != 0;
case LinkOrCopyType::LO:
return
strcmp(path, "program/wizards") != 0 &&
@@ -164,7 +164,8 @@ namespace
strcmp(path, "share/basic") != 0 &&
strcmp(path, "share/Scripts/java") != 0 &&
strcmp(path, "share/Scripts/javascript") != 0 &&
- strcmp(path, "share/config/wizard") != 0;
+ strcmp(path, "share/config/wizard") != 0 &&
+ strcmp(path, "readmes") != 0;
default: // LinkOrCopyType::All
return true;
}
@@ -215,33 +216,22 @@ namespace
}
return true;
}
- case LinkOrCopyType::NoUsr:
default: // LinkOrCopyType::All
return true;
}
}
- void linkOrCopyFile(const char *fpath, const Path& newPath)
+ void linkOrCopyFile(const char* fpath, const std::string& newPath)
{
++linkOrCopyFileCount;
if (linkOrCopyVerboseLogging)
- LOG_INF("Linking file \"" << fpath << "\" to \"" << newPath.toString() << '"');
+ LOG_INF("Linking file \"" << fpath << "\" to \"" << newPath << '"');
- if (link(fpath, newPath.toString().c_str()) == -1)
+ if (!FileUtil::linkOrCopyFile(fpath, newPath.c_str()))
{
- LOG_INF("link(\"" << fpath << "\", \"" <<
- newPath.toString() << "\") failed: " << strerror(errno) << ". Will copy.");
- try
- {
- File(fpath).copyTo(newPath.toString());
- }
- catch (const std::exception& exc)
- {
- LOG_FTL("Copying of '" << fpath << "' to " << newPath.toString() <<
- " failed: " << exc.what() << ". Exiting.");
- Log::shutdown();
- std::_Exit(EX_SOFTWARE);
- }
+ LOG_FTL("Failed to copy or link [" << fpath << "] to [" << newPath << "]. Exiting.");
+ Log::shutdown();
+ std::_Exit(EX_SOFTWARE);
}
}
@@ -271,7 +261,7 @@ namespace
assert(fpath[strlen(sourceForLinkOrCopy.c_str())] == '/');
const char *relativeOldPath = fpath + strlen(sourceForLinkOrCopy.c_str()) + 1;
- Path newPath(destinationForLinkOrCopy, Path(relativeOldPath));
+ const Path newPath(destinationForLinkOrCopy, Path(relativeOldPath));
switch (typeflag)
{
@@ -280,7 +270,7 @@ namespace
File(newPath.parent()).createDirectories();
if (shouldLinkFile(relativeOldPath))
- linkOrCopyFile(fpath, newPath);
+ linkOrCopyFile(fpath, newPath.toString());
break;
case FTW_D:
{
@@ -424,26 +414,6 @@ namespace
cap_free(caps);
}
-
- void symlinkPathToJail(const Path& jailPath, const std::string &loTemplate,
- const std::string &loSubPath)
- {
- Path symlinkSource(jailPath, Path(loTemplate.substr(1)));
- File(symlinkSource.parent()).createDirectories();
-
- std::string symlinkTarget;
- for (int i = 0; i < Path(loTemplate).depth(); i++)
- symlinkTarget += "../";
- symlinkTarget += loSubPath;
-
- LOG_DBG("symlink(\"" << symlinkTarget << "\", \"" << symlinkSource.toString() << "\")");
- if (symlink(symlinkTarget.c_str(), symlinkSource.toString().c_str()) == -1)
- {
- LOG_SYS("symlink(\"" << symlinkTarget << "\", \"" << symlinkSource.toString()
- << "\") failed");
- throw Exception("symlink() failed");
- }
- }
#endif
}
@@ -2618,89 +2588,82 @@ void lokit_main(
// framework/source/services/modulemanager.cxx:198
// So we insure it lives until std::_Exit is called.
std::shared_ptr<lok::Office> loKit;
- Path jailPath;
ChildSession::NoCapsForKit = noCapabilities;
#endif // MOBILEAPP
try
{
#if !MOBILEAPP
- jailPath = Path::forDirectory(childRoot + '/' + jailId);
+ const Path jailPath = Path::forDirectory(childRoot + '/' + jailId);
LOG_INF("Jail path: " << jailPath.toString());
File(jailPath).createDirectories();
chmod(jailPath.toString().c_str(), S_IXUSR | S_IWUSR | S_IRUSR);
if (!ChildSession::NoCapsForKit)
{
- userdir_url = "file:///user";
- instdir_path = '/' + loSubPath + "/program";
+ std::chrono::time_point<std::chrono::steady_clock> jailSetupStartTime
+ = std::chrono::steady_clock::now();
- // Create a symlink inside the jailPath so that the absolute pathname loTemplate, when
- // interpreted inside a chroot at jailPath, points to loSubPath (relative to the chroot).
- symlinkPathToJail(jailPath, loTemplate, loSubPath);
-
- // Font paths can end up as realpaths so match that too.
- char *resolved = realpath(loTemplate.c_str(), nullptr);
- if (resolved)
- {
- if (strcmp(loTemplate.c_str(), resolved) != 0)
- symlinkPathToJail(jailPath, std::string(resolved), loSubPath);
- free (resolved);
- }
-
- Path jailLOInstallation(jailPath, loSubPath);
- jailLOInstallation.makeDirectory();
- File(jailLOInstallation).createDirectory();
+ userdir_url = "file:///tmp/user";
+ instdir_path = '/' + loSubPath + "/program";
// Copy (link) LO installation and other necessary files into it from the template.
- bool bLoopMounted = false;
if (std::getenv("LOOL_BIND_MOUNT"))
{
- Path usrSrcPath(sysTemplate, "usr");
- Path usrDestPath(jailPath, "usr");
- File(usrDestPath).createDirectory();
- std::string mountCommand =
- std::string("loolmount ") +
- usrSrcPath.toString() +
- std::string(" ") +
- usrDestPath.toString();
- LOG_DBG("Initializing jail bind mount.");
- bLoopMounted = !system(mountCommand.c_str());
- LOG_DBG("Initialized jail bind mount.");
- }
+ const std::string destPath = jailPath.toString();
+ LOG_DBG("Mounting " << sysTemplate << " -> " << destPath);
+ if (!JailUtil::bind(sysTemplate, destPath))
+ {
+ LOG_INF("Failed to mount [" << sysTemplate << "] -> [" << destPath
+ << "], will link/copy contents.");
+ linkOrCopy(sysTemplate, destPath, LinkOrCopyType::All);
+ }
+ else
+ JailUtil::remountReadonly(sysTemplate, jailPath.toString());
- linkOrCopy(sysTemplate, jailPath,
- bLoopMounted ? LinkOrCopyType::NoUsr : LinkOrCopyType::All);
- linkOrCopy(loTemplate, jailLOInstallation, LinkOrCopyType::LO);
+ // Mount lotemplate inside it.
+ const std::string loDestPath = Poco::Path(jailPath, "lo").toString();
+ LOG_DBG("Mounting " << loTemplate << " -> " << loDestPath);
+ Poco::File(loDestPath).createDirectories();
+ if (!JailUtil::bind(loTemplate, loDestPath))
+ {
+ LOG_INF("Failed to mount [" << loTemplate << "] -> [" << loDestPath
+ << "], will link/copy contents.");
+ linkOrCopy(sysTemplate, loDestPath, LinkOrCopyType::LO);
+ }
+ else
+ JailUtil::remountReadonly(loTemplate, loDestPath);
+
+ // hard-random tmpdir inside the jail / root
+ const std::string tempRoot = Poco::Path(childRoot, "tmp").toString();
+ Poco::File(tempRoot).createDirectories();
+ const std::string tmpSubDir = Util::createRandomTmpDir(tempRoot);
+ const std::string jailTmpDir = Poco::Path(jailPath, "tmp").toString();
+ if (!JailUtil::bind(tmpSubDir, jailTmpDir))
+ {
+ LOG_ERR("Failed to bind tmp dir in jail.");
+ }
- // Copy some needed files - makes the networking work in the
- // chroot
- const std::initializer_list<const char*> files = {"/etc/passwd", "/etc/group", "/etc/host.conf", "/etc/hosts", "/etc/nsswitch.conf", "/etc/resolv.conf"};
- for (const auto& filename : files)
- {
- const Poco::Path etcPath = Path(jailPath, filename);
- const std::string etcPathString = etcPath.toString();
- if (File(filename).exists() && !File(etcPathString).exists() )
- linkOrCopyFile(filename, etcPath);
+ JailUtil::setupJailDevNodes(destPath + "/tmp");
}
+ else
+ {
+ linkOrCopy(sysTemplate, jailPath, LinkOrCopyType::All);
- LOG_DBG("Initialized jail files.");
+ Poco::Path jailLOInstallation(jailPath, loSubPath);
+ jailLOInstallation.makeDirectory();
+ Poco::File(jailLOInstallation).createDirectory();
+ linkOrCopy(loTemplate, jailLOInstallation, LinkOrCopyType::LO);
- // Create the urandom and random devices
- File(Path(jailPath, "/dev")).createDirectory();
- if (mknod((jailPath.toString() + "/dev/random").c_str(),
- S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH,
- makedev(1, 8)) != 0)
- {
- LOG_SYS("mknod(" << jailPath.toString() << "/dev/random) failed. Mount must not use nodev flag.");
+ JailUtil::setupJailDevNodes(Poco::Path(jailPath, "/tmp").toString());
}
- if (mknod((jailPath.toString() + "/dev/urandom").c_str(),
- S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH,
- makedev(1, 9)) != 0)
- {
- LOG_SYS("mknod(" << jailPath.toString() << "/dev/random) failed. Mount must not use nodev flag.");
- }
+ ::setenv("TMPDIR", "/tmp", 1);
+
+ const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now() - jailSetupStartTime)
+ .count();
+ LOG_DBG("Initialized jail files in " << ms << " ms.");
ProcSMapsFile = open("/proc/self/smaps", O_RDONLY);
if (ProcSMapsFile < 0)
@@ -2730,14 +2693,10 @@ void lokit_main(
else // noCapabilities set
{
LOG_ERR("Security warning - using template " << loTemplate << " as install subpath - skipping chroot jail setup");
- userdir_url = "file:///" + jailPath.toString() + "/user";
+ userdir_url = "file:///" + jailPath.toString() + "/tmp/user";
instdir_path = '/' + loTemplate + "/program";
}
- // hard-random tmpdir inside the jail / root
- std::string tmpSubdir = Util::createRandomTmpDir();
- ::setenv("TMPDIR", tmpSubdir.c_str(), 1);
-
LibreOfficeKit *kit;
{
const char *instdir = instdir_path.c_str();
@@ -3032,15 +2991,15 @@ bool globalPreinit(const std::string &loTemplate)
"javaloader javavm jdbc rpt rptui rptxml ",
0 /* no overwrite */);
- LOG_TRC("Invoking lok_preinit(" << loTemplate << "/program\", \"file:///user\")");
+ LOG_TRC("Invoking lok_preinit(" << loTemplate << "/program\", \"file:///tmp/user\")");
const auto start = std::chrono::steady_clock::now();
- if (preInit((loTemplate + "/program").c_str(), "file:///user") != 0)
+ if (preInit((loTemplate + "/program").c_str(), "file:///tmp/user") != 0)
{
LOG_FTL("lok_preinit() in " << loadedLibrary << " failed");
return false;
}
- LOG_TRC("Finished lok_preinit(" << loTemplate << "/program\", \"file:///user\") in " <<
+ LOG_TRC("Finished lok_preinit(" << loTemplate << "/program\", \"file:///tmp/user\") in " <<
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() <<
" ms.");
return true;
diff --git a/loolwsd-systemplate-setup b/loolwsd-systemplate-setup
index 2500beac4..5b7bda6e8 100755
--- a/loolwsd-systemplate-setup
+++ b/loolwsd-systemplate-setup
@@ -22,6 +22,7 @@ cd / || exit 1
# First essential files and shared objects
find etc/hosts etc/nsswitch.conf etc/resolv.conf \
+ etc/passwd etc/group etc/host.conf \
etc/ld.so.* \
lib/ld-* lib64/ld-* \
lib/libnss_* lib64/libnss_* lib/*/libnss_* \
@@ -64,7 +65,9 @@ grep -v dynamic | cut -d " " -f 3 | grep -E '^(/lib|/usr)' | sort -u | sed -e 's
# This will now copy the file a symlink points to, but whatever.
cpio -p -d -L $CHROOT
-mkdir -p $CHROOT/tmp
+mkdir -p $CHROOT/lo
+mkdir -p $CHROOT/dev
+mkdir -p $CHROOT/tmp/dev
# /usr/share/fonts needs to be taken care of separately because the
# directory time stamps must be preserved for fontconfig to trust
diff --git a/loolwsd.spec.in b/loolwsd.spec.in
index e8747a5a9..fa9919e9c 100644
--- a/loolwsd.spec.in
+++ b/loolwsd.spec.in
@@ -100,6 +100,7 @@ echo "account required pam_unix.so" >> %{buildroot}/etc/pam.d/loolwsd
/usr/bin/loolforkit
/usr/bin/loolconvert
/usr/bin/loolconfig
+/usr/bin/loolmount
/usr/share/loolwsd/discovery.xml
/usr/share/loolwsd/favicon.ico
/usr/share/loolwsd/loleaflet
@@ -140,6 +141,7 @@ getent passwd lool >/dev/null || useradd -g lool -r lool
%post
setcap cap_fowner,cap_mknod,cap_sys_chroot=ep /usr/bin/loolforkit
+setcap cap_sys_admin=ep /usr/bin/loolmount
mkdir -p /var/cache/loolwsd && chown lool:lool /var/cache/loolwsd
rm -rf /var/cache/loolwsd/*
diff --git a/loolwsd.xml.in b/loolwsd.xml.in
index e6886b084..ceb78992e 100644
--- a/loolwsd.xml.in
+++ b/loolwsd.xml.in
@@ -7,6 +7,7 @@
<sys_template_path desc="Path to a template tree with shared libraries etc to be used as source for chroot jails for child processes." type="path" relative="true" default="systemplate"></sys_template_path>
<child_root_path desc="Path to the directory under which the chroot jails for the child processes will be created. Should be on the same file system as systemplate and lotemplate. Must be an empty directory." type="path" relative="true" default="jails"></child_root_path>
+ <mount_jail_tree desc="Controls whether the systemplate and lotemplate contents are mounted or not, which is much faster than the default of linking/copying each file." type="bool" default="true"></mount_jail_tree>
<server_name desc="External hostname:port of the server running loolwsd. If empty, it's derived from the request (please set it if this doesn't work). Must be specified when behind a reverse-proxy or when the hostname is not reachable directly." type="string" default=""></server_name>
<file_server_root_path desc="Path to the directory that should be considered root for the file server. This should be the directory containing loleaflet." type="path" relative="true" default="loleaflet/../"></file_server_root_path>
diff --git a/net/Socket.cpp b/net/Socket.cpp
index cc0e9cf64..2f35b5cce 100644
--- a/net/Socket.cpp
+++ b/net/Socket.cpp
@@ -16,6 +16,7 @@
#include <iomanip>
#include <stdio.h>
#include <unistd.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <zlib.h>
diff --git a/net/Socket.hpp b/net/Socket.hpp
index 55cfe05bb..9ee336950 100644
--- a/net/Socket.hpp
+++ b/net/Socket.hpp
@@ -11,7 +11,6 @@
#include <poll.h>
#include <unistd.h>
-#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
diff --git a/test/run_unit.sh.in b/test/run_unit.sh.in
index 4b893f23c..6fbfc15e7 100755
--- a/test/run_unit.sh.in
+++ b/test/run_unit.sh.in
@@ -74,6 +74,7 @@ echo "Running $tst | $tst_log ...";
if ${trace} \
${abs_top_builddir}/loolwsd --o:sys_template_path="$systemplate_path" \
--o:child_root_path="$jails_path" \
+ --o:mount_jail_tree=false \
--o:storage.filesystem[@allow]=true \
--o:logging.level=trace \
--o:ssl.key_file_path="${abs_top_builddir}/etc/key.pem" \
@@ -90,6 +91,7 @@ else
echo "Test failed on unit: $tst re-run with:"
echo " $ gdb --args ${abs_top_builddir}/loolwsd --o:sys_template_path=\"$systemplate_path\" \\"
echo " --o:child_root_path=\"$jails_path\" \\"
+ echo " --o:mount_jail_tree=false \\"
echo " --o:storage.filesystem[@allow]=true \\"
echo " --o:storage.ssl.enable=false \\"
echo " --o:logging.level=trace \\"
diff --git a/tools/mount.cpp b/tools/mount.cpp
index 7afba1fcd..8f2d78e5a 100644
--- a/tools/mount.cpp
+++ b/tools/mount.cpp
@@ -12,26 +12,134 @@
#include <config.h>
+#include <stdio.h>
+#include <errno.h>
#include <sys/mount.h>
+#include <sys/stat.h>
+#include <unistd.h>
#include <security.h>
-int main(int argc, char **argv)
+void usage(const char* program)
{
+ fprintf(stderr, "Usage: %s <-s|-r> <source path> <target path>\n", program);
+ fprintf(stderr, " %s -u <target>.\n", program);
+ fprintf(stderr, " -b bind and mount the source to target.\n");
+ fprintf(stderr, " -r bind and mount the source to target as readonly.\n");
+ fprintf(stderr, " -u to unmount the target.\n");
+}
+
+int main(int argc, char** argv)
+{
+ const char* program = argv[0];
+
if (!hasCorrectUID("loolmount"))
+ {
+ fprintf(stderr, "%s: incorrect UID.", program);
return 1;
+ }
if (argc < 3)
+ {
+ usage(program);
return 1;
+ }
+
+ const char* option = argv[1];
+ if (argc == 3 && strcmp(option, "-u") == 0) // Unmount
+ {
+ const char* target = argv[2];
+
+ struct stat sb;
+ const bool target_exists = (stat(target, &sb) == 0 && S_ISDIR(sb.st_mode));
- int retval = mount (argv[1], argv[2], nullptr, MS_BIND, nullptr);
- if (retval)
- return retval;
+ // Do nothing if target doesn't exist.
+ if (target_exists)
+ {
+ // Unmount the target, first by detaching. This should succeed.
+ int retval = umount2(target, MNT_DETACH);
+ if (retval != 0)
+ {
+ if (errno != EINVAL)
+ fprintf(stderr, "%s: unmount failed to detach [%s]: %s.\n", program, target,
+ strerror(errno));
+ }
+
+ // Now try to force the unmounting, which isn't supported on all filesystems.
+ retval = umount2(target, MNT_FORCE);
+ if (retval && errno != EINVAL)
+ {
+ fprintf(stderr, "%s: forced unmount of [%s] failed: %s.\n", program, target,
+ strerror(errno));
+ return 1;
+ }
+ }
+ }
+ else if (argc == 4) // Mount
+ {
+ const char* source = argv[2];
+ struct stat sb;
+ if (stat(source, &sb) != 0 || !S_ISDIR(sb.st_mode))
+ {
+ fprintf(stderr, "%s: cannot mount from invalid source directory [%s].\n", program,
+ source);
+ return 1;
+ }
+
+ const char* target = argv[3];
+ const bool target_exists = (stat(target, &sb) == 0 && S_ISDIR(sb.st_mode));
+ if (!target_exists)
+ {
+ fprintf(stderr, "%s: cannot mount on invalid target directory [%s].\n", program,
+ target);
+ return 1;
+ }
+
+ // Mount the source path as the target path.
+ // First bind to mount an existing directory node into the chroot.
+ // MS_BIND ignores other flags.
+ if (strcmp(option, "-b") == 0) // Shared or Bind Mount.
+ {
+ const int retval
+ = mount(source, target, nullptr, (MS_MGC_VAL | MS_BIND | MS_REC), nullptr);
+ if (retval)
+ {
+ fprintf(stderr, "%s: mount failed to bind [%s] to [%s]: %s.\n", program, source,
+ target, strerror(errno));
+ return 1;
+ }
+ }
+ else if (strcmp(option, "-r") == 0) // Readonly Mount.
+ {
+ // Now we need to set read-only and other flags with a remount.
+ int retval = mount(source, target, nullptr,
+ (MS_BIND | MS_REC | MS_REMOUNT | MS_NOATIME | MS_NODEV | MS_NOSUID
+ | MS_RDONLY | MS_SILENT),
+ nullptr);
+ if (retval)
+ {
+ fprintf(stderr, "%s: mount failed remount [%s] readonly: %s.\n", program, target,
+ strerror(errno));
+ return 1;
+ }
+
+ retval = mount(source, target, nullptr, (MS_UNBINDABLE | MS_REC), nullptr);
+ if (retval)
+ {
+ fprintf(stderr, "%s: mount failed make [%s] private: %s.\n", program, target,
+ strerror(errno));
+ return 1;
+ }
+ }
+ }
+ else
+ {
+ usage(program);
+ return 1;
+ }
- // apparently this has to be done in a 2nd pass.
- return mount(argv[1], argv[2], nullptr,
- (MS_BIND | MS_REMOUNT | MS_NOATIME | MS_NODEV |
- MS_NOSUID | MS_RDONLY | MS_SILENT), nullptr);
+ fflush(stderr);
+ return 0;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index 22f5d4847..7b99e16f4 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -39,7 +39,6 @@
#include <stdlib.h>
#include <sysexits.h>
-#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -114,7 +113,8 @@ using Poco::Net::PartHandler;
#include "DocumentBroker.hpp"
#include "Exceptions.hpp"
#include "FileServer.hpp"
-#include <FileUtil.hpp>
+#include <common/FileUtil.hpp>
+#include <common/JailUtil.hpp>
#if defined KIT_IN_PROCESS || MOBILEAPP
# include <Kit.hpp>
#endif
@@ -866,6 +866,8 @@ void LOOLWSD::initialize(Application& self)
}
#endif
+ Util::setApplicationPath(Poco::Path(Application::instance().commandPath()).parent().toString());
+
if (!UnitWSD::init(UnitWSD::UnitType::Wsd, UnitTestLibrary))
{
throw std::runtime_error("Failed to load wsd unit test library.");
@@ -908,6 +910,7 @@ void LOOLWSD::initialize(Application& self)
{ "logging.lokit_sal_log", "-INFO-WARN" },
{ "loleaflet_html", "loleaflet.html" },
{ "loleaflet_logging", "false" },
+ { "mount_jail_tree", "true" },
{ "net.listen", "any" },
{ "net.proto", "all" },
{ "net.service_root", "" },
@@ -1053,6 +1056,9 @@ void LOOLWSD::initialize(Application& self)
LOG_INF("Setting log-level to [trace] and delaying setting to configured [" << LogLevel << "] until after WSD initialization.");
}
+ ServerName = config().getString("server_name");
+ LOG_INF("Initializing loolwsd server [" << ServerName << "].");
+
// Get anonymization settings.
#if LOOLWSD_ANONYMIZE_USER_DATA
AnonymizeUserData = true;
@@ -1168,8 +1174,36 @@ void LOOLWSD::initialize(Application& self)
#endif
SysTemplate = getPathFromConfig("sys_template_path");
+ if (SysTemplate.empty())
+ {
+ LOG_FTL("Missing sys_template_path config entry.");
+ throw MissingOptionException("systemplate");
+ }
+
ChildRoot = getPathFromConfig("child_root_path");
- ServerName = config().getString("server_name");
+ if (ChildRoot.empty())
+ {
+ LOG_FTL("Missing child_root_path config entry.");
+ throw MissingOptionException("childroot");
+ }
+ else
+ {
+ if (ChildRoot[ChildRoot.size() - 1] != '/')
+ ChildRoot += '/';
+
+ // Create a custom sub-path for parallelized unit tests.
+ if (UnitBase::isUnitTesting())
+ {
+ ChildRoot += Util::rng::getHardRandomHexString(8) + '/';
+ LOG_INF("Creating sub-childroot: " + ChildRoot);
+ }
+ else
+ LOG_INF("Creating childroot: " + ChildRoot);
+ }
+
+ // Setup the jails.
+ JailUtil::setupJails(getConfigValue<bool>(conf, "mount_jail_tree", true), ChildRoot,
+ SysTemplate);
LOG_DBG("FileServerRoot before config: " << FileServerRoot);
FileServerRoot = getPathFromConfig("file_server_root_path");
@@ -1188,6 +1222,8 @@ void LOOLWSD::initialize(Application& self)
LOG_INF("NumPreSpawnedChildren set to " << NumPreSpawnedChildren << '.');
#if !MOBILEAPP
+ FileUtil::registerFileSystemForDiskSpaceChecks(ChildRoot);
+
const auto maxConcurrency = getConfigValue<int>(conf, "per_document.max_concurrency", 4);
if (maxConcurrency > 0)
{
@@ -3727,40 +3763,14 @@ int LOOLWSD::innerMain()
// so must check options required in the parent (but not in the
// child) separately now. Also check for options that are
// meaningless for the parent.
- if (SysTemplate.empty())
- {
- LOG_FTL("Missing --systemplate option");
- throw MissingOptionException("systemplate");
- }
-
if (LoTemplate.empty())
{
- LOG_FTL("Missing --lotemplate option");
+ LOG_FTL("Missing --lo-template-path option");
throw MissingOptionException("lotemplate");
}
- if (ChildRoot.empty())
- {
- LOG_FTL("Missing --childroot option");
- throw MissingOptionException("childroot");
- }
- else
- {
- if (ChildRoot[ChildRoot.size() - 1] != '/')
- ChildRoot += '/';
-
- // create a custom sub-path for parallelized unit tests.
- if (UnitBase::isUnitTesting())
- {
- ChildRoot += Util::rng::getHardRandomHexString(8) + "/";
- LOG_TRC("Creating sub-childroot: of " + ChildRoot);
- }
- }
-
- FileUtil::registerFileSystemForDiskSpaceChecks(ChildRoot);
-
if (FileServerRoot.empty())
- FileServerRoot = Poco::Path(Application::instance().commandPath()).parent().toString();
+ FileServerRoot = Util::getApplicationPath();
FileServerRoot = Poco::Path(FileServerRoot).absolute().toString();
LOG_DBG("FileServerRoot: " << FileServerRoot);
#endif
@@ -3957,22 +3967,7 @@ int LOOLWSD::innerMain()
ForKitProc.reset();
#endif
- // In case forkit didn't cleanup properly, don't leave jails behind.
- LOG_INF("Cleaning up childroot directory [" << ChildRoot << "].");
- std::vector<std::string> jails;
- File(ChildRoot).list(jails);
- for (auto& jail : jails)
- {
- const auto path = ChildRoot + jail;
- LOG_INF("Removing jail [" << path << "].");
- FileUtil::removeFile(path, true);
- }
-
- if (UnitBase::isUnitTesting())
- {
- LOG_TRC("Removing sub-childroot: of " + ChildRoot);
- FileUtil::removeFile(ChildRoot, true);
- }
+ JailUtil::cleanupJails(ChildRoot);
#endif // !MOBILEAPP
return EX_OK;
diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp
index d7f6d70f9..8f07b9229 100644
--- a/wsd/Storage.hpp
+++ b/wsd/Storage.hpp
@@ -260,8 +260,8 @@ protected:
private:
const Poco::URI _uri;
- std::string _localStorePath;
- std::string _jailPath;
+ const std::string _localStorePath;
+ const std::string _jailPath;
std::string _jailedFilePath;
std::string _jailedFilePathAnonym;
FileInfo _fileInfo;
More information about the Libreoffice-commits
mailing list