Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fuzz: libprotobuf-mutator integration. #3139

Merged
merged 6 commits into from Apr 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 8 additions & 4 deletions bazel/envoy_build_system.bzl
Expand Up @@ -215,10 +215,14 @@ def envoy_cc_fuzz_test(name, corpus, deps = [], **kwargs):
linkopts = envoy_test_linkopts(),
linkstatic = 1,
args = [PACKAGE_NAME + "/" + corpus],
deps = [
":" + test_lib_name,
"//test/fuzz:main",
],
# No fuzzing on OS X.
deps = select({
"@bazel_tools//tools/osx:darwin": ["//test:dummy_main"],
"//conditions:default": [
":" + test_lib_name,
"//test/fuzz:main",
],
}),
)
native.cc_binary(
name = name + "_driverless",
Expand Down
9 changes: 9 additions & 0 deletions bazel/external/libprotobuf_mutator.BUILD
@@ -0,0 +1,9 @@
cc_library(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmillikin-stripe I'm not super happy with how this turned out, I don't like non-trivial BUILD files in Envoy, but this was simplest vs. getting protobuf as a Bazel dep and the cmake build system to play nice. I've filed google/libprotobuf-mutator#91 to request that we get proper upstream Bazel support for this project. I think the current solution is fine as this is a test-only dependency and it's a pretty simple library, we don't care about tests in this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to simplify this with globs:

srcs = glob(["src/**/*.c", "src/**/*.h"], exclude=["**/*_test.cc"])

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmillikin-stripe can you take another look at what I've done for OS X? I'm not very happy with it, I would like something more like bazelbuild/bazel#3510.

name = "libprotobuf_mutator",
srcs = glob(["src/**/*.cc", "src/**/*.h", "port/protobuf.h"], exclude=["**/*_test.cc"]),
hdrs = ["src/libfuzzer/libfuzzer_macro.h"],
includes = ["."],
include_prefix = "libprotobuf_mutator",
deps = ["//external:protobuf"],
visibility = ["//visibility:public"],
)
11 changes: 11 additions & 0 deletions bazel/repositories.bzl
Expand Up @@ -231,6 +231,7 @@ def envoy_dependencies(path = "@envoy_deps//", skip_targets = []):
_com_github_fmtlib_fmt()
_com_github_gabime_spdlog()
_com_github_gcovr_gcovr()
_com_github_google_libprotobuf_mutator()
_io_opentracing_cpp()
_com_lightstep_tracer_cpp()
_com_github_grpc_grpc()
Expand Down Expand Up @@ -314,6 +315,16 @@ def _com_github_gcovr_gcovr():
actual = "@com_github_gcovr_gcovr//:gcovr",
)

def _com_github_google_libprotobuf_mutator():
_repository_impl(
name = "com_github_google_libprotobuf_mutator",
build_file = "@envoy//bazel/external:libprotobuf_mutator.BUILD",
)
native.bind(
name = "libprotobuf_mutator",
actual = "@com_github_google_libprotobuf_mutator//:libprotobuf_mutator",
)

