run XferCommand via exec

system() runs the provided command via a shell, which is subject to
command injection.  Even though pacman already provides a mechanism to
sign and verify the databases containing the urls, certain distributions
have yet to get their act together and start signing databases, leaving
them vulnerable to MITM attacks.  Replacing the system call with an
almost equivalent exec call removes the possibility of a shell-injection
attack for those users.

Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com>
This commit is contained in:
Andrew Gregory 2019-06-09 09:56:36 -07:00
parent a82b0028e4
commit 808a4f15ce
4 changed files with 117 additions and 19 deletions

View file

@ -29,6 +29,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/utsname.h> /* uname */ #include <sys/utsname.h> /* uname */
#include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
/* pacman */ /* pacman */
@ -152,6 +153,7 @@ int config_free(config_t *oldconfig)
free(oldconfig->xfercommand); free(oldconfig->xfercommand);
free(oldconfig->print_format); free(oldconfig->print_format);
free(oldconfig->arch); free(oldconfig->arch);
wordsplit_free(oldconfig->xfercommand_argv);
free(oldconfig); free(oldconfig);
return 0; return 0;
@ -201,18 +203,86 @@ static char *get_tempfile(const char *path, const char *filename)
return tempfile; return tempfile;
} }
/* system()/exec() hybrid function allowing exec()-style direct execution
* of a command with the simplicity of system()
* - not thread-safe
* - errno may be set by fork(), pipe(), or execvp()
*/
static int systemvp(const char *file, char *const argv[])
{
int pid, err = 0, ret = -1, err_fd[2];
sigset_t oldblock;
struct sigaction sa_ign = { .sa_handler = SIG_IGN }, oldint, oldquit;
if(pipe(err_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(err_fd[0]);
fcntl(err_fd[1], F_SETFD, FD_CLOEXEC);
/* restore signal handling for the child to inherit */
sigaction(SIGINT, &oldint, NULL);
sigaction(SIGQUIT, &oldquit, NULL);
sigprocmask(SIG_SETMASK, &oldblock, NULL);
execvp(file, argv);
/* execvp failed, pass the error back to the parent */
while(write(err_fd[1], &errno, sizeof(errno)) == -1 && errno == EINTR);
_Exit(127);
}
/* parent */
close(err_fd[1]);
if(pid != -1) {
int wret;
while((wret = waitpid(pid, &ret, 0)) == -1 && errno == EINTR);
if(wret > 0) {
while(read(err_fd[0], &err, sizeof(err)) == -1 && errno == EINTR);
}
} else {
/* fork failed, make sure errno is preserved after cleanup */
err = errno;
}
close(err_fd[0]);
sigaction(SIGINT, &oldint, NULL);
sigaction(SIGQUIT, &oldquit, NULL);
sigprocmask(SIG_SETMASK, &oldblock, NULL);
if(err) {
errno = err;
ret = -1;
}
return ret;
}
/** External fetch callback */ /** External fetch callback */
static int download_with_xfercommand(const char *url, const char *localpath, static int download_with_xfercommand(const char *url, const char *localpath,
int force) int force)
{ {
int ret = 0, retval; int ret = 0, retval;
int usepart = 0; int usepart = 0;
int cwdfd; int cwdfd = -1;
struct stat st; struct stat st;
char *parsedcmd, *tempcmd;
char *destfile, *tempfile, *filename; char *destfile, *tempfile, *filename;
const char **argv;
size_t i;
if(!config->xfercommand) { if(!config->xfercommand_argv) {
return -1; return -1;
} }
@ -230,17 +300,26 @@ static int download_with_xfercommand(const char *url, const char *localpath,
unlink(destfile); unlink(destfile);
} }
tempcmd = strdup(config->xfercommand); if((argv = calloc(config->xfercommand_argc + 1, sizeof(char*))) == NULL) {
/* replace all occurrences of %o with fn.part */ size_t bytes = (config->xfercommand_argc + 1) * sizeof(char*);
if(strstr(tempcmd, "%o")) { pm_printf(ALPM_LOG_ERROR,
usepart = 1; _n("malloc failure: could not allocate %zu byte\n",
parsedcmd = strreplace(tempcmd, "%o", tempfile); "malloc failure: could not allocate %zu bytes\n",
free(tempcmd); bytes),
tempcmd = parsedcmd; bytes);
goto cleanup;
}
for(i = 0; i <= config->xfercommand_argc; i++) {
const char *val = config->xfercommand_argv[i];
if(val && strcmp(val, "%o") == 0) {
usepart = 1;
val = tempfile;
} else if(val && strcmp(val, "%u") == 0) {
val = url;
}
argv[i] = val;
} }
/* replace all occurrences of %u with the download URL */
parsedcmd = strreplace(tempcmd, "%u", url);
free(tempcmd);
/* save the cwd so we can restore it later */ /* save the cwd so we can restore it later */
do { do {
@ -256,9 +335,15 @@ static int download_with_xfercommand(const char *url, const char *localpath,
ret = -1; ret = -1;
goto cleanup; goto cleanup;
} }
/* execute the parsed command via /bin/sh -c */
pm_printf(ALPM_LOG_DEBUG, "running command: %s\n", parsedcmd); if(config->logmask & ALPM_LOG_DEBUG) {
retval = system(parsedcmd); char *cmd = arg_to_string(config->xfercommand_argc, (char**)argv);
if(cmd) {
pm_printf(ALPM_LOG_DEBUG, "running command: %s\n", cmd);
free(cmd);
}
}
retval = systemvp(argv[0], (char**)argv);
if(retval == -1) { if(retval == -1) {
pm_printf(ALPM_LOG_WARNING, _("running XferCommand: fork failed!\n")); pm_printf(ALPM_LOG_WARNING, _("running XferCommand: fork failed!\n"));
@ -296,7 +381,7 @@ cleanup:
} }
free(destfile); free(destfile);
free(tempfile); free(tempfile);
free(parsedcmd); free(argv);
return ret; return ret;
} }
@ -544,6 +629,17 @@ static int _parse_options(const char *key, char *value,
pm_printf(ALPM_LOG_DEBUG, "config: logfile: %s\n", value); pm_printf(ALPM_LOG_DEBUG, "config: logfile: %s\n", value);
} }
} else if(strcmp(key, "XferCommand") == 0) { } else if(strcmp(key, "XferCommand") == 0) {
char **c;
if((config->xfercommand_argv = wordsplit(value)) == NULL) {
pm_printf(ALPM_LOG_WARNING,
_("config file %s, line %d: invalid value for '%s' : '%s'\n"),
file, linenum, "XferCommand", value);
return 1;
}
config->xfercommand_argc = 0;
for(c = config->xfercommand_argv; *c; c++) {
config->xfercommand_argc++;
}
config->xfercommand = strdup(value); config->xfercommand = strdup(value);
pm_printf(ALPM_LOG_DEBUG, "config: xfercommand: %s\n", value); pm_printf(ALPM_LOG_DEBUG, "config: xfercommand: %s\n", value);
} else if(strcmp(key, "CleanMethod") == 0) { } else if(strcmp(key, "CleanMethod") == 0) {

View file

@ -125,6 +125,8 @@ typedef struct __config_t {
alpm_list_t *noextract; alpm_list_t *noextract;
alpm_list_t *overwrite_files; alpm_list_t *overwrite_files;
char *xfercommand; char *xfercommand;
char **xfercommand_argv;
size_t xfercommand_argc;
/* our connection to libalpm */ /* our connection to libalpm */
alpm_handle_t *handle; alpm_handle_t *handle;

View file

@ -1,6 +1,6 @@
self.description = "Synchronize the local database" self.description = "Synchronize the local database"
self.option['XferCommand'] = ['/usr/bin/curl %u > %o'] self.option['XferCommand'] = ['/usr/bin/curl %u -o %o']
sp1 = pmpkg("spkg1", "1.0-1") sp1 = pmpkg("spkg1", "1.0-1")
sp1.depends = ["spkg2"] sp1.depends = ["spkg2"]

View file

@ -3,7 +3,7 @@ self.description = "Quick check for using XferCommand"
# this setting forces us to download packages # this setting forces us to download packages
self.cachepkgs = False self.cachepkgs = False
#wget doesn't support file:// urls. curl does #wget doesn't support file:// urls. curl does
self.option['XferCommand'] = ['/usr/bin/curl %u > %o'] self.option['XferCommand'] = ['/usr/bin/curl %u -o %o']
numpkgs = 10 numpkgs = 10
pkgnames = [] pkgnames = []