| /* -*- 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_monotonic_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; |
| } |