blob: f17d1b43b605b2dde08d75b3f51a9471ad57b3eb [file] [log] [blame]
/*
* Copyright (C) 2023 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "Fuzzilli.h"
#include <mutex>
#include <wtf/Assertions.h>
#include <wtf/Compiler.h>
#include <wtf/DataLog.h>
#include <wtf/NeverDestroyed.h>
#if ENABLE(FUZZILLI)
#define REPRL_CRFD 100
#define REPRL_CWFD 101
#define REPRL_DRFD 102
#define REPRL_DWFD 103
#define REPRL_MAX_DATA_SIZE (16 * 1024 * 1024)
#define SHM_SIZE 0x100000
#define MAX_EDGES ((SHM_SIZE - 4) * 8)
#define WRITE_TO_FUZZILLI(data_, size_) RELEASE_ASSERT(write(REPRL_CWFD, data_, size_) == static_cast<ssize_t>(size_))
#define READ_FROM_FUZZILLI(data_, size_) RELEASE_ASSERT(read(REPRL_CRFD, data_, size_) == static_cast<ssize_t>(size_))
struct Fuzzilli::SharedData* Fuzzilli::sharedData { nullptr };
uint32_t* Fuzzilli::edgesStart { nullptr };
uint32_t* Fuzzilli::edgesStop { nullptr };
char* Fuzzilli::reprlInputData { nullptr };
size_t Fuzzilli::numPendingRejectedPromises { 0 };
void Fuzzilli::resetCoverageEdges()
{
uint64_t n = 0;
for (uint32_t* edge = edgesStart; edge < edgesStop && n < MAX_EDGES; edge++)
*edge = ++n;
}
FilePrintStream& Fuzzilli::logFile()
{
static LazyNeverDestroyed<FilePrintStream> result;
static std::once_flag flag;
std::call_once(flag, []() {
if (FILE* file = fdopen(REPRL_DWFD, "w"))
result.construct(file, FilePrintStream::AdoptionMode::Adopt);
else {
result.construct(stdout, FilePrintStream::AdoptionMode::Borrow);
dataLogLn("Fuzzer output channel not available, printing to stdout instead.");
}
});
return result.get();
}
void Fuzzilli::waitForCommand()
{
unsigned action;
READ_FROM_FUZZILLI(&action, sizeof(action));
RELEASE_ASSERT_WITH_MESSAGE(action == 'cexe', "[REPRL] Unknown action: %u", action);
}
SUPPRESS_COVERAGE
void Fuzzilli::initializeCoverage(uint32_t* start, uint32_t* stop)
{
RELEASE_ASSERT_WITH_MESSAGE(!edgesStart && !edgesStop, "Coverage instrumentation is only supported for a single module");
edgesStart = start;
edgesStop = stop;
if (const char* shmKey = getenv("SHM_ID")) {
int32_t fd = shm_open(shmKey, O_RDWR, S_IREAD | S_IWRITE);
RELEASE_ASSERT_WITH_MESSAGE(fd >= 0, "Failed to open shared memory region: %s", strerror(errno));
sharedData = static_cast<SharedData*>(mmap(0, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
RELEASE_ASSERT_WITH_MESSAGE(sharedData != MAP_FAILED, "Failed to mmap shared memory region");
dataLogLn("[COV] edge counters initialized. Shared memory: %s with %zu edges.", shmKey, edgesStop - edgesStart);
} else
sharedData = static_cast<SharedData*>(malloc(SHM_SIZE));
resetCoverageEdges();
sharedData->numEdges = static_cast<uint32_t>(edgesStop - edgesStart);
}
void Fuzzilli::readInput(Vector<char>* buffer)
{
size_t inputSize;
READ_FROM_FUZZILLI(&inputSize, sizeof(inputSize));
RELEASE_ASSERT(inputSize < REPRL_MAX_DATA_SIZE);
buffer->resize(inputSize);
memcpy(buffer->data(), reprlInputData, inputSize);
}
void Fuzzilli::flushReprl(int32_t result)
{
// In REPRL mode, stdout and stderr may be regular files, so we need to fflush them here.
fflush(stdout);
fflush(stderr);
// Check if any rejected promises weren't handled.
if (numPendingRejectedPromises > 0) {
numPendingRejectedPromises = 0;
result = 1;
}
int32_t status = (result & 0xff) << 8;
WRITE_TO_FUZZILLI(&status, sizeof(status));
resetCoverageEdges();
}
void Fuzzilli::initializeReprl()
{
char helo[] = "HELO";
WRITE_TO_FUZZILLI(helo, 4);
READ_FROM_FUZZILLI(helo, 4);
RELEASE_ASSERT_WITH_MESSAGE(!memcmp(helo, "HELO", 4), "[REPRL] Invalid response from parent");
// Mmap the data input buffer.
reprlInputData = static_cast<char*>(mmap(0, REPRL_MAX_DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, REPRL_DRFD, 0));
RELEASE_ASSERT(reprlInputData != MAP_FAILED);
}
extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t* start, uint32_t* stop);
extern "C" void __sanitizer_cov_trace_pc_guard_init(uint32_t* start, uint32_t* stop)
{
// Avoid duplicate initialization.
if (start == stop || *start)
return;
Fuzzilli::initializeCoverage(start, stop);
}
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t* guard);
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t* guard)
{
// There's a small race condition here: if this function executes in two threads for the same
// edge at the same time, the first thread might disable the edge (by setting the guard to zero)
// before the second thread fetches the guard value (and thus the index). However, our
// instrumentation ignores the first edge (see libcoverage.c) and so the race is unproblematic.
uint32_t index = *guard;
Fuzzilli::sharedData->edges[index / 8] |= 1 << (index % 8);
*guard = 0;
}
#endif // ENABLE(FUZZILLI)