xz: Move sandboxing code to sandbox.c and improve Landlock sandbox.
Landlock is now always used just like pledge(2) is: first in more permissive mode and later (under certain common conditions) in a strict mode that doesn't allow opening more files. I put pledge(2) first in sandbox.c because it's the simplest API to use and still somewhat fine-grained for basic applications. So it's the simplest thing to understand for anyone reading sandbox.c.
This commit is contained in:
parent
7312dfbb02
commit
374868d81d
|
@ -1393,6 +1393,8 @@ if(NOT MSVC OR MSVC_VERSION GREATER_EQUAL 1900)
|
||||||
src/xz/options.c
|
src/xz/options.c
|
||||||
src/xz/options.h
|
src/xz/options.h
|
||||||
src/xz/private.h
|
src/xz/private.h
|
||||||
|
src/xz/sandbox.c
|
||||||
|
src/xz/sandbox.h
|
||||||
src/xz/signals.c
|
src/xz/signals.c
|
||||||
src/xz/signals.h
|
src/xz/signals.h
|
||||||
src/xz/suffix.c
|
src/xz/suffix.c
|
||||||
|
|
|
@ -21,6 +21,8 @@ xz_SOURCES = \
|
||||||
options.c \
|
options.c \
|
||||||
options.h \
|
options.h \
|
||||||
private.h \
|
private.h \
|
||||||
|
sandbox.c \
|
||||||
|
sandbox.h \
|
||||||
signals.c \
|
signals.c \
|
||||||
signals.h \
|
signals.h \
|
||||||
suffix.c \
|
suffix.c \
|
||||||
|
|
170
src/xz/file_io.c
170
src/xz/file_io.c
|
@ -28,15 +28,6 @@ static bool warn_fchown;
|
||||||
# include <utime.h>
|
# include <utime.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_CAP_RIGHTS_LIMIT
|
|
||||||
# include <sys/capsicum.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef HAVE_LINUX_LANDLOCK_H
|
|
||||||
# include <linux/landlock.h>
|
|
||||||
# include <sys/syscall.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "tuklib_open_stdxxx.h"
|
#include "tuklib_open_stdxxx.h"
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
|
@ -92,11 +83,6 @@ typedef enum {
|
||||||
/// If true, try to create sparse files when decompressing.
|
/// If true, try to create sparse files when decompressing.
|
||||||
static bool try_sparse = true;
|
static bool try_sparse = true;
|
||||||
|
|
||||||
#ifdef ENABLE_SANDBOX
|
|
||||||
/// True if the conditions for sandboxing (described in main()) have been met.
|
|
||||||
static bool sandbox_allowed = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef TUKLIB_DOSLIKE
|
#ifndef TUKLIB_DOSLIKE
|
||||||
/// File status flags of standard input. This is used by io_open_src()
|
/// File status flags of standard input. This is used by io_open_src()
|
||||||
/// and io_close_src().
|
/// and io_close_src().
|
||||||
|
@ -181,159 +167,6 @@ io_no_sparse(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef ENABLE_SANDBOX
|
|
||||||
extern void
|
|
||||||
io_allow_sandbox(void)
|
|
||||||
{
|
|
||||||
sandbox_allowed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Enables operating-system-specific sandbox if it is possible.
|
|
||||||
/// src_fd is the file descriptor of the input file.
|
|
||||||
static void
|
|
||||||
io_sandbox_enter(int src_fd)
|
|
||||||
{
|
|
||||||
if (!sandbox_allowed) {
|
|
||||||
// This message is more often annoying than useful so
|
|
||||||
// it's commented out. It can be useful when developing
|
|
||||||
// the sandboxing code.
|
|
||||||
//message(V_DEBUG, _("Sandbox is disabled due "
|
|
||||||
// "to incompatible command line arguments"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
#ifdef HAVE_CAP_RIGHTS_LIMIT
|
|
||||||
// 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(user_abort_pipe[0], cap_rights_init(&rights,
|
|
||||||
CAP_EVENT)))
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
if (cap_rights_limit(user_abort_pipe[1], cap_rights_init(&rights,
|
|
||||||
CAP_WRITE)))
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
#elif defined(HAVE_PLEDGE)
|
|
||||||
// pledge() was introduced in OpenBSD 5.9.
|
|
||||||
//
|
|
||||||
// main() unconditionally calls pledge() with fairly relaxed
|
|
||||||
// promises which work in all situations. Here we make the
|
|
||||||
// sandbox more strict.
|
|
||||||
if (pledge("stdio", ""))
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
(void)src_fd;
|
|
||||||
|
|
||||||
#elif defined(HAVE_LINUX_LANDLOCK_H)
|
|
||||||
int landlock_abi = syscall(SYS_landlock_create_ruleset,
|
|
||||||
(void *)NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
|
|
||||||
|
|
||||||
if (landlock_abi > 0) {
|
|
||||||
// We support ABI versions 1-3.
|
|
||||||
if (landlock_abi > 3)
|
|
||||||
landlock_abi = 3;
|
|
||||||
|
|
||||||
// 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>.
|
|
||||||
const struct landlock_ruleset_attr attr = {
|
|
||||||
.handled_access_fs = (1ULL << (12 + landlock_abi)) - 1
|
|
||||||
};
|
|
||||||
|
|
||||||
const int ruleset_fd = syscall(SYS_landlock_create_ruleset,
|
|
||||||
&attr, sizeof(attr), 0U);
|
|
||||||
if (ruleset_fd < 0)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
// 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 at the beginning of this
|
|
||||||
// function 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
|
|
||||||
// main() so we don't do it here again.
|
|
||||||
if (syscall(SYS_landlock_restrict_self, ruleset_fd, 0U) != 0)
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
(void)src_fd;
|
|
||||||
|
|
||||||
#else
|
|
||||||
# error ENABLE_SANDBOX is defined but no sandboxing method was found.
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// This message is annoying in xz -lvv.
|
|
||||||
//message(V_DEBUG, _("Sandbox was successfully enabled"));
|
|
||||||
return;
|
|
||||||
|
|
||||||
error:
|
|
||||||
#ifdef HAVE_CAP_RIGHTS_LIMIT
|
|
||||||
// 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;
|
|
||||||
#endif
|
|
||||||
message_fatal(_("Failed to enable the sandbox"));
|
|
||||||
}
|
|
||||||
#endif // ENABLE_SANDBOX
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef TUKLIB_DOSLIKE
|
#ifndef TUKLIB_DOSLIKE
|
||||||
/// \brief Waits for input or output to become available or for a signal
|
/// \brief Waits for input or output to become available or for a signal
|
||||||
///
|
///
|
||||||
|
@ -889,7 +722,8 @@ io_open_src(const char *src_name)
|
||||||
|
|
||||||
#ifdef ENABLE_SANDBOX
|
#ifdef ENABLE_SANDBOX
|
||||||
if (!error)
|
if (!error)
|
||||||
io_sandbox_enter(pair.src_fd);
|
sandbox_enable_strict_if_allowed(pair.src_fd,
|
||||||
|
user_abort_pipe[0], user_abort_pipe[1]);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return error ? NULL : &pair;
|
return error ? NULL : &pair;
|
||||||
|
|
|
@ -99,12 +99,6 @@ extern void io_write_to_user_abort_pipe(void);
|
||||||
extern void io_no_sparse(void);
|
extern void io_no_sparse(void);
|
||||||
|
|
||||||
|
|
||||||
#ifdef ENABLE_SANDBOX
|
|
||||||
/// \brief main() calls this if conditions for sandboxing have been met.
|
|
||||||
extern void io_allow_sandbox(void);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/// \brief Open the source file
|
/// \brief Open the source file
|
||||||
extern file_pair *io_open_src(const char *src_name);
|
extern file_pair *io_open_src(const char *src_name);
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,6 @@
|
||||||
#include "private.h"
|
#include "private.h"
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
|
||||||
// prctl(PR_SET_NO_NEW_PRIVS, ...) is required with Landlock but it can be
|
|
||||||
// activated even when conditions for strict sandboxing aren't met.
|
|
||||||
#ifdef HAVE_LINUX_LANDLOCK_H
|
|
||||||
# include <sys/prctl.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/// Exit status to use. This can be changed with set_exit_status().
|
/// Exit status to use. This can be changed with set_exit_status().
|
||||||
static enum exit_status_type exit_status = E_SUCCESS;
|
static enum exit_status_type exit_status = E_SUCCESS;
|
||||||
|
@ -148,32 +142,6 @@ read_name(const args_info *args)
|
||||||
int
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
#ifdef HAVE_PLEDGE
|
|
||||||
// OpenBSD's pledge(2) sandbox
|
|
||||||
//
|
|
||||||
// Unconditionally enable sandboxing with fairly relaxed promises.
|
|
||||||
// This is still way better than having no sandbox at all. :-)
|
|
||||||
// More strict promises will be made later in file_io.c if possible.
|
|
||||||
if (pledge("stdio rpath wpath cpath fattr", "")) {
|
|
||||||
// Don't translate the string or use message_fatal() as
|
|
||||||
// those haven't been initialized yet.
|
|
||||||
fprintf(stderr, "%s: Failed to enable the sandbox\n", argv[0]);
|
|
||||||
return E_ERROR;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef HAVE_LINUX_LANDLOCK_H
|
|
||||||
// Prevent the process from gaining new privileges. This must be done
|
|
||||||
// before landlock_restrict_self(2) in file_io.c 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);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(_WIN32) && !defined(__CYGWIN__)
|
#if defined(_WIN32) && !defined(__CYGWIN__)
|
||||||
InitializeCriticalSection(&exit_status_cs);
|
InitializeCriticalSection(&exit_status_cs);
|
||||||
#endif
|
#endif
|
||||||
|
@ -187,6 +155,20 @@ main(int argc, char **argv)
|
||||||
// even indirectly like locale and gettext initializations.
|
// even indirectly like locale and gettext initializations.
|
||||||
io_init();
|
io_init();
|
||||||
|
|
||||||
|
#ifdef ENABLE_SANDBOX
|
||||||
|
// Enable such sandboxing that can always be enabled.
|
||||||
|
// This requires that progname has been set up.
|
||||||
|
// It's also good that io_init() has been called because it
|
||||||
|
// might need to do things that the initial sandbox won't allow.
|
||||||
|
// Otherwise this should be called as early as possible.
|
||||||
|
//
|
||||||
|
// NOTE: Calling this before tuklib_gettext_init() means that
|
||||||
|
// translated error message won't be available if sandbox
|
||||||
|
// initialization fails. However, sandbox_init() shouldn't
|
||||||
|
// fail and this order simply feels better.
|
||||||
|
sandbox_init();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Set up the locale and message translations.
|
// Set up the locale and message translations.
|
||||||
tuklib_gettext_init(PACKAGE, LOCALEDIR);
|
tuklib_gettext_init(PACKAGE, LOCALEDIR);
|
||||||
|
|
||||||
|
@ -241,7 +223,7 @@ main(int argc, char **argv)
|
||||||
signals_init();
|
signals_init();
|
||||||
|
|
||||||
#ifdef ENABLE_SANDBOX
|
#ifdef ENABLE_SANDBOX
|
||||||
// Set a flag that sandboxing is allowed if all these are true:
|
// Set a flag that strict sandboxing is allowed if all these are true:
|
||||||
// - --files or --files0 wasn't used.
|
// - --files or --files0 wasn't used.
|
||||||
// - There is exactly one input file or we are reading from stdin.
|
// - There is exactly one input file or we are reading from stdin.
|
||||||
// - We won't create any files: output goes to stdout or --test
|
// - We won't create any files: output goes to stdout or --test
|
||||||
|
@ -255,7 +237,7 @@ main(int argc, char **argv)
|
||||||
if (args.files_name == NULL && args.arg_count == 1
|
if (args.files_name == NULL && args.arg_count == 1
|
||||||
&& (opt_stdout || strcmp("-", args.arg_names[0]) == 0
|
&& (opt_stdout || strcmp("-", args.arg_names[0]) == 0
|
||||||
|| opt_mode == MODE_LIST))
|
|| opt_mode == MODE_LIST))
|
||||||
io_allow_sandbox();
|
sandbox_allow_strict();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// coder_run() handles compression, decompression, and testing.
|
// coder_run() handles compression, decompression, and testing.
|
||||||
|
|
|
@ -51,11 +51,6 @@
|
||||||
# define STDERR_FILENO (fileno(stderr))
|
# define STDERR_FILENO (fileno(stderr))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(HAVE_CAP_RIGHTS_LIMIT) || defined(HAVE_PLEDGE) \
|
|
||||||
|| defined(HAVE_LINUX_LANDLOCK_H)
|
|
||||||
# define ENABLE_SANDBOX 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Handling SIGTSTP keeps time-keeping for progress indicator correct
|
// Handling SIGTSTP keeps time-keeping for progress indicator correct
|
||||||
// if xz is stopped. It requires use of clock_gettime() as that is
|
// if xz is stopped. It requires use of clock_gettime() as that is
|
||||||
// async-signal safe in POSIX. Require also SIGALRM support since
|
// async-signal safe in POSIX. Require also SIGALRM support since
|
||||||
|
@ -75,6 +70,7 @@
|
||||||
#include "hardware.h"
|
#include "hardware.h"
|
||||||
#include "file_io.h"
|
#include "file_io.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "sandbox.h"
|
||||||
#include "signals.h"
|
#include "signals.h"
|
||||||
#include "suffix.h"
|
#include "suffix.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
|
@ -0,0 +1,295 @@
|
||||||
|
// 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_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;
|
||||||
|
|
||||||
|
if (pledge("stdio", ""))
|
||||||
|
message_fatal(_("Failed to enable the sandbox"));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#elif defined(HAVE_LINUX_LANDLOCK_H)
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
// Landlock //
|
||||||
|
//////////////
|
||||||
|
|
||||||
|
#include <linux/landlock.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
|
||||||
|
|
||||||
|
// Highest Landlock ABI version supported by this file
|
||||||
|
#define LANDLOCK_ABI_MAX 3
|
||||||
|
|
||||||
|
/// 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>.
|
||||||
|
const struct landlock_ruleset_attr attr = {
|
||||||
|
.handled_access_fs = ((1ULL << (12 + landlock_abi)) - 1)
|
||||||
|
& ~required_rights,
|
||||||
|
};
|
||||||
|
|
||||||
|
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.
|
||||||
|
const uint64_t required_rights
|
||||||
|
= LANDLOCK_ACCESS_FS_WRITE_FILE
|
||||||
|
| LANDLOCK_ACCESS_FS_READ_FILE
|
||||||
|
| LANDLOCK_ACCESS_FS_REMOVE_FILE
|
||||||
|
| LANDLOCK_ACCESS_FS_MAKE_REG;
|
||||||
|
|
||||||
|
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.
|
||||||
|
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_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(user_abort_pipe[0], cap_rights_init(&rights,
|
||||||
|
CAP_EVENT)))
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
if (cap_rights_limit(user_abort_pipe[1], 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
|
|
@ -0,0 +1,39 @@
|
||||||
|
// SPDX-License-Identifier: 0BSD
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
/// \file sandbox.h
|
||||||
|
/// \brief Sandbox support
|
||||||
|
//
|
||||||
|
// Author: Lasse Collin
|
||||||
|
//
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#if defined(HAVE_PLEDGE) || defined(HAVE_LINUX_LANDLOCK_H) \
|
||||||
|
|| defined(HAVE_CAP_RIGHTS_LIMIT)
|
||||||
|
# define ENABLE_SANDBOX 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/// \brief Enables early sandboxing that can always be enabled
|
||||||
|
///
|
||||||
|
/// This requires that tuklib_progname() and io_init() have been called.
|
||||||
|
extern void sandbox_init(void);
|
||||||
|
|
||||||
|
|
||||||
|
/// \brief Tell sandboxing code that strict sandboxing can be used
|
||||||
|
///
|
||||||
|
/// This function only sets a flag which will be read by
|
||||||
|
/// sandbox_enable_strict_if_allowed().
|
||||||
|
extern void sandbox_allow_strict(void);
|
||||||
|
|
||||||
|
|
||||||
|
/// \brief Enable sandboxing that allows reading from one file
|
||||||
|
///
|
||||||
|
/// This does nothing if sandbox_allow_strict() hasn't been called.
|
||||||
|
///
|
||||||
|
/// \param src_fd File descriptor open for reading
|
||||||
|
/// \param pipe_event_fd user_abort_pipe[0] from file_io.c
|
||||||
|
/// \param pipe_write_fd user_abort_pipe[1] from file_io.c
|
||||||
|
extern void sandbox_enable_strict_if_allowed(
|
||||||
|
int src_fd, int pipe_event_fd, int pipe_write_fd);
|
Loading…
Reference in New Issue