/* Copyright (C) 2009 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 #include "internal_libabrt.h" int low_free_space(unsigned setting_MaxCrashReportsSize, const char *dump_location) { struct statvfs vfs; if (statvfs(dump_location, &vfs) != 0) { perror_msg("statvfs('%s')", dump_location); return 0; } /* Check that at least MaxCrashReportsSize/4 MBs are free */ /* fs_free_mb_x4 ~= vfs.f_bfree * vfs.f_bsize * 4, expressed in MBytes. * Need to neither overflow nor round f_bfree down too much. */ unsigned long fs_free_mb_x4 = ((unsigned long long)vfs.f_bfree / (1024/4)) * vfs.f_bsize / 1024; if (fs_free_mb_x4 < setting_MaxCrashReportsSize) { error_msg("Only %luMiB is available on %s", fs_free_mb_x4 / 4, dump_location); return 1; } return 0; } /* rhbz#539551: "abrt going crazy when crashing process is respawned". * Check total size of problem dirs, if it overflows, * delete oldest/biggest dirs. */ void trim_problem_dirs(const char *dirname, double cap_size, const char *exclude_path) { const char *excluded_basename = NULL; if (exclude_path) { unsigned len_dirname = strlen(dirname); /* Trim trailing '/'s, but dont trim name "/" to "" */ while (len_dirname > 1 && dirname[len_dirname-1] == '/') len_dirname--; if (strncmp(dirname, exclude_path, len_dirname) == 0 && exclude_path[len_dirname] == '/' ) { /* exclude_path is "dirname/something" */ excluded_basename = exclude_path + len_dirname + 1; } } log_debug("excluded_basename:'%s'", excluded_basename); int count = 20; while (--count >= 0) { /* We exclude our own dir from candidates for deletion (3rd param): */ char *worst_basename = NULL; double cur_size = get_dirsize_find_largest_dir(dirname, &worst_basename, excluded_basename); if (cur_size <= cap_size || !worst_basename) { log_info("cur_size:%.0f cap_size:%.0f, no (more) trimming", cur_size, cap_size); free(worst_basename); break; } log_warning("%s is %.0f bytes (more than %.0fMiB), deleting '%s'", dirname, cur_size, cap_size / (1024*1024), worst_basename); char *d = concat_path_file(dirname, worst_basename); free(worst_basename); delete_dump_dir(d); free(d); } } /** * * @param[out] status See `man 2 wait` for status information. * @return Malloc'ed string */ static char* exec_vp(char **args, int redirect_stderr, int exec_timeout_sec, int *status) { /* Nuke everything which may make setlocale() switch to non-POSIX locale: * we need to avoid having gdb output in some obscure language. */ static const char *const env_vec[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", /* Workaround for * http://sourceware.org/bugzilla/show_bug.cgi?id=9622 * (gdb emitting ESC sequences even with -batch) */ "TERM", NULL }; int flags = EXECFLG_INPUT_NUL | EXECFLG_OUTPUT | EXECFLG_SETSID | EXECFLG_QUIET; if (redirect_stderr) flags |= EXECFLG_ERR2OUT; VERB1 flags &= ~EXECFLG_QUIET; int pipeout[2]; pid_t child = fork_execv_on_steroids(flags, args, pipeout, (char**)env_vec, /*dir:*/ NULL, /*uid(unused):*/ 0); /* We use this function to run gdb and unstrip. Bugs in gdb or corrupted * coredumps were observed to cause gdb to enter infinite loop. * Therefore we have a (largish) timeout, after which we kill the child. */ ndelay_on(pipeout[0]); int t = time(NULL); /* int is enough, no need to use time_t */ int endtime = t + exec_timeout_sec; struct strbuf *buf_out = strbuf_new(); while (1) { int timeout = endtime - t; if (timeout < 0) { kill(child, SIGKILL); strbuf_append_strf(buf_out, "\n" "Timeout exceeded: %u seconds, killing %s.\n" "Looks like gdb hung while generating backtrace.\n" "This may be a bug in gdb. Consider submitting a bug report to gdb developers.\n" "Please attach coredump from this crash to the bug report if you do.\n", exec_timeout_sec, args[0] ); break; } /* We don't check poll result - checking read result is enough */ struct pollfd pfd; pfd.fd = pipeout[0]; pfd.events = POLLIN; poll(&pfd, 1, timeout * 1000); char buff[1024]; int r = read(pipeout[0], buff, sizeof(buff) - 1); if (r <= 0) { /* I did see EAGAIN happening here */ if (r < 0 && errno == EAGAIN) goto next; break; } buff[r] = '\0'; strbuf_append_str(buf_out, buff); next: t = time(NULL); } close(pipeout[0]); /* Prevent having zombie child process, and maybe collect status * (note that status == NULL is ok too) */ safe_waitpid(child, status, 0); return strbuf_free_nobuf(buf_out); } char *run_unstrip_n(const char *dump_dir_name, unsigned timeout_sec) { int flags = EXECFLG_INPUT_NUL | EXECFLG_OUTPUT | EXECFLG_SETSID | EXECFLG_QUIET; VERB1 flags &= ~EXECFLG_QUIET; int pipeout[2]; char* args[4]; args[0] = (char*)"eu-unstrip"; args[1] = xasprintf("--core=%s/"FILENAME_COREDUMP, dump_dir_name); args[2] = (char*)"-n"; args[3] = NULL; pid_t child = fork_execv_on_steroids(flags, args, pipeout, /*env_vec:*/ NULL, /*dir:*/ NULL, /*uid(unused):*/ 0); free(args[1]); /* Bugs in unstrip or corrupted coredumps can cause it to enter infinite loop. * Therefore we have a (largish) timeout, after which we kill the child. */ ndelay_on(pipeout[0]); int t = time(NULL); /* int is enough, no need to use time_t */ int endtime = t + timeout_sec; struct strbuf *buf_out = strbuf_new(); while (1) { int timeout = endtime - t; if (timeout < 0) { kill(child, SIGKILL); strbuf_free(buf_out); buf_out = NULL; break; } /* We don't check poll result - checking read result is enough */ struct pollfd pfd; pfd.fd = pipeout[0]; pfd.events = POLLIN; poll(&pfd, 1, timeout * 1000); char buff[1024]; int r = read(pipeout[0], buff, sizeof(buff) - 1); if (r <= 0) { /* I did see EAGAIN happening here */ if (r < 0 && errno == EAGAIN) goto next; break; } buff[r] = '\0'; strbuf_append_str(buf_out, buff); next: t = time(NULL); } close(pipeout[0]); /* Prevent having zombie child process */ int status; safe_waitpid(child, &status, 0); if (status != 0 || buf_out == NULL) { /* unstrip didnt exit with exit code 0, or we timed out */ strbuf_free(buf_out); return NULL; } return strbuf_free_nobuf(buf_out); } char *get_backtrace(const char *dump_dir_name, unsigned timeout_sec, const char *debuginfo_dirs) { INITIALIZE_LIBABRT(); struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return NULL; char *executable = NULL; if (dd_exist(dd, FILENAME_BINARY)) executable = concat_path_file(dd->dd_dirname, FILENAME_BINARY); else executable = dd_load_text(dd, FILENAME_EXECUTABLE); dd_close(dd); /* Let user know what's going on */ log_warning(_("Generating backtrace")); unsigned i = 0; char *args[25]; args[i++] = (char*)GDB; args[i++] = (char*)"-batch"; struct strbuf *set_debug_file_directory = strbuf_new(); unsigned auto_load_base_index = 0; if(debuginfo_dirs == NULL) { // set non-existent debug file directory to prevent resolving // function names - we need offsets for core backtrace. strbuf_append_str(set_debug_file_directory, "set debug-file-directory /"); } else { strbuf_append_str(set_debug_file_directory, "set debug-file-directory /usr/lib/debug"); struct strbuf *debug_directories = strbuf_new(); const char *p = debuginfo_dirs; while (1) { while (*p == ':') p++; if (*p == '\0') break; const char *colon_or_nul = strchrnul(p, ':'); strbuf_append_strf(debug_directories, "%s%.*s/usr/lib/debug", (debug_directories->len == 0 ? "" : ":"), (int)(colon_or_nul - p), p); p = colon_or_nul; } strbuf_append_strf(set_debug_file_directory, ":%s", debug_directories->buf); args[i++] = (char*)"-iex"; auto_load_base_index = i; args[i++] = xasprintf("add-auto-load-safe-path %s", debug_directories->buf); args[i++] = (char*)"-iex"; args[i++] = xasprintf("add-auto-load-scripts-directory %s", debug_directories->buf); strbuf_free(debug_directories); } args[i++] = (char*)"-ex"; const unsigned debug_dir_cmd_index = i++; args[debug_dir_cmd_index] = strbuf_free_nobuf(set_debug_file_directory); /* "file BINARY_FILE" is needed, without it gdb cannot properly * unwind the stack. Currently the unwind information is located * in .eh_frame which is stored only in binary, not in coredump * or debuginfo. * * Fedora GDB does not strictly need it, it will find the binary * by its build-id. But for binaries either without build-id * (= built on non-Fedora GCC) or which do not have * their debuginfo rpm installed gdb would not find BINARY_FILE * so it is still makes sense to supply "file BINARY_FILE". * * Unfortunately, "file BINARY_FILE" doesn't work well if BINARY_FILE * was deleted (as often happens during system updates): * gdb uses specified BINARY_FILE * even if it is completely unrelated to the coredump. * See https://bugzilla.redhat.com/show_bug.cgi?id=525721 * * TODO: check mtimes on COREFILE and BINARY_FILE and not supply * BINARY_FILE if it is newer (to at least avoid gdb complaining). */ args[i++] = (char*)"-ex"; const unsigned file_cmd_index = i++; args[file_cmd_index] = xasprintf("file %s", executable); free(executable); args[i++] = (char*)"-ex"; const unsigned core_cmd_index = i++; args[core_cmd_index] = xasprintf("core-file %s/"FILENAME_COREDUMP, dump_dir_name); args[i++] = (char*)"-ex"; const unsigned bt_cmd_index = i++; /*args[9] = ... see below */ args[i++] = (char*)"-ex"; args[i++] = (char*)"info sharedlib"; /* glibc's abort() stores its message in __abort_msg variable */ args[i++] = (char*)"-ex"; args[i++] = (char*)"print (char*)__abort_msg"; args[i++] = (char*)"-ex"; args[i++] = (char*)"print (char*)__glib_assert_msg"; args[i++] = (char*)"-ex"; args[i++] = (char*)"info all-registers"; args[i++] = (char*)"-ex"; const unsigned dis_cmd_index = i++; args[dis_cmd_index] = (char*)"disassemble"; args[i++] = NULL; /* Get the backtrace, but try to cap its size */ /* Limit bt depth. With no limit, gdb sometimes OOMs the machine */ unsigned bt_depth = 1024; const char *thread_apply_all = "thread apply all -ascending"; const char *full = "full "; char *bt = NULL; while (1) { args[bt_cmd_index] = xasprintf("%s backtrace %s%u", thread_apply_all, full, bt_depth); bt = exec_vp(args, /*redirect_stderr:*/ 1, timeout_sec, NULL); free(args[bt_cmd_index]); if ((bt && strnlen(bt, 256*1024) < 256*1024) || bt_depth <= 32) { break; } bt_depth /= 2; if (bt) log_warning("Backtrace is too big (%u bytes), reducing depth to %u", (unsigned)strlen(bt), bt_depth); else /* (NB: in fact, current impl. of exec_vp() never returns NULL) */ log_warning("Failed to generate backtrace, reducing depth to %u", bt_depth); free(bt); /* Replace -ex disassemble (which disasms entire function $pc points to) * to a version which analyzes limited, small patch of code around $pc. * (Users reported a case where bare "disassemble" attempted to process * entire .bss). * TODO: what if "$pc-N" underflows? in my test, this happens: * Dump of assembler code from 0xfffffffffffffff0 to 0x30: * End of assembler dump. * (IOW: "empty" dump) */ args[dis_cmd_index] = (char*)"disassemble $pc-20, $pc+64"; if (bt_depth <= 64 && thread_apply_all[0] != '\0') { /* This program likely has gazillion threads, dont try to bt them all */ bt_depth = 128; thread_apply_all = ""; } if (bt_depth <= 64 && full[0] != '\0') { /* Looks like there are gigantic local structures or arrays, disable "full" bt */ bt_depth = 128; full = ""; } } if (auto_load_base_index > 0) { free(args[auto_load_base_index]); free(args[auto_load_base_index + 2]); } free(args[debug_dir_cmd_index]); free(args[file_cmd_index]); free(args[core_cmd_index]); return bt; } char* problem_data_save(problem_data_t *pd) { load_abrt_conf(); struct dump_dir *dd = create_dump_dir_from_problem_data_ext(pd, g_settings_dump_location, /*fs owner*/0); char *problem_id = NULL; if (dd) { problem_id = xstrdup(dd->dd_dirname); dd_close(dd); } log_info("problem id: '%s'", problem_id); return problem_id; } int dump_suid_policy() { /* - values are: 0 - don't dump suided programs - in this case the hook is not called by kernel 1 - create coredump readable by fs_uid 2 - create coredump readable by root only */ int c; int suid_dump_policy = 0; const char *filename = "/proc/sys/fs/suid_dumpable"; FILE *f = fopen(filename, "r"); if (!f) { log_warning("Can't open %s", filename); return suid_dump_policy; } c = fgetc(f); fclose(f); if (c != EOF) suid_dump_policy = c - '0'; //log_warning("suid dump policy is: %i", suid_dump_policy); return suid_dump_policy; } int signal_is_fatal(int signal_no, const char **name) { const char *signame = NULL; switch (signal_no) { case SIGILL : signame = "ILL" ; break; case SIGFPE : signame = "FPE" ; break; case SIGSEGV: signame = "SEGV"; break; case SIGBUS : signame = "BUS" ; break; //Bus error (bad memory access) case SIGABRT: signame = "ABRT"; break; //usually when abort() was called // We have real-world reports from users who see buggy programs // dying with SIGTRAP, uncommented it too: case SIGTRAP: signame = "TRAP"; break; //Trace/breakpoint trap // These usually aren't caused by bugs: //case SIGQUIT: signame = "QUIT"; break; //Quit from keyboard //case SIGSYS : signame = "SYS" ; break; //Bad argument to routine (SVr4) //case SIGXCPU: signame = "XCPU"; break; //CPU time limit exceeded (4.2BSD) //case SIGXFSZ: signame = "XFSZ"; break; //File size limit exceeded (4.2BSD) } if (name != NULL) *name = signame; return signame != NULL; } void ensure_writable_dir_uid_gid(const char *dir, mode_t mode, uid_t uid, gid_t gid) { struct stat sb; int dir_fd; if (mkdir(dir, mode) != 0 && errno != EEXIST) perror_msg_and_die("Can't create '%s'", dir); dir_fd = open(dir, O_DIRECTORY | O_NOFOLLOW); if (dir_fd < 0) perror_msg_and_die("Can't open directory '%s'", dir); if (fstat(dir_fd, &sb) != 0) perror_msg_and_die("Can't stat directory '%s'", dir); if ((sb.st_uid != uid || sb.st_gid != gid) && fchown(dir_fd, uid, gid) != 0) perror_msg_and_die("Can't set owner %u:%u on '%s'", (unsigned int)uid, (unsigned int)gid, dir); if ((sb.st_mode & 07777) != mode && fchmod(dir_fd, mode) != 0) perror_msg_and_die("Can't set mode %o on '%s'", mode, dir); close(dir_fd); } void ensure_writable_dir(const char *dir, mode_t mode, const char *user) { struct passwd *pw = getpwnam(user); if (!pw) perror_msg_and_die("Can't find user '%s'", user); ensure_writable_dir_uid_gid(dir, mode, pw->pw_uid, pw->pw_gid); } void ensure_writable_dir_group(const char *dir, mode_t mode, const char *user, const char *group) { struct passwd *pw = getpwnam(user); if (!pw) perror_msg_and_die("Can't find user '%s'", user); struct group *gr = getgrnam(group); if (!gr) perror_msg_and_die("Can't find group '%s'", group); ensure_writable_dir_uid_gid(dir, mode, pw->pw_uid, gr->gr_gid); } bool dir_is_in_dump_location(const char *dir_name) { unsigned len = strlen(g_settings_dump_location); /* The path must start with "g_settings_dump_location" */ if (strncmp(dir_name, g_settings_dump_location, len) != 0) { log_debug("Bad parent directory: '%s' not in '%s'", g_settings_dump_location, dir_name); return false; } /* and must be a sub-directory of the g_settings_dump_location dir */ const char *base_name = dir_name + len; while (*base_name && *base_name == '/') ++base_name; if (*(base_name - 1) != '/' || !str_is_correct_filename(base_name)) { log_debug("Invalid dump directory name: '%s'", base_name); return false; } /* and we are sure it is a directory */ struct stat sb; if (lstat(dir_name, &sb) < 0) { VERB2 perror_msg("stat('%s')", dir_name); return errno== ENOENT; } return S_ISDIR(sb.st_mode); } bool dir_has_correct_permissions(const char *dir_name, int flags) { struct stat statbuf; if (lstat(dir_name, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) { error_msg("Path '%s' isn't directory", dir_name); return false; } /* Get ABRT's group id */ struct group *gr = getgrnam("abrt"); if (!gr) { error_msg("The group 'abrt' does not exist"); return false; } /* The group must be root or abrt. */ const bool correct_group = statbuf.st_gid == 0 || statbuf.st_gid == gr->gr_gid; /* Require owner 'root' and group 'abrt' for the event shell scripts. * Because the shell scripts are vulnerable to hard link and symbolic link * attacks. */ const bool events = statbuf.st_uid == 0 && correct_group && (statbuf.st_mode & S_IWGRP) == 0 && (statbuf.st_mode & S_IWOTH) == 0; if ((flags & DD_PERM_EVENTS)) return events; /* Be less restrictive and allow the daemos (abrtd, abrt-dbus) to work with * dump directories having group 'root' or 'abrt'. * * 1. Chowning of dump directories switches the ownership to 'user':'abrt' * 2. We want to allow users to delete their problems * 3. We want to allow users to modify their data * 4. The daemons are hardened against hard link and symbolic link issues. */ return correct_group; } bool problem_entry_is_post_create_condition(const char *name) { return strcmp(name, FILENAME_TYPE) == 0 /* Replaced with FILENAME_TYPE on '2015-01-15' * commit a6efe199922165725b200298d5b276b52912f3dd * Users scripts might not be adapted to it yet. */ || strcmp(name, FILENAME_ANALYZER) == 0 /* Compatibility value used in abrt-server. * There still might some scripts using it. */ || strcmp(name, "basename") == 0; } bool allowed_new_user_problem_entry(uid_t uid, const char *name, const char *value) { /* Allow root to create everything */ if (uid == 0) return true; /* Permit non-root users to create everything except post-create condition elements */ if (!problem_entry_is_post_create_condition(name)) return true; /* Permit non-root users to create all types except: C/C++, Koops, vmcore and xorg */ if (strcmp(value, "CCpp") != 0 && strcmp(value, "Kerneloops") != 0 && strcmp(value, "vmcore") != 0 && strcmp(value, "xorg") != 0) return true; error_msg("Only root is permitted to create element '%s' containing '%s'", name, value); return false; }