Compare commits

...

7 Commits

Author SHA1 Message Date
morganamilo
14b9ae7657 don't chown downloads if no download user
Always chowning the files to root causes non root alpm based tools to be
unable to download files.
2024-08-28 17:38:55 +01:00
morganamilo
d1b66cf4be log and set errno when failing to rename download 2024-08-27 23:09:59 +01:00
KaranveerB
6ba5c20e76 pactest: add test for empty replacements strings 2024-08-03 16:29:26 -07:00
KaranveerB
4c18204938 pacman/util.c: fix segfault when replace in strreplace is NULL 2024-08-03 16:29:11 -07:00
Remi Gacogne
cf473bcfbd Ensure that the download process cannot get new privileges
Signed-off-by: Remi Gacogne <rgacogne@archlinux.org>
2024-08-02 00:39:45 +00:00
Remi Gacogne
f142df92c7 Restrict syscalls for the download process whenever possible
Signed-off-by: Remi Gacogne <rgacogne@archlinux.org>
2024-08-02 00:39:45 +00:00
Allan McRae
c3aa1bc123 Fix typo in git source handling
Fixes #171

Signed-off-by: Allan McRae <allan@archlinux.org>
2024-07-16 18:51:03 +10:00
12 changed files with 246 additions and 15 deletions

View File

