From d74fb5f060b76db709b50f5fd37490394e52f975 Mon Sep 17 00:00:00 2001 From: Jia Tan Date: Tue, 19 Dec 2023 21:18:28 +0800 Subject: [PATCH] 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. --- src/xzdec/xzdec.c | 146 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 139 insertions(+), 7 deletions(-) diff --git a/src/xzdec/xzdec.c b/src/xzdec/xzdec.c index 10ce4e82..6eda6a1f 100644 --- a/src/xzdec/xzdec.c +++ b/src/xzdec/xzdec.c @@ -21,6 +21,21 @@ # include #endif +#ifdef HAVE_CAP_RIGHTS_LIMIT +# include +#endif + +#ifdef HAVE_LINUX_LANDLOCK_H +# include +# include +# include +#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); }