blob: 30c52d900d65ce30bedd5ea781cf306314d71875 [file] [log] [blame]
// Copyright 2022 The Centipede Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// A fuzz target used for testing Centipede.
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <utility>
// Function with a single coverage edge. Used by coverage_test.cc.
__attribute__((noinline)) extern "C" void SingleEdgeFunc() {
[[maybe_unused]] static volatile int sink;
sink = 0;
}
// Function with multiple coverage edges. Used by coverage_test.cc.
__attribute__((noinline)) extern "C" void MultiEdgeFunc(uint8_t input) {
static volatile int sink;
if (input) {
sink = 42;
} else {
sink++;
}
}
// Function with indirect call based on the input value.
__attribute__((noinline)) extern "C" void IndirectCallFunc(uint8_t input) {
[[maybe_unused]] static volatile int sink;
using func_type = void (*)();
func_type funcs[4] = {[]() { sink = 0; }, []() { sink = 1; },
[]() { sink = 2; }, []() { sink = 3; }};
funcs[input % 4]();
}
// Used to test data flow instrumentation.
static int non_cost_global[10];
static const int const_global[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
// See https://llvm.org/docs/LibFuzzer.html#fuzz-target.
// coverage_test.cc and centipede_test.sh verify the exact line where
// LLVMFuzzerTestOneInput is declared.
// So if you move the declaration to another line, update these tests.
//
// This test does not use memcmp or similar to keep
// the generated code very simple.
static volatile void *ptr_sink = nullptr;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Print the input. It will be tested in runner_test.
printf("{");
for (size_t i = 0; i < size; i++) {
// This loop generates different coverage counters
// depending on the number of iterations.
printf("%02x%s", (int)data[i], i + 1 == size ? "" : ", ");
}
printf("}\n");
// If the input is 'cntX', run X iterations of a do-while loop.
// Runs one iteration if X is 0. Used to test --use_counter_features.
if (size == 4 && data[0] == 'c' && data[1] == 'n' && data[2] == 't') {
[[maybe_unused]] static volatile int sink;
int num_iterations = data[3];
// We use do-while loop to simplify the control flow here.
do {
sink = --num_iterations;
} while (num_iterations >= 0);
return 0;
}
// Allocate 4Gb of RAM if the input is 'oom'.
// runner_test provokes OOM by feeding 'oom' input here,
// and checks that we can detect the OOM with ulimit.
if (size == 3 && data[0] == 'o' && data[1] == 'o' && data[2] == 'm') {
size_t oom_allocation_size = 1ULL << 32;
void *ptr = malloc(oom_allocation_size);
memset(ptr, 42, oom_allocation_size);
ptr_sink = ptr;
}
// Sleep for 10 seconds if the input is 'slo'.
if (size == 3 && data[0] == 's' && data[1] == 'l' && data[2] == 'o') {
sleep(10);
}
// Call SingleEdgeFunc() if input is "func1".
if (size == 5 && data[0] == 'f' && data[1] == 'u' && data[2] == 'n' &&
data[3] == 'c' && data[4] == '1') {
SingleEdgeFunc();
}
// Call MultiEdgeFunc(data[6]) if input is "func2-?" ('?' is any symbol).
if (size == 7 && data[0] == 'f' && data[1] == 'u' && data[2] == 'n' &&
data[3] == 'c' && data[4] == '2' && data[5] == '-') {
MultiEdgeFunc(data[6]);
}
// Load from `non_cost_global` if input is "glob[0-9]".
// The last digit is the index into non_cost_global.
if (size == 5 && data[0] == 'g' && data[1] == 'l' && data[2] == 'o' &&
data[3] == 'b' && data[4] >= '0' && data[4] <= '9') {
size_t offset = data[4] - '0';
[[maybe_unused]] static volatile int sink;
printf("loading from %p at offset %zd\n", &non_cost_global, offset);
sink = non_cost_global[offset];
}
// Load from `cost_global` if input is "cons[0-9]".
// The last digit is the index into cost_global.
// Keep the inputs for glob[0-9] (above) and cons[0-9] (here) the same length,
// so that it takes the same amount of work for a fuzzer to discover.
if (size == 5 && data[0] == 'c' && data[1] == 'o' && data[2] == 'n' &&
data[3] == 's' && data[4] >= '0' && data[4] <= '9') {
size_t offset = data[4] - '0';
[[maybe_unused]] static volatile int sink;
printf("loading from %p at offset %zd\n", &const_global, offset);
sink = const_global[offset];
}
// If input is "cmpABCDEFGH" (A-H - any bytes), execute a comparison
// instruction between ABCD and EFGH (treated as uint32_t).
if (size == 3 + 4 + 4 && data[0] == 'c' && data[1] == 'm' && data[2] == 'p') {
[[maybe_unused]] static volatile int sink;
uint32_t a, b;
memcpy(&a, data + 3, sizeof(a));
memcpy(&b, data + 7, sizeof(b));
sink = a < b;
}
// Same as above, but for memcmp.
// If input is "mcmpABCDEFGH" (A-H - any bytes), execute a comparison
// instruction via a 4-byte memcmp between ABCD and EFGH.
if (size == 4 + 4 + 4 && data[0] == 'm' && data[1] == 'c' && data[2] == 'm' &&
data[3] == 'p') {
[[maybe_unused]] static volatile int sink;
static volatile int kFour = 4; // volatile to avoid memcmp inlining.
sink = memcmp(data + 4, data + 8, kFour);
}
// If input is "-1", return -1.
// LibFuzzer supports this return value as of 2022-07:
// https://llvm.org/docs/LibFuzzer.html#rejecting-unwanted-inputs
if (size == 2 && data[0] == '-' && data[1] == '1') {
return -1;
}
// If input is pthXYZ (XYZ - any 3 bytes), call a function in
// a function table 3 times, based on the value of X, Y, and Z.
// depending on XYZ but not using any control flow for that.
if (size == 6 && data[0] == 'p' && data[1] == 't' && data[2] == 'h') {
[[maybe_unused]] static volatile int sink;
using func_type = void (*)();
func_type funcs[4] = {[]() { sink = 0; }, []() { sink = 1; },
[]() { sink = 2; }, []() { sink = 3; }};
size_t idx0 = data[3] % 4;
size_t idx1 = data[4] % 4;
size_t idx2 = data[5] % 4;
funcs[idx0]();
funcs[idx1]();
funcs[idx2]();
}
IndirectCallFunc(data[0]);
return 0;
}
// This function *may* be provided by the fuzzing engine.
extern "C" __attribute__((weak)) size_t LLVMFuzzerMutate(uint8_t *data,
size_t size,
size_t max_size);
// Test-friendly custom mutator. See
// https://github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md
// Reverts the bytes in `data` and sometimes adds a number in [100,107)
// at the end.
// If available, LLVMFuzzerMutate is used some of the time.
// Also returns 0 sometimes to simulate mutation failures.
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size,
size_t max_size, unsigned int seed) {
if ((seed % 3) == 0) {
return LLVMFuzzerMutate(data, size, max_size);
}
// TODO(b/267096672): Remove `size > 1` once custom mutator detection is fixed
// in CentipedeDefaultCallbacks ctor.
if ((seed % 3) == 1 && size > 1) {
return 0;
}
for (size_t i = 0; i < size / 2; ++i) {
std::swap(data[i], data[size - i - 1]);
}
if (max_size > size && (seed % 5)) {
data[size] = 100 + (seed % 7);
++size;
}
return size;
}
// Test-friendly custom crossover. See
// https://github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md
// Merges the two inputs together and puts 42 between them.
extern "C" size_t LLVMFuzzerCustomCrossOver(const uint8_t *data1, size_t size1,
const uint8_t *data2, size_t size2,
uint8_t *out, size_t max_out_size,
unsigned int seed) {
size_t new_size = size1 + size2 + 1;
if (new_size > max_out_size) return 0;
memcpy(out, data1, size1);
out[size1] = 42;
memcpy(out + size1 + 1, data2, size2);
return new_size;
}