From 1238381143a9a7ce84839c2582ccd56ff750a440 Mon Sep 17 00:00:00 2001 From: Lasse Collin Date: Tue, 31 Mar 2015 22:19:34 +0300 Subject: [PATCH] xz: Add support for sandboxing with Capsicum. The sandboxing is used conditionally as described in main.c. This isn't optimal but it was much easier to implement than a full sandboxing solution and it still covers the most common use cases where xz is writing to standard output. This should have practically no effect on performance even with small files as fork() isn't needed. C and locale libraries can open files as needed. This has been fine in the past, but it's a problem with things like Capsicum. io_sandbox_enter() tries to ensure that various locale-related files have been loaded before cap_enter() is called, but it's possible that there are other similar problems which haven't been seen yet. Currently Capsicum is available on FreeBSD 10 and later and there is a port to Linux too. Thanks to Loganaden Velvindron for help. --- configure.ac | 41 +++++++++++++++++++++++ src/xz/Makefile.am | 2 +- src/xz/file_io.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++ src/xz/file_io.h | 6 ++++ src/xz/main.c | 18 +++++++++++ src/xz/private.h | 4 +++ 6 files changed, 151 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index c8fdb5e4..aa68e33b 100644 --- a/configure.ac +++ b/configure.ac @@ -468,6 +468,30 @@ AC_MSG_RESULT([$enable_symbol_versions]) AM_CONDITIONAL([COND_SYMVERS], [test "x$enable_symbol_versions" = xyes]) +############## +# Sandboxing # +############## + +AC_MSG_CHECKING([if sandboxing should be used]) +AC_ARG_ENABLE([sandbox], [AS_HELP_STRING([--enable-sandbox=METHOD], + [Sandboxing METHOD can be `auto', `no', or `capsicum'. + The default is `auto' which enables sandboxing if + a supported sandboxing method is found.])], + [], [enable_sandbox=auto]) +case $enable_sandbox in + auto) + AC_MSG_RESULT([maybe (autodetect)]) + ;; + no | capsicum) + AC_MSG_RESULT([$enable_sandbox]) + ;; + *) + AC_MSG_RESULT([]) + AC_MSG_ERROR([--enable-sandbox only accepts `auto', `no', or `capsicum'.]) + ;; +esac + + ############################################################################### # Checks for programs. ############################################################################### @@ -698,6 +722,23 @@ AC_CHECK_DECL([_mm_movemask_epi8], #include #endif]) +# Check for sandbox support. If one is found, set enable_sandbox=found. +case $enable_sandbox in + auto | capsicum) + AX_CHECK_CAPSICUM([enable_sandbox=found], [:]) + ;; +esac + +# If a specific sandboxing method was explicitly requested and it wasn't +# found, give an error. +case $enable_sandbox in + auto | no | found) + ;; + *) + AC_MSG_ERROR([$enable_sandbox support not found]) + ;; +esac + ############################################################################### # If using GCC, set some additional AM_CFLAGS: diff --git a/src/xz/Makefile.am b/src/xz/Makefile.am index 1f4a2791..500be411 100644 --- a/src/xz/Makefile.am +++ b/src/xz/Makefile.am @@ -49,7 +49,7 @@ xz_CPPFLAGS = \ -I$(top_srcdir)/src/liblzma/api \ -I$(top_builddir)/lib -xz_LDADD = $(top_builddir)/src/liblzma/liblzma.la +xz_LDADD = $(top_builddir)/src/liblzma/liblzma.la $(CAPSICUM_LIB) if COND_GNULIB xz_LDADD += $(top_builddir)/lib/libgnu.a diff --git a/src/xz/file_io.c b/src/xz/file_io.c index 20f512a2..308fa1d2 100644 --- a/src/xz/file_io.c +++ b/src/xz/file_io.c @@ -27,6 +27,14 @@ static bool warn_fchown; # include #endif +#ifdef HAVE_CAPSICUM +# ifdef HAVE_SYS_CAPSICUM_H +# include +# else +# include +# endif +#endif + #include "tuklib_open_stdxxx.h" #ifndef O_BINARY @@ -48,6 +56,11 @@ typedef enum { /// If true, try to create sparse files when decompressing. 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 /// File status flags of standard input. This is used by io_open_src() /// and io_close_src(). @@ -139,6 +152,69 @@ 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) { + 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_CAPSICUM + // Capsicum needs FreeBSD 10.0 or later. + cap_rights_t rights; + + if (cap_rights_limit(src_fd, cap_rights_init(&rights, + CAP_EVENT, CAP_FCNTL, CAP_LOOKUP, CAP_READ, CAP_SEEK))) + 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(user_abort_pipe[1], cap_rights_init(&rights, + CAP_EVENT, CAP_WRITE))) + goto error; + + if (cap_enter()) + goto error; + +#else +# error ENABLE_SANDBOX is defined but no sandboxing method was found. +#endif + + message(V_DEBUG, _("Sandbox was successfully enabled")); + return; + +error: + message(V_DEBUG, _("Failed to enable the sandbox")); +} +#endif // ENABLE_SANDBOX + + #ifndef TUKLIB_DOSLIKE /// \brief Waits for input or output to become available or for a signal /// @@ -656,6 +732,11 @@ io_open_src(const char *src_name) const bool error = io_open_src_real(&pair); signals_unblock(); +#ifdef ENABLE_SANDBOX + if (!error) + io_sandbox_enter(pair.src_fd); +#endif + return error ? NULL : &pair; } diff --git a/src/xz/file_io.h b/src/xz/file_io.h index 2de33792..6722aef8 100644 --- a/src/xz/file_io.h +++ b/src/xz/file_io.h @@ -80,6 +80,12 @@ extern void io_write_to_user_abort_pipe(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 extern file_pair *io_open_src(const char *src_name); diff --git a/src/xz/main.c b/src/xz/main.c index 5608229d..5e0789ad 100644 --- a/src/xz/main.c +++ b/src/xz/main.c @@ -205,6 +205,24 @@ main(int argc, char **argv) if (opt_mode != MODE_LIST) signals_init(); +#ifdef ENABLE_SANDBOX + // Set a flag that sandboxing is allowed if all these are true: + // - --files or --files0 wasn't used. + // - There is exactly one input file or we are reading from stdin. + // - We won't create any files: output goes to stdout or --test + // or --list was used. Note that --test implies opt_stdout = true + // but --list doesn't. + // + // This is obviously not ideal but it was easy to implement and + // it covers the most common use cases. + // + // TODO: Make sandboxing work for other situations too. + if (args.files_name == NULL && args.arg_count == 1 + && (opt_stdout || strcmp("-", args.arg_names[0]) == 0 + || opt_mode == MODE_LIST)) + io_allow_sandbox(); +#endif + // coder_run() handles compression, decompression, and testing. // list_file() is for --list. void (*run)(const char *filename) = opt_mode == MODE_LIST diff --git a/src/xz/private.h b/src/xz/private.h index 4acfa8dc..b84cbbb1 100644 --- a/src/xz/private.h +++ b/src/xz/private.h @@ -45,6 +45,10 @@ # define STDERR_FILENO (fileno(stderr)) #endif +#ifdef HAVE_CAPSICUM +# define ENABLE_SANDBOX 1 +#endif + #include "main.h" #include "mytime.h" #include "coder.h"