break-loader.c revision f7d96bdf80129d95cf33f26a778ce2c94a818bd0
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, _DBUS_INT_MAX) ||
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          const char *filename_c;
157          DBusResultCode result;
158
159          _dbus_string_append (&filename, ".message-raw");
160
161          _dbus_string_get_const_data (&filename, &filename_c);
162          printf ("Child failed, writing %s\n",
163                  filename_c);
164
165          result = _dbus_string_save_to_file (data, &filename);
166
167          if (result != DBUS_RESULT_SUCCESS)
168            {
169              fprintf (stderr, "Failed to save failed message data: %s\n",
170                       dbus_result_to_string (result));
171              exit (1); /* so we can see the seed that was printed out */
172            }
173
174          failures_this_iteration += 1;
175
176	  _dbus_string_free (&filename);
177
178          return FALSE;
179        }
180      else
181	{
182	  _dbus_string_free (&filename);
183	  return TRUE;
184	}
185    }
186
187  _dbus_assert_not_reached ("should not be reached");
188  return TRUE;
189}
190
191static void
192randomly_shorten_or_lengthen (const DBusString *orig_data,
193                              DBusString       *mutated)
194{
195  int delta;
196
197  if (orig_data != mutated)
198    {
199      _dbus_string_set_length (mutated, 0);
200
201      if (!_dbus_string_copy (orig_data, 0, mutated, 0))
202        _dbus_assert_not_reached ("out of mem");
203    }
204
205  if (_dbus_string_get_length (mutated) == 0)
206    delta = random_int_in_range (0, 10);
207  else
208    delta = random_int_in_range (- _dbus_string_get_length (mutated),
209                                 _dbus_string_get_length (mutated) * 3);
210
211  if (delta < 0)
212    _dbus_string_shorten (mutated, - delta);
213  else if (delta > 0)
214    {
215      int i = 0;
216
217      i = _dbus_string_get_length (mutated);
218      if (!_dbus_string_lengthen (mutated, delta))
219        _dbus_assert_not_reached ("couldn't lengthen string");
220
221      while (i < _dbus_string_get_length (mutated))
222        {
223          _dbus_string_set_byte (mutated,
224                                 i,
225                                 random_int_in_range (0, 256));
226          ++i;
227        }
228    }
229}
230
231static void
232randomly_change_one_byte (const DBusString *orig_data,
233                          DBusString       *mutated)
234{
235  int i;
236
237  if (orig_data != mutated)
238    {
239      _dbus_string_set_length (mutated, 0);
240
241      if (!_dbus_string_copy (orig_data, 0, mutated, 0))
242        _dbus_assert_not_reached ("out of mem");
243    }
244
245  if (_dbus_string_get_length (mutated) == 0)
246    return;
247
248  i = random_int_in_range (0, _dbus_string_get_length (mutated));
249
250  _dbus_string_set_byte (mutated, i,
251                         random_int_in_range (0, 256));
252}
253
254static void
255randomly_remove_one_byte (const DBusString *orig_data,
256                          DBusString       *mutated)
257{
258  int i;
259
260  if (orig_data != mutated)
261    {
262      _dbus_string_set_length (mutated, 0);
263
264      if (!_dbus_string_copy (orig_data, 0, mutated, 0))
265        _dbus_assert_not_reached ("out of mem");
266    }
267
268  if (_dbus_string_get_length (mutated) == 0)
269    return;
270
271  i = random_int_in_range (0, _dbus_string_get_length (mutated));
272
273  _dbus_string_delete (mutated, i, 1);
274}
275
276
277static void
278randomly_add_one_byte (const DBusString *orig_data,
279                       DBusString       *mutated)
280{
281  int i;
282
283  if (orig_data != mutated)
284    {
285      _dbus_string_set_length (mutated, 0);
286
287      if (!_dbus_string_copy (orig_data, 0, mutated, 0))
288        _dbus_assert_not_reached ("out of mem");
289    }
290
291  i = random_int_in_range (0, _dbus_string_get_length (mutated));
292
293  _dbus_string_insert_byte (mutated, i,
294                            random_int_in_range (0, 256));
295}
296
297static void
298randomly_modify_length (const DBusString *orig_data,
299                        DBusString       *mutated)
300{
301  int i;
302  int byte_order;
303  const char *d;
304  dbus_uint32_t orig;
305  int delta;
306
307  if (orig_data != mutated)
308    {
309      _dbus_string_set_length (mutated, 0);
310
311      if (!_dbus_string_copy (orig_data, 0, mutated, 0))
312        _dbus_assert_not_reached ("out of mem");
313    }
314
315  if (_dbus_string_get_length (mutated) < 12)
316    return;
317
318  _dbus_string_get_const_data (mutated, &d);
319
320  if (!(*d == DBUS_LITTLE_ENDIAN ||
321        *d == DBUS_BIG_ENDIAN))
322    return;
323
324  byte_order = *d;
325
326  i = random_int_in_range (4, _dbus_string_get_length (mutated) - 8);
327  i = _DBUS_ALIGN_VALUE (i, 4);
328
329  orig = _dbus_demarshal_uint32 (mutated, byte_order, i, NULL);
330
331  delta = random_int_in_range (-10, 10);
332
333  _dbus_marshal_set_uint32 (mutated, byte_order, i,
334                            (unsigned) (orig + delta));
335}
336
337static void
338randomly_set_extreme_ints (const DBusString *orig_data,
339                           DBusString       *mutated)
340{
341  int i;
342  int byte_order;
343  const char *d;
344  dbus_uint32_t orig;
345  static int which = 0;
346  unsigned int extreme_ints[] = {
347    _DBUS_INT_MAX,
348    _DBUS_UINT_MAX,
349    _DBUS_INT_MAX - 1,
350    _DBUS_UINT_MAX - 1,
351    _DBUS_INT_MAX - 2,
352    _DBUS_UINT_MAX - 2,
353    (unsigned int) (_DBUS_INT_MAX + 1),
354    (unsigned int) (_DBUS_UINT_MAX + 1),
355    _DBUS_INT_MAX + 2,
356    _DBUS_UINT_MAX + 2,
357    0, 1, 2, 3,
358    (unsigned int) -1,
359    (unsigned int) -2,
360    (unsigned int) -3
361  };
362
363  if (orig_data != mutated)
364    {
365      _dbus_string_set_length (mutated, 0);
366
367      if (!_dbus_string_copy (orig_data, 0, mutated, 0))
368        _dbus_assert_not_reached ("out of mem");
369    }
370
371  if (_dbus_string_get_length (mutated) < 12)
372    return;
373
374  _dbus_string_get_const_data (mutated, &d);
375
376  if (!(*d == DBUS_LITTLE_ENDIAN ||
377        *d == DBUS_BIG_ENDIAN))
378    return;
379
380  byte_order = *d;
381
382  i = random_int_in_range (4, _dbus_string_get_length (mutated) - 8);
383  i = _DBUS_ALIGN_VALUE (i, 4);
384
385  orig = _dbus_demarshal_uint32 (mutated, byte_order, i, NULL);
386
387  which = random_int_in_range (0, _DBUS_N_ELEMENTS (extreme_ints));
388
389  _dbus_assert (which >= 0);
390  _dbus_assert (which < _DBUS_N_ELEMENTS (extreme_ints));
391
392  _dbus_marshal_set_uint32 (mutated, byte_order, i,
393                            extreme_ints[which]);
394}
395
396static int times_we_did_each_thing[6] = { 0, };
397
398static void
399randomly_do_n_things (const DBusString *orig_data,
400                      DBusString       *mutated,
401                      int               n)
402{
403  int i;
404  void (* functions[]) (const DBusString *orig_data,
405                        DBusString       *mutated) =
406    {
407      randomly_shorten_or_lengthen,
408      randomly_change_one_byte,
409      randomly_add_one_byte,
410      randomly_remove_one_byte,
411      randomly_modify_length,
412      randomly_set_extreme_ints
413    };
414
415  _dbus_string_set_length (mutated, 0);
416
417  if (!_dbus_string_copy (orig_data, 0, mutated, 0))
418    _dbus_assert_not_reached ("out of mem");
419
420  i = 0;
421  while (i < n)
422    {
423      int which;
424
425      which = random_int_in_range (0, _DBUS_N_ELEMENTS (functions));
426
427      (* functions[which]) (mutated, mutated);
428      times_we_did_each_thing[which] += 1;
429
430      ++i;
431    }
432}
433
434static dbus_bool_t
435find_breaks_based_on (const DBusString   *filename,
436                      dbus_bool_t         is_raw,
437                      DBusMessageValidity expected_validity,
438                      void               *data)
439{
440  DBusString orig_data;
441  DBusString mutated;
442  const char *filename_c;
443  dbus_bool_t retval;
444  int i;
445
446  _dbus_string_get_const_data (filename, &filename_c);
447
448  retval = FALSE;
449
450  if (!_dbus_string_init (&orig_data, _DBUS_INT_MAX))
451    _dbus_assert_not_reached ("could not allocate string\n");
452
453  if (!_dbus_string_init (&mutated, _DBUS_INT_MAX))
454    _dbus_assert_not_reached ("could not allocate string\n");
455
456  if (!dbus_internal_do_not_use_load_message_file (filename, is_raw,
457                                                   &orig_data))
458    {
459      fprintf (stderr, "could not load file %s\n", filename_c);
460      goto failed;
461    }
462
463  i = 0;
464  while (i < 100)
465    {
466      randomly_change_one_byte (&orig_data, &mutated);
467      try_mutated_data (&mutated);
468
469      ++i;
470    }
471
472  i = 0;
473  while (i < 50)
474    {
475      randomly_modify_length (&orig_data, &mutated);
476      try_mutated_data (&mutated);
477
478      ++i;
479    }
480
481  i = 0;
482  while (i < 50)
483    {
484      randomly_remove_one_byte (&orig_data, &mutated);
485      try_mutated_data (&mutated);
486
487      ++i;
488    }
489
490  i = 0;
491  while (i < 50)
492    {
493      randomly_add_one_byte (&orig_data, &mutated);
494      try_mutated_data (&mutated);
495
496      ++i;
497    }
498
499  i = 0;
500  while (i < 50)
501    {
502      randomly_set_extreme_ints (&orig_data, &mutated);
503      try_mutated_data (&mutated);
504
505      ++i;
506    }
507
508  i = 0;
509  while (i < 15)
510    {
511      randomly_shorten_or_lengthen (&orig_data, &mutated);
512      try_mutated_data (&mutated);
513
514      ++i;
515    }
516
517  i = 0;
518  while (i < 42)
519    {
520      randomly_do_n_things (&orig_data, &mutated, 2);
521      try_mutated_data (&mutated);
522
523      ++i;
524    }
525
526  i = 0;
527  while (i < 42)
528    {
529      randomly_do_n_things (&orig_data, &mutated, 3);
530      try_mutated_data (&mutated);
531
532      ++i;
533    }
534
535  i = 0;
536  while (i < 42)
537    {
538      randomly_do_n_things (&orig_data, &mutated, 4);
539      try_mutated_data (&mutated);
540
541      ++i;
542    }
543
544  retval = TRUE;
545
546 failed:
547
548  _dbus_string_free (&orig_data);
549  _dbus_string_free (&mutated);
550
551  /* FALSE means end the whole process */
552  return retval;
553}
554
555static unsigned int
556get_random_seed (void)
557{
558  DBusString bytes;
559  unsigned int seed;
560  int fd;
561  const char *s;
562
563  seed = 0;
564
565  if (!_dbus_string_init (&bytes, _DBUS_INT_MAX))
566    exit (1);
567
568  fd = open ("/dev/urandom", O_RDONLY);
569  if (fd < 0)
570    goto use_fallback;
571
572  if (_dbus_read (fd, &bytes, 4) != 4)
573    goto use_fallback;
574
575  close (fd);
576
577  _dbus_string_get_const_data (&bytes, &s);
578
579  seed = * (unsigned int*) s;
580  goto out;
581
582 use_fallback:
583  {
584    long tv_usec;
585
586    fprintf (stderr, "could not open/read /dev/urandom, using current time for seed\n");
587
588    _dbus_get_current_time (NULL, &tv_usec);
589
590    seed = tv_usec;
591  }
592
593 out:
594  _dbus_string_free (&bytes);
595
596  return seed;
597}
598
599int
600main (int    argc,
601      char **argv)
602{
603  const char *test_data_dir;
604  const char *failure_dir_c;
605  int total_failures_found;
606
607  if (argc > 1)
608    test_data_dir = argv[1];
609  else
610    {
611      fprintf (stderr, "Must specify a top_srcdir/test/data directory\n");
612      return 1;
613    }
614
615  total_failures_found = 0;
616  total_attempts = 0;
617
618  if (!_dbus_string_init (&failure_dir, _DBUS_INT_MAX))
619    return 1;
620
621  /* so you can leave it overnight safely */
622#define MAX_FAILURES 1000
623
624  while (total_failures_found < MAX_FAILURES)
625    {
626      unsigned int seed;
627
628      failures_this_iteration = 0;
629
630      seed = get_random_seed ();
631
632      _dbus_string_set_length (&failure_dir, 0);
633
634      if (!_dbus_string_append (&failure_dir, "failures-"))
635        return 1;
636
637      if (!_dbus_string_append_uint (&failure_dir, seed))
638        return 1;
639
640      _dbus_string_get_const_data (&failure_dir, &failure_dir_c);
641
642      if (mkdir (failure_dir_c, 0700) < 0)
643        {
644          if (errno != EEXIST)
645            fprintf (stderr, "didn't mkdir %s: %s\n",
646                     failure_dir_c, strerror (errno));
647        }
648
649      printf ("next seed = %u \ttotal failures %d of %d attempts\n",
650              seed, total_failures_found, total_attempts);
651
652      srand (seed);
653
654      if (!dbus_internal_do_not_use_foreach_message_file (test_data_dir,
655                                                          find_breaks_based_on,
656                                                          NULL))
657        {
658          fprintf (stderr, "fatal error iterating over message files\n");
659          rmdir (failure_dir_c);
660          return 1;
661        }
662
663      printf ("  did %d random mutations: %d %d %d %d %d %d\n",
664              _DBUS_N_ELEMENTS (times_we_did_each_thing),
665              times_we_did_each_thing[0],
666              times_we_did_each_thing[1],
667              times_we_did_each_thing[2],
668              times_we_did_each_thing[3],
669              times_we_did_each_thing[4],
670              times_we_did_each_thing[5]);
671
672      printf ("Found %d failures with seed %u stored in %s\n",
673              failures_this_iteration, seed, failure_dir_c);
674
675      total_failures_found += failures_this_iteration;
676
677      rmdir (failure_dir_c); /* does nothing if non-empty */
678    }
679
680  return 0;
681}
682