[PATCHv3] Add optional 'SandboxUser' option to drop privileges before downloading files

Message ID 20211010171135.44931-1-rgacogne@archlinux.org
State New
Headers show
Series [PATCHv3] Add optional 'SandboxUser' option to drop privileges before downloading files | expand

Commit Message

Remi Gacogne Oct. 10, 2021, 5:11 p.m. UTC
Signed-off-by: Remi Gacogne <rgacogne@archlinux.org>
---

This is a new attempt at adding a new option, 'SandboxUser' to drop
privileges for sensitive operations. This only applies to downloading files
for now, but verifying signatures is a likely next candidate, hopefully
a much easier one.

This new version keeps the same logic than the previous one, with small
changes addressing the comments from Allan and Andrew, and a big change to
intercept the callbacks raised in the child process, serialize them before
sending them over a pipe to finally execute them in the parent process.
This is necessary to avoid breaking frontends. At the moment only the log
and download callbacks are thus intercepted, as I did not find any other
kind of callbacks being executed during a download operation while
reviewing the code.
One drawback of the current approach is that for the log callback the format
string is applied in the child, before serializing, and the formatted result
is passed to the callback instead of passing the format string and all the
parameters. It is not obvious to me why a frontend would want to process the
format string and parameters itself, but if there is a valid use-case for that
this patch will break it.

There is also two small changes in src/pacman/callback.c, as we can't assume
anymore that the filename passed to the download callbacks will remain alive
until the last of them has been called. It also looked like one of the
"clean_filename" allocation was leaked.

 doc/pacman.conf.5.asciidoc |   5 +
 lib/libalpm/alpm.h         |  17 ++
 lib/libalpm/dload.c        | 311 ++++++++++++++++++++++++++++++++++++-
 lib/libalpm/handle.c       |  20 +++
 lib/libalpm/handle.h       |   1 +
 lib/libalpm/meson.build    |   1 +
 lib/libalpm/sandbox.c      |  54 +++++++
 src/pacman/callback.c      |  12 +-
 src/pacman/conf.c          |  19 ++-
 src/pacman/conf.h          |   1 +
 src/pacman/pacman-conf.c   |   3 +
 11 files changed, 437 insertions(+), 7 deletions(-)
 create mode 100644 lib/libalpm/sandbox.c

Patch

diff --git doc/pacman.conf.5.asciidoc doc/pacman.conf.5.asciidoc
index 77a3907f..b8126a67 100644
--- doc/pacman.conf.5.asciidoc
+++ doc/pacman.conf.5.asciidoc
@@ -207,6 +207,11 @@  Options
 	positive integer. If this config option is not set then only one download
 	stream is used (i.e. downloads happen sequentially).
 
+*SandboxUser =* username::
+	Specifies the user to switch to for sensitive operations, like downloading
+	files. That user should exist on the system and have the permissions to
+	write to the files located in `DBPath` and `CacheDir`. If this config option
+	is not set then these operations are done as the user running pacman.
 
 Repository Sections
 -------------------
diff --git lib/libalpm/alpm.h lib/libalpm/alpm.h
index 8d8fe243..b206145b 100644
--- lib/libalpm/alpm.h
+++ lib/libalpm/alpm.h
@@ -1833,6 +1833,17 @@  int alpm_option_set_gpgdir(alpm_handle_t *handle, const char *gpgdir);
 /* End of gpdir accessors */
 /** @} */
 
+/** Returns the user to switch to for sensitive operations like downloading a file.
+ * @param handle the context handle
+ * @return the user name
+ */
+const char *alpm_option_get_sandboxuser(alpm_handle_t *handle);
+
+/** Sets the user to switch to for sensitive operations like downloading a file.
+ * @param handle the context handle
+ * @param sandboxuser the user to set
+ */
+int alpm_option_set_sandboxuser(alpm_handle_t *handle, const char *sandboxuser);
 
 /** @name Accessors for use syslog
  *
@@ -2881,6 +2892,12 @@  const char *alpm_version(void);
  * */
 int alpm_capabilities(void);
 