@@ -34,6 +34,7 @@
extern "C" {
#endif
#include <stdbool.h> /* bool */
#include <stdint.h> /* int64_t */
#include <sys/types.h> /* off_t */
#include <stdarg.h> /* va_list */
@@ -2971,9 +2972,10 @@ int alpm_capabilities(void);
* @param handle the context handle
* @param sandboxuser the user to switch to
* @param sandbox_path if non-NULL, restrict writes to this filesystem path
* @param restrict_syscalls whether to deny access to a list of dangerous syscalls
* @return 0 on success, -1 on failure
*/
int alpm_sandbox_setup_child(alpm_handle_t *handle, const char *sandboxuser, const char *sandbox_path);
int alpm_sandbox_setup_child(alpm_handle_t *handle, const char *sandboxuser, const char *sandbox_path, bool restrict_syscalls);
/* End of libalpm_misc */
/** @} */

View File

@@ -73,7 +73,7 @@ static mode_t _getumask(void)
return mask;
}
static int finalize_download_file(const char *filename)
static int finalize_download_file(const char *sandboxuser, const char *filename)
{
struct stat st;
ASSERT(filename != NULL, return -1);
@@ -82,7 +82,9 @@ static int finalize_download_file(const char *filename)
unlink(filename);
return 1;
}
ASSERT(chown(filename, 0, 0) != -1, return -1);
if(sandboxuser) {
ASSERT(chown(filename, 0, 0) != -1, return -1);
}
ASSERT(chmod(filename, ~(_getumask()) & 0666) != -1, return -1);
return 0;
}
@@ -963,7 +965,7 @@ static int curl_download_internal_sandboxed(alpm_handle_t *handle,
_alpm_log(handle, ALPM_LOG_ERROR, _("could not chdir to download directory %s\n"), localpath);
ret = -1;
} else {
ret = alpm_sandbox_setup_child(handle, handle->sandboxuser, localpath);
ret = alpm_sandbox_setup_child(handle, handle->sandboxuser, localpath, true);
if (ret != 0) {
_alpm_log(handle, ALPM_LOG_ERROR, _("switching to sandbox user '%s' failed!\n"), handle->sandboxuser);
_Exit(2);
@@ -1079,11 +1081,11 @@ static int payload_download_fetchcb(struct dload_payload *payload,
return ret;
}
static int move_file(const char *filepath, const char *directory)
static int move_file(const char* sandboxuser, const char *filepath, const char *directory)
{
ASSERT(filepath != NULL, return -1);
ASSERT(directory != NULL, return -1);
int ret = finalize_download_file(filepath);
int ret = finalize_download_file(sandboxuser, filepath);
if(ret != 0) {
return ret;
}
@@ -1105,15 +1107,18 @@ static int finalize_download_locations(alpm_list_t *payloads, const char *localp
int returnvalue = 0;
for(p = payloads; p; p = p->next) {
struct dload_payload *payload = p->data;
alpm_handle_t *handle = payload->handle;
if(payload->tempfile_name) {
move_file(payload->tempfile_name, localpath);
move_file(handle->sandboxuser, payload->tempfile_name, localpath);
}
if(payload->destfile_name) {
int ret = move_file(payload->destfile_name, localpath);
int ret = move_file(handle->sandboxuser, payload->destfile_name, localpath);
if(ret == -1) {
/* ignore error if the file already existed - only signature file was downloaded */
if(payload->mtime_existing_file == 0) {
_alpm_log(handle, ALPM_LOG_ERROR, _("could not move %s into %s (%s)\n"),
payload->destfile_name, localpath, strerror(errno));
returnvalue = -1;
}
}
@@ -1124,7 +1129,7 @@ static int finalize_download_locations(alpm_list_t *payloads, const char *localp
size_t sig_filename_len = strlen(payload->destfile_name) + sizeof(sig_suffix);
MALLOC(sig_filename, sig_filename_len, continue);
snprintf(sig_filename, sig_filename_len, "%s%s", payload->destfile_name, sig_suffix);
move_file(sig_filename, localpath);
move_file(handle->sandboxuser, sig_filename, localpath);
FREE(sig_filename);
}
}
@@ -1265,7 +1270,7 @@ download_signature:
}
if (finalize_download_locations(payloads, localpath) != 0 && ret == 0) {
return -1;
RET_ERR(handle, ALPM_ERR_RETRIEVE, -1);
}
return ret;
}

View File

@@ -26,6 +26,7 @@ libalpm_sources = files('''
remove.h remove.c
sandbox.h sandbox.c
sandbox_fs.h sandbox_fs.c
sandbox_syscalls.h sandbox_syscalls.c
signing.c signing.h
sync.h sync.c
trans.h trans.c

View File

@@ -17,9 +17,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#ifdef HAVE_SYS_PRCTL_H
#include <sys/prctl.h>
#endif /* HAVE_SYS_PRCTL_H */
#include <sys/types.h>
#include <unistd.h>
@@ -27,9 +32,10 @@
#include "log.h"
#include "sandbox.h"
#include "sandbox_fs.h"
#include "sandbox_syscalls.h"
#include "util.h"
int SYMEXPORT alpm_sandbox_setup_child(alpm_handle_t *handle, const char* sandboxuser, const char* sandbox_path)
int SYMEXPORT alpm_sandbox_setup_child(alpm_handle_t *handle, const char* sandboxuser, const char* sandbox_path, bool restrict_syscalls)
{
struct passwd const *pw = NULL;
@@ -39,6 +45,13 @@ int SYMEXPORT alpm_sandbox_setup_child(alpm_handle_t *handle, const char* sandbo
if(sandbox_path != NULL && !handle->disable_sandbox) {
_alpm_sandbox_fs_restrict_writes_to(handle, sandbox_path);
}
#if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_NO_NEW_PRIVS)
/* make sure that we cannot gain more privileges later, failure is fine */
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
#endif /* HAVE_SYS_PRCTL && PR_SET_NO_NEW_PRIVS */
if(restrict_syscalls && !handle->disable_sandbox) {
_alpm_sandbox_syscalls_filter(handle);
}
ASSERT(setgid(pw->pw_gid) == 0, return -1);
ASSERT(setgroups(0, NULL) == 0, return -1);
ASSERT(setuid(pw->pw_uid) == 0, return -1);

View File

@@ -0,0 +1,165 @@
/*
* sandbox_syscalls.c
*
* Copyright (c) 2021-2022 Pacman Development Team <pacman-dev@lists.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 <stddef.h>
#include "config.h"
#include "log.h"
#include "sandbox_syscalls.h"
#include "util.h"
#ifdef HAVE_LIBSECCOMP
# include <seccomp.h>
#endif /* HAVE_LIBSECCOMP */
bool _alpm_sandbox_syscalls_filter(alpm_handle_t *handle)
{
int ret = 0;
#ifdef HAVE_LIBSECCOMP
/* see https://docs.docker.com/engine/security/seccomp/ for inspiration,
as well as systemd's src/shared/seccomp-util.c */
const char *denied_syscalls[] = {
/* kernel modules */
"delete_module",
"finit_module",
"init_module",
/* mount */
"chroot",
"fsconfig",
"fsmount",
"fsopen",
"fspick",
"mount",
"mount_setattr",
"move_mount",
"open_tree",
"pivot_root",
"umount",
"umount2",
/* keyring */
"add_key",
"keyctl",
"request_key",
/* CPU emulation */
"modify_ldt",
"subpage_prot",
"switch_endian",
"vm86",
"vm86old",
/* debug */
"kcmp",
"lookup_dcookie",
"perf_event_open",
"pidfd_getfd",
"ptrace",
"rtas",
"sys_debug_setcontext",
/* set clock */
"adjtimex",
"clock_adjtime",
"clock_adjtime64",
"clock_settime",
"clock_settime64",
"settimeofday",
/* raw IO */
"ioperm",
"iopl",
"pciconfig_iobase",
"pciconfig_read",
"pciconfig_write",
/* kexec */
"kexec_file_load",
"kexec_load",
/* reboot */
"reboot",
/* privileged */
"acct",
"bpf",
"capset",
"chroot",
"fanotify_init",
"fanotify_mark",
"nfsservctl",
"open_by_handle_at",
"pivot_root",
"personality",
/* obsolete */
"_sysctl",
"afs_syscall",
"bdflush",
"break",
"create_module",
"ftime",
"get_kernel_syms",
"getpmsg",
"gtty",
"idle",
"lock",
"mpx",
"prof",
"profil",
"putpmsg",
"query_module",
"security",
"sgetmask",
"ssetmask",
"stime",
"stty",
"sysfs",
"tuxcall",
"ulimit",
"uselib",
"ustat",
"vserver",
/* swap */
"swapon",
"swapoff",
};
/* allow all syscalls that are not listed */
size_t idx;
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW);
int restrictedSyscallsCount = 0;
if(ctx == NULL) {
return false;
}
for(idx = 0; idx < sizeof(denied_syscalls) / sizeof(*denied_syscalls); idx++) {
int syscall = seccomp_syscall_resolve_name(denied_syscalls[idx]);
if(syscall != __NR_SCMP_ERROR) {
if(seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), syscall, 0) != 0) {
_alpm_log(handle, ALPM_LOG_ERROR, _("error restricting syscall %s via seccomp!\n"), denied_syscalls[idx]);
}
else {
restrictedSyscallsCount++;
}
}
}
if(seccomp_load(ctx) != 0) {
ret = errno;
_alpm_log(handle, ALPM_LOG_ERROR, _("error restricting syscalls via seccomp: %d!\n"), ret);
}
else {
_alpm_log(handle, ALPM_LOG_DEBUG, _("successfully restricted %d syscalls via seccomp\n"), restrictedSyscallsCount);
}
seccomp_release(ctx);
#endif /* HAVE_LIBSECCOMP */
return ret == 0;
}

View File

@@ -0,0 +1,26 @@
/*
* sandbox_syscalls.h
*
* Copyright (c) 2021-2022 Pacman Development Team <pacman-dev@lists.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/>.
*/
#ifndef ALPM_SANDBOX_SYSCALLS_H
#define ALPM_SANDBOX_SYSCALLS_H
#include <stdbool.h>
bool _alpm_sandbox_syscalls_filter(alpm_handle_t *handle);
#endif /* ALPM_SANDBOX_SYSCALLS_H */

View File

@@ -122,12 +122,17 @@ else
error('unhandled crypto value @0@'.format(want_crypto))
endif
libseccomp = dependency('libseccomp',
static : get_option('buildstatic'),
required : false)
conf.set('HAVE_LIBSECCOMP', libseccomp.found())
foreach header : [
'linux/landlock.h',
'mntent.h',
'sys/mnttab.h',
'sys/mount.h',
'sys/param.h',
'sys/prctl.h',
'sys/statvfs.h',
'sys/types.h',
'sys/ucred.h',
@@ -327,7 +332,7 @@ libcommon = static_library(
gnu_symbol_visibility : 'hidden',
install : false)
alpm_deps = [crypto_provider, libarchive, libcurl, libintl, gpgme]
alpm_deps = [crypto_provider, libarchive, libcurl, libintl, libseccomp, gpgme]
libalpm_a = static_library(
'alpm_objlib',

View File

@@ -49,7 +49,7 @@ download_git() {
if [[ ! -d "$dir" ]] || dir_is_empty "$dir" ; then
msg2 "$(gettext "Cloning %s %s repo...")" "${repo}" "git"
if ! git clone --origin=origin ---mirror "$url" "$dir"; then
if ! git clone --origin=origin --mirror "$url" "$dir"; then
error "$(gettext "Failure while downloading %s %s repo")" "${repo}" "git"
plainerr "$(gettext "Aborting...")"
exit 1

View File

@@ -246,7 +246,7 @@ static int systemvp(const char *file, char *const argv[])
sigprocmask(SIG_SETMASK, &oldblock, NULL);
if (config->sandboxuser) {
ret = alpm_sandbox_setup_child(config->handle, config->sandboxuser, NULL);
ret = alpm_sandbox_setup_child(config->handle, config->sandboxuser, NULL, false);
if (ret != 0) {
pm_printf(ALPM_LOG_ERROR, _("switching to sandbox user '%s' failed!\n"), config->sandboxuser);
_Exit(ret);

View File

@@ -363,12 +363,16 @@ char *strreplace(const char *str, const char *needle, const char *replace)
const char *p = NULL, *q = NULL;
char *newstr = NULL, *newp = NULL;
alpm_list_t *i = NULL, *list = NULL;
size_t needlesz = strlen(needle), replacesz = strlen(replace);
size_t needlesz = strlen(needle), replacesz;
size_t newsz;
if(!str) {
return NULL;
}
if(!replace) {
replace = "";
}
replacesz = strlen(replace);
p = str;
q = strstr(p, needle);

View File

@@ -127,6 +127,7 @@ pacman_tests = [
'tests/remove-assumeinstalled.py',
'tests/remove-directory-replaced-with-symlink.py',
'tests/remove-optdepend-of-installed-package.py',
'tests/remove-print-empty-replacements.py',
'tests/remove-recursive-cycle.py',
'tests/remove001.py',
'tests/remove002.py',

View File

@@ -0,0 +1,9 @@
self.description = "Print empty replacements when using -Rp"
p = pmpkg("a")
self.addpkg2db("local", p)
self.args = "-Rp --print-format 'foo%%f%%g%%h%%m' %s" % p.name
self.addrule("PACMAN_RETCODE=0")
self.addrule("PACMAN_OUTPUT=^foo$")