xzdec: Add sandbox support for Pledge, Capsicum, and Landlock.

A very strict sandbox is used when the last file is decompressed. The
likely most common use case of xzdec is to decompress a single file.
The Pledge sandbox is applied to the entire process with slightly more
relaxed promises, until the last file is processed.

Thanks to Christian Weisgerber for the initial patch adding Pledge
sandboxing.
This commit is contained in:
Jia Tan 2023-12-19 21:18:28 +08:00
parent b34b6a9912
commit d74fb5f060
1 changed files with 139 additions and 7 deletions

View File

@ -21,6 +21,21 @@
# include <unistd.h>
#endif
#ifdef HAVE_CAP_RIGHTS_LIMIT
# include <sys/capsicum.h>
#endif
#ifdef HAVE_LINUX_LANDLOCK_H
# include <linux/landlock.h>
# include <sys/prctl.h>
# include <sys/syscall.h>
#endif
#if defined(HAVE_CAP_RIGHTS_LIMIT) || defined(HAVE_PLEDGE) \
|| defined(HAVE_LINUX_LANDLOCK_H)
# define ENABLE_SANDBOX 1
#endif
#include "getopt.h"
#include "tuklib_progname.h"
#include "tuklib_exit.h"
@ -280,9 +295,107 @@ uncompress(lzma_stream *strm, FILE *file, const char *filename)
}
#ifdef ENABLE_SANDBOX
static void
sandbox_enter(int src_fd)
{
#if defined(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_READ)))
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_WRITE)))
goto error;
if (cap_rights_limit(STDERR_FILENO, cap_rights_init(&rights, CAP_WRITE)))
goto error;
#elif defined(HAVE_PLEDGE)
// pledge() was introduced in OpenBSD 5.9.
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;
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.
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
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 xzdec will silently run without
// the sandbox.
if (errno == ENOSYS)
return;
#endif
my_errorf("Failed to enable the sandbox");
exit(EXIT_FAILURE);
}
#endif
int
main(int argc, char **argv)
{
#ifdef HAVE_PLEDGE
// OpenBSD's pledge(2) sandbox.
// Initially enable the sandbox slightly more relaxed so that
// the process can still open files. This allows the sandbox to
// be enabled when parsing command line arguments and decompressing
// all files (the more strict sandbox only restricts the last file
// that is decompressed).
if (pledge("stdio rpath", "")) {
my_errorf("Failed to enable the sandbox");
exit(EXIT_FAILURE);
}
#endif
#ifdef HAVE_LINUX_LANDLOCK_H
// Prevent the process from gaining new privileges. The return
// is ignored to keep compatibility with old kernels.
(void)prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
#endif
// Initialize progname which we will be used in error messages.
tuklib_progname_init(argv);
@ -302,24 +415,43 @@ main(int argc, char **argv)
if (optind == argc) {
// No filenames given, decode from stdin.
#ifdef ENABLE_SANDBOX
sandbox_enter(STDIN_FILENO);
#endif
uncompress(&strm, stdin, "(stdin)");
} else {
// Loop through the filenames given on the command line.
do {
FILE *src_file;
const char *src_name;
// "-" indicates stdin.
if (strcmp(argv[optind], "-") == 0) {
uncompress(&strm, stdin, "(stdin)");
src_file = stdin;
src_name = "(stdin)";
} else {
FILE *file = fopen(argv[optind], "rb");
if (file == NULL) {
my_errorf("%s: %s", argv[optind],
src_name = argv[optind];
src_file = fopen(src_name, "rb");
if (src_file == NULL) {
my_errorf("%s: %s", src_name,
strerror(errno));
exit(EXIT_FAILURE);
}
uncompress(&strm, file, argv[optind]);
fclose(file);
}
#ifdef ENABLE_SANDBOX
// Enable the sandbox for the last file. When the
// strict sandbox is enabled the process can no
// longer open additional files. It is likely that
// the most common way to use xzdec is to
// decompress a single file, so this fully protects
// most use cases.
if (optind == argc - 1)
sandbox_enter(fileno(src_file));
#endif
uncompress(&strm, src_file, src_name);
if (src_file != stdin)
fclose(src_file);
} while (++optind < argc);
}