+/** Drop privileges by switching to a different user.
+ * @param sandboxuser the user to switch to
+ * @return 0 on success, the value of errno otherwise
+ */
+int alpm_sandbox_child(const char *sandboxuser);
+
 /* End of libalpm_misc */
 /** @} */
 
diff --git lib/libalpm/dload.c lib/libalpm/dload.c
index 4322318b..582f024f 100644
--- lib/libalpm/dload.c
+++ lib/libalpm/dload.c
@@ -28,6 +28,7 @@ 
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 #include <signal.h>
 
 #ifdef HAVE_NETINET_IN_H
@@ -937,6 +938,310 @@  static int curl_download_internal(alpm_handle_t *handle,
 
 #endif
 
+/** The type of callbacks that can happen during a sandboxed download */
+typedef enum _sandboxed_callbacks_t {
+	ALPM_SANDBOXED_LOG_CB,
+	ALPM_SANDBOXED_DOWNLOAD_CB
+} sandboxed_callbacks_t;
+
+typedef struct _sandboxed_callbacks_context {
+	int callback_pipe;
+} sandboxed_callbacks_context;
+
+__attribute__((format(printf, 3, 0)))
+static void sandbox_log_cb(void *ctx, alpm_loglevel_t level, const char *fmt, va_list args)
+{
+	sandboxed_callbacks_t type = ALPM_SANDBOXED_LOG_CB;
+	sandboxed_callbacks_context *context = ctx;
+	char *string = NULL;
+	int string_size = 0;
+
+	if(!context || context->callback_pipe == -1) {
+		return;
+	}
+
+	string_size = vasprintf(&string, fmt, args);
+	if(string != NULL) {
+		write(context->callback_pipe, &type, sizeof(type));
+		write(context->callback_pipe, &level, sizeof(level));
+		write(context->callback_pipe, &string_size, sizeof(string_size));
+		write(context->callback_pipe, string, string_size);
+		FREE(string);
+	}
+}
+
+static void sandbox_dl_cb(void *ctx, const char *filename, alpm_download_event_type_t event, void *data)
+{
+	sandboxed_callbacks_t type = ALPM_SANDBOXED_DOWNLOAD_CB;
+	sandboxed_callbacks_context *context = ctx;
+	size_t filename_len;
+
+	if(!context || context->callback_pipe == -1) {
+		return;
+	}
+
+	if(!filename || (event != ALPM_DOWNLOAD_INIT && event != ALPM_DOWNLOAD_PROGRESS && event != ALPM_DOWNLOAD_RETRY && event != ALPM_DOWNLOAD_COMPLETED)) {
+		return;
+	}
+
+	filename_len = strlen(filename);
+
+	write(context->callback_pipe, &type, sizeof(type));
+	write(context->callback_pipe, &event, sizeof(event));
+	switch(event) {
+		case ALPM_DOWNLOAD_INIT:
+			write(context->callback_pipe, data, sizeof(alpm_download_event_init_t));
+			break;
+		case ALPM_DOWNLOAD_PROGRESS:
+			write(context->callback_pipe, data, sizeof(alpm_download_event_progress_t));
+			break;
+		case ALPM_DOWNLOAD_RETRY:
+			write(context->callback_pipe, data, sizeof(alpm_download_event_retry_t));
+			break;
+		case ALPM_DOWNLOAD_COMPLETED:
+			write(context->callback_pipe, data, sizeof(alpm_download_event_completed_t));
+			break;
+	}
+	write(context->callback_pipe, &filename_len, sizeof(filename_len));
+	write(context->callback_pipe, filename, filename_len);
+}
+
+static int sandbox_fetch_cb(void *, const char *, const char *, int )
+{
+	fprintf(stderr, "Fetch callback called but not handled in sandboxed download\n");
+	return -1;
+}
+
+static void sandbox_question_cb(void *, alpm_question_t *)
+{
+	fprintf(stderr, "Question callback called but not handled in sandboxed download\n");
+}
+
+static void sandbox_event_cb(void *, alpm_event_t *)
+{
+	fprintf(stderr, "Event callback called but not handled in sandboxed download\n");
+}
+
+static void sandbox_progress_cb(void *, alpm_progress_t, const char *, int, size_t, size_t)
+{
+	fprintf(stderr, "Progress callback called but not handled in sandboxed download\n");
+}
+
+static bool handle_sandboxed_log_cb(alpm_handle_t *handle, int callback_pipe) {
+	alpm_loglevel_t level;
+	char *string = NULL;
+	int string_size = 0;
+	ssize_t got;
+
+	got = read(callback_pipe, &level, sizeof(level));
+	ASSERT(got > 0 && (size_t)got == sizeof(level), return false);
+
+	got = read(callback_pipe, &string_size, sizeof(string_size));
+	ASSERT(got > 0 && (size_t)got == sizeof(string_size), return false);
+
+	MALLOC(string, string_size + 1, return false);
+
+	got = read(callback_pipe, string, string_size);
+	if(got < 0 || got != string_size) {
+		FREE(string);
+		return false;
+	}
+	string[string_size] = 0;
+
+	_alpm_log(handle, level, "%s", string);
+	FREE(string);
+	return true;
+}
+
+static bool handle_sandboxed_download_cb(alpm_handle_t *handle, int callback_pipe) {
+	alpm_download_event_type_t type;
+	char *filename = NULL;
+	size_t filename_size, cb_data_size;
+	ssize_t got;
+	union {
+		alpm_download_event_init_t init;
+		alpm_download_event_progress_t progress;
+		alpm_download_event_retry_t retry;
+		alpm_download_event_completed_t completed;
+	} cb_data;
+
+	got = read(callback_pipe, &type, sizeof(type));
+	ASSERT(got > 0 && (size_t)got == sizeof(type), return false);
+
+	switch (type) {
+	case ALPM_DOWNLOAD_INIT:
+		cb_data_size = sizeof(alpm_download_event_init_t);
+		got = read(callback_pipe, &cb_data.init, cb_data_size);
+		break;
+	case ALPM_DOWNLOAD_PROGRESS:
+		cb_data_size = sizeof(alpm_download_event_progress_t);
+		got = read(callback_pipe, &cb_data.progress, cb_data_size);
+		break;
+	case ALPM_DOWNLOAD_RETRY:
+		cb_data_size = sizeof(alpm_download_event_retry_t);
+		got = read(callback_pipe, &cb_data.retry, cb_data_size);
+		break;
+	case ALPM_DOWNLOAD_COMPLETED:
+		cb_data_size = sizeof(alpm_download_event_completed_t);
+		got = read(callback_pipe, &cb_data.completed, cb_data_size);
+		break;
+	default:
+		return false;
+	}
+	ASSERT(got > 0 && (size_t)got == cb_data_size, return false);
+
+	got = read(callback_pipe, &filename_size, sizeof(filename_size));
+	ASSERT(got > 0 && (size_t)got == sizeof(filename_size), return false);
+
+	MALLOC(filename, filename_size + 1, return false);
+
+	got = read(callback_pipe, filename, filename_size);
+	if(got < 0 || (size_t)got != filename_size) {
+		FREE(filename);
+		return false;
+	}
+	filename[filename_size] = 0;
+
+	handle->dlcb(handle->dlcb_ctx, filename, type, &cb_data);
+	FREE(filename);
+	return true;
+}
+
+/* Download the requested files by launching a process inside a sandbox.
+ * Returns -1 if an error happened for a required file
+ * Returns 0 if a payload was actually downloaded
+ * Returns 1 if no files were downloaded and all errors were non-fatal
+ */
+static int curl_download_internal_sandboxed(alpm_handle_t *handle,
+		alpm_list_t *payloads /* struct dload_payload */,
+		const char *localpath)
+{
+	int pid, err = 0, ret = -1, callbacks_fd[2];
+	sigset_t oldblock;
+	struct sigaction sa_ign = { .sa_handler = SIG_IGN }, oldint, oldquit;
+	sandboxed_callbacks_context callbacks_ctx;
+
+	if(pipe(callbacks_fd) != 0) {
+		return -1;
+	}
+
+	sigaction(SIGINT, &sa_ign, &oldint);
+	sigaction(SIGQUIT, &sa_ign, &oldquit);
+	sigaddset(&sa_ign.sa_mask, SIGCHLD);
+	sigprocmask(SIG_BLOCK, &sa_ign.sa_mask, &oldblock);
+
+	pid = fork();
+
+	/* child */
+	if(pid == 0) {
+		close(callbacks_fd[0]);
+		fcntl(callbacks_fd[1], F_SETFD, FD_CLOEXEC);
+		callbacks_ctx.callback_pipe = callbacks_fd[1];
+		alpm_option_set_logcb(handle, sandbox_log_cb, &callbacks_ctx);
+		alpm_option_set_dlcb(handle, sandbox_dl_cb, &callbacks_ctx);
+		alpm_option_set_fetchcb(handle, sandbox_fetch_cb, &callbacks_ctx);
+		alpm_option_set_eventcb(handle, sandbox_event_cb, &callbacks_ctx);
+		alpm_option_set_questioncb(handle, sandbox_question_cb, &callbacks_ctx);
+		alpm_option_set_progresscb(handle, sandbox_progress_cb, &callbacks_ctx);
+
+		/* restore signal handling for the child to inherit */
+		sigaction(SIGINT, &oldint, NULL);
+		sigaction(SIGQUIT, &oldquit, NULL);
+		sigprocmask(SIG_SETMASK, &oldblock, NULL);
+
+		/* cwd to the download directory */
+		ret = chdir(localpath);
+		if(ret != 0) {
+			_alpm_log(handle, ALPM_LOG_ERROR, _("could not chdir to download directory %s\n"), localpath);
+			ret = -1;
+		} else {
+			ret = alpm_sandbox_child(handle->sandboxuser);
+			if (ret != 0) {
+				_alpm_log(handle, ALPM_LOG_ERROR, _("sandboxing failed!\n"));
+				_Exit(ret | 128);
+			}
+
+			ret = curl_download_internal(handle, payloads, localpath);
+		}
+
+		/* pass the result back to the parent */
+		if(ret == 0) {
+			/* a payload was actually downloaded */
+			exit(0);
+		}
+		else if(ret == 1) {
+			/* no files were downloaded and all errors were non-fatal */
+			_Exit(handle->pm_errno);
+		}
+		else {
+			/* an error happened for a required file */
+			_Exit(handle->pm_errno | 128);
+		}
+	}
+
+	/* parent */
+	close(callbacks_fd[1]);
+
+	if(pid != -1)  {
+		bool done = false;
+		do {
+			sandboxed_callbacks_t callback_type;
+			ssize_t got = read(callbacks_fd[0], &callback_type, sizeof(callback_type));
+			if(got < 0 || (size_t)got != sizeof(callback_type)) {
+				done = true;
+				break;
+			}
+			if(callback_type == ALPM_SANDBOXED_LOG_CB) {
+				if(!handle_sandboxed_log_cb(handle, callbacks_fd[0])) {
+					done = true;
+				}
+			}
+			else if(callback_type == ALPM_SANDBOXED_DOWNLOAD_CB) {
+				if(!handle_sandboxed_download_cb(handle, callbacks_fd[0])) {
+					done = true;
+				}
+			}
+		}
+		while(!done);
+
+		int wret;
+		while((wret = waitpid(pid, &ret, 0)) == -1 && errno == EINTR);
+		if(wret > 0) {
+			if(!WIFEXITED(ret)) {
+				/* the child did not terminate normally */
+				ret = -1;
+			}
+			else {
+				ret = WEXITSTATUS(ret);
+				if(ret & 128) {
+					/* an error happened for a required file, or unexpected exit status */
+					handle->pm_errno = ret & ~128;
+					ret = -1;
+				}
+			}
+		}
+		else {
+			/* waitpid failed */
+			err = errno;
+		}
+	} else {
+		/* fork failed, make sure errno is preserved after cleanup */
+		err = errno;
+	}
+
+	close(callbacks_fd[0]);
+
+	sigaction(SIGINT, &oldint, NULL);
+	sigaction(SIGQUIT, &oldquit, NULL);
+	sigprocmask(SIG_SETMASK, &oldblock, NULL);
+
+	if(err) {
+		errno = err;
+		ret = -1;
+	}
+  return ret;
+}
+
 /* Returns -1 if an error happened for a required file
  * Returns 0 if a payload was actually downloaded
  * Returns 1 if no files were downloaded and all errors were non-fatal
@@ -947,7 +1252,11 @@  int _alpm_download(alpm_handle_t *handle,
 {
 	if(handle->fetchcb == NULL) {
 #ifdef HAVE_LIBCURL
-		return curl_download_internal(handle, payloads, localpath);
+		if(handle->sandboxuser) {
+			return curl_download_internal_sandboxed(handle, payloads, localpath);
+		} else {
+			return curl_download_internal(handle, payloads, localpath);
+		}
 #else
 		RET_ERR(handle, ALPM_ERR_EXTERNAL_DOWNLOAD, -1);
 #endif
diff --git lib/libalpm/handle.c lib/libalpm/handle.c
index e6b683cb..1aeec85b 100644
--- lib/libalpm/handle.c
+++ lib/libalpm/handle.c
@@ -79,6 +79,7 @@  void _alpm_handle_free(alpm_handle_t *handle)
 	FREE(handle->lockfile);
 	FREELIST(handle->architectures);
 	FREE(handle->gpgdir);
+	FREE(handle->sandboxuser);
 	FREELIST(handle->noupgrade);
 	FREELIST(handle->noextract);
 	FREELIST(handle->ignorepkg);
@@ -270,6 +271,12 @@  const char SYMEXPORT *alpm_option_get_gpgdir(alpm_handle_t *handle)
 	return handle->gpgdir;
 }
 
+const char SYMEXPORT *alpm_option_get_sandboxuser(alpm_handle_t *handle)
+{
+	CHECK_HANDLE(handle, return NULL);
+	return handle->sandboxuser;
+}
+
 int SYMEXPORT alpm_option_get_usesyslog(alpm_handle_t *handle)
 {
 	CHECK_HANDLE(handle, return -1);
@@ -573,6 +580,19 @@  int SYMEXPORT alpm_option_set_gpgdir(alpm_handle_t *handle, const char *gpgdir)
 	return 0;
 }
 
+int SYMEXPORT alpm_option_set_sandboxuser(alpm_handle_t *handle, const char *sandboxuser)
+{
+	CHECK_HANDLE(handle, return -1);
+	if(handle->sandboxuser) {
+		FREE(handle->sandboxuser);
+	}
+
+	STRDUP(handle->sandboxuser, sandboxuser, RET_ERR(handle, ALPM_ERR_MEMORY, -1));
+
+	_alpm_log(handle, ALPM_LOG_DEBUG, "option 'sandboxuser' = %s\n", handle->sandboxuser);
+	return 0;
+}
+
 int SYMEXPORT alpm_option_set_usesyslog(alpm_handle_t *handle, int usesyslog)
 {
 	CHECK_HANDLE(handle, return -1);
diff --git lib/libalpm/handle.h lib/libalpm/handle.h
index 2e85f283..0fff5374 100644
--- lib/libalpm/handle.h
+++ lib/libalpm/handle.h
@@ -91,6 +91,7 @@  struct _alpm_handle_t {
 	char *logfile;           /* Name of the log file */
 	char *lockfile;          /* Name of the lock file */
 	char *gpgdir;            /* Directory where GnuPG files are stored */
+	char *sandboxuser;       /* User to switch to for sensitive operations like downloading files */
 	alpm_list_t *cachedirs;  /* Paths to pacman cache directories */
 	alpm_list_t *hookdirs;   /* Paths to hook directories */
 	alpm_list_t *overwrite_files; /* Paths that may be overwritten */
diff --git lib/libalpm/meson.build lib/libalpm/meson.build
index 607e91a3..224fbf5f 100644
--- lib/libalpm/meson.build
+++ lib/libalpm/meson.build
@@ -24,6 +24,7 @@  libalpm_sources = files('''
   pkghash.h pkghash.c
   rawstr.c
   remove.h remove.c
+  sandbox.h sandbox.c
   signing.c signing.h
   sync.h sync.c
   trans.h trans.c
diff --git lib/libalpm/sandbox.c lib/libalpm/sandbox.c
new file mode 100644
index 00000000..0a2675c0
--- /dev/null
+++ lib/libalpm/sandbox.c
@@ -0,0 +1,54 @@ 
+/*
+ *  sandbox.c
+ *
+ *  Copyright (c) 2021 Pacman Development Team <pacman-dev@archlinux.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "alpm.h"
+#include "util.h"
+
+static int switch_to_user(const char *user)
+{
+	struct passwd const *pw = NULL;
+	ASSERT(user != NULL, return 1);
+	ASSERT(getuid() == 0, return 1);
+	ASSERT((pw = getpwnam(user)), return errno);
+	ASSERT(setgid(pw->pw_gid) == 0, return errno);
+	ASSERT(setgroups(0, NULL) == 0, return errno);
+	ASSERT(setuid(pw->pw_uid) == 0, return errno);
+	return 0;
+}
+
+/* check exported library symbols with: nm -C -D <lib> */
+#define SYMEXPORT __attribute__((visibility("default")))
+
+int SYMEXPORT alpm_sandbox_child(const char* sandboxuser)
+{
+	int result = 0;
+
+	if(sandboxuser != NULL) {
+		result = switch_to_user(sandboxuser);
+	}
+
+	return result;
+}
diff --git src/pacman/callback.c src/pacman/callback.c
index 2579b98a..da064114 100644
--- src/pacman/callback.c
+++ src/pacman/callback.c
@@ -57,7 +57,7 @@  static alpm_list_t *output = NULL;
 #endif
 
 struct pacman_progress_bar {
-	const char *filename;
+	char *filename;
 	off_t xfered; /* Current amount of transferred data */
 	off_t total_size;
 	size_t downloaded;
@@ -252,7 +252,7 @@  void cb_event(void *ctx, alpm_event_t *event)
 				alpm_event_hook_run_t *e = &event->hook_run;
 				int digits = number_length(e->total);
 				printf("(%*zu/%*zu) %s\n", digits, e->position,
-						digits, e->total, 
+						digits, e->total,
 						e->desc ? e->desc : e->name);
 			}
 			break;
@@ -748,7 +748,8 @@  static void init_total_progressbar(void)
 {
 	totalbar = calloc(1, sizeof(struct pacman_progress_bar));
 	assert(totalbar);
-	totalbar->filename = _("Total");
+	totalbar->filename = strdup(_("Total"));
+	assert(totalbar->filename);
 	totalbar->init_time = get_time_ms();
 	totalbar->total_size = list_total;
 	totalbar->howmany = list_total_pkgs;
@@ -888,7 +889,8 @@  static void dload_init_event(const char *filename, alpm_download_event_init_t *d
 
 	struct pacman_progress_bar *bar = calloc(1, sizeof(struct pacman_progress_bar));
 	assert(bar);
-	bar->filename = filename;
+	bar->filename = strdup(filename);
+	assert(bar->filename);
 	bar->init_time = get_time_ms();
 	bar->rate = 0.0;
 	multibar_ui.active_downloads = alpm_list_add(multibar_ui.active_downloads, bar);
@@ -904,6 +906,7 @@  static void dload_init_event(const char *filename, alpm_download_event_init_t *d
 		printf("\n");
 		multibar_ui.cursor_lineno++;
 	}
+	free(cleaned_filename);
 }
 
 /* Update progress bar rate/eta stats.
@@ -1091,6 +1094,7 @@  static void dload_complete_event(const char *filename, alpm_download_event_compl
 			multibar_ui.active_downloads = alpm_list_remove_item(
 				multibar_ui.active_downloads, head);
 			free(head);
+			free(j->filename);
 			free(j);
 		} else {
 			break;
diff --git src/pacman/conf.c src/pacman/conf.c
index 12fee64c..3a170ff3 100644
--- src/pacman/conf.c
+++ src/pacman/conf.c
@@ -155,6 +155,7 @@  int config_free(config_t *oldconfig)
 	free(oldconfig->dbpath);
 	free(oldconfig->logfile);
 	free(oldconfig->gpgdir);
+	free(oldconfig->sandboxuser);
 	FREELIST(oldconfig->hookdirs);
 	FREELIST(oldconfig->cachedirs);
 	free(oldconfig->xfercommand);
@@ -215,7 +216,7 @@  static char *get_tempfile(const char *path, const char *filename)
  * - not thread-safe
  * - errno may be set by fork(), pipe(), or execvp()
  */
-static int systemvp(const char *file, char *const argv[])
+static int systemvp(const char *file, char *const argv[], const char *sandboxuser)
 {
 	int pid, err = 0, ret = -1, err_fd[2];
 	sigset_t oldblock;
@@ -242,6 +243,14 @@  static int systemvp(const char *file, char *const argv[])
 		sigaction(SIGQUIT, &oldquit, NULL);
 		sigprocmask(SIG_SETMASK, &oldblock, NULL);
 
+		if (sandboxuser) {
+			ret = alpm_sandbox_child(sandboxuser);
+			if (ret != 0) {
+				pm_printf(ALPM_LOG_ERROR, _("Switching to sandbox user %s failed!\n"), sandboxuser);
+				_Exit(ret);
+			}
+		}
+
 		execvp(file, argv);
 
 		/* execvp failed, pass the error back to the parent */
@@ -352,7 +361,7 @@  static int download_with_xfercommand(void *ctx, const char *url,
 			free(cmd);
 		}
 	}
-	retval = systemvp(argv[0], (char**)argv);
+	retval = systemvp(argv[0], (char**)argv, config->sandboxuser);
 
 	if(retval == -1) {
 		pm_printf(ALPM_LOG_WARNING, _("running XferCommand: fork failed!\n"));
@@ -668,6 +677,11 @@  static int _parse_options(const char *key, char *value,
 				config->logfile = strdup(value);
 				pm_printf(ALPM_LOG_DEBUG, "config: logfile: %s\n", value);
 			}
+		} else if(strcmp(key, "SandboxUser") == 0) {
+			if(!config->sandboxuser) {
+				config->sandboxuser = strdup(value);
+				pm_printf(ALPM_LOG_DEBUG, "config: sandboxuser: %s\n", value);
+			}
 		} else if(strcmp(key, "XferCommand") == 0) {
 			char **c;
 			if((config->xfercommand_argv = wordsplit(value)) == NULL) {
@@ -904,6 +918,7 @@  static int setup_libalpm(void)
 	alpm_option_set_architectures(handle, config->architectures);
 	alpm_option_set_checkspace(handle, config->checkspace);
 	alpm_option_set_usesyslog(handle, config->usesyslog);
+	alpm_option_set_sandboxuser(handle, config->sandboxuser);
 
 	alpm_option_set_ignorepkgs(handle, config->ignorepkg);
 	alpm_option_set_ignoregroups(handle, config->ignoregrp);
diff --git src/pacman/conf.h src/pacman/conf.h
index 04350d39..675e06fb 100644
--- src/pacman/conf.h
+++ src/pacman/conf.h
@@ -67,6 +67,7 @@  typedef struct __config_t {
 	char *logfile;
 	char *gpgdir;
 	char *sysroot;
+	char *sandboxuser;
 	alpm_list_t *hookdirs;
 	alpm_list_t *cachedirs;
 	alpm_list_t *architectures;
diff --git src/pacman/pacman-conf.c src/pacman/pacman-conf.c
index 600f1622..05a6bcd8 100644
--- src/pacman/pacman-conf.c
+++ src/pacman/pacman-conf.c
@@ -251,6 +251,7 @@  static void dump_config(void)
 	show_list_str("HookDir", config->hookdirs);
 	show_str("GPGDir", config->gpgdir);
 	show_str("LogFile", config->logfile);
+	show_str("SandboxUser", config->sandboxuser);
 
 	show_list_str("HoldPkg", config->holdpkg);
 	show_list_str("IgnorePkg", config->ignorepkg);
@@ -349,6 +350,8 @@  static int list_directives(void)
 			show_str("GPGDir", config->gpgdir);
 		} else if(strcasecmp(i->data, "LogFile") == 0) {
 			show_str("LogFile", config->logfile);
+		} else if(strcasecmp(i->data, "SandboxUser") == 0) {
+			show_str("SandboxUser", config->sandboxuser);
 
 		} else if(strcasecmp(i->data, "HoldPkg") == 0) {
 			show_list_str("HoldPkg", config->holdpkg);