Add Dawn Wire Server LPM Fuzzer [1/N]

Add scaffolding for structured Dawn wire fuzzer.

This CL contains a basic fuzzer for Dawn wire server
that shows some simple design ideas:

1) A basic protobuf spec that is generated using dawn.json
2) conversion from protobuf message to a dawn wire server
command.

This is not the complete implementation and serves as a
foundation for the fuzzer so that subsequent CLs will be
easier to review.

Bug: chromium:1374747
Change-Id: Ife1642dda13d01d3308bdd5fe56cf85978399fd3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/109406
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Brendon Tiszka <tiszka@chromium.org>
This commit is contained in:
Brendon Tiszka 2023-02-08 20:43:18 +00:00 committed by Dawn LUCI CQ
parent e4b4c4737e
commit d0b284b00b
13 changed files with 530 additions and 2 deletions

View File

@ -111,3 +111,12 @@ The schema of `dawn_wire.json` is a dictionary with the following keys:
## OpenGL loader generator
The code to load OpenGL entrypoints from a `GetProcAddress` function is generated from [`gl.xml`](../third_party/khronos/gl.xml) and the [list of extensions](../src/dawn/native/opengl/supported_extensions.json) it supports.
## Dawn lpmfuzz generator
One of Dawn's Fuzzers utilizes the information in [`dawn.json`, `dawn_wire.json`, `dawn_lpm.json`] to generate the `.proto` and `.cpp` files required for a [libprotobuf-mutator fuzzer](https://github.com/google/libprotobuf-mutator) that fuzzes Dawn Wire Server's stack with more effectiveness in some areas than plain libfuzzer.
At this time it is used to generate:
- the `dawn_lpm.proto` file used to describe the grammar for the fuzzer
- the serializer `DawnLPMSerializer.cpp` that takes an arbitrary number of protobuf structures that were defined in `dawn_lpm.proto` and serializes them to be passed to `DawnWireServer::HandleCommands`.

View File

@ -80,3 +80,25 @@ template("dawn_json_generator") {
forward_variables_from(invoker, "*", [ "target" ])
}
}
template("dawn_json_lpm_generator") {
dawn_generator(target_name) {
script = "${dawn_root}/generator/dawn_json_generator.py"
# The base arguments for the generator: from this dawn.json, generate this
# target using templates in this directory.
args = [
"--dawn-json",
rebase_path("${dawn_root}/dawn.json", root_build_dir),
"--wire-json",
rebase_path("${dawn_root}/dawn_wire.json", root_build_dir),
"--lpm-json",
rebase_path("${dawn_root}/src/dawn/fuzzers/lpmfuzz/dawn_lpm.json",
root_build_dir),
"--targets",
invoker.target,
]
forward_variables_from(invoker, "*", [ "target" ])
}
}

View File

@ -777,7 +777,7 @@ class MultiGeneratorFromDawnJSON(Generator):
def add_commandline_arguments(self, parser):
allowed_targets = [
'dawn_headers', 'cpp_headers', 'cpp', 'proc', 'mock_api', 'wire',
'native_utils'
'native_utils', 'dawn_lpmfuzz_cpp', 'dawn_lpmfuzz_proto'
]
parser.add_argument('--dawn-json',
@ -788,6 +788,10 @@ class MultiGeneratorFromDawnJSON(Generator):
default=None,
type=str,
help='The DAWN WIRE JSON definition to use.')
parser.add_argument("--lpm-json",
default=None,
type=str,
help='The DAWN LPM FUZZER definitions to use.')
parser.add_argument(
'--targets',
required=True,
@ -795,6 +799,7 @@ class MultiGeneratorFromDawnJSON(Generator):
help=
'Comma-separated subset of targets to output. Available targets: '
+ ', '.join(allowed_targets))
def get_file_renders(self, args):
with open(args.dawn_json) as f:
loaded_json = json.loads(f.read())
@ -806,6 +811,11 @@ class MultiGeneratorFromDawnJSON(Generator):
with open(args.wire_json) as f:
wire_json = json.loads(f.read())
lpm_json = None
if args.lpm_json:
with open(args.lpm_json) as f:
lpm_json = json.loads(f.read())
renders = []
params_dawn = parse_json(loaded_json,
@ -1025,12 +1035,53 @@ class MultiGeneratorFromDawnJSON(Generator):
'src/dawn/wire/server/ServerPrototypes_autogen.inc',
wire_params))
if 'dawn_lpmfuzz_proto' in targets:
params_dawn_wire = parse_json(loaded_json,
enabled_tags=['dawn', 'deprecated'],
disabled_tags=['native'])
additional_params = compute_wire_params(params_dawn_wire,
wire_json)
lpm_params = [
RENDER_PARAMS_BASE, params_dawn_wire, {}, additional_params
]
renders.append(
FileRender('dawn/fuzzers/lpmfuzz/dawn_lpm.proto',
'src/dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.proto',
lpm_params))
if 'dawn_lpmfuzz_cpp' in targets:
params_dawn_wire = parse_json(loaded_json,
enabled_tags=['dawn', 'deprecated'],
disabled_tags=['native'])
additional_params = compute_wire_params(params_dawn_wire,
wire_json)
lpm_params = [
RENDER_PARAMS_BASE, params_dawn_wire, {}, additional_params
]
renders.append(
FileRender(
'dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp',
'src/dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.cpp',
lpm_params))
renders.append(
FileRender(
'dawn/fuzzers/lpmfuzz/DawnLPMSerializer.h',
'src/dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.h',
lpm_params))
return renders
def get_dependencies(self, args):
deps = [os.path.abspath(args.dawn_json)]
if args.wire_json != None:
deps += [os.path.abspath(args.wire_json)]
if args.lpm_json != None:
deps += [os.path.abspath(args.lpm_json)]
return deps

