dbus/test/break-loader.c
David Zeuthen 8703514397 Avoid using monotonic time in the DBUS_COOKIE_SHA1 authentication method
When libdbus-1 moved to using monotonic time support for the
DBUS_COOKIE_SHA1 authentication was broken, in particular
interoperability with non-libdbus-1 implementations such as GDBus.

The problem is that if monotonic clocks are available in the OS,
_dbus_get_current_time() will not return the number of seconds since
the Epoch so using it for DBUS_COOKIE_SHA1 will violate the D-Bus
specification. If both peers are using libdbus-1 it's not a problem
since both ends will use the wrong time and thus agree. However, if
the other end is another implementation and following the spec it will
not work.

First, we change _dbus_get_current_time() back so it always returns
time since the Epoch and we then rename it _dbus_get_real_time() to
make this clear. We then introduce _dbus_get_monotonic_time() and
carefully make all current users of _dbus_get_current_time() use it,
if applicable. During this audit, one of the callers,
_dbus_generate_uuid(), was currently using monotonic time but it was
decided to make it use real time instead.

Signed-off-by: David Zeuthen <davidz@redhat.com>
Reviewed-by: Simon McVittie <simon.mcvittie@collabora.co.uk>
Bug: https://bugs.freedesktop.org/show_bug.cgi?id=48580
2012-04-12 11:18:07 -04:00

764 lines
18 KiB
C

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