[Pkg-telepathy-commits] [libnice] 218/265: tests: Add a test-pseudotcp-fuzzy test for fuzzing PseudoTcpSocket

Simon McVittie smcv at debian.org
Wed May 14 12:05:10 UTC 2014


This is an automated email from the git hooks/post-receive script.

smcv pushed a commit to branch debian
in repository libnice.

commit b8508d2711900227a6b33e92d7f3f0b5b8420c38
Author: Philip Withnall <philip.withnall at collabora.co.uk>
Date:   Wed Apr 16 12:41:47 2014 +0100

    tests: Add a test-pseudotcp-fuzzy test for fuzzing PseudoTcpSocket
    
    As documented at the top of the file, this creates two PseudoTcpSockets
    in a loopback configuration, and mutates the raw packets which are sent
    between them, hoping to cause assertion failures and illegal memory
    accesses in the pseudotcp code.
    
    Best run under Valgrind or GDB.
---
 .gitignore                   |   1 +
 tests/Makefile.am            |   3 +
 tests/test-pseudotcp-fuzzy.c | 467 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 471 insertions(+)

diff --git a/.gitignore b/.gitignore
index 67ec2d8..6c28802 100644
--- a/.gitignore
+++ b/.gitignore
@@ -142,6 +142,7 @@ tests/test-send-recv
 tests/test-mainloop
 tests/test-priority
 tests/test-pseudotcp
+tests/test-pseudotcp-fuzzy
 tests/test-restart
 tests/test-thread
 tests/test-new-dribble
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 211e6c9..5828bc8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -22,6 +22,7 @@ COMMON_LDADD = $(top_builddir)/agent/libagent.la $(top_builddir)/socket/libsocke
 
 check_PROGRAMS = \
 	test-pseudotcp \
+	test-pseudotcp-fuzzy \
 	test-bsd \
 	test \
 	test-address \
@@ -52,6 +53,8 @@ noinst_HEADERS = test-io-stream-common.h
 
 test_pseudotcp_LDADD = $(COMMON_LDADD)
 
+test_pseudotcp_fuzzy_LDADD = $(COMMON_LDADD) -lm
+
 test_bsd_LDADD = $(COMMON_LDADD)
 
 test_LDADD = $(COMMON_LDADD)
