diff options
Diffstat (limited to 'src/daemon/dumpsocket.cpp')
-rw-r--r-- | src/daemon/dumpsocket.cpp | 582 |
1 files changed, 199 insertions, 383 deletions
diff --git a/src/daemon/dumpsocket.cpp b/src/daemon/dumpsocket.cpp index 8fd14292..44955351 100644 --- a/src/daemon/dumpsocket.cpp +++ b/src/daemon/dumpsocket.cpp @@ -15,184 +15,127 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include <glib.h> -#include <sys/un.h> #include "abrtlib.h" -#include "dumpsocket.h" -#include "crash_types.h" -//#include "abrt_exception.h" +#include "dump_dir.h" +#include "crash_types.h" /* FILENAME_foo */ #include "hooklib.h" -#define SOCKET_FILE VAR_RUN"/abrt/abrt.socket" -#define SOCKET_PERMISSION 0666 /* Maximal length of backtrace. */ #define MAX_BACKTRACE_SIZE (1024*1024) - /* Amount of data received from one client for a message before reporting error. */ #define MAX_MESSAGE_SIZE (4*MAX_BACKTRACE_SIZE) +/* Maximal number of characters read from socket at once. */ +#define INPUT_BUFFER_SIZE (8*1024) +/* We exit after this many seconds */ +#define TIMEOUT 10 -/* Maximum number of simultaneously opened client connections. */ -#define MAX_CLIENT_COUNT 10 -/* Interval between checks of client halt, in seconds. */ -#define CLIENT_CHECK_INTERVAL 10 +/* +Unix socket in ABRT daemon for creating new dump directories. + +Why to use socket for creating dump dirs? Security. When a Python +script throws unexpected exception, ABRT handler catches it, running +as a part of that broken Python application. The application is running +with certain SELinux privileges, for example it can not execute other +programs, or to create files in /var/cache or anything else required +to properly fill a dump directory. Adding these privileges to every +application would weaken the security. +The most suitable solution is for the Python application +to open a socket where ABRT daemon is listening, write all relevant +data to that socket, and close it. ABRT daemon handles the rest. + +** Protocol + +Initializing new dump: +open /var/run/abrt.socket + +Providing dump data (hook writes to the socket): +-> "PID=" + number 0 - PID_MAX (/proc/sys/kernel/pid_max) + \0 +-> "EXECUTABLE=" + string (maximum length ~MAX_PATH) + \0 +-> "BACKTRACE=" + string (maximum length 1 MB) + \0 +-> "ANALYZER=" + string (maximum length 100 bytes) + \0 +-> "BASENAME=" + string (maximum length 100 bytes, no slashes) + \0 +-> "REASON=" + string (maximum length 512 bytes) + \0 + +Finalizing dump creation: +-> "DONE" + \0 +*/ -/* Interval with no data received from client, after which the client is - considered halted, in seconds. */ -#define CLIENT_HALT_INTERVAL 10 -/* Maximal number of characters read from socket at once. */ -#define INPUT_BUFFER_SIZE 1024 +/* Buffer for incomplete incoming messages. */ +static char *messagebuf_data = NULL; +static unsigned messagebuf_len = 0; -static GIOChannel *channel = NULL; -static guint channel_cb_id = 0; -static int client_count = 0; +static unsigned total_bytes_read = 0; -/* Information about single socket session. */ -struct client -{ - /* Client user id */ - uid_t uid; - /* Buffer for incomplete incoming messages. */ - GByteArray *messagebuf; - /* Executable. */ - char *executable; - /* Process ID. */ - int pid; - char *backtrace; - /* Python, Ruby etc. */ - char *analyzer; - /* Directory base name: python (or pyhook), ruby etc. */ - char *basename; - /* Crash reason. - * Python example: "CCMainWindow.py:1:<module>:ZeroDivisionError: - * integer division or modulo by zero" - */ - char *reason; - /* Last time some data were received over the socket - * from the client. - */ - time_t lastwrite; - /* Timer checking client halt. */ - guint timer_id; - /* Client socket callback id. */ - guint socket_id; - /* Client socket channel */ - GIOChannel *channel; -}; - -static gboolean server_socket_cb(GIOChannel *source, - GIOCondition condition, - gpointer data); - -/* Releases all memory that belongs to a client session. */ -static void client_free(struct client *client) -{ - /* Delete the uncompleted message if there is some. */ - g_byte_array_free(client->messagebuf, TRUE); - free(client->executable); - free(client->backtrace); - free(client->analyzer); - free(client->basename); - free(client->reason); - g_source_remove(client->timer_id); - g_source_remove(client->socket_id); - g_io_channel_unref(client->channel); - free(client); - --client_count; - if (!channel_cb_id) - { - channel_cb_id = g_io_add_watch(channel, - (GIOCondition)(G_IO_IN | G_IO_PRI), - (GIOFunc)server_socket_cb, - NULL); - if (!channel_cb_id) - perror_msg_and_die("dumpsocket: Can't add socket watch"); - } -} +static uid_t client_uid = (uid_t)-1L; -/* Callback called by glib main loop at regular intervals when - some client is connected. */ -static gboolean client_check_cb(gpointer data) -{ - struct client *client = (struct client*)data; - if (time(NULL) - client->lastwrite > CLIENT_HALT_INTERVAL) - { - log("dumpsocket: client socket timeout reached, closing connection"); - client_free(client); - return FALSE; - } - return TRUE; -} +static int pid; +static char *executable; +static char *backtrace; +/* "python", "ruby" etc. */ +static char *analyzer; +/* Directory base name: "pyhook", "ruby" etc. */ +static char *dir_basename; +/* Crash reason. + * Python example: + * "CCMainWindow.py:1:<module>:ZeroDivisionError: integer division or modulo by zero" + */ +static char *reason; -/* Caller is responsible to free() the returned value. */ -static char *giocondition_to_string(GIOCondition condition) -{ - struct strbuf *strbuf = strbuf_new(); - if (condition & G_IO_HUP) - strbuf_append_str(strbuf, "G_IO_HUP | "); - if (condition & G_IO_ERR) - strbuf_append_str(strbuf, "G_IO_ERR | "); - if (condition & G_IO_NVAL) - strbuf_append_str(strbuf, "G_IO_NVAL | "); - if (condition & G_IO_IN) - strbuf_append_str(strbuf, "G_IO_IN | "); - if (condition & G_IO_OUT) - strbuf_append_str(strbuf, "G_IO_OUT | "); - if (condition & G_IO_PRI) - strbuf_append_str(strbuf, "G_IO_PRI | "); - if (strbuf->len == 0) - strbuf_append_str(strbuf, "none"); - else - { - /* remove the last " | " */ - strbuf->len -= 3; - strbuf->buf[strbuf->len] = '\0'; - } - char *result = strbuf->buf; - strbuf_free_nobuf(strbuf); - return result; -} /* Create a new debug dump from client session. * Caller must ensure that all fields in struct client * are properly filled. */ -static void create_debug_dump(struct client *client) +static void create_debug_dump() { /* Create temp directory with the debug dump. This directory is renamed to final directory name after all files have been stored into it. */ char *path = xasprintf(DEBUG_DUMPS_DIR"/%s-%ld-%u.new", - client->basename, + dir_basename, (long)time(NULL), - client->pid); + pid); /* No need to check the path length, as all variables used are limited, and dd_create() fails if the path is too long. */ - dump_dir_t *dd = dd_init();; - if (!dd_create(dd, path, client->uid)) + dump_dir_t *dd = dd_init(); + if (!dd_create(dd, path, client_uid)) { dd_delete(dd); dd_close(dd); - error_msg_and_die("dumpsocket: Error while creating crash dump %s", path); + error_msg_and_die("Error creating crash dump %s", path); } - dd_save_text(dd, FILENAME_ANALYZER, client->analyzer); - dd_save_text(dd, FILENAME_EXECUTABLE, client->executable); - dd_save_text(dd, FILENAME_BACKTRACE, client->backtrace); - dd_save_text(dd, FILENAME_REASON, client->reason); + dd_save_text(dd, FILENAME_ANALYZER, analyzer); + dd_save_text(dd, FILENAME_EXECUTABLE, executable); + dd_save_text(dd, FILENAME_BACKTRACE, backtrace); + dd_save_text(dd, FILENAME_REASON, reason); /* Obtain and save the command line. */ - char *cmdline = get_cmdline(client->pid); // never NULL + char *cmdline = get_cmdline(pid); // never NULL dd_save_text(dd, FILENAME_CMDLINE, cmdline); free(cmdline); /* Store id of the user whose application crashed. */ char uid_str[sizeof(long) * 3 + 2]; - sprintf(uid_str, "%lu", (long)client->uid); + sprintf(uid_str, "%lu", (long)client_uid); dd_save_text(dd, CD_UID, uid_str); dd_close(dd); @@ -204,11 +147,9 @@ static void create_debug_dump(struct client *client) strcpy(path, newpath); free(newpath); - log("dumpsocket: Saved %s crash dump of pid %u to %s", - client->analyzer, client->pid, path); + log("Saved %s crash dump of pid %u to %s", analyzer, pid, path); - /* Handle free space checking. */ -//FIXME! needs to use globals, like the rest of daemon does! + /* Trim old crash dumps if necessary */ unsigned maxCrashReportsSize = 0; parse_conf(NULL, &maxCrashReportsSize, NULL, NULL); if (maxCrashReportsSize > 0) @@ -252,25 +193,23 @@ static char *try_to_get_string(const char *message, return NULL; const char *contents = message + strlen(tag); - if ((printable && !printable_str(contents)) || - (!allow_slashes && strchr(contents, '/'))) - { - error_msg("dumpsocket: Received %s contains invalid characters -> skipping", tag); + if ((printable && !printable_str(contents)) + || (!allow_slashes && strchr(contents, '/')) + ) { + error_msg("Received %s contains invalid characters, skipping", tag); return NULL; } if (strlen(contents) > max_len) { - char *max_len_str = g_format_size_for_display(max_len); - error_msg("dumpsocket: Received %s too long -> trimming to %s", tag, max_len_str); - g_free(max_len_str); + error_msg("Received %s too long, trimming to %lu", tag, (long)max_len); } return xstrndup(contents, max_len); } /* Handles a message received from client over socket. */ -static void process_message(struct client *client, const char *message) +static void process_message(const char *message) { /* @param tag * The message identifier. Message starting with it @@ -287,17 +226,20 @@ static void process_message(struct client *client, const char *message) * Whether to allow slashes to be a part of input. */ #define HANDLE_INCOMING_STRING(tag, field, max_len, printable, allow_slashes) \ - char *field = try_to_get_string(message, tag, max_len, printable, allow_slashes); \ - if (field) \ +{ \ + char *s = try_to_get_string(message, tag, max_len, printable, allow_slashes); \ + if (s) \ { \ - free(client->field); \ - client->field = field; \ + free(field); \ + field = s; \ + VERB3 log("Saved %s%s", tag, s); \ return; \ - } + } \ +} HANDLE_INCOMING_STRING("EXECUTABLE=", executable, PATH_MAX, true, true); HANDLE_INCOMING_STRING("BACKTRACE=", backtrace, MAX_BACKTRACE_SIZE, false, true); - HANDLE_INCOMING_STRING("BASENAME=", basename, 100, true, false); + HANDLE_INCOMING_STRING("BASENAME=", dir_basename, 100, true, false); HANDLE_INCOMING_STRING("ANALYZER=", analyzer, 100, true, true); HANDLE_INCOMING_STRING("REASON=", reason, 512, false, true); @@ -306,261 +248,135 @@ static void process_message(struct client *client, const char *message) /* PID is not handled as a string, we convert it to pid_t. */ if (starts_with(message, "PID=")) { - /* xatou() cannot be used here, because it would - * kill whole daemon by non-numeric string. - */ - char *endptr; - errno = 0; - const char *nptr = message + strlen("PID="); - unsigned long number = strtoul(nptr, &endptr, 10); - /* pid == 0 is error, the lowest PID is 1. */ - if (errno || nptr == endptr || *endptr != '\0' || number > UINT_MAX || number == 0) - { - error_msg("dumpsocket: invalid PID received -> ignoring"); - return; - } - client->pid = number; + pid = xatou(message + strlen("PID=")); + if (pid < 1) + /* pid == 0 is error, the lowest PID is 1. */ + error_msg_and_die("Malformed or out-of-range number: '%s'", message + strlen("PID=")); + VERB3 log("Saved PID %u", pid); return; } /* Creates debug dump if all fields were already provided. */ if (starts_with(message, "DONE")) { - if (client->pid == 0 || - client->backtrace == NULL || - client->executable == NULL || - client->analyzer == NULL || - client->basename == NULL || - client->reason == NULL) - { - error_msg("dumpsocket: DONE received, but some data are missing -> ignoring"); - return; + if (!pid || !backtrace || !executable + || !analyzer || !dir_basename || !reason + ) { + error_msg_and_die("Got DONE, but some data are missing. Aborting"); } - create_debug_dump(client); - return; + /* Write out the crash dump. Don't let alarm to interrupt here */ + alarm(0); + create_debug_dump(); + + /* Reset alarm and the counter which detects oversized dumps */ + alarm(TIMEOUT); + total_bytes_read = 0; } } -/* Callback called by glib main loop when ABRT receives data that have - * been written to the socket by some client. - */ -static gboolean client_socket_cb(GIOChannel *source, - GIOCondition condition, - gpointer data) -{ - struct client *client = (struct client*)data; - - /* Detailed logging, useful for debugging. */ - if (g_verbose >= 3) - { - char *cond = giocondition_to_string(condition); - log("dumpsocket: client condition %s", cond); - free(cond); - } +static void dummy_handler(int sig_unused) {} - /* Handle incoming data. */ - if (condition & (G_IO_IN | G_IO_PRI)) +int main(int argc, char **argv) +{ + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + enum { + OPT_s = (1 << 0), + }; + int optflags = 0; + int opt; + while ((opt = getopt(argc, argv, "u:vs")) != -1) { - guint loop = client->messagebuf->len; - gsize len; - gchar buf[INPUT_BUFFER_SIZE]; - GError *err = NULL; - /* Read data in chunks of size INPUT_BUFFER_SIZE. This allows to limit the number of - bytes received (to prevent memory exhaustion). */ - do { - GIOStatus result = g_io_channel_read_chars(source, buf, INPUT_BUFFER_SIZE, &len, &err); - if (result == G_IO_STATUS_ERROR) - { - g_assert(err); - error_msg("dumpsocket: Error while reading data from client socket: %s", err->message); - g_error_free(err); - client_free(client); - return FALSE; - } - - if (g_verbose >= 3) - log("dumpsocket: received %zu bytes of data", len); - - /* Append the incoming data to the message buffer. */ - g_byte_array_append(client->messagebuf, (guint8*)buf, len); - - if (client->messagebuf->len > MAX_MESSAGE_SIZE) - { - error_msg("dumpsocket: Message too long."); - client_free(client); - return FALSE; - } - } while (len > 0); - - /* Check, if we received a complete message now. */ - for (; loop < client->messagebuf->len; ++loop) + switch (opt) { - if (client->messagebuf->data[loop] != '\0') - continue; - - VERB3 log("dumpsocket: Processing message: %s", - client->messagebuf->data); - - /* Process the message. */ - process_message(client, (char*)client->messagebuf->data); - /* Remove the message including the ending \0 */ - g_byte_array_remove_range(client->messagebuf, 0, loop + 1); - loop = 0; + case 'u': + client_uid = xatoi_u(optarg); + break; + case 'v': + g_verbose++; + break; + case 's': + optflags |= OPT_s; + break; + default: + error_msg_and_die( + "Usage: abrt-server [-v] [-u UID]\n" + "\n(So far only) creates crash dumps" + "\nOptions:" + "\n\t-v\tVerbose" + "\n\t-s\tLog to syslog" + "\n\t-u UID\tUse UID as client uid" + ); } - - /* Update the last write access time */ - client->lastwrite = time(NULL); } - /* Handle socket disconnection. - It is important to do it after handling G_IO_IN, because sometimes - G_IO_HUP comes together with G_IO_IN. It means that some data arrived - and then the socket has been closed. - */ - if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + msg_prefix = xasprintf("abrt-server[%u]", getpid()); + if (optflags & OPT_s) { - log("dumpsocket: Socket client disconnected"); - client_free(client); - return FALSE; + openlog(msg_prefix, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; } - return TRUE; -} - -/* If the status indicates failure, report it. */ -static void check_status(GIOStatus status, GError *err, const char *operation) -{ - if (status == G_IO_STATUS_NORMAL) - return; - - if (err) - { - error_msg("dumpsocket: Error while %s: %s", operation, err->message); - g_error_free(err); - } - else - error_msg("dumpsocket: Error while %s", operation); -} - -/* Initializes a new client session data structure. */ -static struct client *client_new(int socket) -{ - struct client *client = (struct client*)xzalloc(sizeof(struct client)); - - /* Get credentials for the socket client. */ - struct ucred cr; - socklen_t crlen = sizeof(struct ucred); - if (0 != getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &cr, &crlen)) - perror_msg_and_die("dumpsocket: Failed to get client uid"); - if (crlen != sizeof(struct ucred)) - error_msg_and_die("dumpsocket: Failed to get client uid (crlen)"); - client->uid = cr.uid; - - client->messagebuf = g_byte_array_new(); - client->lastwrite = time(NULL); - - close_on_exec_on(socket); - - /* Create client IO channel. */ - client->channel = g_io_channel_unix_new(socket); - g_io_channel_set_close_on_unref(client->channel, TRUE); - - /* Set nonblocking access. */ - GError *err = NULL; - GIOStatus status = g_io_channel_set_flags(client->channel, G_IO_FLAG_NONBLOCK, &err); - check_status(status, err, "setting NONBLOCK flag"); - - /* Disable channel encoding to protect binary data. */ - err = NULL; - status = g_io_channel_set_encoding(client->channel, NULL, &err); - check_status(status, err, "setting encoding"); - - /* Start timer to check the client problems. */ - client->timer_id = g_timeout_add_seconds(CLIENT_CHECK_INTERVAL, client_check_cb, client); - if (!client->timer_id) - error_msg_and_die("dumpsocket: Can't add client timer"); - - /* Register client callback. */ - client->socket_id = g_io_add_watch(client->channel, - (GIOCondition)(G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL), - client_socket_cb, - client); - if (!client->socket_id) - error_msg_and_die("dumpsocket: Can't add client socket watch"); - - ++client_count; - return client; -} - -/* Callback called by glib main loop when a client newly opens ABRT's socket. */ -static gboolean server_socket_cb(GIOChannel *source, - GIOCondition condition, - gpointer data) -{ - /* Check the limit for number of simultaneously attached clients. */ - if (client_count >= MAX_CLIENT_COUNT) + /* Set up timeout handling */ + /* Part 1 - need this to make SIGALRM interrupt syscalls + * (as opposed to restarting them): I want read syscall to be interrupted + */ + struct sigaction sa; + /* sa.sa_flags.SA_RESTART bit is clear: make signal interrupt syscalls */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = dummy_handler; /* pity, SIG_DFL won't do */ + sigaction(SIGALRM, &sa, NULL); + /* Part 2 - set the timeout per se */ + alarm(TIMEOUT); + + if (client_uid == (uid_t)-1L) { - error_msg("dumpsocket: Too many clients, refusing connection."); - /* To avoid infinite loop caused by the descriptor in "ready" state, - the callback must be disabled. - It is added back in client_free(). */ - g_source_remove(channel_cb_id); - channel_cb_id = 0; - return TRUE; + /* Get uid of the connected client */ + struct ucred cr; + socklen_t crlen = sizeof(cr); + if (0 != getsockopt(STDIN_FILENO, SOL_SOCKET, SO_PEERCRED, &cr, &crlen)) + perror_msg_and_die("getsockopt(SO_PEERCRED)"); + if (crlen != sizeof(cr)) + error_msg_and_die("%s: bad crlen %d", "getsockopt(SO_PEERCRED)", (int)crlen); + client_uid = cr.uid; } - int socket = accept(g_io_channel_unix_get_fd(source), NULL, NULL); - if (socket == -1) + /* Loop until EOF/error/timeout */ + while (1) { - perror_msg("dumpsocket: Server can not accept client"); - return TRUE; - } - - log("dumpsocket: New client connected"); - client_new(socket); - return TRUE; -} + messagebuf_data = xrealloc(messagebuf_data, messagebuf_len + INPUT_BUFFER_SIZE); + int rd = read(STDIN_FILENO, messagebuf_data + messagebuf_len, INPUT_BUFFER_SIZE); + if (rd < 0) + { + if (errno == EINTR) /* SIGALRM? */ + error_msg_and_die("Timed out"); + perror_msg_and_die("read"); + } + if (rd == 0) + break; -/* Initializes the dump socket, usually in /var/run directory - * (the path depends on compile-time configuration). - */ -void dumpsocket_init() -{ - struct sockaddr_un local; - unlink(SOCKET_FILE); /* not caring about the result */ - int socketfd = xsocket(AF_UNIX, SOCK_STREAM, 0); - close_on_exec_on(socketfd); - memset(&local, 0, sizeof(local)); - local.sun_family = AF_UNIX; - strcpy(local.sun_path, SOCKET_FILE); - xbind(socketfd, (struct sockaddr*)&local, sizeof(local)); - xlisten(socketfd, MAX_CLIENT_COUNT); - - if (chmod(SOCKET_FILE, SOCKET_PERMISSION) != 0) - perror_msg_and_die("dumpsocket: failed to chmod socket file"); - - channel = g_io_channel_unix_new(socketfd); - g_io_channel_set_close_on_unref(channel, TRUE); - channel_cb_id = g_io_add_watch(channel, - (GIOCondition)(G_IO_IN | G_IO_PRI), - server_socket_cb, - NULL); - if (!channel_cb_id) - perror_msg_and_die("dumpsocket: Can't add socket watch"); -} + VERB3 log("Received %u bytes of data", rd); + messagebuf_len += rd; + total_bytes_read += rd; + if (total_bytes_read > MAX_MESSAGE_SIZE) + error_msg_and_die("Message is too long, aborting"); -/* Releases all resources used by dumpsocket. */ -void dumpsocket_shutdown() -{ - /* Set everything to pre-initialization state. */ - if (channel) - { - /* This one is for g_io_add_watch. */ - if (channel_cb_id) - g_source_remove(channel_cb_id); - /* This one is for g_io_channel_unix_new. */ - g_io_channel_unref(channel); - channel = NULL; + while (1) + { + unsigned len = strnlen(messagebuf_data, messagebuf_len); + if (len >= messagebuf_len) + break; + /* messagebuf has at least one NUL - process the line */ + process_message(messagebuf_data); + messagebuf_len -= (len + 1); + memmove(messagebuf_data, messagebuf_data + len + 1, messagebuf_len); + } } + + VERB1 log("EOF detected, exiting"); + return 0; } |