[WIP]

Change-Id: I48049113e92e3b44a14f1a86dda9aa895d5ec016
diff --git a/system/uapp/fuzz/local-agent.cpp b/system/uapp/fuzz/local-agent.cpp
new file mode 100644
index 0000000..b5ee07f
--- /dev/null
+++ b/system/uapp/fuzz/local-agent.cpp
@@ -0,0 +1,95 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzz/agent.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <magenta/types.h>
+#include <magenta/errors.h>
+
+namespace fuzz {
+
+class LocalAgent : public Agent {
+protected:
+    void OnStdout(const char* str) override {
+        fprintf(stdout, "%s\n", str);
+    }
+
+    void OnStderr(const char* str) override {
+        fprintf(stderr, "%s\n", str);
+    }
+};
+
+} // namespace fuzz
+
+namespace {
+
+void Quit(const char *errfmt, ...) {
+    if (errfmt) {
+        va_list ap;
+        printf("error: ");
+        va_start(ap, errfmt);
+        vprintf(errfmt, ap);
+        va_end(ap);
+        printf("\n");
+    }
+    printf("usage: local-agent [-t msecs] -- <fuzzer> <fuzzer-args>\n");
+    exit(errfmt ? 1 : 0);
+}
+
+void ParseNVal(uint32_t* nval, const char* arg) {
+    char* endptr = nullptr;
+    unsigned long long value = strtoul(arg, &endptr, 0);
+    if (arg[0] == '\0' || endptr[0] != '\0') {
+        Quit("unable to parse number: %s", arg);
+    }
+    if (value > UINT32_MAX) {
+        Quit("value is too large: %s", arg);
+    }
+    *nval = static_cast<uint32_t>(value);
+}
+
+} // namespace
+
+int main(int argc, const char** argv) {
+    fuzz::LocalAgent agent;
+    uint32_t timeout = 5000;
+    uint32_t *nval = nullptr;
+    const char **sval = nullptr;
+    // Consume our own name
+    ++argv;
+    --argc;
+    while (argc != 0) {
+        // Save the current argument and advance to the next.
+        const char* arg = *argv;
+        ++argv;
+        --argc;
+        if (nval) {
+            // Numeric value requested.
+            ParseNVal(nval, arg);
+            nval = nullptr;
+        } else if (sval) {
+            // String value requested
+            *sval = arg;
+            sval = nullptr;
+        } else if (strcmp(arg, "-h") == 0 || strcmp(arg, "-?") == 0 ||
+                   strcmp(arg, "--help") == 0) {
+            // Print usage and exit!
+            Quit(nullptr);
+        } else if (strcmp(arg, "-t") == 0 || strcmp(arg, "--timeout") == 0) {
+            // Queue the timeout as the next thing to parse.
+            nval = &timeout;
+        } else if (strcmp(arg, "--") == 0) {
+            // The rest are fuzzer arguments
+            break;
+        } else {
+            // Add more options here if needed...
+            Quit("unknown option: %s", arg);
+        }
+    }
+    return agent.Run(argc, argv, timeout);
+}
diff --git a/system/uapp/fuzz/rules.mk b/system/uapp/fuzz/rules.mk
new file mode 100644
index 0000000..d6d233d
--- /dev/null
+++ b/system/uapp/fuzz/rules.mk
@@ -0,0 +1,28 @@
+# Copyright 2016 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_NAME := fuzz/local-agent
+
+MODULE_TYPE := userapp
+
+MODULE_SRCS += \
+    $(LOCAL_DIR)/local-agent.cpp
+
+MODULE_LIBS := \
+    system/ulib/c \
+    system/ulib/launchpad \
+    system/ulib/magenta \
+
+MODULE_STATIC_LIBS := \
+    system/ulib/fuzz \
+    system/ulib/mx \
+    system/ulib/mxalloc \
+    system/ulib/mxcpp \
+    system/ulib/mxtl \
+
+include make/module.mk
diff --git a/system/ulib/fuzz/agent.cpp b/system/ulib/fuzz/agent.cpp
new file mode 100644
index 0000000..05b5014
--- /dev/null
+++ b/system/ulib/fuzz/agent.cpp
@@ -0,0 +1,252 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzz/agent.h>
+
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fuzz/channel.h>
+#include <launchpad/launchpad.h>
+#include <magenta/errors.h>
+#include <magenta/status.h>
+#include <magenta/types.h>
+#include <mx/channel.h>
+#include <mx/object.h>
+#include <mx/time.h>
+#include <mxalloc/new.h>
+
+namespace fuzz {
+
+namespace {
+
+// Max length of time to wait while reading handshake messages.
+const uint32_t kHandshakeTimeout = MX_MSEC(500);
+
+const size_t kMaxLineLen = 1024;
+
+const char* kSentinel = "agent";
+
+// Helper for converting POSIX errno's into mx_status_t's.
+mx_status_t ToStatus(ssize_t rc) {
+    if (rc >= 0) {
+        return NO_ERROR;
+    }
+    if (rc == EPIPE) {
+        return ERR_PEER_CLOSED;
+    }
+    if (rc == EIO) {
+        return ERR_IO;
+    }
+    return ERR_INTERNAL;
+}
+
+} // namespace
+
+// Public methods
+
+Agent::~Agent() {}
+
+mx_status_t Agent::Run(int argc, const char** argv, uint32_t timeout) {
+    mx_status_t rc;
+    // We must have an executable, and we limit ourselves to 255 parameters.
+    if (argc == 0 || argc > UINT8_MAX || !argv) {
+        return ERR_INVALID_ARGS;
+    }
+    argv0_ = argv[0];
+    // Initialize the mutex and lock it before spawning the stdout and stderr
+    // threads.  This will keep them blocked until the handshake is complete.
+    // If we encounter an error, it is fine to release the lock as they will not
+    // have a valid fd and will exit immediately.
+
+    // Create the thread signaling event.
+    if ((rc = mx::event::create(0, &ready_)) != NO_ERROR) {
+        return rc;
+    }
+    ready_.signal(MX_USER_SIGNAL_ALL, MX_SIGNAL_NONE);
+    // Create the stdout and stderr threads, which will initially wait for the
+    // ready signal. On error, the event will be destroyed, waking those threads
+    // with an error.
+    if (thrd_create(&out_thrd_, Agent::HandleStdout, this) != thrd_success) {
+        return ERR_NO_RESOURCES;
+    }
+    if (thrd_create(&err_thrd_, Agent::HandleStderr, this) != thrd_success) {
+        return ERR_NO_RESOURCES;
+    }
+    // Create the channel and perform the handshake.
+    mx_handle_t remote = MX_HANDLE_INVALID;
+    if ((rc = fuzzer_.Listen(&remote)) != NO_ERROR ||
+        (rc = Launch(argc, argv, remote)) != NO_ERROR ||
+        (rc = Handshake(timeout)) != NO_ERROR) {
+        return rc;
+    }
+    // Signal the threads and start processing state messages.
+    ready_.signal(MX_SIGNAL_NONE, MX_EVENT_SIGNALED);
+    return HandleState();
+}
+
+// Protected methods
+
+Agent::Agent()
+    : argv0_(nullptr), start_(0), stdin_(-1), stdout_(-1), stderr_(-1) {}
+
+mx_status_t Agent::Launch(int argc, const char** argv, mx_handle_t remote) {
+    launchpad_t* launchpad;
+    // Create a process from the supplied arguments and channel.
+    launchpad_create(MX_HANDLE_INVALID, argv[0], &launchpad);
+    launchpad_load_from_file(launchpad, argv[0]);
+    launchpad_set_args(launchpad, argc, argv);
+    launchpad_add_handle(launchpad, remote, Channel::kHandleInfo);
+    // Clone everything except stdio.  Set those up as pipes instead.
+    launchpad_clone(launchpad, LP_CLONE_ALL & ~LP_CLONE_MXIO_STDIO);
+    launchpad_add_pipe(launchpad, &stdin_, STDIN_FILENO);
+    launchpad_add_pipe(launchpad, &stdout_, STDOUT_FILENO);
+    launchpad_add_pipe(launchpad, &stderr_, STDERR_FILENO);
+    // Launch!
+    return launchpad_go(launchpad, proc_.get_address(), nullptr);
+}
+
+mx_status_t Agent::ToStdin(const char* in) {
+    size_t len = strlen(in);
+    if (len > INT_MAX) {
+        return ERR_OUT_OF_RANGE;
+    }
+    return ToStatus(write(stdin_, in, strlen(in)));
+}
+
+// Private static methods (i.e. threads)
+
+int Agent::HandleStdout(void* arg) {
+    Agent* agent = static_cast<Agent*>(arg);
+    return agent->HandleStdio(agent->stdout_);
+}
+
+int Agent::HandleStderr(void* arg) {
+    Agent* agent = static_cast<Agent*>(arg);
+    return agent->HandleStdio(agent->stderr_);
+}
+
+// Private methods
+
+mx_status_t Agent::Handshake(mx_time_t timeout) {
+    mx_status_t rc;
+    fuzzer_.SetTimeout(kHandshakeTimeout);
+    // Send the START message with the timeout.
+    if ((rc = fuzzer_.Write(&timeout, sizeof(timeout))) != NO_ERROR) {
+        return rc;
+    }
+    // Write the current state (may be empty).
+    if ((rc = fuzzer_.Write(state_.get(), state_.size())) != NO_ERROR) {
+        return rc;
+    }
+    // Read the initial state.
+    if ((rc = fuzzer_.ReadBuf(&state_)) != NO_ERROR) {
+        return rc;
+    }
+    fuzzer_.SetTimeout(timeout);
+    return NO_ERROR;
+}
+
+mx_status_t Agent::HandleState() {
+    mx_status_t rc;
+    while ((rc = fuzzer_.ReadBuf(&state_)) == NO_ERROR) {
+    }
+    fuzzer_.Close();
+    // Other end didn't respond; might have crashed
+    if (rc == ERR_TIMED_OUT || rc == ERR_PEER_CLOSED) {
+        return HandleCrash();
+    }
+    return rc;
+}
+
+mx_status_t Agent::HandleCrash() {
+    mx_status_t rc;
+    mx_info_process_t info;
+    char buf[kMaxLineLen];
+    if ((rc = proc_.get_info(MX_INFO_PROCESS, &info, sizeof(info), nullptr,
+                             nullptr)) != NO_ERROR) {
+        // Report mx_object_get_info failure.
+        snprintf(buf, sizeof(buf),
+                 "%s: unable to get process info for %s; "
+                 "mx_object_get_info returned %s",
+                 kSentinel, argv0_, mx_status_get_string(rc));
+    } else if (!info.exited) {
+        // Still running; must have timed out.
+        mx_time_t duration =
+            mx::time::get(MX_CLOCK_MONOTONIC) - fuzzer_.GetLast();
+        mx_time_t timeout = fuzzer_.GetTimeout();
+        snprintf(buf, sizeof(buf),
+                 "%s: %s has not responded for %" PRIu64 ".%09" PRIu64
+                 " seconds; timeout is %" PRIu64 ".%09" PRIu64,
+                 kSentinel, argv0_, duration / MX_SEC(1), duration % MX_SEC(1),
+                 timeout / MX_SEC(1), timeout % MX_SEC(1));
+    } else if (info.return_code != 0) {
+        // Only log non-zero exits
+        snprintf(buf, sizeof(buf), "%s: %s exited with exit code %d", kSentinel,
+                 argv0_, info.return_code);
+    } else {
+        // nothing to log.
+        return NO_ERROR;
+    }
+    OnStderr(buf);
+    return NO_ERROR;
+}
+
+mx_status_t Agent::HandleStdio(int fd) {
+    mx_status_t rc;
+    // Reserve space for buffering the I/O.
+    char buf[kMaxLineLen + 1];
+    buf[kMaxLineLen] = '\0';
+    // Determine what method handles the output.
+    void (Agent::*OnStdio)(const char* str);
+    if (fd == stdout_) {
+        OnStdio = &fuzz::Agent::OnStdout;
+    } else if (fd == stderr_) {
+        OnStdio = &fuzz::Agent::OnStderr;
+    } else {
+        return ERR_NOT_SUPPORTED;
+    }
+    // Wait for the handshake to complete.
+    if ((rc = ready_.wait_one(MX_EVENT_SIGNALED, MX_TIME_INFINITE, nullptr)) !=
+        NO_ERROR) {
+        return rc;
+    }
+    // Loop, reading output from the fd.
+    size_t len = 0;
+    while (true) {
+        ssize_t n = read(fd, buf + len, kMaxLineLen - len);
+        if (n <= 0) {
+            rc = ToStatus(n);
+            break;
+        }
+        len += n;
+        char last = buf[len - 1];
+        // Tokenize the string into lines, and process each one.
+        char* line = strtok(buf, "\n");
+        char* next;
+        while ((next = strtok(nullptr, "\n"))) {
+            (this->*OnStdio)(line);
+            line = next;
+        }
+        // If the buffer is full, flush it.  Otherwise, save the leftovers.
+        len = strlen(line);
+        if (last == '\0' || last == '\n' || len == kMaxLineLen) {
+            (this->*OnStdio)(line);
+            len = 0;
+        } else {
+            memmove(buf, line, len);
+        }
+    }
+    // Flush whatever remains
+    if (len > 0) {
+        (this->*OnStdio)(buf);
+    }
+    return rc;
+}
+
+} // namespace fuzz
diff --git a/system/ulib/fuzz/channel.cpp b/system/ulib/fuzz/channel.cpp
new file mode 100644
index 0000000..9d92459
--- /dev/null
+++ b/system/ulib/fuzz/channel.cpp
@@ -0,0 +1,190 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzz/channel.h>
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <magenta/errors.h>
+#include <magenta/processargs.h>
+#include <magenta/types.h>
+#include <mx/channel.h>
+#include <mx/time.h>
+
+namespace fuzz {
+
+const mx_time_t Channel::kDefaultTimeout = MX_MSEC(200);
+const uint32_t Channel::kHandleInfo = PA_HND(PA_USER0, 0);
+const size_t Channel::kMaxMessageLen = 0x10000;
+static_assert(Channel::kMaxMessageLen < UINT32_MAX,
+              "max msg len must fit in uint32_t");
+
+namespace {
+
+mx_time_t Deadline(mx_time_t timeout) {
+    return (timeout == 0 ? MX_TIME_INFINITE
+                         : mx::time::get(MX_CLOCK_MONOTONIC) + timeout);
+}
+
+mx_status_t Read(const mx::channel& rx, void* buf, size_t len,
+                 mx_time_t deadline) {
+    mx_status_t rc;
+    // Exit early if nothing to do.
+    if (len == 0) {
+        return NO_ERROR;
+    }
+    // Must have buffer if length is not zero.
+    if (!buf) {
+        return ERR_INVALID_ARGS;
+    }
+    // Must be connected.
+    if (!rx) {
+        return ERR_BAD_STATE;
+    }
+    // Limit message size
+    if (len > Channel::kMaxMessageLen) {
+        return ERR_OUT_OF_RANGE;
+    }
+    uint32_t len32 = static_cast<uint32_t>(len);
+    // Wait for the channel to become readable
+    mx_time_t interval = mx::time::get(MX_CLOCK_MONOTONIC);
+    while (true) {
+        rc = rx.read(0, buf, len32, nullptr, nullptr, 0, nullptr);
+        // Exit unless we need to wait
+        if (rc != ERR_SHOULD_WAIT) {
+            return rc;
+        }
+        // Limit polling to 10/second.
+        interval = mx::time::get(MX_CLOCK_MONOTONIC) + MX_MSEC(100);
+        rc = rx.wait_one(MX_CHANNEL_READABLE, interval, nullptr);
+        if (rc == NO_ERROR) {
+            continue;
+        }
+        if (rc != ERR_TIMED_OUT) {
+            return rc;
+        }
+        if (deadline < interval) {
+            return ERR_TIMED_OUT;
+        }
+    }
+}
+
+} // namespace
+
+Channel::Channel() : last_(0), timeout_(kDefaultTimeout) {}
+
+Channel::~Channel() {}
+
+mx_status_t Channel::Listen(mx_handle_t* handle) {
+    mx_status_t rc;
+    // Must have somewhere to save the other end.
+    if (!handle || *handle != MX_HANDLE_INVALID) {
+        return ERR_INVALID_ARGS;
+    }
+    // Must not already be connected.
+    if (channel_) {
+        return ERR_BAD_STATE;
+    }
+    // Create the channel.
+    mx::channel remote;
+    if ((rc = mx::channel::create(0, &channel_, &remote)) != NO_ERROR) {
+        return rc;
+    }
+    *handle = remote.release();
+    last_ = mx::time::get(MX_CLOCK_MONOTONIC);
+    return NO_ERROR;
+}
+
+mx_status_t Channel::Connect(mx_handle_t handle) {
+    // Must be a valid handle.
+    if (handle == MX_HANDLE_INVALID) {
+        return ERR_INVALID_ARGS;
+    }
+    // Must not already be connected.
+    if (channel_) {
+        return ERR_BAD_STATE;
+    }
+    channel_.reset(handle);
+    last_ = mx::time::get(MX_CLOCK_MONOTONIC);
+    return NO_ERROR;
+}
+
+mx_status_t Channel::ReadVal(void* out, size_t len) {
+    mx_status_t rc;
+    // Get the envelope.
+    mx_time_t deadline = Deadline(timeout_);
+    uint32_t len32;
+    if ((rc = Read(channel_, &len32, sizeof(len32), deadline)) != NO_ERROR) {
+        return rc;
+    }
+    // Check the opcode and length are what we expected.
+    if (len != len32) {
+        return ERR_IO;
+    }
+    // Read the data.
+    if ((rc = Read(channel_, out, len, deadline)) != NO_ERROR) {
+        return rc;
+    }
+    last_ = mx::time::get(MX_CLOCK_MONOTONIC);
+    return NO_ERROR;
+}
+
+mx_status_t Channel::ReadBuf(mxtl::Array<uint8_t>* out) {
+    mx_status_t rc;
+    // Must have output variables.
+    if (!out) {
+        return ERR_INVALID_ARGS;
+    }
+    // Get the envelope.
+    mx_time_t deadline = Deadline(timeout_);
+    uint32_t len;
+    if ((rc = Read(channel_, &len, sizeof(len), deadline)) != NO_ERROR) {
+        return rc;
+    }
+    // Dynamically allocate the requested space (possibly 0).
+    if ((rc = AllocArray(out, len)) != NO_ERROR) {
+        return rc;
+    }
+    // Read the data.
+    if ((rc = Read(channel_, out->get(), len, deadline)) != NO_ERROR) {
+        return rc;
+    }
+    last_ = mx::time::get(MX_CLOCK_MONOTONIC);
+    return NO_ERROR;
+}
+
+mx_status_t Channel::Write(const void* buf, size_t len) const {
+    mx_status_t rc;
+    // Must have data or zero length.
+    if (!buf && len != 0) {
+        return ERR_INVALID_ARGS;
+    }
+    // Must be connected.
+    if (!channel_) {
+        return ERR_BAD_STATE;
+    }
+    // Limit message size
+    if (len > kMaxMessageLen) {
+        return ERR_OUT_OF_RANGE;
+    }
+    // Send the envelope
+    uint32_t len32 = static_cast<uint32_t>(len);
+    if ((rc = channel_.write(0, &len32, sizeof(len32), nullptr, 0)) !=
+        NO_ERROR) {
+        return rc;
+    }
+    // Done if no body.
+    if (len == 0) {
+        return NO_ERROR;
+    }
+    return channel_.write(0, buf, len32, nullptr, 0);
+}
+
+void Channel::Close() {
+    channel_.reset();
+}
+
+} // namespace fuzz
diff --git a/system/ulib/fuzz/fuzzer.cpp b/system/ulib/fuzz/fuzzer.cpp
new file mode 100644
index 0000000..cf396c3
--- /dev/null
+++ b/system/ulib/fuzz/fuzzer.cpp
@@ -0,0 +1,139 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzz/fuzzer.h>
+
+#include <stdarg.h>
+#include <stddef.h>
+
+#include <fuzz/channel.h>
+#include <fuzz/state_handler.h>
+#include <magenta/errors.h>
+#include <magenta/process.h>
+#include <magenta/types.h>
+#include <mx/time.h>
+#include <mxalloc/new.h>
+#include <mxtl/algorithm.h>
+
+namespace fuzz {
+
+// Public methods
+
+Fuzzer::Fuzzer() : monitoring_(false), timeout_(0), handlers_(nullptr) {
+    AddHandler(&prng_);
+}
+
+Fuzzer::~Fuzzer() {}
+
+mx_status_t Fuzzer::AddHandler(StateHandler* handler) {
+    mx_status_t rc;
+    if ((rc = handler->Chain(this, handlers_)) != NO_ERROR) {
+        return rc;
+    }
+    handlers_ = handler;
+    return NO_ERROR;
+}
+
+mx_status_t Fuzzer::Start() {
+    mx_handle_t handle = mx_get_startup_handle(Channel::kHandleInfo);
+    return Handshake(handle);
+}
+
+mx_status_t Fuzzer::Draw(void* buf, size_t len) {
+    return prng_.Draw(buf, len);
+}
+
+void Fuzzer::SignalModified() {
+    if (timeout_ == 0) {
+        modified_.signal(MX_SIGNAL_NONE, MX_EVENT_SIGNALED);
+    }
+}
+
+// Protected methods
+
+mx_status_t Fuzzer::Handshake(mx_handle_t handle) {
+    mx_status_t rc;
+    // Retrieve the channel handle and ready the 'Start' message.
+    if ((rc = agent_.Connect(handle)) != NO_ERROR) {
+        return rc;
+    }
+    // Perform the handshake steps
+    timeout_ = 0;
+    mx_time_t timeout;
+    if ((rc = agent_.ReadVal(&timeout, sizeof(timeout))) != NO_ERROR) {
+        return rc;
+    }
+    // Read the current state from the agent.
+    if ((rc = agent_.ReadBuf(&state_)) != NO_ERROR) {
+        return rc;
+    }
+    // If no state, randomly generate one.
+    if (state_.size() == 0 &&
+        ((rc = AllocArray(&state_, handlers_->GetSnapshotLength())) !=
+             NO_ERROR ||
+         (rc = Draw(state_.get(), state_.size())) != NO_ERROR)) {
+        return rc;
+    }
+    // Set the state
+    if ((rc = handlers_->Revert(state_.get(), state_.size())) != NO_ERROR) {
+        return rc;
+    }
+    // Send the initial state back to the agent.
+    if ((rc = agent_.Write(state_.get(), state_.size())) != NO_ERROR) {
+        return rc;
+    }
+    // Create the state modification signal and make sure it's cleared.
+    if ((rc = mx::event::create(0, &modified_)) != NO_ERROR) {
+        return rc;
+    }
+    modified_.signal(MX_USER_SIGNAL_ALL, MX_SIGNAL_NONE);
+    // Start the heartbeat thread, which will initially wait for a signal.
+    if (thrd_create(&heart_, Fuzzer::HeartMonitor, this) != thrd_success) {
+        return ERR_NO_RESOURCES;
+    }
+    monitoring_ = true;
+    // This will wake up the heartbeat thread and get it going
+    timeout_ = timeout;
+    modified_.signal(MX_SIGNAL_NONE, MX_EVENT_SIGNALED);
+    return NO_ERROR;
+}
+
+mx_status_t Fuzzer::Join() {
+    mx_status_t rc;
+    if (!monitoring_) {
+        return NO_ERROR;
+    }
+    thrd_join(heart_, &rc);
+    return rc;
+}
+
+// Private static methods
+
+int Fuzzer::HeartMonitor(void* arg) {
+    Fuzzer* fuzzer = static_cast<Fuzzer*>(arg);
+    return fuzzer->SendHeartbeats();
+}
+
+// Private methods
+
+mx_status_t Fuzzer::SendHeartbeats() {
+    mx_status_t rc;
+    mx_time_t deadline;
+    while (true) {
+        deadline =
+            (timeout_ == 0 ? MX_TIME_INFINITE
+                           : mx::time::get(MX_CLOCK_MONOTONIC) + timeout_);
+        // Wait until first of signal or deadline.
+        modified_.wait_one(MX_EVENT_SIGNALED, deadline, nullptr);
+        // Clear the signal.
+        modified_.signal(MX_USER_SIGNAL_ALL, MX_SIGNAL_NONE);
+        handlers_->Snapshot(state_.get(), state_.size());
+        // Send the state back.
+        if ((rc = agent_.Write(state_.get(), state_.size())) != NO_ERROR) {
+            return rc;
+        }
+    }
+}
+
+} // namespace fuzz
diff --git a/system/ulib/fuzz/include/fuzz/agent.h b/system/ulib/fuzz/include/fuzz/agent.h
new file mode 100644
index 0000000..8142a19
--- /dev/null
+++ b/system/ulib/fuzz/include/fuzz/agent.h
@@ -0,0 +1,75 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <threads.h>
+
+#include <fuzz/channel.h>
+#include <magenta/types.h>
+#include <mx/event.h>
+#include <mx/process.h>
+#include <mxtl/array.h>
+#include <mxtl/macros.h>
+
+namespace fuzz {
+
+class Agent {
+public:
+    virtual ~Agent();
+
+    // Runs the Agent.  This will start the fuzzer process described by |argc|
+    // and |argv|, perform the handshake, and enter the message processing loop.
+    // This method returns when the fuzzer exits or an error is encountered.
+    mx_status_t Run(int argc, const char** argv, uint32_t timeout);
+
+protected:
+    Agent();
+
+    // Starts the fuzzer process and passes it the other end of the channel.
+    // This method is protected to allow unit tests to override it with a
+    // version that does not start a process.
+    virtual mx_status_t Launch(int argc, const char** argv, mx_handle_t remote);
+
+    mx_status_t ToStdin(const char* in);
+    virtual void OnStdout(const char* str) = 0;
+    virtual void OnStderr(const char* str) = 0;
+
+private:
+    DISALLOW_COPY_ASSIGN_AND_MOVE(Agent);
+
+    static int HandleStdout(void* arg);
+    static int HandleStderr(void* arg);
+
+    mx_status_t Handshake(mx_time_t timeout);
+    mx_status_t HandleState();
+    mx_status_t HandleCrash();
+
+    mx_status_t HandleStdio(int fd);
+
+    // Name of the fuzzer being run.
+    const char* argv0_;
+    // Control channel to fuzzer
+    Channel fuzzer_;
+    // Process structure for running fuzzer.
+    mx::process proc_;
+    // Recorded start time.
+    mx_time_t start_;
+    // Last reported fuzzer state.
+    mxtl::Array<uint8_t> state_;
+    // Pipes to/from the fuzzer process.
+    int stdin_;
+    int stdout_;
+    int stderr_;
+
+    thrd_t out_thrd_;
+    thrd_t err_thrd_;
+
+    mx::event ready_;
+};
+
+} // namespace fuzz
diff --git a/system/ulib/fuzz/include/fuzz/channel.h b/system/ulib/fuzz/include/fuzz/channel.h
new file mode 100644
index 0000000..843feb0
--- /dev/null
+++ b/system/ulib/fuzz/include/fuzz/channel.h
@@ -0,0 +1,76 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <stdint.h>
+
+#include <magenta/types.h>
+#include <mx/channel.h>
+#include <mxalloc/new.h>
+#include <mxtl/array.h>
+#include <mxtl/macros.h>
+#include <mxtl/type_support.h>
+
+namespace fuzz {
+
+class Channel final {
+public:
+    static const mx_time_t kDefaultTimeout;
+    static const uint32_t kHandleInfo;
+    static const size_t kMaxMessageLen;
+
+    Channel();
+    ~Channel();
+
+    mx_time_t GetLast() const { return last_; }
+
+    mx_time_t GetTimeout() const { return timeout_; }
+
+    void SetTimeout(mx_time_t timeout) { timeout_ = timeout; }
+
+    mx_status_t Listen(mx_handle_t* handle);
+
+    mx_status_t Connect(mx_handle_t handle);
+
+    mx_status_t ReadVal(void* out, size_t len);
+
+    mx_status_t ReadBuf(mxtl::Array<uint8_t>* out);
+
+    mx_status_t Write(const void* buf, size_t len) const;
+
+    void Close();
+
+private:
+    DISALLOW_COPY_ASSIGN_AND_MOVE(Channel);
+
+    mx::channel channel_;
+
+    mx_time_t last_;
+
+    mx_time_t timeout_;
+};
+
+// Helper function for allocating mxtl::Arrays.
+template <typename T> mx_status_t AllocArray(mxtl::Array<T>* out, size_t len) {
+    // Must have array.
+    if (!out) {
+        return ERR_INVALID_ARGS;
+    }
+    // If length is zero, just free the array and return.
+    if (len == 0) {
+        out->reset();
+        return NO_ERROR;
+    }
+    // Allocate memory; the old memory will be freed upon reset.
+    AllocChecker ac;
+    T* buf = new (&ac) T[len];
+    if (!ac.check()) {
+        return ERR_NO_MEMORY;
+    }
+    out->reset(buf, len);
+    return NO_ERROR;
+}
+
+} // namespace fuzz
diff --git a/system/ulib/fuzz/include/fuzz/fuzzer.h b/system/ulib/fuzz/include/fuzz/fuzzer.h
new file mode 100644
index 0000000..67b080c
--- /dev/null
+++ b/system/ulib/fuzz/include/fuzz/fuzzer.h
@@ -0,0 +1,76 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <stdint.h>
+#include <threads.h>
+
+#include <fuzz/channel.h>
+#include <fuzz/seeded_prng.h>
+#include <fuzz/state_handler.h>
+#include <magenta/types.h>
+#include <mx/event.h>
+#include <mxtl/array.h>
+#include <mxtl/macros.h>
+
+namespace fuzz {
+
+class Fuzzer {
+public:
+    Fuzzer();
+    virtual ~Fuzzer();
+
+    // Sets a state helper.  This must be called before |ReceiveStart|.  It will
+    // consume state from a 'Start' message and gather it for a 'Heartbeat'
+    // message every heartbeat period.
+    mx_status_t AddHandler(StateHandler* handler);
+
+    // Connects to the agent, performs the handshake, and starts the heartbeat
+    // monitor thread.
+    mx_status_t Start();
+
+    mx_status_t Draw(void* buf, size_t len);
+
+    // Notifies the fuzzer that the state managed by one or more of the handlers
+    // has changed.  If this fuzzer is running with a timeout of 0, this
+    // triggers sending a state message.
+    void SignalModified();
+
+    mx_status_t SendFault(const void* buf, size_t len);
+
+protected:
+    // Performs the handshake with the agent.  This will start the heart monitor
+    // thread.  This method is protected to allow unit tests to call it with a
+    // provided handle, instead of a startup handle.
+    mx_status_t Handshake(mx_handle_t handle);
+
+    mx_status_t Join();
+
+private:
+    DISALLOW_COPY_ASSIGN_AND_MOVE(Fuzzer);
+
+    static int HeartMonitor(void* arg);
+
+    mx_status_t SendHeartbeats();
+
+    Channel agent_;
+
+    thrd_t heart_;
+
+    bool monitoring_;
+
+    mxtl::Array<uint8_t> state_;
+
+    mx::event modified_;
+
+    mx_time_t timeout_;
+
+    SeededPRNG prng_;
+
+    // One or more state helpers chained together.
+    StateHandler* handlers_;
+};
+
+} // namespace fuzz
diff --git a/system/ulib/fuzz/include/fuzz/seeded_prng.h b/system/ulib/fuzz/include/fuzz/seeded_prng.h
new file mode 100644
index 0000000..8032e92
--- /dev/null
+++ b/system/ulib/fuzz/include/fuzz/seeded_prng.h
@@ -0,0 +1,43 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <fuzz/state_handler.h>
+
+#include <stdint.h>
+
+#include <magenta/types.h>
+#include <mxtl/macros.h>
+
+namespace fuzz {
+
+class SeededPRNG : public StateHandler {
+public:
+    SeededPRNG();
+    virtual ~SeededPRNG();
+
+    // Gets the length of state information for just this helper, and not the
+    // whole chain.
+    size_t GetStateLength() const override;
+
+    // Returns pseudorandom bytes.
+    mx_status_t Draw(void* buf, size_t len);
+
+protected:
+    // Collects state information for just this helper, and not the whole chain.
+    mx_status_t GetState(uint8_t* buf) const override;
+
+    // Consumes state information for just this helper, and not the whole chain.
+    mx_status_t SetState(const uint8_t* buf) override;
+
+private:
+    DISALLOW_COPY_ASSIGN_AND_MOVE(SeededPRNG);
+
+    uint8_t key_[32];
+    uint8_t nonce_[12];
+    uint32_t counter_;
+};
+
+} // namespace fuzz
diff --git a/system/ulib/fuzz/include/fuzz/state_handler.h b/system/ulib/fuzz/include/fuzz/state_handler.h
new file mode 100644
index 0000000..a4322d0
--- /dev/null
+++ b/system/ulib/fuzz/include/fuzz/state_handler.h
@@ -0,0 +1,76 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <stdint.h>
+#include <threads.h>
+
+#include <magenta/types.h>
+#include <mx/event.h>
+#include <mxtl/macros.h>
+
+namespace fuzz {
+
+class Fuzzer;
+
+class StateHandler {
+public:
+    virtual ~StateHandler();
+
+    // Adds the chain of handlers.  This should only be called through
+    // Fuzzer::AddHandler.
+    mx_status_t Chain(Fuzzer* fuzzer, StateHandler* chain);
+
+    // Gets the length of state information for just this helper, and not the
+    // whole chain.
+    virtual size_t GetStateLength() const = 0;
+
+    // Returns the total space needed for managing state with this chain of
+    // StateHandlers.
+    size_t GetSnapshotLength() const;
+
+    // Consumes state information from |buf|.  Returns an error if |len| is less
+    // than |GetLength()| or if the state can't be parsed.  This method is
+    // called by |Fuzzer::SendHeartbeats()|.  Others may need to call it if
+    // using fuzz::StateHandler without fuzz::Fuzzer.
+    mx_status_t Revert(const uint8_t* buf, size_t len);
+
+    // Collects state information and saves it to |buf|.  Returns an error if
+    // |len| is less than |GetLength()| or if the state can't be saved.  This
+    // method is called by |Fuzzer::Handshake()|.  Others may need to call it if
+    // using fuzz::StateHandler without fuzz::Fuzzer.
+    mx_status_t Snapshot(uint8_t* buf, size_t len);
+
+protected:
+    StateHandler();
+
+    // Gets a pointer to the state handler's mutex.  Derived classes should use
+    // this to create an AutoLock in methods that access or modify state., e.g.
+    //    AutoLock lock(GetLock());
+    mtx_t* GetLock();
+
+    // Collects state information for just this helper, and not the whole chain.
+    virtual mx_status_t GetState(uint8_t* buf) const = 0;
+
+    // Consumes state information for just this helper, and not the whole chain.
+    virtual mx_status_t SetState(const uint8_t* buf) = 0;
+
+    // Signals the saved event that the state has changed.  This is useful with
+    // "slow" starts, where a previous platform crash is being isolated.
+    mx_status_t SignalModified();
+
+private:
+    DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(StateHandler);
+
+    // The fuzzer this handler has been added to.
+    Fuzzer* fuzzer_;
+
+    mtx_t lock_;
+
+    // The next link in the helper chain.  This is null if it's the last link.
+    StateHandler* next_;
+};
+
+} // namespace fuzz
diff --git a/system/ulib/fuzz/rules.mk b/system/ulib/fuzz/rules.mk
new file mode 100644
index 0000000..2ae0827
--- /dev/null
+++ b/system/ulib/fuzz/rules.mk
@@ -0,0 +1,28 @@
+# Copyright 2016 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_TYPE := userlib
+
+MODULE_SRCS += \
+    $(LOCAL_DIR)/agent.cpp \
+    $(LOCAL_DIR)/channel.cpp \
+    $(LOCAL_DIR)/fuzzer.cpp \
+    $(LOCAL_DIR)/seeded_prng.cpp \
+    $(LOCAL_DIR)/state_handler.cpp \
+
+MODULE_LIBS := \
+    system/ulib/launchpad \
+    system/ulib/magenta \
+    system/ulib/mx \
+    system/ulib/mxalloc \
+    system/ulib/mxcpp \
+    system/ulib/mxtl \
+    third_party/ulib/cryptolib \
+    third_party/ulib/boring-crypto \
+
+include make/module.mk
diff --git a/system/ulib/fuzz/seeded_prng.cpp b/system/ulib/fuzz/seeded_prng.cpp
new file mode 100644
index 0000000..c4197fc
--- /dev/null
+++ b/system/ulib/fuzz/seeded_prng.cpp
@@ -0,0 +1,76 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzz/seeded_prng.h>
+
+#include <string.h>
+
+#include <boring-crypto/chacha.h>
+#include <lib/crypto/cryptolib.h>
+#include <magenta/assert.h>
+#include <mxtl/algorithm.h>
+#include <mxtl/auto_lock.h>
+
+namespace fuzz {
+
+SeededPRNG::SeededPRNG() : key_{0}, nonce_{0}, counter_(0) {}
+
+SeededPRNG::~SeededPRNG() {}
+
+static_assert(UINT32_MAX <= SIZE_MAX);
+mx_status_t SeededPRNG::Draw(void* buf, size_t len) {
+    mtx_t* mtx = GetLock();
+    if (!mtx) {
+        return ERR_BAD_STATE;
+    }
+    mxtl::AutoLock lock(mtx);
+    if (len > UINT32_MAX) {
+        return ERR_OUT_OF_RANGE;
+    }
+    memset(buf, 0, len);
+    // Check for overflow.  Chacha20 uses a block size of 64 bytes.
+    uint32_t len32 = static_cast<uint32_t>(len);
+    uint32_t blk32 = 64;
+    uint32_t delta = mxtl::roundup(len32, blk32);
+    MX_DEBUG_ASSERT(len == 0 || delta != 0);
+    if (counter_ + delta < counter_) {
+        static_assert(sizeof(nonce_) <= clSHA256_DIGEST_SIZE);
+        uint8_t tmp[clSHA256_DIGEST_SIZE];
+        clSHA256(nonce_, sizeof(nonce_), tmp);
+        memcpy(nonce_, tmp, sizeof(nonce_));
+        counter_ = 0;
+    }
+    // We don't have to memset to get random vaklues, but do to get the same
+    // random values for a given initial state.
+    uint8_t* bytes = static_cast<uint8_t*>(buf);
+    memset(bytes, 0, len);
+    // Draw the bytes and increment the counter.
+    CRYPTO_chacha_20(bytes, bytes, len, key_, nonce_, counter_);
+    counter_ += delta;
+    return SignalModified();
+}
+
+size_t SeededPRNG::GetStateLength() const {
+    return sizeof(nonce_) + sizeof(counter_);
+}
+
+mx_status_t SeededPRNG::GetState(uint8_t* buf) const {
+    memcpy(buf, nonce_, sizeof(nonce_));
+    buf += sizeof(nonce_);
+    memcpy(buf, &counter_, sizeof(counter_));
+    return NO_ERROR;
+}
+
+mx_status_t SeededPRNG::SetState(const uint8_t* buf) {
+    memcpy(nonce_, buf, sizeof(nonce_));
+    buf += sizeof(nonce_);
+    memcpy(&counter_, buf, sizeof(counter_));
+    // For the key, just use the digest of the nonce.  There's sufficient
+    // entropy for fuzzing already (128 bits) and no security implications.
+    static_assert(sizeof(key_) == clSHA256_DIGEST_SIZE);
+    clSHA256(nonce_, sizeof(nonce_), key_);
+    return NO_ERROR;
+}
+
+} // namespace fuzz
diff --git a/system/ulib/fuzz/state_handler.cpp b/system/ulib/fuzz/state_handler.cpp
new file mode 100644
index 0000000..f94bb61
--- /dev/null
+++ b/system/ulib/fuzz/state_handler.cpp
@@ -0,0 +1,97 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzz/state_handler.h>
+
+#include <assert.h>
+#include <stdint.h>
+
+#include <fuzz/fuzzer.h>
+#include <magenta/errors.h>
+#include <magenta/types.h>
+#include <mxtl/auto_lock.h>
+
+namespace fuzz {
+
+// Public methods
+
+StateHandler::~StateHandler() {}
+
+size_t StateHandler::GetSnapshotLength() const {
+    return (!next_ ? 0 : next_->GetSnapshotLength()) + GetStateLength();
+}
+
+mx_status_t StateHandler::Snapshot(uint8_t* buf, size_t len) {
+    mx_status_t rc;
+    assert(fuzzer_);
+    mxtl::AutoLock lock(&lock_);
+    size_t state_len = GetStateLength();
+    if (len < state_len) {
+        return ERR_BUFFER_TOO_SMALL;
+    }
+    if ((rc = GetState(buf)) != NO_ERROR) {
+        return rc;
+    }
+    return (!next_ ? NO_ERROR
+                   : next_->Snapshot(buf + state_len, len - state_len));
+}
+
+mx_status_t StateHandler::Revert(const uint8_t* buf, size_t len) {
+    mx_status_t rc;
+    assert(fuzzer_);
+    mxtl::AutoLock lock(&lock_);
+    size_t state_len = GetStateLength();
+    if (len < state_len) {
+        return ERR_BUFFER_TOO_SMALL;
+    }
+    if ((rc = SetState(buf)) != NO_ERROR) {
+        return rc;
+    }
+    return (!next_ ? NO_ERROR
+                   : next_->Revert(buf + state_len, len - state_len));
+}
+
+// Protected methods
+
+StateHandler::StateHandler() : fuzzer_(nullptr), next_(nullptr) {}
+
+mtx_t* StateHandler::GetLock() {
+    if (!fuzzer_) {
+        return nullptr;
+    }
+    return &lock_;
+}
+
+mx_status_t StateHandler::Chain(Fuzzer* fuzzer, StateHandler* chain) {
+    // Fuzzer must be valid.
+    if (!fuzzer) {
+        return ERR_INVALID_ARGS;
+    }
+    // Chain must be null or having a matching parent.
+    if (chain && fuzzer != chain->fuzzer_) {
+        return ERR_INVALID_ARGS;
+    }
+    // Must not already be chained.
+    if (fuzzer_) {
+        return ERR_BAD_STATE;
+    }
+    // Initialize the lock.
+    if (mtx_init(&lock_, mtx_plain) != thrd_success) {
+        return ERR_NO_RESOURCES;
+    }
+    fuzzer_ = fuzzer;
+    next_ = chain;
+    return NO_ERROR;
+}
+
+mx_status_t StateHandler::SignalModified() {
+    // Fuzzer must be set
+    if (!fuzzer_) {
+        return ERR_BAD_STATE;
+    }
+    fuzzer_->SignalModified();
+    return NO_ERROR;
+}
+
+} // namespace fuzz
diff --git a/system/utest/fuzz/agent.cpp b/system/utest/fuzz/agent.cpp
new file mode 100644
index 0000000..2e095d1
--- /dev/null
+++ b/system/utest/fuzz/agent.cpp
@@ -0,0 +1,247 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzz/agent.h>
+
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <threads.h>
+
+#include <fuzz/channel.h>
+#include <magenta/types.h>
+#include <mx/event.h>
+#include <mx/time.h>
+#include <unittest/unittest.h>
+
+#include "util.h"
+
+namespace fuzz {
+
+// An implementation of fuzz::Agent that is useful for unit testing that class.
+class TestAgent : public Agent {
+public:
+    static constexpr const char* kArg = "foo";
+    static const uint32_t kTimeout = 100;
+
+    static constexpr const char* kFault = "test fault";
+    static constexpr size_t kFaultLen = strlen(kFault) + 1;
+
+    TestAgent() {}
+    virtual ~TestAgent() {}
+
+    mx_status_t StartTest(Channel* fuzzer) {
+        mx_status_t rc;
+        fuzzer_ = fuzzer;
+        if ((rc = mx::event::create(0, &launch_)) != NO_ERROR) {
+            return rc;
+        }
+        if ((rc = launch_.signal(MX_USER_SIGNAL_ALL, MX_SIGNAL_NONE)) !=
+            NO_ERROR) {
+            return rc;
+        }
+        if (thrd_create(&thrd_, AgentThread, this) != thrd_success) {
+            return ERR_NO_RESOURCES;
+        }
+        mx_time_t deadline = mx::time::get(MX_CLOCK_MONOTONIC) + MX_MSEC(100);
+        if ((rc = launch_.wait_one(MX_EVENT_SIGNALED, deadline, nullptr)) !=
+            NO_ERROR) {
+            return rc;
+        }
+        return NO_ERROR;
+    }
+
+    mx_status_t StopTest() {
+        int rc;
+        thrd_join(thrd_, &rc);
+        return rc;
+    }
+
+protected:
+    void OnStdout(const char* str) override {
+        // No-op
+    }
+
+    void OnStderr(const char* str) override {
+        // No-op
+    }
+
+    // Don't start a process.  Just save the channel handle somewhere we can
+    // retrieve it.
+    mx_status_t Launch(int argc, const char** argv,
+                       mx_handle_t remote) override {
+        mx_status_t rc = fuzzer_->Connect(remote);
+        launch_.signal(MX_SIGNAL_NONE, MX_EVENT_SIGNALED);
+        return rc;
+    }
+
+private:
+    static int AgentThread(void* arg) {
+        Agent* agent = static_cast<Agent*>(arg);
+        const char* argv = TestAgent::kArg;
+        return agent->Run(1, &argv, kTimeout);
+    }
+
+    Channel* fuzzer_;
+    mx::event launch_;
+    thrd_t thrd_;
+};
+
+} // namespace fuzz
+
+namespace {
+
+using fuzz::AllocArray;
+using fuzz::Channel;
+using fuzz::TestAgent;
+
+bool AgentBadArgs() {
+    BEGIN_TEST_WITH_RC;
+    TestAgent agent;
+    const char* argv = TestAgent::kArg;
+    ASSERT_RC(agent.Run(0, &argv, 0), ERR_INVALID_ARGS);
+    ASSERT_RC(agent.Run(UINT8_MAX + 1, &argv, 0), ERR_INVALID_ARGS);
+    ASSERT_RC(agent.Run(1, nullptr, 0), ERR_INVALID_ARGS);
+    END_TEST;
+}
+
+bool AgentTimeoutOnStart() {
+    BEGIN_TEST_WITH_RC;
+    TestAgent agent;
+    Channel fuzzer;
+    // Start agent thread
+    ASSERT_RC(agent.StartTest(&fuzzer), NO_ERROR);
+    // Agent thread should end with error
+    ASSERT_RC(agent.StopTest(), ERR_TIMED_OUT);
+    END_TEST;
+}
+
+bool AgentCloseOnStart() {
+    BEGIN_TEST_WITH_RC;
+    TestAgent agent;
+    Channel fuzzer;
+    // Start agent thread
+    ASSERT_RC(agent.StartTest(&fuzzer), NO_ERROR);
+    fuzzer.Close();
+    // Agent thread should end with error
+    ASSERT_RC(agent.StopTest(), ERR_PEER_CLOSED);
+    END_TEST;
+}
+
+bool AgentTimeoutOnInitialState() {
+    BEGIN_TEST_WITH_RC;
+    TestAgent agent;
+    Channel fuzzer;
+    mx_time_t timeout;
+    // Start agent thread
+    ASSERT_RC(agent.StartTest(&fuzzer), NO_ERROR);
+    ASSERT_RC(fuzzer.ReadVal(&timeout, sizeof(timeout)), NO_ERROR);
+    // Agent thread should end with error
+    ASSERT_RC(agent.StopTest(), ERR_TIMED_OUT);
+    END_TEST;
+}
+
+bool AgentCloseOnInitialState() {
+    BEGIN_TEST_WITH_RC;
+    TestAgent agent;
+    Channel fuzzer;
+    mx_time_t timeout;
+    // Start agent thread
+    ASSERT_RC(agent.StartTest(&fuzzer), NO_ERROR);
+    ASSERT_RC(fuzzer.ReadVal(&timeout, sizeof(timeout)), NO_ERROR);
+    fuzzer.Close();
+    // Agent thread should end with error
+    ASSERT_RC(agent.StopTest(), ERR_PEER_CLOSED);
+    END_TEST;
+}
+
+bool AgentTimeoutOnStateReply() {
+    BEGIN_TEST_WITH_RC;
+    TestAgent agent;
+    Channel fuzzer;
+    mx_time_t timeout;
+    mxtl::Array<uint8_t> buf;
+    // Start agent thread
+    ASSERT_RC(agent.StartTest(&fuzzer), NO_ERROR);
+    ASSERT_RC(fuzzer.ReadVal(&timeout, sizeof(timeout)), NO_ERROR);
+    ASSERT_RC(fuzzer.ReadBuf(&buf), NO_ERROR);
+    // Agent thread should end with error
+    ASSERT_RC(agent.StopTest(), ERR_TIMED_OUT);
+    END_TEST;
+}
+
+bool AgentCloseOnStateReply() {
+    BEGIN_TEST_WITH_RC;
+    TestAgent agent;
+    Channel fuzzer;
+    mx_time_t timeout;
+    mxtl::Array<uint8_t> buf;
+    // Start agent thread
+    ASSERT_RC(agent.StartTest(&fuzzer), NO_ERROR);
+    ASSERT_RC(fuzzer.ReadVal(&timeout, sizeof(timeout)), NO_ERROR);
+    ASSERT_RC(fuzzer.ReadBuf(&buf), NO_ERROR);
+    fuzzer.Close();
+    // Agent thread should end with error
+    ASSERT_RC(agent.StopTest(), ERR_PEER_CLOSED);
+    END_TEST;
+}
+
+bool AgentTimeoutOnMessage() {
+    BEGIN_TEST_WITH_RC;
+    TestAgent agent;
+    Channel fuzzer;
+    mx_time_t timeout;
+    mxtl::Array<uint8_t> buf;
+    // Start agent thread
+    ASSERT_RC(agent.StartTest(&fuzzer), NO_ERROR);
+    ASSERT_RC(fuzzer.ReadVal(&timeout, sizeof(timeout)), NO_ERROR);
+    ASSERT_RC(fuzzer.ReadBuf(&buf), NO_ERROR);
+    // Get current state
+    ASSERT_RC(AllocArray(&buf, sizeof(mx_time_t)), NO_ERROR);
+    mx_time_t* now = reinterpret_cast<mx_time_t*>(buf.get());
+    // Send state
+    *now = mx::time::get(MX_CLOCK_MONOTONIC);
+    fuzzer.Write(buf.get(), buf.size());
+    // Agent thread should end with error
+    ASSERT_RC(agent.StopTest(), NO_ERROR);
+    // TODO: stderr?
+    END_TEST;
+}
+
+bool AgentCloseOnMessage() {
+    BEGIN_TEST_WITH_RC;
+    TestAgent agent;
+    Channel fuzzer;
+    mx_time_t timeout;
+    mxtl::Array<uint8_t> buf;
+    // Start agent thread
+    ASSERT_RC(agent.StartTest(&fuzzer), NO_ERROR);
+    ASSERT_RC(fuzzer.ReadVal(&timeout, sizeof(timeout)), NO_ERROR);
+    ASSERT_RC(fuzzer.ReadBuf(&buf), NO_ERROR);
+    // Get current state
+    ASSERT_RC(AllocArray(&buf, sizeof(mx_time_t)), NO_ERROR);
+    mx_time_t* now = reinterpret_cast<mx_time_t*>(buf.get());
+    // Send state
+    *now = mx::time::get(MX_CLOCK_MONOTONIC);
+    ASSERT_RC(fuzzer.Write(buf.get(), buf.size()), NO_ERROR);
+    fuzzer.Close();
+    // Agent thread should end with error
+    ASSERT_RC(agent.StopTest(), NO_ERROR);
+    // TODO: stderr?
+    END_TEST;
+}
+
+} // namespace
+
+BEGIN_TEST_CASE(FuzzAgentTests)
+RUN_TEST(AgentBadArgs)
+RUN_TEST(AgentTimeoutOnStart)
+RUN_TEST(AgentCloseOnStart)
+RUN_TEST(AgentTimeoutOnInitialState)
+RUN_TEST(AgentCloseOnInitialState)
+RUN_TEST(AgentTimeoutOnStateReply)
+RUN_TEST(AgentCloseOnStateReply)
+RUN_TEST(AgentTimeoutOnMessage)
+RUN_TEST(AgentCloseOnMessage)
+END_TEST_CASE(FuzzAgentTests)
diff --git a/system/utest/fuzz/channel.cpp b/system/utest/fuzz/channel.cpp
new file mode 100644
index 0000000..0d84019
--- /dev/null
+++ b/system/utest/fuzz/channel.cpp
@@ -0,0 +1,170 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzz/channel.h>
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <magenta/types.h>
+#include <unittest/unittest.h>
+
+#include "util.h"
+
+namespace {
+
+using fuzz::Channel;
+
+// Helper method to (re-)initialize the channels for testing.
+bool ChannelReset(Channel* rx, Channel* tx) {
+    mx_handle_t handle = MX_HANDLE_INVALID;
+    if (!rx || !tx) {
+        return false;
+    }
+    rx->Close();
+    tx->Close();
+    if (rx->Listen(&handle) != NO_ERROR || tx->Connect(handle) != NO_ERROR) {
+        return false;
+    }
+    rx->SetTimeout(100);
+    return true;
+}
+
+bool ChannelListen() {
+    BEGIN_TEST_WITH_RC;
+    mx_handle_t handle = MX_HANDLE_INVALID;
+    Channel rx, tx;
+    // Listen without capturing handle
+    ASSERT_RC(rx.Listen(nullptr), ERR_INVALID_ARGS);
+    // Listen correctly
+    ASSERT_RC(rx.Listen(&handle), NO_ERROR);
+    // Listen with handle in use
+    ASSERT_RC(tx.Listen(&handle), ERR_INVALID_ARGS);
+    // Listen with channel connected
+    handle = MX_HANDLE_INVALID;
+    ASSERT_RC(rx.Listen(&handle), ERR_BAD_STATE);
+    END_TEST;
+}
+
+bool ChannelConnect() {
+    BEGIN_TEST_WITH_RC;
+    mx_handle_t handle = MX_HANDLE_INVALID;
+    Channel rx, tx;
+    // Connect to invalid handle
+    ASSERT_RC(tx.Connect(handle), ERR_INVALID_ARGS);
+    // Listen and connect
+    ASSERT_RC(rx.Listen(&handle), NO_ERROR);
+    ASSERT_RC(tx.Connect(handle), NO_ERROR);
+    // Connect to handle in use
+    ASSERT_RC(tx.Connect(handle), ERR_BAD_STATE);
+    END_TEST;
+}
+
+bool ChannelWrite() {
+    BEGIN_TEST_WITH_RC;
+    Channel rx, tx;
+    uint8_t buf[Channel::kMaxMessageLen + 1];
+    memset(buf, 0xff, sizeof(buf));
+    ASSERT_TRUE(ChannelReset(&rx, &tx), "channel reset failed");
+    // Send a zero length buffer.
+    ASSERT_RC(tx.Write(nullptr, 0), NO_ERROR);
+    // Send a nonzero length buffer without data
+    ASSERT_RC(tx.Write(nullptr, 1), ERR_INVALID_ARGS);
+    // Send a too large buffer
+    ASSERT_RC(tx.Write(buf, sizeof(buf)), ERR_OUT_OF_RANGE);
+    // Send a max length buffer
+    ASSERT_RC(tx.Write(buf, Channel::kMaxMessageLen), NO_ERROR);
+    END_TEST;
+}
+
+bool ChannelReadValue() {
+    BEGIN_TEST_WITH_RC;
+    Channel rx, tx;
+    uint8_t u8 = 8;
+    uint64_t u64 = 64;
+    // Read without connecting
+    ASSERT_RC(rx.ReadVal(&u8, sizeof(u8)), ERR_BAD_STATE);
+    // Read without writing
+    ASSERT_TRUE(ChannelReset(&rx, &tx), "channel reset failed");
+    ASSERT_RC(rx.ReadVal(&u8, sizeof(u8)), ERR_TIMED_OUT);
+    // Read without output
+    ASSERT_TRUE(ChannelReset(&rx, &tx), "channel reset failed");
+    ASSERT_RC(tx.Write(&u8, sizeof(u8)), NO_ERROR);
+    ASSERT_RC(rx.ReadVal(nullptr, sizeof(u8)), ERR_INVALID_ARGS);
+    // Read a message with the wrong size
+    ASSERT_TRUE(ChannelReset(&rx, &tx), "channel reset failed");
+    ASSERT_RC(tx.Write(&u64, sizeof(u64)), NO_ERROR);
+    ASSERT_RC(rx.ReadVal(&u8, sizeof(u8)), ERR_IO);
+    // Read a valid message
+    ASSERT_TRUE(ChannelReset(&rx, &tx), "channel reset failed");
+    ASSERT_RC(tx.Write(&u8, sizeof(u8)), NO_ERROR);
+    ASSERT_RC(rx.ReadVal(&u8, sizeof(u8)), NO_ERROR);
+    // Check the received value
+    ASSERT_EQ(u8, 8, "unexpected value");
+    END_TEST;
+}
+
+bool ChannelReadBuffer() {
+    BEGIN_TEST_WITH_RC;
+    Channel rx, tx;
+    uint8_t buf[Channel::kMaxMessageLen];
+    memset(buf, 0xff, sizeof(buf));
+    mxtl::Array<uint8_t> out;
+    // Read without connecting
+    ASSERT_RC(rx.ReadBuf(&out), ERR_BAD_STATE);
+    // Read without writing
+    ASSERT_TRUE(ChannelReset(&rx, &tx), "channel reset failed");
+    ASSERT_RC(rx.ReadBuf(&out), ERR_TIMED_OUT);
+    // Read with missing fields
+    ASSERT_TRUE(ChannelReset(&rx, &tx), "channel reset failed");
+    ASSERT_RC(tx.Write(buf, sizeof(buf)), NO_ERROR);
+    ASSERT_RC(rx.ReadBuf(nullptr), ERR_INVALID_ARGS);
+    // Read with matching opcode mask
+    ASSERT_TRUE(ChannelReset(&rx, &tx), "channel reset failed");
+    ASSERT_RC(tx.Write(buf, sizeof(buf)), NO_ERROR);
+    ASSERT_RC(rx.ReadBuf(&out), NO_ERROR);
+    // Check the received buffer
+    ASSERT_EQ(out.size(), sizeof(buf), "unexpected buffer length");
+    // hexdump8(out.get(), out.size());
+    for (size_t i = 0; i < out.size(); ++i) {
+        ASSERT_EQ(out[i], 0xff, "unexpected buffer contents");
+    }
+    END_TEST;
+}
+
+bool ChannelEndToEnd() {
+    BEGIN_TEST_WITH_RC;
+    Channel server, client;
+    uint8_t buf[32];
+    memset(buf, 0xff, sizeof(buf));
+    uint32_t n = sizeof(buf);
+    mxtl::Array<uint8_t> out;
+    // Send and receive the 'start' message
+    ASSERT_TRUE(ChannelReset(&server, &client), "channel reset failed");
+    ASSERT_RC(server.Write(&n, sizeof(n)), NO_ERROR);
+    ASSERT_RC(client.ReadVal(&n, sizeof(n)), NO_ERROR);
+    ASSERT_EQ(n, sizeof(buf), "unexpected value");
+    // Send and receive the 'stop' message
+    n /= 2;
+    ASSERT_RC(client.Write(buf, n), NO_ERROR);
+    ASSERT_RC(server.ReadBuf(&out), NO_ERROR);
+    // Check the received buffer
+    ASSERT_EQ(out.size(), n, "unexpected buffer length");
+    for (size_t i = 0; i < out.size(); ++i) {
+        ASSERT_EQ(out[i], 0xff, "unexpected buffer contents");
+    }
+    END_TEST;
+}
+
+} // namespace
+
+BEGIN_TEST_CASE(FuzzChannelTests)
+RUN_TEST(ChannelListen)
+RUN_TEST(ChannelConnect)
+RUN_TEST(ChannelWrite)
+RUN_TEST(ChannelReadValue)
+RUN_TEST(ChannelReadBuffer)
+RUN_TEST(ChannelEndToEnd)
+END_TEST_CASE(FuzzChannelTests)
diff --git a/system/utest/fuzz/fuzzer.cpp b/system/utest/fuzz/fuzzer.cpp
new file mode 100644
index 0000000..c4b5bf0
--- /dev/null
+++ b/system/utest/fuzz/fuzzer.cpp
@@ -0,0 +1,172 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzz/fuzzer.h>
+
+#include <stddef.h>
+#include <stdint.h>
+#include <threads.h>
+
+#include <fuzz/channel.h>
+#include <fuzz/seeded_prng.h>
+#include <magenta/types.h>
+#include <unittest/unittest.h>
+
+#include "util.h"
+
+namespace fuzz {
+
+// An implementation of fuzz::Fuzzer that is useful for unit testing that class.
+class TestFuzzer : public Fuzzer {
+public:
+    static constexpr const char* kFault = "test fault";
+    static constexpr size_t kFaultLen = strlen(kFault) + 1;
+
+    TestFuzzer() : handle_(MX_HANDLE_INVALID) {}
+    virtual ~TestFuzzer() {}
+
+    mx_status_t TestHandshake(mx_handle_t handle) { return Handshake(handle); }
+
+    mx_status_t StartTest(Channel* agent) {
+        mx_status_t rc;
+        if ((rc = agent->Listen(&handle_)) != NO_ERROR) {
+            return rc;
+        }
+        if (thrd_create(&thrd_, HandshakeThread, this) != thrd_success) {
+            return ERR_NO_RESOURCES;
+        }
+        agent->SetTimeout(200);
+        return NO_ERROR;
+    }
+
+    mx_status_t StopTest(Channel* agent) {
+        int rc;
+        thrd_join(thrd_, &rc);
+        return rc;
+    }
+
+private:
+    static int HandshakeThread(void* arg) {
+        TestFuzzer* fuzzer = static_cast<TestFuzzer*>(arg);
+        mx_status_t rc;
+        if ((rc = fuzzer->Handshake(fuzzer->handle_)) != NO_ERROR) {
+            return rc;
+        }
+        return fuzzer->Join();
+    }
+
+    thrd_t thrd_;
+    mx_handle_t handle_;
+};
+
+} // namespace fuzz
+
+namespace {
+
+using fuzz::Channel;
+using fuzz::SeededPRNG;
+using fuzz::TestFuzzer;
+
+bool FuzzerBadHandle() {
+    BEGIN_TEST_WITH_RC;
+    TestFuzzer fuzzer;
+    mx_handle_t handle = MX_HANDLE_INVALID;
+    // Try handshake with a bad handle
+    ASSERT_RC(fuzzer.TestHandshake(handle), ERR_INVALID_ARGS);
+    END_TEST;
+}
+
+bool FuzzerTimeoutOnStart() {
+    BEGIN_TEST_WITH_RC;
+    TestFuzzer fuzzer;
+    Channel agent;
+    // Start fuzzer thread
+    ASSERT_RC(fuzzer.StartTest(&agent), NO_ERROR);
+    // Fuzzer thread should end with error
+    ASSERT_RC(fuzzer.StopTest(&agent), ERR_TIMED_OUT);
+    END_TEST;
+}
+
+bool FuzzerCloseOnStart() {
+    BEGIN_TEST_WITH_RC;
+    TestFuzzer fuzzer;
+    Channel agent;
+    // Start fuzzer thread
+    ASSERT_RC(fuzzer.StartTest(&agent), NO_ERROR);
+    agent.Close();
+    // Fuzzer thread should end with error
+    ASSERT_RC(fuzzer.StopTest(&agent), ERR_PEER_CLOSED);
+    END_TEST;
+}
+
+bool FuzzerTimeoutOnInitialState() {
+    BEGIN_TEST_WITH_RC;
+    TestFuzzer fuzzer;
+    Channel agent;
+    // Start fuzzer thread
+    ASSERT_RC(fuzzer.StartTest(&agent), NO_ERROR);
+    mx_time_t timeout = 100;
+    agent.Write(&timeout, sizeof(timeout));
+    // Fuzzer thread should end with error
+    ASSERT_RC(fuzzer.StopTest(&agent), ERR_TIMED_OUT);
+    END_TEST;
+}
+
+bool FuzzerCloseOnInitialState() {
+    BEGIN_TEST_WITH_RC;
+    TestFuzzer fuzzer;
+    Channel agent;
+    // Start fuzzer thread
+    ASSERT_RC(fuzzer.StartTest(&agent), NO_ERROR);
+    mx_time_t timeout = 100;
+    agent.Write(&timeout, sizeof(timeout));
+    agent.Close();
+    // Fuzzer thread should end with error
+    ASSERT_RC(fuzzer.StopTest(&agent), ERR_PEER_CLOSED);
+    END_TEST;
+}
+
+bool FuzzerCloseOnStateReply() {
+    BEGIN_TEST_WITH_RC;
+    TestFuzzer fuzzer;
+    Channel agent;
+    // Start fuzzer thread
+    ASSERT_RC(fuzzer.StartTest(&agent), NO_ERROR);
+    mx_time_t timeout = 100;
+    agent.Write(&timeout, sizeof(timeout));
+    agent.Write(nullptr, 0);
+    agent.Close();
+    // Fuzzer thread should end with error
+    ASSERT_RC(fuzzer.StopTest(&agent), ERR_PEER_CLOSED);
+    END_TEST;
+}
+
+bool FuzzerCloseOnMessage() {
+    BEGIN_TEST_WITH_RC;
+    TestFuzzer fuzzer;
+    Channel agent;
+    mxtl::Array<uint8_t> buf;
+    // Start fuzzer thread
+    ASSERT_RC(fuzzer.StartTest(&agent), NO_ERROR);
+    mx_time_t timeout = 100;
+    agent.Write(&timeout, sizeof(timeout));
+    agent.Write(nullptr, 0);
+    agent.ReadBuf(&buf);
+    agent.Close();
+    // Fuzzer thread should end with error
+    ASSERT_RC(fuzzer.StopTest(&agent), ERR_PEER_CLOSED);
+    END_TEST;
+}
+
+} // namespace
+
+BEGIN_TEST_CASE(FuzzFuzzerTests)
+RUN_TEST(FuzzerBadHandle)
+RUN_TEST(FuzzerTimeoutOnStart)
+RUN_TEST(FuzzerCloseOnStart)
+RUN_TEST(FuzzerTimeoutOnInitialState)
+RUN_TEST(FuzzerCloseOnInitialState)
+RUN_TEST(FuzzerCloseOnStateReply)
+RUN_TEST(FuzzerCloseOnMessage)
+END_TEST_CASE(FuzzFuzzerTests)
diff --git a/system/utest/fuzz/main.c b/system/utest/fuzz/main.c
new file mode 100644
index 0000000..ed1a365
--- /dev/null
+++ b/system/utest/fuzz/main.c
@@ -0,0 +1,9 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <unittest/unittest.h>
+
+int main(int argc, char** argv) {
+    return unittest_run_all_tests(argc, argv) ? 0 : -1;
+}
diff --git a/system/utest/fuzz/rules.mk b/system/utest/fuzz/rules.mk
new file mode 100644
index 0000000..15e2371
--- /dev/null
+++ b/system/utest/fuzz/rules.mk
@@ -0,0 +1,38 @@
+# Copyright 2017 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_TYPE := usertest
+
+MODULE_SRCS += \
+    $(LOCAL_DIR)/agent.cpp \
+    $(LOCAL_DIR)/channel.cpp \
+    $(LOCAL_DIR)/fuzzer.cpp \
+    $(LOCAL_DIR)/seeded_prng.cpp \
+    $(LOCAL_DIR)/main.c
+
+
+MODULE_NAME := fuzz-test
+
+MODULE_LIBS := \
+    system/ulib/c \
+    system/ulib/launchpad \
+    system/ulib/magenta \
+    system/ulib/unittest \
+
+MODULE_STATIC_LIBS := \
+    system/ulib/fuzz \
+    system/ulib/mx \
+    system/ulib/mxalloc \
+    system/ulib/mxcpp \
+    system/ulib/mxtl \
+    system/ulib/pretty \
+    third_party/ulib/boring-crypto \
+    third_party/ulib/cryptolib \
+
+
+include make/module.mk
diff --git a/system/utest/fuzz/seeded_prng.cpp b/system/utest/fuzz/seeded_prng.cpp
new file mode 100644
index 0000000..37cacd6
--- /dev/null
+++ b/system/utest/fuzz/seeded_prng.cpp
@@ -0,0 +1,153 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuzz/seeded_prng.h>
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <fuzz/fuzzer.h>
+#include <magenta/types.h>
+#include <unittest/unittest.h>
+
+#include "util.h"
+
+namespace {
+
+using fuzz::Fuzzer;
+using fuzz::SeededPRNG;
+
+bool SeededPrngChain() {
+    BEGIN_TEST_WITH_RC;
+    SeededPRNG prng1, prng2;
+    Fuzzer fuzzer1, fuzzer2;
+    // Chain without fuzzer.
+    ASSERT_RC(prng1.Chain(nullptr, nullptr), ERR_INVALID_ARGS);
+    // Make a single element chain.
+    ASSERT_RC(prng1.Chain(&fuzzer1, nullptr), NO_ERROR);
+    // Chain when already chained.
+    ASSERT_RC(prng1.Chain(&fuzzer1, nullptr), ERR_BAD_STATE);
+    // Chain with mismatched fuzzer.
+    ASSERT_RC(prng2.Chain(&fuzzer2, &prng1), ERR_INVALID_ARGS);
+    // Make a 2 element chain.
+    ASSERT_RC(prng2.Chain(&fuzzer1, &prng1), NO_ERROR);
+    END_TEST;
+}
+
+bool SeededPrngCheckLengths() {
+    BEGIN_TEST_WITH_RC;
+    SeededPRNG prng1, prng2;
+    Fuzzer fuzzer;
+    // Check unchained length
+    size_t length = prng1.GetStateLength();
+    ASSERT_EQ(prng1.GetSnapshotLength(), length, "unchained total != length");
+    // Check chained length
+    ASSERT_RC(prng1.Chain(&fuzzer, nullptr), NO_ERROR);
+    ASSERT_RC(prng2.Chain(&fuzzer, &prng1), NO_ERROR);
+    ASSERT_EQ(prng2.GetSnapshotLength(), length * 2,
+              "chained total != sum of lengths");
+    END_TEST;
+}
+
+bool SeededPrngDraw() {
+    BEGIN_TEST_WITH_RC;
+    SeededPRNG prng1;
+    Fuzzer fuzzer;
+    uint64_t x, y;
+    // Draw without initializing (via Chain)
+    ASSERT_RC(prng1.Draw(&x, sizeof(x)), ERR_BAD_STATE);
+    // Initialize and draw
+    ASSERT_RC(prng1.Chain(&fuzzer, nullptr), NO_ERROR);
+    ASSERT_RC(prng1.Draw(&x, sizeof(x)), NO_ERROR);
+    // Draw too much, but only if size_t is more than 32 bits.
+    size_t n = UINT32_MAX + 1;
+    if (n != 0) {
+        prng1.Draw(&x, UINT32_MAX + 1);
+    }
+    // Check that outputs change.
+    prng1.Draw(&x, sizeof(x));
+    prng1.Draw(&y, sizeof(y));
+    // P(x == y) is about 2^-128.  It's possible, but if it happens it is
+    // overwhelmingly more likely due to a bug than due to getting "lucky".
+    ASSERT_NEQ(x, y, "not random enough");
+    END_TEST;
+}
+
+bool SeededPrngRevert() {
+    BEGIN_TEST_WITH_RC;
+    SeededPRNG prng1, prng2;
+    Fuzzer fuzzer;
+    uint64_t drawn1, drawn2, x;
+    // Chain handlers
+    ASSERT_RC(prng2.Chain(&fuzzer, nullptr), NO_ERROR);
+    ASSERT_RC(prng1.Chain(&fuzzer, &prng2), NO_ERROR);
+    // Allocate snapshots
+    size_t total = prng1.GetSnapshotLength();
+    uint8_t snapshot[total + 1];
+    // Randomize state
+    prng1.Draw(snapshot, total);
+    ASSERT_RC(prng1.Revert(snapshot, total + 1), NO_ERROR);
+    ASSERT_RC(prng1.Revert(snapshot, total - 1), ERR_BUFFER_TOO_SMALL);
+    ASSERT_RC(prng2.Revert(snapshot, total), NO_ERROR);
+    ASSERT_RC(prng1.Revert(snapshot, total), NO_ERROR);
+    // Save values after snapshot
+    prng1.Draw(&drawn1, sizeof(drawn1));
+    prng2.Draw(&drawn2, sizeof(drawn2));
+    // Revert to snapshot
+    ASSERT_RC(prng1.Revert(snapshot, total), NO_ERROR);
+    // Check the reverted values
+    prng1.Draw(&x, sizeof(x));
+    ASSERT_EQ(x, drawn1, "not same after revert");
+    prng2.Draw(&x, sizeof(x));
+    ASSERT_EQ(x, drawn2, "not same after revert");
+    END_TEST;
+}
+
+bool SeededPrngSnapshot() {
+    BEGIN_TEST_WITH_RC;
+    SeededPRNG prng1, prng2;
+    Fuzzer fuzzer;
+    uint64_t drawn1, drawn2, x;
+    // Chain handlers
+    ASSERT_RC(prng2.Chain(&fuzzer, nullptr), NO_ERROR);
+    ASSERT_RC(prng1.Chain(&fuzzer, &prng2), NO_ERROR);
+    // Allocate snapshots
+    size_t total = prng1.GetSnapshotLength();
+    uint8_t snapshot1[total + 1];
+    uint8_t snapshot2[total + 1];
+    // Randomize state
+    prng1.Draw(snapshot1, total);
+    ASSERT_RC(prng1.Revert(snapshot1, total), NO_ERROR);
+    // Modify the state
+    prng1.Draw(&x, sizeof(x));
+    prng1.Draw(&x, sizeof(x));
+    prng2.Draw(&x, sizeof(x));
+    // Snapshot the current state
+    ASSERT_RC(prng1.Snapshot(snapshot2, total + 1), NO_ERROR);
+    ASSERT_RC(prng1.Snapshot(snapshot2, total - 1), ERR_BUFFER_TOO_SMALL);
+    ASSERT_RC(prng2.Snapshot(snapshot2, total), NO_ERROR);
+    ASSERT_RC(prng1.Snapshot(snapshot2, total), NO_ERROR);
+    // Save values after snapshot
+    prng1.Draw(&drawn1, sizeof(drawn1));
+    prng2.Draw(&drawn2, sizeof(drawn2));
+    // Revert to snapshot
+    ASSERT_RC(prng1.Revert(snapshot1, total), NO_ERROR);
+    ASSERT_RC(prng1.Revert(snapshot2, total), NO_ERROR);
+    // Check the reverted values
+    prng1.Draw(&x, sizeof(x));
+    ASSERT_EQ(x, drawn1, "not same after revert");
+    prng2.Draw(&x, sizeof(x));
+    ASSERT_EQ(x, drawn2, "not same after revert");
+    END_TEST;
+}
+
+} // namespace
+
+BEGIN_TEST_CASE(FuzzSeededPrngTests)
+RUN_TEST(SeededPrngChain)
+RUN_TEST(SeededPrngCheckLengths)
+RUN_TEST(SeededPrngDraw)
+RUN_TEST(SeededPrngRevert)
+RUN_TEST(SeededPrngSnapshot)
+END_TEST_CASE(FuzzSeededPrngTests)
diff --git a/system/utest/fuzz/util.h b/system/utest/fuzz/util.h
new file mode 100644
index 0000000..5285170
--- /dev/null
+++ b/system/utest/fuzz/util.h
@@ -0,0 +1,17 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <stdio.h> // TODO
+
+#include <magenta/status.h>
+#include <magenta/types.h>
+#include <unittest/unittest.h>
+
+#define BEGIN_TEST_WITH_RC                                                     \
+    BEGIN_TEST;                                                                \
+    mx_status_t __rc;
+#define ASSERT_RC(expr, err)                                                   \
+    ASSERT_EQ(__rc = (expr), err, mx_status_get_string(__rc))