From patchwork Sun Sep 5 12:42:49 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Remi Gacogne X-Patchwork-Id: 1963 Return-Path: Delivered-To: patchwork@archlinux.org Received: from mail.archlinux.org [95.216.189.61] by patchwork.archlinux.org with IMAP (fetchmail-6.4.21) for (single-drop); Sun, 05 Sep 2021 12:44:49 +0000 (UTC) Received: from mail.archlinux.org by mail.archlinux.org with LMTP id H34dOMC7NGEGQAQAK+/4rw (envelope-from ) for ; Sun, 05 Sep 2021 12:44:48 +0000 Received: from lists.archlinux.org (lists.archlinux.org [95.217.236.249]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mail.archlinux.org (Postfix) with ESMTPS id 041478CD16A; Sun, 5 Sep 2021 12:44:48 +0000 (UTC) Received: from lists.archlinux.org (localhost [IPv6:::1]) by lists.archlinux.org (Postfix) with ESMTP id CD9D87D8259; Sun, 5 Sep 2021 12:44:47 +0000 (UTC) X-Original-To: pacman-dev@lists.archlinux.org Delivered-To: pacman-dev@lists.archlinux.org Received: from mail.archlinux.org (mail.archlinux.org [95.216.189.61]) by lists.archlinux.org (Postfix) with ESMTPS id 5E9CE7D8250 for ; Sun, 5 Sep 2021 12:44:45 +0000 (UTC) From: Remi Gacogne DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=archlinux.org; s=dkim-ed25519; t=1630845884; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Q8KGpXF/R3yR7PvLWIcpaE5sZQQePJIR0qsYrCB/V2U=; b=8ikiaqgJ5bWcEGY3/gUpGGR5/GBLzAvdLrgVMoSX746XOUbEVpM+CJssBjQBWrgmkQwETD bA0hW5/MayOj5GDw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=archlinux.org; s=dkim-rsa; t=1630845884; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Q8KGpXF/R3yR7PvLWIcpaE5sZQQePJIR0qsYrCB/V2U=; b=Yn2B6+6vJTwmYsgD7nvKZHRo+VdPzY1ILXRUmtNd5XGTELytPSKhajjcTk1OWHPeayviXy aj2xYI1FGrg4paogqH7gEnfdCXCDj/88kIrwD2GMIeJXcYCrj716RzEJTHJpFeXJaB6dDz VLA6FcG3rDEq6+VC671fLqALwddsEqolXlariEfIupbUX8t/uohIvUgFLd0jTc1xwaxG7l YYVrEV2gkPWojJJec8EYsyIeQklLbixN7LLigfJfRzxqVtdZAQfFklX5TC20+lw6oN5QAP dH6GL26LwRvaJc7CbI6taryE+qGbnxAq3P7qIMuZNkqTg+kyMN6WEgvBO/QaOuZG91mWlr VIwSpsys2pcxVgoumZYTnUmh4GP6CGJran8/4bp64DrfqXEC+QmmCNrFIYIkoBxQCv5MOQ /tzGY83yFYkQbSXhh4rqKZJhK9RRa04P+dSy5cVycDvupT6ywr0Q+gu5EaHIg+w1z1aLuc VWVIV8haTNQ0oF6DRIcxmMne2TKlePFo6VElF0C8lQC1Ra3PUgBi5DH1TTA0oM0738Bs1f 8ZlbtxaTULJ4nYs3ioAz0cCdZUuOqQ+20p5eJ3vy59/ekQzuy9dd0gk3EB74Kvf8VEdvl4 Sfz/EWO8LoOUw3DepMP4DFf9AkQzOJG0UhOfB90OllN5eRL6kKRAk= To: pacman-dev@lists.archlinux.org Date: Sun, 5 Sep 2021 14:42:49 +0200 Message-Id: <20210905124248.5354-1-rgacogne@archlinux.org> X-Mailer: git-send-email 2.33.0 In-Reply-To: <219e872d-b68a-942d-46cc-39154d0efb17@archlinux.org> References: <219e872d-b68a-942d-46cc-39154d0efb17@archlinux.org> MIME-Version: 1.0 Subject: [pacman-dev] [PATCHv2] Add optional 'SandboxUser' option to drop privileges before downloading files X-BeenThere: pacman-dev@lists.archlinux.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Discussion list for pacman development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Discussion list for pacman development Errors-To: pacman-dev-bounces@lists.archlinux.org Sender: "pacman-dev" Authentication-Results: mail.archlinux.org; dkim=fail ("headers eddsa verify failed") header.d=archlinux.org header.s=dkim-ed25519 header.b=8ikiaqgJ; dkim=fail ("headers rsa verify failed") header.d=archlinux.org header.s=dkim-rsa header.b=Yn2B6+6v; dmarc=pass (policy=none) header.from=archlinux.org; spf=pass (mail.archlinux.org: domain of pacman-dev-bounces@lists.archlinux.org designates 95.217.236.249 as permitted sender) smtp.mailfrom=pacman-dev-bounces@lists.archlinux.org X-Rspamd-Queue-Id: 041478CD16A X-Spamd-Result: default: False [2.09 / 15.00]; SPOOF_REPLYTO(2.00)[archlinux.org,lists.archlinux.org]; MID_CONTAINS_FROM(1.00)[]; R_MISSING_CHARSET(0.50)[]; DMARC_POLICY_ALLOW_WITH_FAILURES(-0.50)[]; RCVD_IN_DNSWL_MED(-0.40)[95.217.236.249:from,95.216.189.61:received]; MAILLIST(-0.20)[mailman]; R_SPF_ALLOW(-0.20)[+ip4:95.217.236.249:c]; MIME_GOOD(-0.10)[text/plain]; HAS_LIST_UNSUB(-0.01)[]; FROM_HAS_DN(0.00)[]; HAS_REPLYTO(0.00)[pacman-dev@lists.archlinux.org]; ARC_NA(0.00)[]; PREVIOUSLY_DELIVERED(0.00)[pacman-dev@lists.archlinux.org]; RCPT_COUNT_ONE(0.00)[1]; FROM_NEQ_ENVFROM(0.00)[rgacogne@archlinux.org,pacman-dev-bounces@lists.archlinux.org]; RCVD_TLS_LAST(0.00)[]; NEURAL_HAM(-0.00)[-1.000]; R_DKIM_REJECT(0.00)[archlinux.org:s=dkim-ed25519,archlinux.org:s=dkim-rsa]; TO_DN_NONE(0.00)[]; ASN(0.00)[asn:24940, ipnet:95.217.0.0/16, country:DE]; RCVD_COUNT_THREE(0.00)[3]; DMARC_POLICY_ALLOW(0.00)[archlinux.org,none]; DKIM_TRACE(0.00)[archlinux.org:-]; MIME_TRACE(0.00)[0:+]; FORGED_SENDER_MAILLIST(0.00)[] X-Rspamd-Server: mail.archlinux.org This second version contains only the portable part of the sandboxing, switching to a different user when a new option, 'SandboxUser', is set. To sum up the changes from the last version: - The non-portable parts (preventing new privileges from being gained, dropping capabilities, syscalls filtering and filesystem protection) are gone for now, I'll propose them via smaller patches if this change is accepted. - The value returned by the sub-process is now passed by the exit status only, instead of using a pipe. - The 'UseSandbox' option has been removed. - The alpm_sandbox.{c,h} files have been renamed to sandbox.{c,h}. - A short description of the 'SandboxUser' option has been added to pacman.conf.5. Cheers, Remi Signed-off-by: Remi Gacogne --- doc/pacman.conf.5.asciidoc | 5 ++ lib/libalpm/alpm.h | 5 ++ lib/libalpm/dload.c | 102 ++++++++++++++++++++++++++++++++++++- lib/libalpm/handle.c | 13 +++++ lib/libalpm/handle.h | 1 + lib/libalpm/meson.build | 1 + lib/libalpm/sandbox.c | 63 +++++++++++++++++++++++ lib/libalpm/sandbox.h | 31 +++++++++++ src/pacman/conf.c | 19 ++++++- src/pacman/conf.h | 1 + src/pacman/pacman-conf.c | 3 ++ 11 files changed, 241 insertions(+), 3 deletions(-) create mode 100644 lib/libalpm/sandbox.c create mode 100644 lib/libalpm/sandbox.h diff --git doc/pacman.conf.5.asciidoc doc/pacman.conf.5.asciidoc index 77a3907f..5b20b177 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`. 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 a5f4a6ae..5473558a 100644 --- lib/libalpm/alpm.h +++ lib/libalpm/alpm.h @@ -1837,6 +1837,11 @@ int alpm_option_set_gpgdir(alpm_handle_t *handle, const char *gpgdir); /* End of gpdir accessors */ /** @} */ +/** 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 * diff --git lib/libalpm/dload.c lib/libalpm/dload.c index 4322318b..03e74ae2 100644 --- lib/libalpm/dload.c +++ lib/libalpm/dload.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #ifdef HAVE_NETINET_IN_H @@ -46,6 +47,7 @@ #include "alpm_list.h" #include "alpm.h" #include "log.h" +#include "sandbox.h" #include "util.h" #include "handle.h" @@ -937,6 +939,100 @@ static int curl_download_internal(alpm_handle_t *handle, #endif +/* 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; + sigset_t oldblock; + struct sigaction sa_ign = { .sa_handler = SIG_IGN }, oldint, oldquit; + + 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) { + /* 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_WARNING, _("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_WARNING, _("sandboxing failed!\n")); + } + + 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(1); + } + else { + /* an error happened for a required file */ + _Exit(2); + } + } + + /* parent */ + + if(pid != -1) { + 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 = WIFEXITED(ret); + if(ret != 0 && ret != 1) { + /* an error happened for a required file, or unexpected exit status */ + ret = -1; + } + } + } + else { + /* waitpid failed */ + err = errno; + } + } else { + /* fork failed, make sure errno is preserved after cleanup */ + err = errno; + } + + 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 +1043,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..5c914faa 100644 --- lib/libalpm/handle.c +++ lib/libalpm/handle.c @@ -573,6 +573,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 b1526c67..e0587fc7 100644 --- lib/libalpm/handle.h +++ lib/libalpm/handle.h @@ -90,6 +90,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..3c491176 --- /dev/null +++ lib/libalpm/sandbox.c @@ -0,0 +1,63 @@ +/* + * sandbox.c + * + * Copyright (c) 2021 Pacman Development Team + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include "sandbox.h" + +static int switch_to_user(const char *user) +{ + struct passwd const *pw = NULL; + if(getuid() != 0) { + return 1; + } + pw = getpwnam(user); + if(pw == NULL) { + return errno; + } + if(setgid(pw->pw_gid) != 0) { + return errno; + } + if(setgroups(0, NULL)) { + return errno; + } + if(setuid(pw->pw_uid) != 0) { + return errno; + } + return 0; +} + +/* check exported library symbols with: nm -C -D */ +#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 lib/libalpm/sandbox.h lib/libalpm/sandbox.h new file mode 100644 index 00000000..47611575 --- /dev/null +++ lib/libalpm/sandbox.h @@ -0,0 +1,31 @@ +/* + * sandbox.h + * + * Copyright (c) 2021 Pacman Development Team + * + * 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 . + */ +#ifndef ALPM_SANDBOX_H +#define ALPM_SANDBOX_H + +#ifdef __cplusplus +extern "C" { +#endif + +int alpm_sandbox_child(const char *sandboxuser); + +#ifdef __cplusplus +} +#endif +#endif /* ALPM_SANDBOX_H */ diff --git src/pacman/conf.c src/pacman/conf.c index 12fee64c..15d05e8c 100644 --- src/pacman/conf.c +++ src/pacman/conf.c @@ -33,6 +33,8 @@ #include #include +#include + /* pacman */ #include "conf.h" #include "ini.h" @@ -215,7 +217,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 +244,13 @@ 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_WARNING, _("Switching to sandbox user %s failed!\n"), sandboxuser); + } + } + 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);