mirror of https://git.tukaani.org/xz.git
356 lines
9.4 KiB
C
356 lines
9.4 KiB
C
// SPDX-License-Identifier: 0BSD
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
/// \file sandbox.c
|
|
/// \brief Sandbox support
|
|
//
|
|
// Author: Lasse Collin
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "private.h"
|
|
|
|
|
|
#ifndef ENABLE_SANDBOX
|
|
|
|
// Prevent an empty translation unit when no sandboxing is supported.
|
|
typedef int dummy;
|
|
|
|
#else
|
|
|
|
/// If the conditions for strict sandboxing (described in main())
|
|
/// have been met, sandbox_allow_strict() can be called to set this
|
|
/// variable to true.
|
|
static bool strict_sandbox_allowed = false;
|
|
|
|
|
|
extern void
|
|
sandbox_allow_strict(void)
|
|
{
|
|
strict_sandbox_allowed = true;
|
|
return;
|
|
}
|
|
|
|
|
|
// Strict sandboxing prevents opening any files. This *tries* to ensure
|
|
// that any auxiliary files that might be required are already open.
|
|
//
|
|
// Returns true if strict sandboxing is allowed, false otherwise.
|
|
static bool
|
|
prepare_for_strict_sandbox(void)
|
|
{
|
|
if (!strict_sandbox_allowed)
|
|
return false;
|
|
|
|
const char dummy_str[] = "x";
|
|
|
|
// Try to ensure that both libc and xz locale files have been
|
|
// loaded when NLS is enabled.
|
|
snprintf(NULL, 0, "%s%s", _(dummy_str), strerror(EINVAL));
|
|
|
|
// Try to ensure that iconv data files needed for handling multibyte
|
|
// characters have been loaded. This is needed at least with glibc.
|
|
tuklib_mbstr_width(dummy_str, NULL);
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
#if defined(HAVE_PLEDGE)
|
|
|
|
///////////////
|
|
// pledge(2) //
|
|
///////////////
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
extern void
|
|
sandbox_init(void)
|
|
{
|
|
if (pledge("stdio rpath wpath cpath fattr", "")) {
|
|
// gettext hasn't been initialized yet so
|
|
// there's no point to call it here.
|
|
message_fatal("Failed to enable the sandbox");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
extern void
|
|
sandbox_enable_read_only(void)
|
|
{
|
|
// We will be opening files for reading but
|
|
// won't create or remove any files.
|
|
if (pledge("stdio rpath", ""))
|
|
message_fatal(_("Failed to enable the sandbox"));
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
extern void
|
|
sandbox_enable_strict_if_allowed(int src_fd lzma_attribute((__unused__)),
|
|
int pipe_event_fd lzma_attribute((__unused__)),
|
|
int pipe_write_fd lzma_attribute((__unused__)))
|
|
{
|
|
if (!prepare_for_strict_sandbox())
|
|
return;
|
|
|
|
// All files that need to be opened have already been opened.
|
|
if (pledge("stdio", ""))
|
|
message_fatal(_("Failed to enable the sandbox"));
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
#elif defined(HAVE_LINUX_LANDLOCK)
|
|
|
|
//////////////
|
|
// Landlock //
|
|
//////////////
|
|
|
|
#include <linux/landlock.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/prctl.h>
|
|
|
|
|
|
// Highest Landlock ABI version supported by this file:
|
|
// - For ABI versions 1-3 we don't need anything from <linux/landlock.h>
|
|
// that isn't part of version 1.
|
|
// - For ABI version 4 we need the larger struct landlock_ruleset_attr
|
|
// with the handled_access_net member. That is bundled with the macros
|
|
// LANDLOCK_ACCESS_NET_BIND_TCP and LANDLOCK_ACCESS_NET_CONNECT_TCP.
|
|
#ifdef LANDLOCK_ACCESS_NET_BIND_TCP
|
|
# define LANDLOCK_ABI_MAX 4
|
|
#else
|
|
# define LANDLOCK_ABI_MAX 3
|
|
#endif
|
|
|
|
|
|
/// Landlock ABI version supported by the kernel
|
|
static int landlock_abi;
|
|
|
|
|
|
// The required_rights should have those bits set that must not be restricted.
|
|
// This function will then bitwise-and ~required_rights with a mask matching
|
|
// the Landlock ABI version, leaving only those bits set that are supported
|
|
// by the ABI and allowed to be restricted by the function argument.
|
|
static void
|
|
enable_landlock(uint64_t required_rights)
|
|
{
|
|
assert(landlock_abi <= LANDLOCK_ABI_MAX);
|
|
|
|
if (landlock_abi <= 0)
|
|
return;
|
|
|
|
// We want to set all supported flags in handled_access_fs.
|
|
// This way the ruleset will initially forbid access to all
|
|
// actions that the available Landlock ABI version supports.
|
|
// Exceptions can be added using landlock_add_rule(2) to
|
|
// allow certain actions on certain files or directories.
|
|
//
|
|
// The same flag values are used on all archs. ABI v2 and v3
|
|
// both add one new flag.
|
|
//
|
|
// First in ABI v1: LANDLOCK_ACCESS_FS_EXECUTE = 1ULL << 0
|
|
// Last in ABI v1: LANDLOCK_ACCESS_FS_MAKE_SYM = 1ULL << 12
|
|
// Last in ABI v2: LANDLOCK_ACCESS_FS_REFER = 1ULL << 13
|
|
// Last in ABI v3: LANDLOCK_ACCESS_FS_TRUNCATE = 1ULL << 14
|
|
//
|
|
// This makes it simple to set the mask based on the ABI
|
|
// version and we don't need to care which flags are #defined
|
|
// in the installed <linux/landlock.h> for ABI versions 1-3.
|
|
const struct landlock_ruleset_attr attr = {
|
|
.handled_access_fs = ~required_rights
|
|
& ((1ULL << (12 + my_min(3, landlock_abi))) - 1),
|
|
#if LANDLOCK_ABI_MAX >= 4
|
|
.handled_access_net = landlock_abi < 4 ? 0 :
|
|
(LANDLOCK_ACCESS_NET_BIND_TCP
|
|
| LANDLOCK_ACCESS_NET_CONNECT_TCP),
|
|
#endif
|
|
};
|
|
|
|
const int ruleset_fd = syscall(SYS_landlock_create_ruleset,
|
|
&attr, sizeof(attr), 0U);
|
|
if (ruleset_fd < 0)
|
|
message_fatal(_("Failed to enable the sandbox"));
|
|
|
|
// All files we need should have already been opened. Thus,
|
|
// we don't need to add any rules using landlock_add_rule(2)
|
|
// before activating the sandbox.
|
|
//
|
|
// NOTE: It's possible that the hack prepare_for_strict_sandbox()
|
|
// isn't be good enough. It tries to get translations and
|
|
// libc-specific files loaded but if it's not good enough
|
|
// then perhaps a Landlock rule to allow reading from /usr
|
|
// and/or the xz installation prefix would be needed.
|
|
//
|
|
// prctl(PR_SET_NO_NEW_PRIVS, ...) was already called in
|
|
// sandbox_init() so we don't do it here again.
|
|
if (syscall(SYS_landlock_restrict_self, ruleset_fd, 0U) != 0)
|
|
message_fatal(_("Failed to enable the sandbox"));
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
extern void
|
|
sandbox_init(void)
|
|
{
|
|
// Prevent the process from gaining new privileges. This must be done
|
|
// before landlock_restrict_self(2) but since we will never need new
|
|
// privileges, this call can be done here already.
|
|
//
|
|
// This is supported since Linux 3.5. Ignore the return value to
|
|
// keep compatibility with old kernels. landlock_restrict_self(2)
|
|
// will fail if the no_new_privs attribute isn't set, thus if prctl()
|
|
// fails here the error will still be detected when it matters.
|
|
(void)prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
|
|
|
|
// Get the highest Landlock ABI version supported by the kernel.
|
|
landlock_abi = syscall(SYS_landlock_create_ruleset,
|
|
(void *)NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
|
|
|
|
// The kernel might support a newer ABI than this file.
|
|
if (landlock_abi > LANDLOCK_ABI_MAX)
|
|
landlock_abi = LANDLOCK_ABI_MAX;
|
|
|
|
// These are all in ABI version 1 already. We don't need truncate
|
|
// rights because files are created with open() using O_EXCL and
|
|
// without O_TRUNC.
|
|
//
|
|
// LANDLOCK_ACCESS_FS_READ_DIR is included here to get a clear error
|
|
// message if xz is given a directory name. Without this permission
|
|
// the message would be "Permission denied" but with this permission
|
|
// it's "Is a directory, skipping". It could be worked around with
|
|
// stat()/lstat() but just giving this permission is simpler and
|
|
// shouldn't make the sandbox much weaker in practice.
|
|
const uint64_t required_rights
|
|
= LANDLOCK_ACCESS_FS_WRITE_FILE
|
|
| LANDLOCK_ACCESS_FS_READ_FILE
|
|
| LANDLOCK_ACCESS_FS_READ_DIR
|
|
| LANDLOCK_ACCESS_FS_REMOVE_FILE
|
|
| LANDLOCK_ACCESS_FS_MAKE_REG;
|
|
|
|
enable_landlock(required_rights);
|
|
return;
|
|
}
|
|
|
|
|
|
extern void
|
|
sandbox_enable_read_only(void)
|
|
{
|
|
// We will be opening files for reading but
|
|
// won't create or remove any files.
|
|
const uint64_t required_rights
|
|
= LANDLOCK_ACCESS_FS_READ_FILE
|
|
| LANDLOCK_ACCESS_FS_READ_DIR;
|
|
enable_landlock(required_rights);
|
|
return;
|
|
}
|
|
|
|
|
|
extern void
|
|
sandbox_enable_strict_if_allowed(int src_fd lzma_attribute((__unused__)),
|
|
int pipe_event_fd lzma_attribute((__unused__)),
|
|
int pipe_write_fd lzma_attribute((__unused__)))
|
|
{
|
|
if (!prepare_for_strict_sandbox())
|
|
return;
|
|
|
|
// Allow all restrictions that the kernel supports with the
|
|
// highest Landlock ABI version that the kernel or xz supports.
|
|
//
|
|
// NOTE: LANDLOCK_ACCESS_FS_READ_DIR isn't needed here because
|
|
// the only input file has already been opened.
|
|
enable_landlock(0);
|
|
return;
|
|
}
|
|
|
|
|
|
#elif defined(HAVE_CAP_RIGHTS_LIMIT)
|
|
|
|
//////////////
|
|
// Capsicum //
|
|
//////////////
|
|
|
|
#include <sys/capsicum.h>
|
|
|
|
|
|
extern void
|
|
sandbox_init(void)
|
|
{
|
|
// Nothing to do.
|
|
return;
|
|
}
|
|
|
|
|
|
extern void
|
|
sandbox_enable_read_only(void)
|
|
{
|
|
// Nothing to do.
|
|
return;
|
|
}
|
|
|
|
|
|
extern void
|
|
sandbox_enable_strict_if_allowed(
|
|
int src_fd, int pipe_event_fd, int pipe_write_fd)
|
|
{
|
|
if (!prepare_for_strict_sandbox())
|
|
return;
|
|
|
|
// Capsicum needs FreeBSD 10.2 or later.
|
|
cap_rights_t rights;
|
|
|
|
if (cap_enter())
|
|
goto error;
|
|
|
|
if (cap_rights_limit(src_fd, cap_rights_init(&rights,
|
|
CAP_EVENT, CAP_FCNTL, CAP_LOOKUP, CAP_READ, CAP_SEEK)))
|
|
goto error;
|
|
|
|
// If not reading from stdin, remove all capabilities from it.
|
|
if (src_fd != STDIN_FILENO && cap_rights_limit(
|
|
STDIN_FILENO, cap_rights_clear(&rights)))
|
|
goto error;
|
|
|
|
if (cap_rights_limit(STDOUT_FILENO, cap_rights_init(&rights,
|
|
CAP_EVENT, CAP_FCNTL, CAP_FSTAT, CAP_LOOKUP,
|
|
CAP_WRITE, CAP_SEEK)))
|
|
goto error;
|
|
|
|
if (cap_rights_limit(STDERR_FILENO, cap_rights_init(&rights,
|
|
CAP_WRITE)))
|
|
goto error;
|
|
|
|
if (cap_rights_limit(pipe_event_fd, cap_rights_init(&rights,
|
|
CAP_EVENT)))
|
|
goto error;
|
|
|
|
if (cap_rights_limit(pipe_write_fd, cap_rights_init(&rights,
|
|
CAP_WRITE)))
|
|
goto error;
|
|
|
|
return;
|
|
|
|
error:
|
|
// If a kernel is configured without capability mode support or
|
|
// used in an emulator that does not implement the capability
|
|
// system calls, then the Capsicum system calls will fail and set
|
|
// errno to ENOSYS. In that case xz will silently run without
|
|
// the sandbox.
|
|
if (errno == ENOSYS)
|
|
return;
|
|
|
|
message_fatal(_("Failed to enable the sandbox"));
|
|
}
|
|
|
|
#endif
|