/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* dbus-break-loader.c Program to find byte streams that break the message loader * * Copyright (C) 2003 Red Hat Inc. * * Licensed under the Academic Free License version 2.1 * * 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 <config.h> #include <dbus/dbus.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sys/wait.h> #include <string.h> #define DBUS_COMPILATION #include <dbus/dbus-string.h> #include <dbus/dbus-internals.h> #include <dbus/dbus-test.h> #include <dbus/dbus-marshal-basic.h> #undef DBUS_COMPILATION static DBusString failure_dir; static int total_attempts; static int failures_this_iteration; static int random_int_in_range (int start, int end) { /* such elegant math */ double gap; double v_double; int v; if (start == end) return start; _dbus_assert (end > start); gap = end - start - 1; /* -1 to not include "end" */ v_double = ((double)start) + (((double)rand ())/RAND_MAX) * gap; if (v_double < 0.0) v = (v_double - 0.5); else v = (v_double + 0.5); if (v < start) { fprintf (stderr, "random_int_in_range() generated %d for range [%d,%d)\n", v, start, end); v = start; } else if (v >= end) { fprintf (stderr, "random_int_in_range() generated %d for range [%d,%d)\n", v, start, end); v = end - 1; } /* printf (" %d of [%d,%d)\n", v, start, end); */ return v; } static dbus_bool_t try_mutated_data (const DBusString *data) { int pid; total_attempts += 1; /* printf (" attempt %d\n", total_attempts); */ pid = fork (); if (pid < 0) { fprintf (stderr, "fork() failed: %s\n", strerror (errno)); exit (1); return FALSE; } if (pid == 0) { /* Child, try loading the data */ if (!dbus_internal_do_not_use_try_message_data (data, _DBUS_MESSAGE_UNKNOWN)) exit (1); else exit (0); } else { /* Parent, wait for child */ int status; DBusString filename; dbus_bool_t failed; if (waitpid (pid, &status, 0) < 0) { fprintf (stderr, "waitpid() failed: %s\n", strerror (errno)); exit (1); return FALSE; } failed = FALSE; if (!_dbus_string_init (&filename) || !_dbus_string_copy (&failure_dir, 0, &filename, 0) || !_dbus_string_append_byte (&filename, '/')) { fprintf (stderr, "out of memory\n"); exit (1); } _dbus_string_append_int (&filename, total_attempts); if (WIFEXITED (status)) { if (WEXITSTATUS (status) != 0) { _dbus_string_append (&filename, "-exited-"); _dbus_string_append_int (&filename, WEXITSTATUS (status)); failed = TRUE; } } else if (WIFSIGNALED (status)) { _dbus_string_append (&filename, "signaled-"); _dbus_string_append_int (&filename, WTERMSIG (status)); failed = TRUE; } if (failed) { DBusError error; _dbus_string_append (&filename, ".message-raw"); printf ("Child failed, writing %s\n", _dbus_string_get_const_data (&filename)); dbus_error_init (&error); if (!_dbus_string_save_to_file (data, &filename, FALSE, &error)) { fprintf (stderr, "Failed to save failed message data: %s\n", error.message); dbus_error_free (&error); exit (1); /* so we can see the seed that was printed out */ } failures_this_iteration += 1; _dbus_string_free (&filename); return FALSE; } else { _dbus_string_free (&filename); return TRUE; } } _dbus_assert_not_reached ("should not be reached"); return TRUE; } static void randomly_shorten_or_lengthen (const DBusString *orig_data, DBusString *mutated) { int delta; if (orig_data != mutated) { _dbus_string_set_length (mutated, 0); if (!_dbus_string_copy (orig_data, 0, mutated, 0)) _dbus_assert_not_reached ("out of mem"); } if (_dbus_string_get_length (mutated) == 0) delta = random_int_in_range (0, 10); else delta = random_int_in_range (- _dbus_string_get_length (mutated), _dbus_string_get_length (mutated) * 3); if (delta < 0) _dbus_string_shorten (mutated, - delta); else if (delta > 0) { int i = 0; i = _dbus_string_get_length (mutated); if (!_dbus_string_lengthen (mutated, delta)) _dbus_assert_not_reached ("couldn't lengthen string"); while (i < _dbus_string_get_length (mutated)) { _dbus_string_set_byte (mutated, i, random_int_in_range (0, 256)); ++i; } } } static void randomly_change_one_byte (const DBusString *orig_data, DBusString *mutated) { int i; if (orig_data != mutated) { _dbus_string_set_length (mutated, 0); if (!_dbus_string_copy (orig_data, 0, mutated, 0)) _dbus_assert_not_reached ("out of mem"); } if (_dbus_string_get_length (mutated) == 0) return; i = random_int_in_range (0, _dbus_string_get_length (mutated)); _dbus_string_set_byte (mutated, i, random_int_in_range (0, 256)); } static void randomly_remove_one_byte (const DBusString *orig_data, DBusString *mutated) { int i; if (orig_data != mutated) { _dbus_string_set_length (mutated, 0); if (!_dbus_string_copy (orig_data, 0, mutated, 0)) _dbus_assert_not_reached ("out of mem"); } if (_dbus_string_get_length (mutated) == 0) return; i = random_int_in_range (0, _dbus_string_get_length (mutated)); _dbus_string_delete (mutated, i, 1); } static void randomly_add_one_byte (const DBusString *orig_data, DBusString *mutated) { int i; if (orig_data != mutated) { _dbus_string_set_length (mutated, 0); if (!_dbus_string_copy (orig_data, 0, mutated, 0)) _dbus_assert_not_reached ("out of mem"); } i = random_int_in_range (0, _dbus_string_get_length (mutated)); _dbus_string_insert_bytes (mutated, i, 1, random_int_in_range (0, 256)); } static void randomly_modify_length (const DBusString *orig_data, DBusString *mutated) { int i; int byte_order; const char *d; dbus_uint32_t orig; int delta; if (orig_data != mutated) { _dbus_string_set_length (mutated, 0); if (!_dbus_string_copy (orig_data, 0, mutated, 0)) _dbus_assert_not_reached ("out of mem"); } if (_dbus_string_get_length (mutated) < 12) return; d = _dbus_string_get_const_data (mutated); if (!(*d == DBUS_LITTLE_ENDIAN || *d == DBUS_BIG_ENDIAN)) return; byte_order = *d; i = random_int_in_range (4, _dbus_string_get_length (mutated) - 8); i = _DBUS_ALIGN_VALUE (i, 4); orig = _dbus_demarshal_uint32 (mutated, byte_order, i, NULL); delta = random_int_in_range (-10, 10); _dbus_marshal_set_uint32 (mutated, byte_order, i, (unsigned) (orig + delta)); } static void randomly_set_extreme_ints (const DBusString *orig_data, DBusString *mutated) { int i; int byte_order; const char *d; dbus_uint32_t orig; static int which = 0; unsigned int extreme_ints[] = { _DBUS_INT_MAX, _DBUS_UINT_MAX, _DBUS_INT_MAX - 1, _DBUS_UINT_MAX - 1, _DBUS_INT_MAX - 2, _DBUS_UINT_MAX - 2, _DBUS_INT_MAX - 17, _DBUS_UINT_MAX - 17, _DBUS_INT_MAX / 2, _DBUS_INT_MAX / 3, _DBUS_UINT_MAX / 2, _DBUS_UINT_MAX / 3, 0, 1, 2, 3, (unsigned int) -1, (unsigned int) -2, (unsigned int) -3 }; if (orig_data != mutated) { _dbus_string_set_length (mutated, 0); if (!_dbus_string_copy (orig_data, 0, mutated, 0)) _dbus_assert_not_reached ("out of mem"); } if (_dbus_string_get_length (mutated) < 12) return; d = _dbus_string_get_const_data (mutated); if (!(*d == DBUS_LITTLE_ENDIAN || *d == DBUS_BIG_ENDIAN)) return; byte_order = *d; i = random_int_in_range (4, _dbus_string_get_length (mutated) - 8); i = _DBUS_ALIGN_VALUE (i, 4); orig = _dbus_demarshal_uint32 (mutated, byte_order, i, NULL); which = random_int_in_range (0, _DBUS_N_ELEMENTS (extreme_ints)); _dbus_assert (which >= 0); _dbus_assert (which < _DBUS_N_ELEMENTS (extreme_ints)); _dbus_marshal_set_uint32 (mutated, byte_order, i, extreme_ints[which]); } static int random_type (void) { const char types[] = { DBUS_TYPE_INVALID, DBUS_TYPE_NIL, DBUS_TYPE_BYTE, DBUS_TYPE_BOOLEAN, DBUS_TYPE_INT32, DBUS_TYPE_UINT32, DBUS_TYPE_INT64, DBUS_TYPE_UINT64, DBUS_TYPE_DOUBLE, DBUS_TYPE_STRING, DBUS_TYPE_CUSTOM, DBUS_TYPE_ARRAY, DBUS_TYPE_DICT, DBUS_TYPE_OBJECT_PATH }; _dbus_assert (_DBUS_N_ELEMENTS (types) == DBUS_NUMBER_OF_TYPES + 1); return types[ random_int_in_range (0, _DBUS_N_ELEMENTS (types)) ]; } static void randomly_change_one_type (const DBusString *orig_data, DBusString *mutated) { int i; int len; if (orig_data != mutated) { _dbus_string_set_length (mutated, 0); if (!_dbus_string_copy (orig_data, 0, mutated, 0)) _dbus_assert_not_reached ("out of mem"); } if (_dbus_string_get_length (mutated) == 0) return; len = _dbus_string_get_length (mutated); i = random_int_in_range (0, len); /* Look for a type starting at a random location, * and replace with a different type */ while (i < len) { int b; b = _dbus_string_get_byte (mutated, i); if (_dbus_type_is_valid (b)) { _dbus_string_set_byte (mutated, i, random_type ()); return; } ++i; } } static int times_we_did_each_thing[7] = { 0, }; static void randomly_do_n_things (const DBusString *orig_data, DBusString *mutated, int n) { int i; void (* functions[]) (const DBusString *orig_data, DBusString *mutated) = { randomly_shorten_or_lengthen, randomly_change_one_byte, randomly_add_one_byte, randomly_remove_one_byte, randomly_modify_length, randomly_set_extreme_ints, randomly_change_one_type }; _dbus_string_set_length (mutated, 0); if (!_dbus_string_copy (orig_data, 0, mutated, 0)) _dbus_assert_not_reached ("out of mem"); i = 0; while (i < n) { int which; which = random_int_in_range (0, _DBUS_N_ELEMENTS (functions)); (* functions[which]) (mutated, mutated); times_we_did_each_thing[which] += 1; ++i; } } static dbus_bool_t find_breaks_based_on (const DBusString *filename, dbus_bool_t is_raw, DBusMessageValidity expected_validity, void *data) { DBusString orig_data; DBusString mutated; const char *filename_c; dbus_bool_t retval; int i; filename_c = _dbus_string_get_const_data (filename); retval = FALSE; if (!_dbus_string_init (&orig_data)) _dbus_assert_not_reached ("could not allocate string\n"); if (!_dbus_string_init (&mutated)) _dbus_assert_not_reached ("could not allocate string\n"); if (!dbus_internal_do_not_use_load_message_file (filename, is_raw, &orig_data)) { fprintf (stderr, "could not load file %s\n", filename_c); goto failed; } printf (" changing one random byte 100 times\n"); i = 0; while (i < 100) { randomly_change_one_byte (&orig_data, &mutated); try_mutated_data (&mutated); ++i; } printf (" changing length 50 times\n"); i = 0; while (i < 50) { randomly_modify_length (&orig_data, &mutated); try_mutated_data (&mutated); ++i; } printf (" removing one byte 50 times\n"); i = 0; while (i < 50) { randomly_remove_one_byte (&orig_data, &mutated); try_mutated_data (&mutated); ++i; } printf (" adding one byte 50 times\n"); i = 0; while (i < 50) { randomly_add_one_byte (&orig_data, &mutated); try_mutated_data (&mutated); ++i; } printf (" changing ints to boundary values 50 times\n"); i = 0; while (i < 50) { randomly_set_extreme_ints (&orig_data, &mutated); try_mutated_data (&mutated); ++i; } printf (" changing typecodes 50 times\n"); i = 0; while (i < 50) { randomly_change_one_type (&orig_data, &mutated); try_mutated_data (&mutated); ++i; } printf (" changing message length 15 times\n"); i = 0; while (i < 15) { randomly_shorten_or_lengthen (&orig_data, &mutated); try_mutated_data (&mutated); ++i; } printf (" randomly making 2 of above modifications 42 times\n"); i = 0; while (i < 42) { randomly_do_n_things (&orig_data, &mutated, 2); try_mutated_data (&mutated); ++i; } printf (" randomly making 3 of above modifications 42 times\n"); i = 0; while (i < 42) { randomly_do_n_things (&orig_data, &mutated, 3); try_mutated_data (&mutated); ++i; } printf (" randomly making 4 of above modifications 42 times\n"); i = 0; while (i < 42) { randomly_do_n_things (&orig_data, &mutated, 4); try_mutated_data (&mutated); ++i; } retval = TRUE; failed: _dbus_string_free (&orig_data); _dbus_string_free (&mutated); /* FALSE means end the whole process */ return retval; } static unsigned int get_random_seed (void) { DBusString bytes; unsigned int seed; int fd; const char *s; seed = 0; if (!_dbus_string_init (&bytes)) exit (1); fd = open ("/dev/urandom", O_RDONLY); if (fd < 0) goto use_fallback; if (_dbus_read (fd, &bytes, 4) != 4) goto use_fallback; close (fd); s = _dbus_string_get_const_data (&bytes); seed = * (unsigned int*) s; goto out; use_fallback: { long tv_usec; fprintf (stderr, "could not open/read /dev/urandom, using current time for seed\n"); _dbus_get_current_time (NULL, &tv_usec); seed = tv_usec; } out: _dbus_string_free (&bytes); return seed; } int main (int argc, char **argv) { const char *test_data_dir; const char *failure_dir_c; int total_failures_found; if (argc > 1) test_data_dir = argv[1]; else { fprintf (stderr, "Must specify a top_srcdir/test/data directory\n"); return 1; } total_failures_found = 0; total_attempts = 0; if (!_dbus_string_init (&failure_dir)) return 1; /* so you can leave it overnight safely */ #define MAX_FAILURES 1000 while (total_failures_found < MAX_FAILURES) { unsigned int seed; failures_this_iteration = 0; seed = get_random_seed (); _dbus_string_set_length (&failure_dir, 0); if (!_dbus_string_append (&failure_dir, "failures-")) return 1; if (!_dbus_string_append_uint (&failure_dir, seed)) return 1; failure_dir_c = _dbus_string_get_const_data (&failure_dir); if (mkdir (failure_dir_c, 0700) < 0) { if (errno != EEXIST) fprintf (stderr, "didn't mkdir %s: %s\n", failure_dir_c, strerror (errno)); } printf ("next seed = %u \ttotal failures %d of %d attempts\n", seed, total_failures_found, total_attempts); srand (seed); if (!dbus_internal_do_not_use_foreach_message_file (test_data_dir, find_breaks_based_on, NULL)) { fprintf (stderr, "fatal error iterating over message files\n"); rmdir (failure_dir_c); return 1; } printf (" did %d random mutations: %d %d %d %d %d %d %d\n", _DBUS_N_ELEMENTS (times_we_did_each_thing), times_we_did_each_thing[0], times_we_did_each_thing[1], times_we_did_each_thing[2], times_we_did_each_thing[3], times_we_did_each_thing[4], times_we_did_each_thing[5], times_we_did_each_thing[6]); printf ("Found %d failures with seed %u stored in %s\n", failures_this_iteration, seed, failure_dir_c); total_failures_found += failures_this_iteration; rmdir (failure_dir_c); /* does nothing if non-empty */ } return 0; }