/*
Copyright (C) 2011 ABRT Team
Copyright (C) 2011 RedHat inc.
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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "libabrt.h"
#define EXECUTABLE "abrt-action-install-debuginfo"
#define IGNORE_RESULT(func_call) do { if (func_call) /* nothing */; } while (0)
/* A binary wrapper is needed around python scripts if we want
* to run them in sgid/suid mode.
*
* This is such a wrapper.
*/
int main(int argc, char **argv)
{
/* I18n */
setlocale(LC_ALL, "");
#if ENABLE_NLS
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
#endif
abrt_init(argv);
/* Can't keep these strings/structs static: _() doesn't support that */
const char *program_usage_string = _(
"& [-y] [-i BUILD_IDS_FILE|-i -] [-e PATH[:PATH]...]\n"
"\t[-r REPO]\n"
"\n"
"Installs debuginfo packages for all build-ids listed in BUILD_IDS_FILE to\n"
"ABRT system cache."
);
enum {
OPT_v = 1 << 0,
OPT_y = 1 << 1,
OPT_i = 1 << 2,
OPT_e = 1 << 3,
OPT_r = 1 << 4,
OPT_s = 1 << 5,
OPT_R = 1 << 6,
};
const char *build_ids = "build_ids";
const char *exact = NULL;
const char *repo = NULL;
const char *size_mb = NULL;
const char *releasever = NULL;
struct options program_options[] = {
OPT__VERBOSE(&g_verbose),
OPT_BOOL ('y', "yes", NULL, _("Noninteractive, assume 'Yes' to all questions")),
OPT_STRING('i', "ids", &build_ids, "BUILD_IDS_FILE", _("- means STDIN, default: build_ids")),
OPT_STRING('e', "exact", &exact, "EXACT", _("Download only specified files")),
OPT_STRING('r', "repo", &repo, "REPO", _("Pattern to use when searching for repos, default: *debug*")),
OPT_STRING('s', "size_mb", &size_mb, "SIZE_MB", _("Ignored option")),
OPT_STRING('R', "releasever", &releasever, "RELEASEVER", _("OS release version")),
OPT_END()
};
const unsigned opts = parse_opts(argc, argv, program_options, program_usage_string);
const gid_t egid = getegid();
const gid_t rgid = getgid();
const uid_t euid = geteuid();
/* We need to open the build ids file under the caller's UID/GID to avoid
* information disclosures when reading files with changed UID.
* Unfortunately, we cannot replace STDIN with the new fd because ABRT uses
* STDIN to communicate with the caller. So, the following code opens a
* dummy file descriptor to the build ids file and passes the new fd's proc
* path to the wrapped program in the ids argument.
* The new fd remains opened, the OS will close it for us. */
char *build_ids_self_fd = NULL;
if (strcmp("-", build_ids) != 0)
{
if (setregid(egid, rgid) < 0)
perror_msg_and_die("setregid(egid, rgid)");
const int build_ids_fd = open(build_ids, O_RDONLY);
if (setregid(rgid, egid) < 0)
perror_msg_and_die("setregid(rgid, egid)");
if (build_ids_fd < 0)
perror_msg_and_die("Failed to open file '%s'", build_ids);
/* We are not going to free this memory. There is no place to do so. */
build_ids_self_fd = xasprintf("/proc/self/fd/%d", build_ids_fd);
}
char tmp_directory[] = LARGE_DATA_TMP_DIR"/abrt-tmp-debuginfo.XXXXXX";
if (mkdtemp(tmp_directory) == NULL)
perror_msg_and_die("Failed to create working directory");
log_info("Created working directory: %s", tmp_directory);
/* name, -v, --ids, -, -y, -e, EXACT, -r, REPO, -t, PATH, --releaseve, VER, --, NULL */
const char *args[15];
{
const char *verbs[] = { "", "-v", "-vv", "-vvv" };
unsigned i = 0;
args[i++] = EXECUTABLE;
args[i++] = "--ids";
args[i++] = (build_ids_self_fd != NULL) ? build_ids_self_fd : "-";
if (g_verbose > 0)
args[i++] = verbs[g_verbose <= 3 ? g_verbose : 3];
if ((opts & OPT_y))
args[i++] = "-y";
if ((opts & OPT_e))
{
args[i++] = "--exact";
args[i++] = exact;
}
if ((opts & OPT_r))
{
args[i++] = "--repo";
args[i++] = repo;
}
if ((opts & OPT_R))
{
args[i++] = "--releasever";
args[i++] = releasever;
}
args[i++] = "--tmpdir";
args[i++] = tmp_directory;
args[i++] = "--";
args[i] = NULL;
}
/* Switch real user/group to effective ones.
* Otherwise yum library gets confused - gets EPERM (why??).
*/
/* do setregid only if we have to, to not upset selinux needlessly */
if (egid != rgid)
{
IGNORE_RESULT(setregid(egid, egid));
/* We are sgid'ed! */
/* Prevent malicious user from messing up with sgid'ed process: */
#if 1
// We forgot to sanitize PYTHONPATH. And who knows what else we forgot
// (especially considering *future* new variables of this kind).
// We switched to clearing entire environment instead:
// However since we communicate through environment variables
// we have to keep a whitelist of variables to keep.
static const char *whitelist[] = {
"REPORT_CLIENT_SLAVE", // Check if the app is being run as a slave
"LANG",
};
const size_t wlsize = sizeof(whitelist)/sizeof(char*);
char *setlist[sizeof(whitelist)/sizeof(char*)] = { 0 };
char *p = NULL;
for (size_t i = 0; i < wlsize; i++)
if ((p = getenv(whitelist[i])) != NULL)
setlist[i] = xstrdup(p);
// Now we can clear the environment
clearenv();
// And once again set whitelisted variables
for (size_t i = 0; i < wlsize; i++)
if (setlist[i] != NULL)
{
xsetenv(whitelist[i], setlist[i]);
free(setlist[i]);
}
#else
/* Clear dangerous stuff from env */
static const char forbid[] =
"LD_LIBRARY_PATH" "\0"
"LD_PRELOAD" "\0"
"LD_TRACE_LOADED_OBJECTS" "\0"
"LD_BIND_NOW" "\0"
"LD_AOUT_LIBRARY_PATH" "\0"
"LD_AOUT_PRELOAD" "\0"
"LD_NOWARN" "\0"
"LD_KEEPDIR" "\0"
;
const char *p = forbid;
do {
unsetenv(p);
p += strlen(p) + 1;
} while (*p);
#endif
/* Set safe PATH */
// Adding configure --bindir and --sbindir to the PATH so that
// abrt-action-install-debuginfo doesn't fail when spawning
// abrt-action-trim-files
char path_env[] = "PATH=/usr/sbin:/sbin:/usr/bin:/bin:"BIN_DIR":"SBIN_DIR;
if (euid != 0)
strcpy(path_env, "PATH=/usr/bin:/bin:"BIN_DIR);
putenv(path_env);
/* Use safe umask */
umask(0022);
}
pid_t pid = fork();
if (pid < 0)
perror_msg_and_die("fork");
if (pid == 0)
{
execvp(EXECUTABLE, (char **)args);
error_msg_and_die("Can't execute %s", EXECUTABLE);
}
int status;
if (safe_waitpid(pid, &status, 0) < 0)
perror_msg_and_die("waitpid");
if (rmdir(tmp_directory) >= 0)
log_info("Removed working directory: %s", tmp_directory);
else if (errno != ENOENT)
perror_msg("Failed to remove working directory");
/* Normal execution should exit here. */
if (WIFEXITED(status))
return WEXITSTATUS(status);
if (WIFSIGNALED(status))
error_msg_and_die("Child terminated with signal %d", WTERMSIG(status));
error_msg_and_die("Child exit failed");
}