libreoffice-online/common/JailUtil.cpp
Ashod Nakashian 9f7f6dca6a wsd: cleanup realpath call
The new utility is safer and more readable.

Change-Id: I3a86675378d458cb004e5534dbf2b401936d0e57
Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98183
Tested-by: Jenkins
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com>
Reviewed-by: Ashod Nakashian <ashnakash@gmail.com>
2020-07-06 13:54:54 +02:00

360 lines
12 KiB
C++

/* -*- 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.
const std::string resolved = FileUtil::realpath(loTemplate);
if (loTemplate != resolved)
symlinkPathToJail(sysTemplate, resolved, loSubPath);
}
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: */