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

Carbon fuzzing 3/3: added actual fuzzer implementation and a fuzzverter utility for investigating crashing protos #1156

Merged
merged 46 commits into from
Apr 11, 2022
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
bda1633
finished fuzzer and added fuzzverter util
pk19604014 Mar 28, 2022
750874d
fixed typo
pk19604014 Mar 28, 2022
8bc26d1
renamed cmd line params
pk19604014 Mar 29, 2022
4e4dfe7
Merge branch 'trunk' of https://github.com/carbon-language/carbon-lan…
pk19604014 Mar 29, 2022
b9c07e8
fixed libproto_mutator download path
pk19604014 Mar 29, 2022
08379f6
small fixes
pk19604014 Mar 29, 2022
a2c20cc
small fixes
pk19604014 Mar 30, 2022
0e5cd09
merged
pk19604014 Mar 30, 2022
c1f0112
merged
pk19604014 Mar 30, 2022
befcfe2
small fixes
pk19604014 Mar 30, 2022
25a8f28
renamed sample corpus proto
pk19604014 Mar 30, 2022
466aab0
small fixes
pk19604014 Mar 30, 2022
7ce3607
try building on github with LIBCPP_DEBUG enabled
pk19604014 Mar 31, 2022
62e128a
temporarily marked proto fuzzer as a manual test
pk19604014 Mar 31, 2022
23797f9
code review
pk19604014 Mar 31, 2022
2c49b2e
use a dedicated proto-fuzzer feature to work around LIBCPP_DEBUG=1 cr…
pk19604014 Apr 1, 2022
f233eda
code review comments, added README.md
pk19604014 Apr 2, 2022
ddb5095
Merge branch 'trunk' of https://github.com/carbon-language/carbon-lan…
pk19604014 Apr 2, 2022
4d0e85a
minor fixes to the text
pk19604014 Apr 3, 2022
4b912de
Merge branch 'trunk' of https://github.com/carbon-language/carbon-lan…
pk19604014 Apr 6, 2022
ccb0b85
Update bazel/cc_toolchains/clang_cc_toolchain_config.bzl
pk19604014 Apr 6, 2022
a84a165
Merge branch 'trunk' of https://github.com/carbon-language/carbon-lan…
pk19604014 Apr 7, 2022
3b1aae7
use Carbon source representation for "empty Main()" instead of text f…
pk19604014 Apr 7, 2022
ce6e738
fixed typo
pk19604014 Apr 7, 2022
e976307
made FuzzerUtil produce the full carbon source (proto converted + Mai…
pk19604014 Apr 7, 2022
719d486
typo
pk19604014 Apr 7, 2022
4c0860b
Merge branch 'trunk' of https://github.com/carbon-language/carbon-lan…
pk19604014 Apr 7, 2022
a3b48c9
switched to text proto format per code review
pk19604014 Apr 7, 2022
1063754
Merge branch 'trunk' of https://github.com/carbon-language/carbon-lan…
pk19604014 Apr 7, 2022
7ffb036
Update executable_semantics/prelude.h
pk19604014 Apr 8, 2022
3b090e4
Update executable_semantics/fuzzing/README.md
pk19604014 Apr 8, 2022
06c5c5c
Update executable_semantics/fuzzing/README.md
pk19604014 Apr 8, 2022
380f874
Update executable_semantics/fuzzing/README.md
pk19604014 Apr 8, 2022
7e2bfaa
Update executable_semantics/fuzzing/README.md
pk19604014 Apr 8, 2022
c792dd6
Update executable_semantics/fuzzing/README.md
pk19604014 Apr 8, 2022
63da289
review comments
pk19604014 Apr 8, 2022
fe9669d
merged
pk19604014 Apr 8, 2022
24c4e73
removed unnecessary file mode variables
pk19604014 Apr 8, 2022
2de29d9
Update executable_semantics/fuzzing/fuzzverter.cpp
pk19604014 Apr 8, 2022
684a949
Update executable_semantics/fuzzing/README.md
pk19604014 Apr 8, 2022
febf112
Update executable_semantics/fuzzing/README.md
pk19604014 Apr 8, 2022
b9c7fee
code review comments
pk19604014 Apr 8, 2022
31d4775
Merge branch 'trunk' of https://github.com/carbon-language/carbon-lan…
pk19604014 Apr 11, 2022
f02f5f2
Update executable_semantics/syntax/BUILD
pk19604014 Apr 11, 2022
b6dedc2
Merge branch 'executable_semantics_fuzzer' of https://github.com/pk19…
pk19604014 Apr 11, 2022
35adb05
buildifier
pk19604014 Apr 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ build:asan --features=asan
# Configuration for enabling LibFuzzer (along with ASan).
build:fuzzer --features=fuzzer

# Proto fuzzer specific configuration.
build:proto-fuzzer --features=proto-fuzzer

# Always allow tests to symbolize themselves with whatever `llvm-symbolize` is
# in the users environment.
build --test_env=ASAN_SYMBOLIZER_PATH
Expand Down
14 changes: 14 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,20 @@ rules_proto_dependencies()

rules_proto_toolchains()

###############################################################################
# libprotobuf_mutator - for structured fuzzer testing.
###############################################################################

libprotobuf_mutator_version = "1.0"

http_archive(
name = "com_google_libprotobuf_mutator",
build_file = "@//:third_party/libprotobuf_mutator/BUILD.txt",
sha256 = "792f250fb546bde8590e72d64311ea00a70c175fd77df6bb5e02328fa15fe28e",
strip_prefix = "libprotobuf-mutator-%s" % libprotobuf_mutator_version,
urls = ["https://github.com/google/libprotobuf-mutator/archive/v%s.tar.gz" % libprotobuf_mutator_version],
)

###############################################################################
# Example conversion repositories
###############################################################################
Expand Down
19 changes: 18 additions & 1 deletion bazel/cc_toolchains/clang_cc_toolchain_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,17 @@ def _impl(ctx):
)],
)

