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"