def _io_opentracing_cpp():
_repository_impl("io_opentracing_cpp")
native.bind(
Expand Down
4 changes: 4 additions & 0 deletions bazel/repository_locations.bzl
Expand Up @@ -34,6 +34,10 @@ REPOSITORY_LOCATIONS = dict(
commit = "c0d77201039c7b119b18bc7fb991564c602dd75d",
remote = "https://github.com/gcovr/gcovr",
),
com_github_google_libprotobuf_mutator = dict(
commit = "c3d2faf04a1070b0b852b0efdef81e1a81ba925e",
remote = "https://github.com/google/libprotobuf-mutator",
),
com_github_grpc_grpc = dict(
commit = "66b9770a8ad326c1ee0dbedc5a8f32a52a604567", # v1.10.1
remote = "https://github.com/grpc/grpc.git",
Expand Down
3 changes: 3 additions & 0 deletions source/common/protobuf/utility.cc
Expand Up @@ -128,6 +128,9 @@ bool ValueUtil::equal(const ProtobufWkt::Value& v1, const ProtobufWkt::Value& v2
}

switch (kind) {
case ProtobufWkt::Value::KIND_NOT_SET:
return v2.kind_case() == ProtobufWkt::Value::KIND_NOT_SET;

case ProtobufWkt::Value::kNullValue:
return true;

Expand Down
6 changes: 6 additions & 0 deletions test/BUILD
Expand Up @@ -8,6 +8,12 @@ load(

envoy_package()

# TODO(htuch): remove when we have a solution for https://github.com/bazelbuild/bazel/issues/3510
envoy_cc_test_library(
name = "dummy_main",
srcs = ["dummy_main.cc"],
)

envoy_cc_test_library(
name = "main",
srcs = [
Expand Down
2 changes: 1 addition & 1 deletion test/common/common/base64_fuzz_test.cc
Expand Up @@ -5,7 +5,7 @@
namespace Envoy {
namespace Fuzz {

void Runner::execute(const uint8_t* buf, size_t len) {
DEFINE_FUZZER(const uint8_t* buf, size_t len) {
Envoy::Base64::encode(reinterpret_cast<const char*>(buf), len);
}

Expand Down
8 changes: 8 additions & 0 deletions test/common/protobuf/BUILD
Expand Up @@ -2,6 +2,7 @@ licenses(["notice"]) # Apache 2

load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_fuzz_test",
"envoy_cc_test",
"envoy_package",
)
Expand All @@ -18,3 +19,10 @@ envoy_cc_test(
"@envoy_api//envoy/config/bootstrap/v2:bootstrap_cc",
],
)

envoy_cc_fuzz_test(
name = "value_util_fuzz_test",
srcs = ["value_util_fuzz_test.cc"],
corpus = "value_util_corpus",
deps = ["//source/common/protobuf:utility_lib"],
)
Empty file.
1 change: 1 addition & 0 deletions test/common/protobuf/value_util_corpus/string_value
@@ -0,0 +1 @@
string_value: "foo"
58 changes: 58 additions & 0 deletions test/common/protobuf/value_util_corpus/struct_value
@@ -0,0 +1,58 @@
struct_value {
fields {
key: "null"
value {
null_value: NULL_VALUE
}
}
fields {
key: "number"
value {
number_value: 3.14159265358979323
}
}
fields {
key: "string"
value {
string_value: "string"
}
}
fields {
key: "bool"
value {
bool_value: true
}
}
fields {
key: "list"
value {
list_value {
values {
string_value: "some"
}
values {
string_value: "thing"
}
}
}
}
fields {
key: "struct"
value {
struct_value {
fields {
key: "foo"
value {
number_value: 42
}
}
fields {
key: "bar"
value {
number_value: 37
}
}
}
}
}
}
11 changes: 11 additions & 0 deletions test/common/protobuf/value_util_fuzz_test.cc
@@ -0,0 +1,11 @@
#include "common/protobuf/utility.h"

#include "test/fuzz/fuzz_runner.h"

namespace Envoy {
namespace Fuzz {

DEFINE_PROTO_FUZZER(const ProtobufWkt::Value& input) { ValueUtil::equal(input, input); }

} // namespace Fuzz
} // namespace Envoy
6 changes: 6 additions & 0 deletions test/dummy_main.cc
@@ -0,0 +1,6 @@
// Dummy main implementation for nooping tests.
// TODO(htuch): remove when we have a solution for
// https://github.com/bazelbuild/bazel/issues/3510

// NOLINT(namespace-envoy)
int main(int /*argc*/, char** /*argv*/) { return 0; }
1 change: 1 addition & 0 deletions test/fuzz/BUILD
Expand Up @@ -25,6 +25,7 @@ envoy_cc_test_library(
name = "fuzz_runner_lib",
srcs = ["fuzz_runner.cc"],
hdrs = ["fuzz_runner.h"],
external_deps = ["libprotobuf_mutator"],
deps = [
"//source/common/common:logger_lib",
"//source/common/common:thread_lib",
Expand Down
22 changes: 19 additions & 3 deletions test/fuzz/README.md
Expand Up @@ -34,10 +34,12 @@ exist, populated with files that will act as the corpus.
Your fuzz test will ultimately be driven by a simple interface:

```c++
void Envoy::Fuzz::Runner::execute(const uint8_t* data, size_t size);
DEFINE_FUZZER(const uint8_t* data, size_t size) {
// Your test code goes here
}
```

It is up to your test `execute()` implementation to map this buffer of data to
It is up to your test `DEFINE_FUZZER` implementation to map this buffer of data to
meaningful semantics, e.g. a stream of network bytes or a protobuf binary input.

The fuzz test will be executed in two environments:
Expand All @@ -53,7 +55,7 @@ The fuzz test will be executed in two environments:

## Defining a new fuzz test

1. Write a fuzz test module implementing the `Envoy::Fuzz::Runner::execute`
1. Write a fuzz test module implementing the `DEFINE_FUZZER`
interface. E.g.
[`test/common/common/base64_fuzz_test.cc`](../../test/common/common/base64_fuzz_test.cc).

Expand All @@ -66,3 +68,17 @@ The fuzz test will be executed in two environments:

4. Run the `envoy_cc_fuzz_test` target. E.g. `bazel test
//test/common/common:base64_fuzz_test`.

## Protobuf fuzz tests

We also have integration with
[libprotobuf-mutator](https://github.com/google/libprotobuf-mutator), allowing
tests built on a protobuf input to work directly with a typed protobuf object,
rather than a raw buffer. The interface to this is as described at
https://github.com/google/libprotobuf-mutator#integrating-with-libfuzzer:

```c++
DEFINE_PROTO_FUZZER(const MyMessageType& input) {
// Your test code goes here
}
```
5 changes: 0 additions & 5 deletions test/fuzz/fuzz_runner.cc
Expand Up @@ -26,8 +26,3 @@ extern "C" int LLVMFuzzerInitialize(int* /*argc*/, char*** argv) {
Envoy::Fuzz::Runner::setupEnvironment(1, *argv);
return 0;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* buf, size_t len) {
Envoy::Fuzz::Runner::execute(buf, len);
return 0;
}
33 changes: 24 additions & 9 deletions test/fuzz/fuzz_runner.h
Expand Up @@ -3,6 +3,10 @@
#include <cstdint>
#include <cwchar>

// Bring in DEFINE_PROTO_FUZZER definition as per
// https://github.com/google/libprotobuf-mutator#integrating-with-libfuzzer.
#include "libprotobuf_mutator/src/libfuzzer/libfuzzer_macro.h"

namespace Envoy {
namespace Fuzz {

Expand All @@ -15,13 +19,6 @@ class Runner {
* @param argv array of command-line args.
*/
static void setupEnvironment(int argc, char** argv);

/**
* Execute a single fuzz test. This should be implemented by the
* envoy_cc_fuzz_test target. See
* https://llvm.org/docs/LibFuzzer.html#fuzz-target.
*/
static void execute(const uint8_t* data, size_t size);
};

} // namespace Fuzz
Expand All @@ -31,6 +28,24 @@ class Runner {
// https://llvm.org/docs/LibFuzzer.html#startup-initialization.
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv);

// Entry point from fuzzing library driver, see
// https://llvm.org/docs/LibFuzzer.html#fuzz-target.
// See https://llvm.org/docs/LibFuzzer.html#fuzz-target.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);

#define DEFINE_TEST_ONE_INPUT_IMPL \
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { \
EnvoyTestOneInput(data, size); \
return 0; \
}

/**
* Define a fuzz test. This should be used to define a fuzz_cc_fuzz_test_target with:
*
* DEFINE_FUZZER(const uint8_t* buf, size_t len) {
* // Do some test stuff with buf/len.
* return 0;
* }
*/
#define DEFINE_FUZZER \
static void EnvoyTestOneInput(const uint8_t* buf, size_t len); \
DEFINE_TEST_ONE_INPUT_IMPL \
static void EnvoyTestOneInput