blob: f9d3c89840d7f6604770ac7c80ff5ab4846dfc9f [file] [log] [blame]
// Copyright 2021 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 "src/sys/fuzzing/libfuzzer/testing/fuzzer.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <zircon/process.h>
#include <zircon/status.h>
#include <random>
#include <test/fuzzer/cpp/fidl.h>
#include "src/sys/fuzzing/common/options.h"
#include "src/sys/fuzzing/common/result.h"
#include "src/sys/fuzzing/common/testing/sanitizer.h"
#include "src/sys/fuzzing/libfuzzer/testing/feedback.h"
namespace fuzzing {
using ::test::fuzzer::Relay;
using ::test::fuzzer::RelayPtr;
using ::test::fuzzer::SignaledBuffer;
TestFuzzer::TestFuzzer() {
context_ = ComponentContext::Create();
eventpair_ = std::make_unique<AsyncEventPair>(context_->executor());
}
int TestFuzzer::TestOneInput(const uint8_t *data, size_t size) {
zx_status_t retval = ZX_ERR_SHOULD_WAIT;
auto task = fpromise::make_promise([this, relay = RelayPtr(), connect = Future<SignaledBuffer>()](
Context &context) mutable -> ZxResult<> {
// First, connect to the unit test via the relay, if necessary.
if (eventpair_->IsConnected() && !relay) {
return fpromise::ok();
}
if (!relay) {
auto handler = context_->MakeRequestHandler<Relay>();
auto executor = context_->executor();
handler(relay.NewRequest(executor->dispatcher()));
}
if (!connect) {
Bridge<SignaledBuffer> bridge;
relay->WatchTestData(bridge.completer.bind());
connect = bridge.consumer.promise_or(fpromise::error());
}
if (!connect(context)) {
return fpromise::pending();
}
if (connect.is_error()) {
return fpromise::error(ZX_ERR_PEER_CLOSED);
}
auto signaled_buffer = connect.take_value();
if (auto status = test_input_buffer_.Link(std::move(signaled_buffer.test_input));
status != ZX_OK) {
return fpromise::error(status);
}
if (auto status = feedback_buffer_.Link(std::move(signaled_buffer.feedback));
status != ZX_OK) {
return fpromise::error(status);
}
eventpair_->Pair(std::move(signaled_buffer.eventpair));
relay->Finish();
return fpromise::ok();
})
.and_then([this, data, size] {
if (auto status = test_input_buffer_.Write(data, size); status != ZX_OK) {
return AsZxResult(status);
}
// Notify the unit test that the test input is ready, and wait for its
// notification that feedback is ready.
return AsZxResult(eventpair_->SignalPeer(0, kStart));
})
.and_then(eventpair_->WaitFor(kStart))
.and_then([this](const zx_signals_t &observed) {
return AsZxResult(eventpair_->SignalSelf(observed, 0));
})
.and_then([this]() -> ZxResult<> {
const auto *feedback =
reinterpret_cast<const RelayedFeedback *>(feedback_buffer_.data());
for (size_t i = 0; i < feedback->num_counters; ++i) {
const auto *counter = &feedback->counters[i];
SetCoverage(counter->offset, counter->value);
}
if (feedback->leak_suspected) {
// Without a call to the |free_hook|, the fake sanitizer should suspect a
// leak.
Malloc(sizeof(*this));
}
switch (feedback->result) {
case FuzzResult::NO_ERRORS:
// Notify the unit test that the fuzzer completed the run.
return AsZxResult(eventpair_->SignalPeer(0, kFinish));
case FuzzResult::BAD_MALLOC:
printf("DEDUP_TOKEN: BAD_MALLOC\n");
Malloc(size_t(-1));
break;
case FuzzResult::CRASH:
printf("DEDUP_TOKEN: CRASH\n");
Crash();
break;
case FuzzResult::DEATH:
printf("DEDUP_TOKEN: DEATH\n");
Die();
break;
case FuzzResult::EXIT:
// Don't call exit() here; the atexit handlers will invoke the executors
// destructors, which will panic since we're mid-task. Signal via error.
printf("DEDUP_TOKEN: EXIT\n");
return fpromise::error(ZX_ERR_STOP);
case FuzzResult::LEAK:
LeakMemory();
return fpromise::ok();
case FuzzResult::OOM:
printf("DEDUP_TOKEN: OOM\n");
OOM();
break;
case FuzzResult::TIMEOUT:
printf("DEDUP_TOKEN: TIMEOUT\n");
Timeout();
break;
}
FX_NOTREACHED();
return fpromise::error(ZX_ERR_INTERNAL);
})
.then([&retval](const ZxResult<> &result) {
retval = result.is_ok() ? ZX_OK : result.error();
return fpromise::ok();
});
// Compare with async-test.h. Unlike a real fuzzer, this fake fuzzer runs its async loop on the
// current thread. To make |LLVMFuzzerTestOneInput| synchronous, this method needs to periodically
// kick the loop until the promise above completes.
context_->ScheduleTask(std::move(task));
while (true) {
if (auto status = context_->RunUntilIdle(); status != ZX_OK) {
FX_LOGS(WARNING) << "Loop stopped unexpectedly: " << zx_status_get_string(status);
return status;
}
// See the comment on the |FuzzResult::EXIT| case above. It is safe to call exit() here.
if (retval == ZX_ERR_STOP) {
exit(0);
}
if (retval != ZX_ERR_SHOULD_WAIT) {
return retval;
}
zx::nanosleep(zx::deadline_after(zx::msec(10)));
}
}
void TestFuzzer::Crash() { __builtin_trap(); }
void TestFuzzer::OOM() {
// Grow at a rate of ~100 Mb/s. Even with a low RSS limit, it may take a couple seconds to OOM,
// as libFuzzer's RSS thread runs once per second.
std::minstd_rand prng;
std::vector<std::vector<uint8_t>> blocks;
const size_t block_size = 1ULL << 20;
while (true) {
std::vector<uint8_t> block(block_size, static_cast<uint8_t>(prng()));
blocks.push_back(std::move(block));
zx::nanosleep(zx::deadline_after(zx::msec(10)));
}
}
void TestFuzzer::Timeout() {
// Make sure libFuzzer's -timeout flag is set to something reasonable before calling this!
zx::nanosleep(zx::time::infinite());
}
} // namespace fuzzing
// Forward the fuzz target function required by libFuzzer to the |gFuzzer| object.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
static fuzzing::TestFuzzer fuzzer;
return fuzzer.TestOneInput(data, size);
}