proto_fuzzer = feature(
name = "proto-fuzzer",
enabled = False,
requires = [feature_set(["nonhost"])],

# TODO: this should really be `fuzzer`, but `-fsanitize=fuzzer` triggers
# a clang crash when running `bazel test --config=fuzzer ...`. See
# https://github.com/carbon-language/carbon-lang/issues/1173
implies = ["asan"],
)

linux_flags_feature = feature(
name = "linux_flags",
enabled = True,
Expand Down Expand Up @@ -528,7 +539,12 @@ def _impl(ctx):
"-D_LIBCPP_DEBUG=1",
])],
with_features = [
with_feature_set(not_features = ["opt"]),
# _LIBCPP_DEBUG=1 causes protobuf code to crash when linked
# with `-fsanitize=fuzzer`, possibly because of ODR
# violations caused by Carbon source and pre-compiled llvm
# Fuzzer driver library built with different _LIBCPP_DEBUG
# values.
with_feature_set(not_features = ["opt", "proto-fuzzer"]),
],
),
],
Expand Down Expand Up @@ -751,6 +767,7 @@ def _impl(ctx):
asan,
enable_asan_in_fastbuild,
fuzzer,
proto_fuzzer,
layering_check,
module_maps,
use_module_maps,
Expand Down
16 changes: 15 additions & 1 deletion executable_semantics/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,25 @@ filegroup(
srcs = ["data/prelude.carbon"],
)

cc_library(
name = "prelude",
srcs = ["prelude.cpp"],
hdrs = ["prelude.h"],
data = [":standard_libraries"],
deps = [
"//common:error",
"//executable_semantics/ast:declaration",
"//executable_semantics/common:arena",
"//executable_semantics/common:nonnull",
"//executable_semantics/syntax",
],
)
pk19604014 marked this conversation as resolved.
Show resolved Hide resolved