diff --git a/tests/test-pseudotcp-fuzzy.c b/tests/test-pseudotcp-fuzzy.c
new file mode 100644
index 0000000..0ec9882
--- /dev/null
+++ b/tests/test-pseudotcp-fuzzy.c
@@ -0,0 +1,467 @@
+/* vim: et ts=2 sw=2 tw=80: */
+/*
+ * This file is part of the Nice GLib ICE library.
+ *
+ * (C) 2010, 2014 Collabora Ltd.
+ *  Contact: Philip Withnall
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Nice GLib ICE library.
+ *
+ * The Initial Developers of the Original Code are Collabora Ltd and Nokia
+ * Corporation. All Rights Reserved.
+ *
+ * Contributors:
+ *   Philip Withnall, Collabora Ltd.
+ *   Youness Alaoui, Collabora Ltd.
+ *
+ * Alternatively, the contents of this file may be used under the terms of the
+ * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
+ * case the provisions of LGPL are applicable instead of those above. If you
+ * wish to allow use of your version of this file only under the terms of the
+ * LGPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replace
+ * them with the notice and other provisions required by the LGPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the LGPL.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <locale.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <math.h>
+
+#include "pseudotcp.h"
+
+
+/**
+ * A fuzzing test for the pseudotcp socket. This connects two sockets in a
+ * loopback arrangement, with the packet output from one being fed to the other,
+ * and vice-versa. Fuzzing happens on the packet interface between the two,
+ * mutating the packets slightly and seeing what happens.
+ *
+ * The input data to the left-most socket is read from a file. The output data
+ * from the loopback is written to another file, although this probably isn’t
+ * very useful. If no files are provided, a small amount of dummy data is sent
+ * through the sockets instead. This almost certainly won’t catch any bugs, and
+ * is just present to allow this test to be run as part of `make check` so it
+ * doesn’t bit rot.
+ *
+ * A good command to generate an input file is:
+ *     dd if=/dev/urandom of=rand count=10000 ibs=1024
+ *
+ * None of the data is validated, and the test results are effectively the 1-bit
+ * value of ‘did it crash?’. In particular, the output file is not validated,
+ * and the TCP packets emitted by both sockets are not checked for validity.
+ *
+ * It is suggested that this test is run under GDB and Valgrind. Any crashes or
+ * errors which are detected can be reproduced by providing the same input file
+ * and seed (using the --seed option). The seed is printed out at the beginning
+ * of each test run.
+ */
+
+
+PseudoTcpSocket *left;
+PseudoTcpSocket *right;
+GMainLoop *main_loop = NULL;
+GRand *prng = NULL;
+gint retval = 0;
+FILE *in = NULL;
+FILE *out = NULL;
+int total_read = 0;
+int total_wrote = 0;
+guint left_clock = 0;
+guint right_clock = 0;
+gboolean left_closed = FALSE;
+gboolean right_closed = FALSE;
+gboolean reading_done = FALSE;
+
+/* Number of bytes of payload each socket has received so far. */
+guint32 left_stream_pos = 0;
+guint32 right_stream_pos = 0;
+
+/* Configuration options. */
+gint64 seed = 0;
+guint32 fuzz_start_pos = 1;  /* bytes into stream payload; after the SYN-ACKs */
+guint n_changes_lambda = 2;  /* lambda parameter for a Poisson distribution
+                              * controlling the number of mutations made to each
+                              * packet */
+
+
+static void
+adjust_clock (PseudoTcpSocket *sock);
+
+
+static void
+write_to_sock (PseudoTcpSocket *sock)
+{
+  gchar buf[1024];
+  gsize len;
+  gint wlen;
+  guint total = 0;
+
+  while (TRUE) {
+    len = fread (buf, 1, sizeof(buf), in);
+    if (len == 0) {
+      g_debug ("Done reading data from file");
+      g_assert (feof (in));
+      reading_done = TRUE;
+      pseudo_tcp_socket_close (sock, FALSE);
+      break;
+    } else {
+      wlen = pseudo_tcp_socket_send (sock, buf, len);
+      g_debug ("Sending %" G_GSIZE_FORMAT " bytes : %d", len, wlen);
+      total += wlen;
+      total_read += wlen;
+      if (wlen < (gint) len) {
+        g_debug ("seeking  %ld from %lu", wlen - len, ftell (in));
+        fseek (in, wlen - len, SEEK_CUR);
+        g_assert (!feof (in));
+        g_debug ("Socket queue full after %d bytes written", total);
+        break;
+      }
+    }
+  }
+  adjust_clock (sock);
+}
+
+static void
+opened (PseudoTcpSocket *sock, gpointer data)
+{
+  g_debug ("Socket %p Opened", sock);
+  if (sock == left) {
+    if (in)
+      write_to_sock (sock);
+    else {
+      pseudo_tcp_socket_send (sock, "abcdefghijklmnopqrstuvwxyz", 26);
+      reading_done = TRUE;
+      pseudo_tcp_socket_close (sock, FALSE);
+    }
+  }
+}
+
+static void
+readable (PseudoTcpSocket *sock, gpointer data)
+{
+  gchar buf[1024];
+  gint len;
+  g_debug ("Socket %p Readable", sock);
+
+  do {
+    len = pseudo_tcp_socket_recv (sock, buf, sizeof(buf));
+
+    if (len > 0) {
+      g_debug ("Read %d bytes", len);
+      if (out) {
+        if (fwrite (buf, len, 1, out) == 0)
+          g_debug ("Error writing to output file");
+        else {
+          total_wrote += len;
+
+          g_assert (total_wrote <= total_read);
+          g_debug ("Written %d bytes, need %d bytes", total_wrote, total_read);
+          if (total_wrote == total_read && feof (in)) {
+            g_assert (reading_done);
+            pseudo_tcp_socket_close (sock, FALSE);
+          }
+        }
+      } else {
+        pseudo_tcp_socket_close (sock, FALSE);
+      }
+    }
+  } while (len > 0);
+
+  if (len == -1 &&
+      pseudo_tcp_socket_get_error (sock) != EWOULDBLOCK) {
+    g_printerr ("Error reading from socket: error code %d.\n",
+        pseudo_tcp_socket_get_error (sock));
+
+    retval = -1;
+    g_main_loop_quit (main_loop);
+    return;
+  }
+}
+
+static void
+writable (PseudoTcpSocket *sock, gpointer data)
+{
+  g_debug ("Socket %p Writable", sock);
+  if (in && sock == left)
+    write_to_sock (sock);
+}
+
+static void
+closed (PseudoTcpSocket *sock, guint32 err, gpointer data)
+{
+  /* Don’t treat this as an error, since we’re throwing rubbish into the
+   * socket and can hardly expect it to complete successfully. */
+  g_debug ("Socket %p Closed : %d", sock, err);
+  retval = 0;
+  g_main_loop_quit (main_loop);
+}
+
+struct notify_data {
+  PseudoTcpSocket *sock;
+  guint32 len;
+  guint32 stream_pos;
+  guint8 buffer[];
+};
+
+/**
+ * random_int_poisson:
+ * @lambda: Lambda parameter for the distribution function, which must be
+ * non-zero
+ *
+ * Generate a random variable from a Poisson distribution with parameter
+ * @lambda. This consumes one %gdouble’s worth of randomness from the global
+ * @prng.
+ *
+ * This is implemented using the inverse transform of the Poisson CDF, and is
+ * guaranteed to return in time linearly proportional to @lambda.
+ *
+ * Returns: Poisson-distributed pseudo-random variable
+ */
+static guint32
+random_int_poisson (guint lambda)
+{
+  gdouble U;
+  guint32 i;
+  gdouble p, F;
+
+  g_return_val_if_fail (lambda > 0, 0);
+
+  /*
+   * Reference: http://www.cs.bgu.ac.il/~mps042/invtransnote.htm,
+   * §Simulating a Poisson random variable.
+   */
+  U = g_rand_double (prng);  /* step 1 */
+  i = 0;
+  p = exp (0.0 - (gdouble) lambda);
+  F = p;  /* step 2 */
+
+  while (U >= F) {  /* step 3 */
+    p = (lambda * p) / (i + 1);
+    F += p;
+    i += 1;  /* step 4 and 5 */
+  }
+
+  return i;
+}
+
+static guint32
+fuzz_packet (guint8 *buf, guint32 len, guint32 stream_pos)
+{
+  guint32 i;
+  guint n_changes;
+#define TCP_HEADER_LENGTH 32 /* bytes; or thereabouts (include some options) */
+
+  /* Do we want to fuzz this packet? */
+  if (stream_pos < fuzz_start_pos) {
+    return len;
+  }
+
+  /* Get fuzzing. Only bother fuzzing the header; fuzzing the payload is
+   * pointless. Weight the number of changes towards having only a few changes,
+   * since that makes them less likely to be summarily rejected. */
+  n_changes = random_int_poisson (n_changes_lambda);
+  g_debug ("Making %u changes for bytes at stream position %u:",
+      n_changes, stream_pos);
+
+  for (i = 0; i < n_changes; i++) {
+    guint32 pos = g_rand_int_range (prng, 0, MIN (len, TCP_HEADER_LENGTH));
+    g_debug (" • Changing byte %u.", stream_pos + pos);
+    buf[pos] = g_rand_int_range (prng, 0, G_MAXUINT8 + 1);
+  }
+
+  return len;
+}
+
+static gboolean
+notify_packet (gpointer user_data)
+{
+  struct notify_data *data = (struct notify_data*) user_data;
+
+  /* Fuzz the packet. */
+  data->len = fuzz_packet (data->buffer, data->len, data->stream_pos);
+
+  pseudo_tcp_socket_notify_packet (data->sock,
+      (gchar *) data->buffer, data->len);
+  adjust_clock (data->sock);
+
+  g_free (data);
+  return FALSE;
+}
+
+static PseudoTcpWriteResult
+write_packet (PseudoTcpSocket *sock, const gchar *buffer, guint32 len,
+    gpointer user_data)
+{
+  struct notify_data *data;
+  PseudoTcpState state;
+  g_object_get (sock, "state", &state, NULL);
+
+  data = g_malloc (sizeof(struct notify_data) + len);
+
+  g_debug ("Socket %p(%d) Writing : %d bytes", sock, state, len);
+
+  memcpy (data->buffer, buffer, len);
+  data->len = len;
+
+  if (sock == left) {
+    data->stream_pos = left_stream_pos;
+    left_stream_pos += len;
+    data->sock = right;
+  } else {
+    data->stream_pos = right_stream_pos;
+    right_stream_pos += len;
+    data->sock = left;
+  }
+
+  g_idle_add (notify_packet, data);
+
+  return WR_SUCCESS;
+}
+
+
+static gboolean notify_clock (gpointer data)
+{
+  PseudoTcpSocket *sock = (PseudoTcpSocket *)data;
+  //g_debug ("Socket %p: Notifying clock", sock);
+  pseudo_tcp_socket_notify_clock (sock);
+  adjust_clock (sock);
+  return FALSE;
+}
+
+static void adjust_clock (PseudoTcpSocket *sock)
+{
+  guint64 timeout = 0;
+
+  if (pseudo_tcp_socket_get_next_clock (sock, &timeout)) {
+    timeout -= g_get_monotonic_time () / 1000;
+    g_debug ("Socket %p: Adjusting clock to %ld ms", sock, timeout);
+    if (sock == left) {
+      if (left_clock != 0)
+         g_source_remove (left_clock);
+      left_clock = g_timeout_add (timeout, notify_clock, sock);
+    } else {
+      if (right_clock != 0)
+         g_source_remove (right_clock);
+      right_clock = g_timeout_add (timeout, notify_clock, sock);
+    }
+  } else {
+    g_debug ("Socket %p should be destroyed, it's done", sock);
+    if (sock == left)
+      left_closed = TRUE;
+    else
+      right_closed = TRUE;
+    if (left_closed && right_closed)
+      g_main_loop_quit (main_loop);
+  }
+}
+
+static GOptionEntry entries[] = {
+  { "seed", 's', 0, G_OPTION_ARG_INT64, &seed, "PRNG seed", "N" },
+  { "fuzz-start-position", 'p', 0, G_OPTION_ARG_INT, &fuzz_start_pos,
+    "Number of bytes into the stream to start fuzzing after", "B" },
+  { "fuzz-n-changes-lambda", 'l', 0, G_OPTION_ARG_INT, &n_changes_lambda,
+    "Lambda value for the Poisson distribution controlling the number of "
+    "changes made to each packet", "λ" },
+  { NULL }
+};
+
+int main (int argc, char *argv[])
+{
+  PseudoTcpCallbacks cbs = {
+    NULL, opened, readable, writable, closed, write_packet
+  };
+  GOptionContext *context;
+  GError *error = NULL;
+
+  setlocale (LC_ALL, "");
+  g_type_init ();
+
+  /* Configuration. */
+  context = g_option_context_new ("— fuzz-test the pseudotcp socket");
+  g_option_context_add_main_entries (context, entries, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, &error)) {
+    g_printerr ("Option parsing failed: %s\n", error->message);
+    goto context_error;
+  }
+
+  if (n_changes_lambda == 0) {
+    g_printerr ("Option parsing failed: %s\n",
+        "Lambda values must be positive.");
+    goto context_error;
+  }
+
+  g_option_context_free (context);
+
+  /* Tweak the configuration. */
+  if (seed == 0) {
+    seed = g_get_real_time ();
+  }
+
+  /* Open the input and output files */
+  if (argc >= 3) {
+    in = fopen (argv[1], "r");
+    out = fopen (argv[2], "w");
+  }
+
+  /* Set up the main loop and sockets. */
+  main_loop = g_main_loop_new (NULL, FALSE);
+
+  g_print ("Using seed: %" G_GINT64_FORMAT ", start position: %u, λ: %u\n",
+      seed, fuzz_start_pos, n_changes_lambda);
+  prng = g_rand_new_with_seed (seed);
+
+  pseudo_tcp_set_debug_level (PSEUDO_TCP_DEBUG_VERBOSE);
+
+  left = pseudo_tcp_socket_new (0, &cbs);
+  right = pseudo_tcp_socket_new (0, &cbs);
+  g_debug ("Left: %p. Right: %p", left, right);
+
+  pseudo_tcp_socket_notify_mtu (left, 1496);
+  pseudo_tcp_socket_notify_mtu (right, 1496);
+
+  pseudo_tcp_socket_connect (left);
+  adjust_clock (left);
+  adjust_clock (right);
+
+  /* Run the main loop. */
+  g_main_loop_run (main_loop);
+  g_main_loop_unref (main_loop);
+
+  g_object_unref (left);
+  g_object_unref (right);
+
+  g_rand_free (prng);
+
+  if (in != NULL)
+    fclose (in);
+  if (out != NULL)
+    fclose (out);
+
+  return retval;
+
+context_error:
+  g_printerr ("\n%s\n", g_option_context_get_help (context, TRUE, NULL));
+  g_option_context_free (context);
+
+  return 1;
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-telepathy/libnice.git



More information about the Pkg-telepathy-commits mailing list