View File

@ -0,0 +1,50 @@
// Copyright 2023 The Dawn 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
//
// http://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.
#include "dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.h"
#include "dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.h"
#include "dawn/wire/Wire.h"
#include "dawn/wire/WireClient.h"
#include "dawn/wire/WireCmd_autogen.h"
#include "dawn/wire/client/ApiObjects_autogen.h"
#include "dawn/webgpu.h"
#include "dawn/wire/client/Client.h"
namespace dawn::wire {
void SerializedData(const fuzzing::Program& program, dawn::wire::ChunkedCommandSerializer serializer) {
DawnLPMObjectIdProvider provider;
for (const fuzzing::Command& command : program.commands()) {
switch (command.command_case()) {
{% for command in cmd_records["command"] %}
case fuzzing::Command::k{{command.name.CamelCase()}}: {
{{ command.name.CamelCase() }}Cmd {{ 'cmd' }};
// TODO(chromium:1374747): Populate command buffer with serialized code from generated
// protobuf structures. Currently, this will nullptr-deref constantly.
memset(&{{ 'cmd' }}, 0, sizeof({{ command.name.CamelCase() }}Cmd));
serializer.SerializeCommand(cmd, provider);
break;
}
{% endfor %}
default: {
break;
}
}
}
}
} // namespace dawn::wire

View File

@ -0,0 +1,46 @@
// Copyright 2023 The Dawn 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
//
// http://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.
#ifndef SRC_DAWN_FUZZERS_DAWNLPMSERIALIZER_H_
#define SRC_DAWN_FUZZERS_DAWNLPMSERIALIZER_H_
#include "dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.pb.h"
#include "dawn/wire/ChunkedCommandSerializer.h"
#include "dawn/wire/WireCmd_autogen.h"
namespace dawn::wire {
class DawnLPMObjectIdProvider : public ObjectIdProvider {
private:
// Implementation of the ObjectIdProvider interface
{% for type in by_category["object"] %}
WireResult GetId({{as_cType(type.name)}} object, ObjectId* out) const final {
*out = reinterpret_cast<uintptr_t>(object);
return WireResult::Success;
}
WireResult GetOptionalId({{as_cType(type.name)}} object, ObjectId* out) const final {
*out = reinterpret_cast<uintptr_t>(object);
return WireResult::Success;
}
{% endfor %}
};
void SerializedData(const fuzzing::Program& program,
dawn::wire::ChunkedCommandSerializer serializer);
} // namespace dawn::wire
#endif // SRC_DAWN_FUZZERS_DAWNLPMSERIALIZER_H_

View File

@ -0,0 +1,32 @@
// Copyright 2023 The Dawn 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
//
// http://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.
syntax = "proto2";
package fuzzing;
{% for command in cmd_records["command"] %}
message {{command.name.CamelCase()}} {}
{% endfor %}
message Command {
oneof command {
{% for command in cmd_records["command"] %}
{{command.name.CamelCase()}} {{command.name.camelCase()}} = {{ loop.index }};
{% endfor %}
}
}
message Program {
repeated Command commands = 1;
}

View File

