break-loader.c revision fa05de9230d62e7c427b5313796fc6ccd4d0ff60
1/* -*- mode: C; c-file-style: "gnu" -*- */
2/* dbus-break-loader.c  Program to find byte streams that break the message loader
3 *
4 * Copyright (C) 2003  Red Hat Inc.
5 *
6 * Licensed under the Academic Free License version 1.2
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 *
22 */
23
24#include <dbus/dbus.h>
25#include <sys/stat.h>
26#include <sys/types.h>
27#include <fcntl.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <unistd.h>
31#include <errno.h>
32#include <sys/wait.h>
33#include <string.h>
34
35#define DBUS_COMPILATION
36#include <dbus/dbus-string.h>
37#include <dbus/dbus-internals.h>
38#include <dbus/dbus-test.h>
39#include <dbus/dbus-marshal.h>
40#undef DBUS_COMPILATION
41
42static DBusString failure_dir;
43static int total_attempts;
44static int failures_this_iteration;
45
46static int
47random_int_in_range (int start,
48                     int end)
49{
50  /* such elegant math */
51  double gap;
52  double v_double;
53  int v;
54
55  if (start == end)
56    return start;
57
58  _dbus_assert (end > start);
59
60  gap = end - start - 1; /* -1 to not include "end" */
61  v_double = ((double)start) + (((double)rand ())/RAND_MAX) * gap;
62  if (v_double < 0.0)
63    v = (v_double - 0.5);
64  else
65    v = (v_double + 0.5);
66
67  if (v < start)
68    {
69      fprintf (stderr, "random_int_in_range() generated %d for range [%d,%d)\n",
70               v, start, end);
71      v = start;
72    }
73  else if (v >= end)
74    {
75      fprintf (stderr, "random_int_in_range() generated %d for range [%d,%d)\n",
76               v, start, end);
77      v = end - 1;
78    }
79
80  /* printf ("  %d of [%d,%d)\n", v, start, end); */
81
82  return v;
83}
84
85static dbus_bool_t
86try_mutated_data (const DBusString *data)
87{
88  int pid;
89
90  total_attempts += 1;
91  /* printf ("  attempt %d\n", total_attempts); */
92
93  pid = fork ();
94
95  if (pid < 0)
96    {
97      fprintf (stderr, "fork() failed: %s\n",
98               strerror (errno));
99      exit (1);
100      return FALSE;
101    }
102
103  if (pid == 0)
104    {
105      /* Child, try loading the data */
106      if (!dbus_internal_do_not_use_try_message_data (data, _DBUS_MESSAGE_UNKNOWN))
107        exit (1);
108      else
109        exit (0);
110    }
111  else
112    {
113      /* Parent, wait for child */
114      int status;
115      DBusString filename;
116      dbus_bool_t failed;
117
118      if (waitpid (pid, &status, 0) < 0)
119        {
120          fprintf (stderr, "waitpid() failed: %s\n", strerror (errno));
121          exit (1);
122          return FALSE;
123        }
124
125      failed = FALSE;
126
127      if (!_dbus_string_init (&filename) ||
128          !_dbus_string_copy (&failure_dir, 0,
129                              &filename, 0) ||
130          !_dbus_string_append_byte (&filename, '/'))
131        {
132          fprintf (stderr, "out of memory\n");
133          exit (1);
134        }
135
136      _dbus_string_append_int (&filename, total_attempts);
137
138      if (WIFEXITED (status))
139        {
140          if (WEXITSTATUS (status) != 0)
141            {
142              _dbus_string_append (&filename, "-exited-");
143              _dbus_string_append_int (&filename, WEXITSTATUS (status));
144              failed = TRUE;
145            }
146        }
147      else if (WIFSIGNALED (status))
148        {
149          _dbus_string_append (&filename, "signaled-");
150          _dbus_string_append_int (&filename, WTERMSIG (status));
151          failed = TRUE;
152        }
153
154      if (failed)
155        {
156          DBusError error;
157
158          _dbus_string_append (&filename, ".message-raw");
159
160          printf ("Child failed, writing %s\n", _dbus_string_get_const_data (&filename));
161
162          dbus_error_init (&error);
163          if (!_dbus_string_save_to_file (data, &filename, &error))
164            {
165              fprintf (stderr, "Failed to save failed message data: %s\n",
166                       error.message);
167              dbus_error_free (&error);
168              exit (1); /* so we can see the seed that was printed out */
169            }
170
171          failures_this_iteration += 1;
172
173	  _dbus_string_free (&filename);
174
175          return FALSE;
176        }
177      else
178	{
179	  _dbus_string_free (&filename);
180	  return TRUE;
181	}
182    }
183
184  _dbus_assert_not_reached ("should not be reached");
185  return TRUE;
186}
187
188static void
189randomly_shorten_or_lengthen (const DBusString *orig_data,
190                              DBusString       *mutated)
191{
192  int delta;
193
194  if (orig_data != mutated)
195    {
196      _dbus_string_set_length (mutated, 0);
197
198      if (!_dbus_string_copy (orig_data, 0, mutated, 0))
199        _dbus_assert_not_reached ("out of mem");
200    }
201
202  if (_dbus_string_get_length (mutated) == 0)
203    delta = random_int_in_range (0, 10);
204  else
205    delta = random_int_in_range (- _dbus_string_get_length (mutated),
206                                 _dbus_string_get_length (mutated) * 3);
207
208  if (delta < 0)
209    _dbus_string_shorten (mutated, - delta);
210  else if (delta > 0)
211    {
212      int i = 0;
213
214      i = _dbus_string_get_length (mutated);
215      if (!_dbus_string_lengthen (mutated, delta))
216        _dbus_assert_not_reached ("couldn't lengthen string");
217
218      while (i < _dbus_string_get_length (mutated))
219        {
220          _dbus_string_set_byte (mutated,
221                                 i,
222                                 random_int_in_range (0, 256));
223          ++i;
224        }
225    }
226}
227
228static void
229randomly_change_one_byte (const DBusString *orig_data,
230                          DBusString       *mutated)
231{
232  int i;
233
234  if (orig_data != mutated)
235    {
236      _dbus_string_set_length (mutated, 0);
237
238      if (!_dbus_string_copy (orig_data, 0, mutated, 0))
239        _dbus_assert_not_reached ("out of mem");
240    }
241
242  if (_dbus_string_get_length (mutated) == 0)
243    return;
244
245  i = random_int_in_range (0, _dbus_string_get_length (mutated));
246
247  _dbus_string_set_byte (mutated, i,
248                         random_int_in_range (0, 256));
249}
250
251static void
252randomly_remove_one_byte (const DBusString *orig_data,
253                          DBusString       *mutated)
254{
255  int i;
256
257  if (orig_data != mutated)
258    {
259      _dbus_string_set_length (mutated, 0);
260
261      if (!_dbus_string_copy (orig_data, 0, mutated, 0))
262        _dbus_assert_not_reached ("out of mem");
263    }
264
265  if (_dbus_string_get_length (mutated) == 0)
266    return;
267
268  i = random_int_in_range (0, _dbus_string_get_length (mutated));
269
270  _dbus_string_delete (mutated, i, 1);
271}
272
273
274static void
275randomly_add_one_byte (const DBusString *orig_data,
276                       DBusString       *mutated)
277{
278  int i;
279
280  if (orig_data != mutated)
281    {
282      _dbus_string_set_length (mutated, 0);
283
284      if (!_dbus_string_copy (orig_data, 0, mutated, 0))
285        _dbus_assert_not_reached ("out of mem");
286    }
287
288  i = random_int_in_range (0, _dbus_string_get_length (mutated));
289
290  _dbus_string_insert_byte (mutated, i,
291                            random_int_in_range (0, 256));
292}
293
294static void
295randomly_modify_length (const DBusString *orig_data,
296                        DBusString       *mutated)
297{
298  int i;
299  int byte_order;
300  const char *d;
301  dbus_uint32_t orig;
302  int delta;
303
304  if (orig_data != mutated)
305    {
306      _dbus_string_set_length (mutated, 0);
307
308      if (!_dbus_string_copy (orig_data, 0, mutated, 0))
309        _dbus_assert_not_reached ("out of mem");
310    }
311
312  if (_dbus_string_get_length (mutated) < 12)
313    return;
314
315  d = _dbus_string_get_const_data (mutated);
316
317  if (!(*d == DBUS_LITTLE_ENDIAN ||
318        *d == DBUS_BIG_ENDIAN))
319    return;
320
321  byte_order = *d;
322
323  i = random_int_in_range (4, _dbus_string_get_length (mutated) - 8);
324  i = _DBUS_ALIGN_VALUE (i, 4);
325
326  orig = _dbus_demarshal_uint32 (mutated, byte_order, i, NULL);
327
328  delta = random_int_in_range (-10, 10);
329
330  _dbus_marshal_set_uint32 (mutated, byte_order, i,
331                            (unsigned) (orig + delta));
332}
333
334static void
335randomly_set_extreme_ints (const DBusString *orig_data,
336                           DBusString       *mutated)
337{
338  int i;
339  int byte_order;
340  const char *d;
341  dbus_uint32_t orig;
342  static int which = 0;
343  unsigned int extreme_ints[] = {
344    _DBUS_INT_MAX,
345    _DBUS_UINT_MAX,
346    _DBUS_INT_MAX - 1,
347    _DBUS_UINT_MAX - 1,
348    _DBUS_INT_MAX - 2,
349    _DBUS_UINT_MAX - 2,
350    (unsigned int) (_DBUS_INT_MAX + 1),
351    (unsigned int) (_DBUS_UINT_MAX + 1),
352    _DBUS_INT_MAX + 2,
353    _DBUS_UINT_MAX + 2,
354    0, 1, 2, 3,
355    (unsigned int) -1,
356    (unsigned int) -2,
357    (unsigned int) -3
358  };
359
360  if (orig_data != mutated)
361    {
362      _dbus_string_set_length (mutated, 0);
363
364      if (!_dbus_string_copy (orig_data, 0, mutated, 0))
365        _dbus_assert_not_reached ("out of mem");
366    }
367
368  if (_dbus_string_get_length (mutated) < 12)
369    return;
370
371  d = _dbus_string_get_const_data (mutated);
372
373  if (!(*d == DBUS_LITTLE_ENDIAN ||
374        *d == DBUS_BIG_ENDIAN))
375    return;
376
377  byte_order = *d;
378
379  i = random_int_in_range (4, _dbus_string_get_length (mutated) - 8);
380  i = _DBUS_ALIGN_VALUE (i, 4);
381
382  orig = _dbus_demarshal_uint32 (mutated, byte_order, i, NULL);
383
384  which = random_int_in_range (0, _DBUS_N_ELEMENTS (extreme_ints));
385
386  _dbus_assert (which >= 0);
387  _dbus_assert (which < _DBUS_N_ELEMENTS (extreme_ints));
388
389  _dbus_marshal_set_uint32 (mutated, byte_order, i,
390                            extreme_ints[which]);
391}
392
393static int times_we_did_each_thing[6] = { 0, };
394
395static void
396randomly_do_n_things (const DBusString *orig_data,
397                      DBusString       *mutated,
398                      int               n)
399{
400  int i;
401  void (* functions[]) (const DBusString *orig_data,
402                        DBusString       *mutated) =
403    {
404      randomly_shorten_or_lengthen,
405      randomly_change_one_byte,
406      randomly_add_one_byte,
407      randomly_remove_one_byte,
408      randomly_modify_length,
409      randomly_set_extreme_ints
410    };
411
412  _dbus_string_set_length (mutated, 0);
413
414  if (!_dbus_string_copy (orig_data, 0, mutated, 0))
415    _dbus_assert_not_reached ("out of mem");
416
417  i = 0;
418  while (i < n)
419    {
420      int which;
421
422      which = random_int_in_range (0, _DBUS_N_ELEMENTS (functions));
423
424      (* functions[which]) (mutated, mutated);
425      times_we_did_each_thing[which] += 1;
426
427      ++i;
428    }
429}
430
431static dbus_bool_t
432find_breaks_based_on (const DBusString   *filename,
433                      dbus_bool_t         is_raw,
434                      DBusMessageValidity expected_validity,
435                      void               *data)
436{
437  DBusString orig_data;
438  DBusString mutated;
439  const char *filename_c;
440  dbus_bool_t retval;
441  int i;
442
443  filename_c = _dbus_string_get_const_data (filename);
444
445  retval = FALSE;
446
447  if (!_dbus_string_init (&orig_data))
448    _dbus_assert_not_reached ("could not allocate string\n");
449
450  if (!_dbus_string_init (&mutated))
451    _dbus_assert_not_reached ("could not allocate string\n");
452
453  if (!dbus_internal_do_not_use_load_message_file (filename, is_raw,
454                                                   &orig_data))
455    {
456      fprintf (stderr, "could not load file %s\n", filename_c);
457      goto failed;
458    }
459
460  i = 0;
461  while (i < 100)
462    {
463      randomly_change_one_byte (&orig_data, &mutated);
464      try_mutated_data (&mutated);
465
466      ++i;
467    }
468
469  i = 0;
470  while (i < 50)
471    {
472      randomly_modify_length (&orig_data, &mutated);
473      try_mutated_data (&mutated);
474
475      ++i;
476    }
477
478  i = 0;
479  while (i < 50)
480    {
481      randomly_remove_one_byte (&orig_data, &mutated);
482      try_mutated_data (&mutated);
483
484      ++i;
485    }
486
487  i = 0;
488  while (i < 50)
489    {
490      randomly_add_one_byte (&orig_data, &mutated);
491      try_mutated_data (&mutated);
492
493      ++i;
494    }
495
496  i = 0;
497  while (i < 50)
498    {
499      randomly_set_extreme_ints (&orig_data, &mutated);
500      try_mutated_data (&mutated);
501
502      ++i;
503    }
504
505  i = 0;
506  while (i < 15)
507    {
508      randomly_shorten_or_lengthen (&orig_data, &mutated);
509      try_mutated_data (&mutated);
510
511      ++i;
512    }
513
514  i = 0;
515  while (i < 42)
516    {
517      randomly_do_n_things (&orig_data, &mutated, 2);
518      try_mutated_data (&mutated);
519
520      ++i;
521    }
522
523  i = 0;
524  while (i < 42)
525    {
526      randomly_do_n_things (&orig_data, &mutated, 3);
527      try_mutated_data (&mutated);
528
529      ++i;
530    }
531
532  i = 0;
533  while (i < 42)
534    {
535      randomly_do_n_things (&orig_data, &mutated, 4);
536      try_mutated_data (&mutated);
537
538      ++i;
539    }
540
541  retval = TRUE;
542
543 failed:
544
545  _dbus_string_free (&orig_data);
546  _dbus_string_free (&mutated);
547
548  /* FALSE means end the whole process */
549  return retval;
550}
551
552static unsigned int
553get_random_seed (void)
554{
555  DBusString bytes;
556  unsigned int seed;
557  int fd;
558  const char *s;
559
560  seed = 0;
561
562  if (!_dbus_string_init (&bytes))
563    exit (1);
564
565  fd = open ("/dev/urandom", O_RDONLY);
566  if (fd < 0)
567    goto use_fallback;
568
569  if (_dbus_read (fd, &bytes, 4) != 4)
570    goto use_fallback;
571
572  close (fd);
573
574  s = _dbus_string_get_const_data (&bytes);
575
576  seed = * (unsigned int*) s;
577  goto out;
578
579 use_fallback:
580  {
581    long tv_usec;
582
583    fprintf (stderr, "could not open/read /dev/urandom, using current time for seed\n");
584
585    _dbus_get_current_time (NULL, &tv_usec);
586
587    seed = tv_usec;
588  }
589
590 out:
591  _dbus_string_free (&bytes);
592
593  return seed;
594}
595
596int
597main (int    argc,
598      char **argv)
599{
600  const char *test_data_dir;
601  const char *failure_dir_c;
602  int total_failures_found;
603
604  if (argc > 1)
605    test_data_dir = argv[1];
606  else
607    {
608      fprintf (stderr, "Must specify a top_srcdir/test/data directory\n");
609      return 1;
610    }
611
612  total_failures_found = 0;
613  total_attempts = 0;
614
615  if (!_dbus_string_init (&failure_dir))
616    return 1;
617
618  /* so you can leave it overnight safely */
619#define MAX_FAILURES 1000
620
621  while (total_failures_found < MAX_FAILURES)
622    {
623      unsigned int seed;
624
625      failures_this_iteration = 0;
626
627      seed = get_random_seed ();
628
629      _dbus_string_set_length (&failure_dir, 0);
630
631      if (!_dbus_string_append (&failure_dir, "failures-"))
632        return 1;
633
634      if (!_dbus_string_append_uint (&failure_dir, seed))
635        return 1;
636
637      failure_dir_c = _dbus_string_get_const_data (&failure_dir);
638
639      if (mkdir (failure_dir_c, 0700) < 0)
640        {
641          if (errno != EEXIST)
642            fprintf (stderr, "didn't mkdir %s: %s\n",
643                     failure_dir_c, strerror (errno));
644        }
645
646      printf ("next seed = %u \ttotal failures %d of %d attempts\n",
647              seed, total_failures_found, total_attempts);
648
649      srand (seed);
650
651      if (!dbus_internal_do_not_use_foreach_message_file (test_data_dir,
652                                                          find_breaks_based_on,
653                                                          NULL))
654        {
655          fprintf (stderr, "fatal error iterating over message files\n");
656          rmdir (failure_dir_c);
657          return 1;
658        }
659
660      printf ("  did %d random mutations: %d %d %d %d %d %d\n",
661              _DBUS_N_ELEMENTS (times_we_did_each_thing),
662              times_we_did_each_thing[0],
663              times_we_did_each_thing[1],
664              times_we_did_each_thing[2],
665              times_we_did_each_thing[3],
666              times_we_did_each_thing[4],
667              times_we_did_each_thing[5]);
668
669      printf ("Found %d failures with seed %u stored in %s\n",
670              failures_this_iteration, seed, failure_dir_c);
671
672      total_failures_found += failures_this_iteration;
673
674      rmdir (failure_dir_c); /* does nothing if non-empty */
675    }
676
677  return 0;
678}
679