cc_binary(
name = "executable_semantics",
srcs = ["main.cpp"],
data = [":standard_libraries"],
deps = [
":prelude",
"//common:error",
"//executable_semantics/common:arena",
"//executable_semantics/common:nonnull",
Expand Down
49 changes: 49 additions & 0 deletions executable_semantics/fuzzing/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

load("//bazel/fuzzing:rules.bzl", "cc_fuzz_test")

cc_library(
name = "ast_to_proto_lib",
srcs = ["ast_to_proto.cpp"],
Expand All @@ -15,6 +17,18 @@ cc_library(
],
)

cc_library(
name = "fuzzer_util",
srcs = ["fuzzer_util.cpp"],
hdrs = ["fuzzer_util.h"],
deps = [
"//common:check",
"//common/fuzzing:carbon_cc_proto",
"//common/fuzzing:proto_to_carbon_lib",
"@llvm-project//llvm:Support",
],
)

cc_test(
name = "ast_to_proto_test",
srcs = ["ast_to_proto_test.cpp"],
Expand All @@ -36,6 +50,22 @@ cc_test(
],
)

cc_binary(
name = "fuzzverter",
srcs = ["fuzzverter.cpp"],
deps = [
":ast_to_proto_lib",
":fuzzer_util",
"//common:error",
"//common/fuzzing:carbon_cc_proto",
"//executable_semantics/common:error",
"//executable_semantics/common:nonnull",
"//executable_semantics/syntax",
"@com_google_protobuf//:protobuf_headers",
"@llvm-project//llvm:Support",
],
)

cc_test(
name = "proto_to_carbon_test",
srcs = ["proto_to_carbon_test.cpp"],
Expand All @@ -57,3 +87,22 @@ cc_test(
"@llvm-project//llvm:Support",
],
)

# Needs `--config=proto-fuzzer` for `bazel build` / `bazel test`.
cc_fuzz_test(
pk19604014 marked this conversation as resolved.
Show resolved Hide resolved
name = "executable_semantics_fuzzer",
size = "small",
srcs = ["executable_semantics_fuzzer.cpp"],
corpus = glob(["fuzzer_corpus/**"]),
tags = ["manual"],
deps = [
":fuzzer_util",
"//common/fuzzing:carbon_cc_proto",
"//executable_semantics:prelude",
"//executable_semantics/interpreter:exec_program",
"//executable_semantics/syntax",
"@com_google_libprotobuf_mutator//:libprotobuf_mutator",
"@com_google_protobuf//:protobuf_headers",
"@llvm-project//llvm:Support",
],
)
89 changes: 89 additions & 0 deletions executable_semantics/fuzzing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Executable semantics structured fuzzer

<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->

## Overview

Fuzz testing is based on generating a large amount of random inputs for a
software component in order to trigger bugs and unexpected behavior. Basic
fuzzing uses randomly generated arrays of bytes as inputs, which works great for
some applications but is problematic for testing the logic that operates on
highly structured data, as most random inputs are immediately rejected as
invalid before any interesting parts of the code get a chance to run.

Structured fuzzing addresses this issue by ensuring the randomly generated data
is itself structured, and as such has a high chance of presenting a valid input.

`executable_semantics_fuzzer` is a structured fuzzer based on
[libprotobuf-mutator](https://github.com/google/libprotobuf-mutator), which is a
library to randomly mutate
[protobuffers](https://github.com/protocolbuffers/protobuf).

The input to the fuzzer is an instance of `Carbon::Fuzzing::Carbon` proto
randomly generated by the `libprotobuf-mutator` framework.
`executable_semantics_fuzzer` converts the proto to a Carbon source code string,
and tries to parse and execute the code using `executable_semantics`
implementation.

## Fuzzer data format

`libprotobuf-mutator` supports fuzzer inputs in either text or binary protocol
buffer format. `executable_semantics_fuzzer` uses text proto format with
`Carbon` proto message definition in `common/fuzzing/carbon.proto`.

## Running the fuzzer

The fuzzer can be run in 'unit test' mode, where the fuzzer executes on each
input file from the `fuzzer_corpus/` folder, or in 'fuzzing' mode, where the
fuzzer will keep generating random inputs and executing the logic on them until
a crash is triggered, or forever in a bug-free program ;).

To run in 'unit test' mode:

```bash
bazel test --config=proto-fuzzer --test_output=all //executable_semantics/fuzzing:executable_semantics_fuzzer
```

To run in 'fuzzing' mode:

```bash
bazel build --config=proto-fuzzer //executable_semantics/fuzzing:executable_semantics_fuzzer

bazel-bin/executable_semantics/fuzzing/executable_semantics_fuzzer
```

It's also possible to run the fuzzer on a single input:

```bash
bazel-bin/executable_semantics/fuzzing/executable_semantics_fuzzer /tmp/crash.textproto
```

## Investigating a crash

To reproduce a crash, run the fuzzer on the crashing input as described above.

A separate tool called `fuzzverter` can be used for things like converting a
crashing input to Carbon source code for running `executable_semantics` on the
code directly.

To convert a `Fuzzing::Carbon` text proto to Carbon source:

```bash
bazel-bin/executable_semantics/fuzzing/fuzzverter --mode=proto_to_carbon --input /tmp/crash.textproto
pk19604014 marked this conversation as resolved.
Show resolved Hide resolved
```

## Generating new fuzzer corpus entries

The ability of the fuzzing framework to generate 'interesting' inputs can be
improved by providing 'seed' inputs known as the fuzzer corpus. The inputs need
to be a `Fuzzing::Carbon` text proto.

To generate a text proto from Carbon source:

```bash
bazel-bin/executable_semantics/fuzzing/fuzzverter --mode=carbon_to_proto --input /tmp/crash.carbon --output /tmp/crash.textproto
pk19604014 marked this conversation as resolved.
Show resolved Hide resolved
```
42 changes: 42 additions & 0 deletions executable_semantics/fuzzing/executable_semantics_fuzzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <google/protobuf/text_format.h>
#include <libprotobuf_mutator/src/libfuzzer/libfuzzer_macro.h>

#include "common/fuzzing/carbon.pb.h"
#include "executable_semantics/fuzzing/fuzzer_util.h"
#include "executable_semantics/interpreter/exec_program.h"
#include "executable_semantics/prelude.h"
#include "executable_semantics/syntax/parse.h"
#include "llvm/Support/raw_ostream.h"

namespace Carbon {

// Parses and executes a fuzzer-generated program.
void ParseAndExecute(const Fuzzing::CompilationUnit& compilation_unit) {
const std::string source = FuzzerUtil::ProtoToCarbon(compilation_unit);

Arena arena;
ErrorOr<AST> ast = ParseFromString(&arena, "Fuzzer.carbon", source,
/*trace=*/false);
if (!ast.ok()) {
llvm::errs() << "Parsing failed: " << ast.error().message() << "\n";
return;
}
AddPrelude("executable_semantics/data/prelude.carbon", &arena,
&ast->declarations);
const ErrorOr<int> result = ExecProgram(&arena, *ast, /*trace=*/false);
if (!result.ok()) {
llvm::errs() << "Execution failed: " << result.error().message() << "\n";
return;
}
llvm::outs() << "Executed OK: " << *result << "\n";
}

} // namespace Carbon

DEFINE_TEXT_PROTO_FUZZER(const Carbon::Fuzzing::Carbon& input) {
Carbon::ParseAndExecute(input.compilation_unit());
}
Empty file.
32 changes: 32 additions & 0 deletions executable_semantics/fuzzing/fuzzer_util.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "executable_semantics/fuzzing/fuzzer_util.h"

#include "common/check.h"
#include "common/fuzzing/proto_to_carbon.h"

namespace Carbon {

// Appended to fuzzer-generated Carbon source when the source is missing
// `Main()` definition, to prevent early error return in semantic analysis.
static constexpr char EmptyMain[] = R"(
fn Main() -> i32 {
return 0;
}
)";

auto FuzzerUtil::ProtoToCarbon(const Fuzzing::CompilationUnit& compilation_unit)
-> std::string {
const bool has_main = std::any_of(
compilation_unit.declarations().begin(),
compilation_unit.declarations().end(),
[](const Fuzzing::Declaration& decl) {
return decl.kind_case() == Fuzzing::Declaration::kFunction &&
decl.function().name() == "Main";
});
return Carbon::ProtoToCarbon(compilation_unit) + (has_main ? "" : EmptyMain);
}

} // namespace Carbon
19 changes: 19 additions & 0 deletions executable_semantics/fuzzing/fuzzer_util.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#ifndef EXECUTABLE_SEMANTICS_FUZZING_FUZZER_UTIL_H_
#define EXECUTABLE_SEMANTICS_FUZZING_FUZZER_UTIL_H_

#include "common/fuzzing/carbon.pb.h"

namespace Carbon::FuzzerUtil {

// Converts `compilation_unit` to Carbon. Adds an default `Main()`
// definition if one is not present in the proto.
auto ProtoToCarbon(const Fuzzing::CompilationUnit& compilation_unit)
pk19604014 marked this conversation as resolved.
Show resolved Hide resolved
-> std::string;

} // namespace Carbon::FuzzerUtil

#endif // EXECUTABLE_SEMANTICS_FUZZING_FUZZER_UTIL_H_
Loading