@ -12,9 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import("//build_overrides/build.gni")
import("../../../scripts/dawn_overrides_with_defaults.gni")
import("//build_overrides/build.gni")
import("${dawn_root}/scripts/dawn_features.gni")
import("${dawn_root}/src/dawn/fuzzers/dawn_fuzzers.gni")
# We only have libfuzzer in Chromium builds but if we build fuzzer targets only
# there, we would risk breaking fuzzer targets all the time when making changes
# to Dawn. To avoid that, we make fuzzer targets compile in standalone builds
@ -110,6 +113,68 @@ dawn_fuzzer_test("dawn_wire_server_and_vulkan_backend_fuzzer") {
additional_configs = [ "${dawn_root}/src/dawn/common:internal_config" ]
}
if (is_dawn_lpm_fuzzer && build_with_chromium && dawn_use_swiftshader) {
import("//third_party/protobuf/proto_library.gni")
import("${dawn_root}/generator/dawn_generator.gni")
dawn_json_lpm_generator("dawn_lpmfuzz_cpp") {
target = "dawn_lpmfuzz_cpp"
outputs = [
"src/dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.cpp",
"src/dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.h",
]
}
dawn_json_lpm_generator("dawn_lpmfuzz_proto") {
target = "dawn_lpmfuzz_proto"
outputs = [ "src/dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.proto" ]
}
proto_library("dawn_lpm_proto") {
proto_in_dir = "//"
sources = get_target_outputs(":dawn_lpmfuzz_proto")
use_protobuf_full = true
deps = [
":dawn_lpmfuzz_proto",
"//third_party/protobuf:protobuf_full",
]
}
copy("copy_dawn_lpm_proto_outputs") {
# Hardcoded filenames because we can't get_target_outputs from a proto_library
# TODO(tiszka): crbug.com/1410213
sources = [
"$root_out_dir/gen/$root_out_dir/gen/third_party/dawn/src/dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.pb.cc",
"$root_out_dir/gen/$root_out_dir/gen/third_party/dawn/src/dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.pb.h",
]
outputs = [ "$root_out_dir/gen/third_party/dawn/src/dawn/fuzzers/lpmfuzz/{{source_file_part}}" ]
deps = [ ":dawn_lpm_proto" ]
}
dawn_fuzzer_test("dawn_lpm_fuzzer_and_vulkan_backend") {
sources = get_target_outputs(":dawn_lpmfuzz_cpp")
sources += [
"lpmfuzz/DawnLPMFuzzer.cpp",
"lpmfuzz/DawnLPMFuzzer.h",
"lpmfuzz/DawnLPMFuzzerAndVulkanBackend.cpp",
]
deps = [
":copy_dawn_lpm_proto_outputs",
":dawn_lpmfuzz_cpp",
"${dawn_root}/src/dawn:cpp",
"${dawn_root}/src/dawn:proc",
"${dawn_root}/src/dawn/common",
"${dawn_root}/src/dawn/native:static",
"${dawn_root}/src/dawn/utils",
"${dawn_root}/src/dawn/wire:static",
"//third_party/dawn/src/dawn/fuzzers:dawn_lpm_proto",
"//third_party/libprotobuf-mutator",
]
}
}
# A group target to build all the fuzzers
group("fuzzers") {
testonly = true

View File

@ -0,0 +1,19 @@
# Copyright 2023 The Dawn 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
#
# http://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.
# Build flag for dawn lpm fuzzers
declare_args() {
is_dawn_lpm_fuzzer = false
}

View File

@ -0,0 +1,15 @@
// Copyright 2023 The Dawn 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
//
// http://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.
#define INSTANCE_OBJECT_ID 1

View File

@ -0,0 +1,133 @@
// Copyright 2023 The Dawn 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
//
// http://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.
#include <fstream>
#include <memory>
#include <vector>
#include "dawn/common/Assert.h"
#include "dawn/common/Log.h"
#include "dawn/common/SystemUtils.h"
#include "dawn/dawn_proc.h"
#include "dawn/fuzzers/lpmfuzz/DawnLPMConstants.h"
#include "dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.h"
#include "dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.h"
#include "dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.pb.h"
#include "dawn/native/DawnNative.h"
#include "dawn/utils/SystemUtils.h"
#include "dawn/utils/TerribleCommandBuffer.h"
#include "dawn/webgpu_cpp.h"
#include "dawn/wire/ChunkedCommandSerializer.h"
#include "dawn/wire/WireClient.h"
#include "dawn/wire/WireServer.h"
#include "testing/libfuzzer/libfuzzer_exports.h"
namespace {
class DevNull : public dawn::wire::CommandSerializer {
public:
size_t GetMaximumAllocationSize() const override {
// Some fuzzer bots have a 2GB allocation limit. Pick a value reasonably below that.
return 1024 * 1024 * 1024;
}
void* GetCmdSpace(size_t size) override {
if (size > buf.size()) {
buf.resize(size);
}
return buf.data();
}
bool Flush() override { return true; }
private:
std::vector<char> buf;
};
std::unique_ptr<dawn::native::Instance> sInstance;
WGPUProcDeviceCreateSwapChain sOriginalDeviceCreateSwapChain = nullptr;
static bool (*sAdapterSupported)(const dawn::native::Adapter&) = nullptr;
WGPUSwapChain ErrorDeviceCreateSwapChain(WGPUDevice device,
WGPUSurface surface,
const WGPUSwapChainDescriptor*) {
WGPUSwapChainDescriptor desc = {};
// A 0 implementation will trigger a swapchain creation error.
desc.implementation = 0;
return sOriginalDeviceCreateSwapChain(device, surface, &desc);
}
} // namespace
int DawnLPMFuzzer::Initialize(int* argc, char*** argv) {
// TODO(crbug.com/1038952): The Instance must be static because destructing the vkInstance with
// Swiftshader crashes libFuzzer. When this is fixed, move this into Run so that error injection
// for adapter discovery can be fuzzed.
sInstance = std::make_unique<dawn::native::Instance>();
sInstance->DiscoverDefaultAdapters();
return 0;
}
int DawnLPMFuzzer::Run(const fuzzing::Program& program,
bool (*AdapterSupported)(const dawn::native::Adapter&)) {
sAdapterSupported = AdapterSupported;
DawnProcTable procs = dawn::native::GetProcs();
// Swapchains receive a pointer to an implementation. The fuzzer will pass garbage in so we
// intercept calls to create swapchains and make sure they always return error swapchains.
// This is ok for fuzzing because embedders of dawn_wire would always define their own
// swapchain handling.
sOriginalDeviceCreateSwapChain = procs.deviceCreateSwapChain;
procs.deviceCreateSwapChain = ErrorDeviceCreateSwapChain;
// Override requestAdapter to find an adapter that the fuzzer supports.
procs.instanceRequestAdapter = [](WGPUInstance cInstance,
const WGPURequestAdapterOptions* options,
WGPURequestAdapterCallback callback, void* userdata) {
std::vector<dawn::native::Adapter> adapters = sInstance->GetAdapters();
for (dawn::native::Adapter adapter : adapters) {
if (sAdapterSupported(adapter)) {
WGPUAdapter cAdapter = adapter.Get();
dawn::native::GetProcs().adapterReference(cAdapter);
callback(WGPURequestAdapterStatus_Success, cAdapter, nullptr, userdata);
return;
}
}
callback(WGPURequestAdapterStatus_Unavailable, nullptr, "No supported adapter.", userdata);
};
dawnProcSetProcs(&procs);
DevNull devNull;
dawn::wire::WireServerDescriptor serverDesc = {};
serverDesc.procs = &procs;
serverDesc.serializer = &devNull;
std::unique_ptr<dawn::wire::WireServer> wireServer(new dawn_wire::WireServer(serverDesc));
wireServer->InjectInstance(sInstance->Get(), INSTANCE_OBJECT_ID, 0);
static utils::TerribleCommandBuffer* mCommandBuffer = new utils::TerribleCommandBuffer();
static dawn::wire::ChunkedCommandSerializer mSerializer =
dawn::wire::ChunkedCommandSerializer(mCommandBuffer);
mCommandBuffer->SetHandler(wireServer.get());
dawn::wire::SerializedData(program, mSerializer);
mCommandBuffer->Flush();
// Note: Deleting the server will release all created objects.
// Deleted devices will wait for idle on destruction.
wireServer = nullptr;
return 0;
}

View File

@ -0,0 +1,34 @@
// Copyright 2023 The Dawn 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
//
// http://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.
#ifndef SRC_DAWN_FUZZERS_DAWNLPMFUZZER_H_
#define SRC_DAWN_FUZZERS_DAWNLPMFUZZER_H_
#include "dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.pb.h"
#include "dawn/webgpu_cpp.h"
namespace dawn::native {
class Adapter;
} // namespace dawn::native
namespace DawnLPMFuzzer {
int Initialize(int* argc, char*** argv);
int Run(const fuzzing::Program& program, bool (*AdapterSupported)(const dawn::native::Adapter&));
} // namespace DawnLPMFuzzer
#endif // SRC_DAWN_FUZZERS_DAWNLPMFUZZER_H_

View File

@ -0,0 +1,33 @@
// Copyright 2023 The Dawn 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
//
// http://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.
#include "dawn/common/GPUInfo.h"
#include "dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.h"
#include "dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.pb.h"
#include "dawn/native/DawnNative.h"
#include "testing/libfuzzer/libfuzzer_exports.h"
#include "testing/libfuzzer/proto/lpm_interface.h"
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
return DawnLPMFuzzer::Initialize(argc, argv);
}
DEFINE_PROTO_FUZZER(const fuzzing::Program& program) {
DawnLPMFuzzer::Run(program, [](const dawn::native::Adapter& adapter) {
wgpu::AdapterProperties properties;
adapter.GetProperties(&properties);
return gpu_info::IsGoogleSwiftshader(properties.vendorID, properties.deviceID);
});
}

View File

@ -0,0 +1,19 @@
{
"_comment": [
"Copyright 2023 The Dawn 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",
"",
" http://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."
],
"_doc": "See docs/dawn/codegen.md"
}