aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--BUILD.gn5
-rw-r--r--fuzzers/BUILD.gn54
-rwxr-xr-xfuzzers/create_seed_file_pair.py73
-rw-r--r--fuzzers/file_pair.proto15
-rwxr-xr-xfuzzers/generate_fuzzer_data.py81
-rw-r--r--fuzzers/raw_apply_fuzzer.cc59
-rw-r--r--fuzzers/testdata/.gitignore4
-rw-r--r--fuzzers/testdata/new_eventlog_provider.dll.sha11
-rw-r--r--fuzzers/testdata/old_eventlog_provider.dll.sha11
9 files changed, 293 insertions, 0 deletions
diff --git a/BUILD.gn b/BUILD.gn
index f36c949..49f54fb 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -209,4 +209,9 @@ group("zucchini_fuzzers") {
"//components/zucchini/fuzzers:zucchini_disassembler_win32_fuzzer",
"//components/zucchini/fuzzers:zucchini_patch_fuzzer",
]
+
+ # Ensure protoc is available.
+ if (current_toolchain == host_toolchain) {
+ deps += [ "//components/zucchini/fuzzers:zucchini_raw_apply_fuzzer" ]
+ }
}
diff --git a/fuzzers/BUILD.gn b/fuzzers/BUILD.gn
index 2e489fd..66c3d18 100644
--- a/fuzzers/BUILD.gn
+++ b/fuzzers/BUILD.gn
@@ -3,6 +3,7 @@
# found in the LICENSE file.
import("//testing/libfuzzer/fuzzer_test.gni")
+import("//third_party/protobuf/proto_library.gni")
# To download the corpus for local fuzzing use:
# gsutil -m rsync \
@@ -28,3 +29,56 @@ fuzzer_test("zucchini_patch_fuzzer") {
]
seed_corpus = "testdata/patch_fuzzer"
}
+
+proto_library("zucchini_file_pair_proto") {
+ sources = [
+ "file_pair.proto",
+ ]
+}
+
+# Ensure protoc is available.
+if (current_toolchain == host_toolchain) {
+ action("zucchini_raw_apply_seed") {
+ script = "generate_fuzzer_data.py"
+
+ args = [
+ "--raw",
+ "old_eventlog_provider.dll", # <old_file>
+ "new_eventlog_provider.dll", # <new_file>
+ "eventlog_provider.patch", # <patch_file> (temporary)
+
+ # <output_dir>
+ rebase_path("$target_gen_dir/testdata/raw_apply_fuzzer", root_build_dir),
+ ]
+
+ # Files depended upon.
+ sources = [
+ "create_seed_file_pair.py",
+ "testdata/new_eventlog_provider.dll",
+ "testdata/old_eventlog_provider.dll",
+ ]
+
+ # Outputs: necessary for validation.
+ outputs = [
+ "$target_gen_dir/testdata/raw_apply_fuzzer/seed_proto.bin",
+ ]
+ deps = [
+ "//components/zucchini:zucchini",
+ "//third_party/protobuf:protoc",
+ ]
+ }
+
+ fuzzer_test("zucchini_raw_apply_fuzzer") {
+ sources = [
+ "raw_apply_fuzzer.cc",
+ ]
+ deps = [
+ ":zucchini_file_pair_proto",
+ "//base",
+ "//components/zucchini:zucchini_lib",
+ "//third_party/libprotobuf-mutator",
+ ]
+ seed_corpus = "$target_gen_dir/testdata/raw_apply_fuzzer"
+ seed_corpus_deps = [ ":zucchini_raw_apply_seed" ]
+ }
+}
diff --git a/fuzzers/create_seed_file_pair.py b/fuzzers/create_seed_file_pair.py
new file mode 100755
index 0000000..a44db7b
--- /dev/null
+++ b/fuzzers/create_seed_file_pair.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Create binary protobuf encoding for fuzzer seeds.
+
+This script is used to generate binary encoded protobuf seeds for fuzzers
+related to Zucchini-gen and -apply, which take pairs of files are arguments. The
+binary protobuf format is faster to parse so it is the preferred method for
+encoding the seeds. For gen related fuzzers this should only need to be run
+once. For any apply related fuzzers this should be rerun whenever the patch
+format is changed.
+"""
+
+import argparse
+import logging
+import os
+import subprocess
+import sys
+
+ABS_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__)))
+PROTO_DEFINITION_FILE = 'file_pair.proto'
+OUTPUT_FORMAT = b'old_file: "{}"\nnew_or_patch_file: "{}"'
+
+def parse_args():
+ """Parse commandline args."""
+ parser = argparse.ArgumentParser()
+ parser.add_argument('protoc_path', help='Path to protoc.')
+ parser.add_argument('old_file', help='Old file to generate/apply patch.')
+ parser.add_argument('new_or_patch_file',
+ help='New file to generate or patch to apply.')
+ parser.add_argument('output_file', help='File to write binary protobuf to.')
+ return parser.parse_args()
+
+
+def read_to_proto_escaped_string(filename):
+ """Reads a file and converts it to hex escape sequences."""
+ with open(filename, 'rb') as f:
+ # Note that string_escape escapes all non-ASCII printable characters
+ # excluding ", which needs to be manually escaped.
+ return f.read().encode('string_escape').replace('"', '\\"')
+
+
+def main():
+ args = parse_args()
+ # Create an ASCII string representing a protobuf.
+ content = OUTPUT_FORMAT.format(read_to_proto_escaped_string(args.old_file),
+ read_to_proto_escaped_string(
+ args.new_or_patch_file))
+
+ # Encode the ASCII protobuf as a binary protobuf.
+ ps = subprocess.Popen([args.protoc_path, '--proto_path=%s' % ABS_PATH,
+ '--encode=zucchini.fuzzers.FilePair',
+ os.path.join(ABS_PATH, PROTO_DEFINITION_FILE)],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ # Write the string to the subprocess. Single line IO is fine as protoc returns
+ # a string.
+ output = ps.communicate(input=content)
+ ps.wait()
+ if ps.returncode:
+ logging.error('Binary protobuf encoding failed.')
+ return ps.returncode
+
+ # Write stdout of the subprocess for protoc to the |output_file|.
+ with open(args.output_file, 'wb') as f:
+ f.write(output[0])
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/fuzzers/file_pair.proto b/fuzzers/file_pair.proto
new file mode 100644
index 0000000..2216381
--- /dev/null
+++ b/fuzzers/file_pair.proto
@@ -0,0 +1,15 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+package zucchini.fuzzers;
+
+// NEXT_TAG = 3
+message FilePair {
+ // File to generate patch from or apply patch to.
+ required bytes old_file = 1;
+ // New file to generate patch or the patch to apply.
+ required bytes new_or_patch_file = 2;
+}
diff --git a/fuzzers/generate_fuzzer_data.py b/fuzzers/generate_fuzzer_data.py
new file mode 100755
index 0000000..b69f278
--- /dev/null
+++ b/fuzzers/generate_fuzzer_data.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Script for generating new binary protobuf seeds for fuzzers.
+
+Currently supports creating a single seed binary protobuf of the form
+zucchini.fuzzer.FilePair.
+"""
+
+import argparse
+import hashlib
+import logging
+import os
+import platform
+import subprocess
+import sys
+
+ABS_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__)))
+ABS_TESTDATA_PATH = os.path.join(ABS_PATH, 'testdata')
+
+def parse_args():
+ """Parses arguments from command-line."""
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--raw', help='Whether to use Raw Zucchini.',
+ action='store_true')
+ parser.add_argument('old_file', help='Old file to generate/apply patch.')
+ parser.add_argument('new_file', help='New file to generate patch from.')
+ parser.add_argument('patch_file', help='Patch filename to use.')
+ parser.add_argument('output_dir',
+ help='Directory to write binary protobuf to.')
+ return parser.parse_args()
+
+
+def gen(old_file, new_file, patch_file, output_dir, is_raw, is_win):
+ """Generates a new patch and binary encodes a protobuf pair."""
+ # Create output directory if missing.
+ if not os.path.exists(output_dir):
+ os.makedirs(output_dir)
+
+ # Handle Windows executable names.
+ zucchini = 'zucchini'
+ protoc = 'protoc'
+ if is_win:
+ zucchini += '.exe'
+ protoc += '.exe'
+
+ zuc_cmd = [os.path.abspath(zucchini), '-gen']
+ if is_raw:
+ zuc_cmd.append('-raw')
+ # Generate a new patch.
+ ret = subprocess.call(zuc_cmd + [old_file, new_file, patch_file],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ if ret:
+ logging.error('Patch generation failed for ({}, {})'.format(old_file,
+ new_file))
+ return ret
+ # Binary encode the protobuf pair.
+ ret = subprocess.call([os.path.join(ABS_PATH, 'create_seed_file_pair.py'),
+ os.path.abspath(protoc), old_file, patch_file,
+ os.path.join(output_dir, 'seed_proto.bin')],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ os.remove(patch_file)
+ return ret
+
+
+def main():
+ args = parse_args()
+ return gen(os.path.join(ABS_TESTDATA_PATH, args.old_file),
+ os.path.join(ABS_TESTDATA_PATH, args.new_file),
+ os.path.join(ABS_TESTDATA_PATH, args.patch_file),
+ os.path.abspath(args.output_dir),
+ args.raw,
+ platform.system() == 'Windows')
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/fuzzers/raw_apply_fuzzer.cc b/fuzzers/raw_apply_fuzzer.cc
new file mode 100644
index 0000000..da3230a
--- /dev/null
+++ b/fuzzers/raw_apply_fuzzer.cc
@@ -0,0 +1,59 @@
+// Copyright 2018 The Chromium 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 <stdint.h>
+#include <stdlib.h>
+
+#include <iostream>
+#include <vector>
+
+#include "base/environment.h"
+#include "base/logging.h"
+#include "components/zucchini/buffer_view.h"
+#include "components/zucchini/fuzzers/file_pair.pb.h"
+#include "components/zucchini/patch_reader.h"
+#include "components/zucchini/zucchini.h"
+#include "testing/libfuzzer/proto/lpm_interface.h"
+
+struct Environment {
+ Environment() {
+ logging::SetMinLogLevel(3); // Disable console spamming.
+ }
+};
+
+Environment* env = new Environment();
+
+DEFINE_BINARY_PROTO_FUZZER(const zucchini::fuzzers::FilePair& file_pair) {
+ // Dump code for debugging.
+ if (base::Environment::Create()->HasVar("LPM_DUMP_NATIVE_INPUT")) {
+ std::cout << "Old File: " << file_pair.old_file() << std::endl
+ << "Patch File: " << file_pair.new_or_patch_file() << std::endl;
+ }
+
+ // Prepare data.
+ zucchini::ConstBufferView old_image(
+ reinterpret_cast<const uint8_t*>(file_pair.old_file().data()),
+ file_pair.old_file().size());
+ zucchini::ConstBufferView patch_file(
+ reinterpret_cast<const uint8_t*>(file_pair.new_or_patch_file().data()),
+ file_pair.new_or_patch_file().size());
+
+ // Generate a patch reader.
+ auto patch_reader = zucchini::EnsemblePatchReader::Create(patch_file);
+ // Abort if the patch can't be read.
+ if (!patch_reader.has_value())
+ return;
+
+ // Create the underlying new file.
+ size_t new_size = patch_reader->header().new_size;
+ // Reject unreasonably large "new" files that fuzzed patch may specify.
+ if (new_size > 64 * 1024)
+ return;
+ std::vector<uint8_t> new_data(new_size);
+ zucchini::MutableBufferView new_image(new_data.data(), new_size);
+
+ // Fuzz target.
+ zucchini::Apply(old_image, *patch_reader, new_image);
+ // No need to check whether output exist, or if so, whether it's valid.
+}
diff --git a/fuzzers/testdata/.gitignore b/fuzzers/testdata/.gitignore
new file mode 100644
index 0000000..d345889
--- /dev/null
+++ b/fuzzers/testdata/.gitignore
@@ -0,0 +1,4 @@
+# Exclude testdata binaries.
+*.bin
+*.dll
+*.patch
diff --git a/fuzzers/testdata/new_eventlog_provider.dll.sha1 b/fuzzers/testdata/new_eventlog_provider.dll.sha1
new file mode 100644
index 0000000..bbf56f9
--- /dev/null
+++ b/fuzzers/testdata/new_eventlog_provider.dll.sha1
@@ -0,0 +1 @@
+89ce67035d2d2dae33cb2d98d4762e955b93df95 \ No newline at end of file
diff --git a/fuzzers/testdata/old_eventlog_provider.dll.sha1 b/fuzzers/testdata/old_eventlog_provider.dll.sha1
new file mode 100644
index 0000000..5daf440
--- /dev/null
+++ b/fuzzers/testdata/old_eventlog_provider.dll.sha1
@@ -0,0 +1 @@
+c80fdce994ba043956e192f650d894555460ff9b \ No newline at end of file