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>
diff --git a/docs/dawn/codegen.md b/docs/dawn/codegen.md
index 1bded3f..359fdde 100644
--- a/docs/dawn/codegen.md
+++ b/docs/dawn/codegen.md
@@ -111,3 +111,12 @@
 ## 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`.
diff --git a/generator/dawn_generator.gni b/generator/dawn_generator.gni
index 9b6183f..6a4e090 100644
--- a/generator/dawn_generator.gni
+++ b/generator/dawn_generator.gni
@@ -80,3 +80,25 @@
     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" ])
+  }
+}
diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py
index e818d2a..8eb0d6c 100644
--- a/generator/dawn_json_generator.py
+++ b/generator/dawn_json_generator.py
@@ -777,7 +777,7 @@
     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 @@
                             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 @@
             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 @@
             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 @@
                     '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
 
 
diff --git a/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp b/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp
new file mode 100644
index 0000000..76d8a7a
--- /dev/null
+++ b/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.cpp
@@ -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
diff --git a/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.h b/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.h
new file mode 100644
index 0000000..f5d0ef5
--- /dev/null
+++ b/generator/templates/dawn/fuzzers/lpmfuzz/DawnLPMSerializer.h
@@ -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_
diff --git a/generator/templates/dawn/fuzzers/lpmfuzz/dawn_lpm.proto b/generator/templates/dawn/fuzzers/lpmfuzz/dawn_lpm.proto
new file mode 100644
index 0000000..3c30312
--- /dev/null
+++ b/generator/templates/dawn/fuzzers/lpmfuzz/dawn_lpm.proto
@@ -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;
+}
\ No newline at end of file
diff --git a/src/dawn/fuzzers/BUILD.gn b/src/dawn/fuzzers/BUILD.gn
index f7ea2a0..875a115 100644
--- a/src/dawn/fuzzers/BUILD.gn
+++ b/src/dawn/fuzzers/BUILD.gn
@@ -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 @@
   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
diff --git a/src/dawn/fuzzers/dawn_fuzzers.gni b/src/dawn/fuzzers/dawn_fuzzers.gni
new file mode 100644
index 0000000..d14d417
--- /dev/null
+++ b/src/dawn/fuzzers/dawn_fuzzers.gni
@@ -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
+}
diff --git a/src/dawn/fuzzers/lpmfuzz/DawnLPMConstants.h b/src/dawn/fuzzers/lpmfuzz/DawnLPMConstants.h
new file mode 100644
index 0000000..2d173da
--- /dev/null
+++ b/src/dawn/fuzzers/lpmfuzz/DawnLPMConstants.h
@@ -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
diff --git a/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.cpp b/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.cpp
new file mode 100644
index 0000000..3b7533d
--- /dev/null
+++ b/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.cpp
@@ -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;
+}
diff --git a/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.h b/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.h
new file mode 100644
index 0000000..f0dcaee
--- /dev/null
+++ b/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.h
@@ -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_
diff --git a/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzerAndVulkanBackend.cpp b/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzerAndVulkanBackend.cpp
new file mode 100644
index 0000000..d25e0a1
--- /dev/null
+++ b/src/dawn/fuzzers/lpmfuzz/DawnLPMFuzzerAndVulkanBackend.cpp
@@ -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);
+    });
+}
diff --git a/src/dawn/fuzzers/lpmfuzz/dawn_lpm.json b/src/dawn/fuzzers/lpmfuzz/dawn_lpm.json
new file mode 100644
index 0000000..992426c
--- /dev/null
+++ b/src/dawn/fuzzers/lpmfuzz/dawn_lpm.json
@@ -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"
+}
\ No newline at end of file