Add Dawn Wire Server LPM Fuzzer [4/N]

Implements C++ serializer implementation that translates protobuf
objects into Dawn serial data.

1) A generator that builds a fuzzing harness that converts LPM structured data
into serialized bytes, that are then sent to Dawn Wire Server.
2) Object store for dawn objects that are allocated and
freed.

Bug: chromium:1374747
Change-Id: I09c1be6cdc2eccf4a91de808f19494d97d01b3d6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/114720
Commit-Queue: Brendon Tiszka <tiszka@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
Brendon Tiszka 2023-05-15 22:22:07 +00:00 committed by Dawn LUCI CQ
parent 8e42cfa7ea
commit 27521c6b91
12 changed files with 508 additions and 43 deletions

View File

@ -19,21 +19,21 @@
"commands": { "commands": {
"buffer map async": [ "buffer map async": [
{ "name": "buffer id", "type": "ObjectId" }, { "name": "buffer id", "type": "ObjectId", "id_type": "buffer" },
{ "name": "request serial", "type": "uint64_t" }, { "name": "request serial", "type": "uint64_t" },
{ "name": "mode", "type": "map mode" }, { "name": "mode", "type": "map mode" },
{ "name": "offset", "type": "uint64_t"}, { "name": "offset", "type": "uint64_t"},
{ "name": "size", "type": "uint64_t"} { "name": "size", "type": "uint64_t"}
], ],
"buffer update mapped data": [ "buffer update mapped data": [
{ "name": "buffer id", "type": "ObjectId" }, { "name": "buffer id", "type": "ObjectId", "id_type": "buffer" },
{ "name": "write data update info length", "type": "uint64_t" }, { "name": "write data update info length", "type": "uint64_t" },
{ "name": "write data update info", "type": "uint8_t", "annotation": "const*", "length": "write data update info length", "skip_serialize": true}, { "name": "write data update info", "type": "uint8_t", "annotation": "const*", "length": "write data update info length", "skip_serialize": true},
{ "name": "offset", "type": "uint64_t"}, { "name": "offset", "type": "uint64_t"},
{ "name": "size", "type": "uint64_t"} { "name": "size", "type": "uint64_t"}
], ],
"device create buffer": [ "device create buffer": [
{ "name": "device id", "type": "ObjectId" }, { "name": "device id", "type": "ObjectId", "id_type": "device" },
{ "name": "descriptor", "type": "buffer descriptor", "annotation": "const*" }, { "name": "descriptor", "type": "buffer descriptor", "annotation": "const*" },
{ "name": "result", "type": "ObjectHandle", "handle_type": "buffer" }, { "name": "result", "type": "ObjectHandle", "handle_type": "buffer" },
{ "name": "read handle create info length", "type": "uint64_t" }, { "name": "read handle create info length", "type": "uint64_t" },
@ -42,19 +42,19 @@
{ "name": "write handle create info", "type": "uint8_t", "annotation": "const*", "length": "write handle create info length", "skip_serialize": true} { "name": "write handle create info", "type": "uint8_t", "annotation": "const*", "length": "write handle create info length", "skip_serialize": true}
], ],
"device create compute pipeline async": [ "device create compute pipeline async": [
{ "name": "device id", "type": "ObjectId" }, { "name": "device id", "type": "ObjectId", "id_type": "device"},
{ "name": "request serial", "type": "uint64_t" }, { "name": "request serial", "type": "uint64_t" },
{ "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "compute pipeline"}, { "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "compute pipeline"},
{ "name": "descriptor", "type": "compute pipeline descriptor", "annotation": "const*"} { "name": "descriptor", "type": "compute pipeline descriptor", "annotation": "const*"}
], ],
"device create render pipeline async": [ "device create render pipeline async": [
{ "name": "device id", "type": "ObjectId" }, { "name": "device id", "type": "ObjectId", "id_type": "device" },
{ "name": "request serial", "type": "uint64_t" }, { "name": "request serial", "type": "uint64_t" },
{ "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "render pipeline"}, { "name": "pipeline object handle", "type": "ObjectHandle", "handle_type": "render pipeline"},
{ "name": "descriptor", "type": "render pipeline descriptor", "annotation": "const*"} { "name": "descriptor", "type": "render pipeline descriptor", "annotation": "const*"}
], ],
"device pop error scope": [ "device pop error scope": [
{ "name": "device id", "type": "ObjectId" }, { "name": "device id", "type": "ObjectId", "id_type": "device" },
{ "name": "request serial", "type": "uint64_t" } { "name": "request serial", "type": "uint64_t" }
], ],
"destroy object": [ "destroy object": [
@ -62,19 +62,19 @@
{ "name": "object id", "type": "ObjectId" } { "name": "object id", "type": "ObjectId" }
], ],
"queue on submitted work done": [ "queue on submitted work done": [
{ "name": "queue id", "type": "ObjectId" }, { "name": "queue id", "type": "ObjectId", "id_type": "queue" },
{ "name": "signal value", "type": "uint64_t" }, { "name": "signal value", "type": "uint64_t" },
{ "name": "request serial", "type": "uint64_t" } { "name": "request serial", "type": "uint64_t" }
], ],
"queue write buffer": [ "queue write buffer": [
{"name": "queue id", "type": "ObjectId" }, {"name": "queue id", "type": "ObjectId", "id_type": "queue" },
{"name": "buffer id", "type": "ObjectId" }, {"name": "buffer id", "type": "ObjectId", "id_type": "buffer" },
{"name": "buffer offset", "type": "uint64_t"}, {"name": "buffer offset", "type": "uint64_t"},
{"name": "data", "type": "uint8_t", "annotation": "const*", "length": "size", "wire_is_data_only": true}, {"name": "data", "type": "uint8_t", "annotation": "const*", "length": "size", "wire_is_data_only": true},
{"name": "size", "type": "uint64_t"} {"name": "size", "type": "uint64_t"}
], ],
"queue write texture": [ "queue write texture": [
{"name": "queue id", "type": "ObjectId" }, {"name": "queue id", "type": "ObjectId", "id_type": "queue" },
{"name": "destination", "type": "image copy texture", "annotation": "const*"}, {"name": "destination", "type": "image copy texture", "annotation": "const*"},
{"name": "data", "type": "uint8_t", "annotation": "const*", "length": "data size", "wire_is_data_only": true}, {"name": "data", "type": "uint8_t", "annotation": "const*", "length": "data size", "wire_is_data_only": true},
{"name": "data size", "type": "uint64_t"}, {"name": "data size", "type": "uint64_t"},
@ -82,17 +82,17 @@
{"name": "writeSize", "type": "extent 3D", "annotation": "const*"} {"name": "writeSize", "type": "extent 3D", "annotation": "const*"}
], ],
"shader module get compilation info": [ "shader module get compilation info": [
{ "name": "shader module id", "type": "ObjectId" }, { "name": "shader module id", "type": "ObjectId", "id_type": "shader module" },
{ "name": "request serial", "type": "uint64_t" } { "name": "request serial", "type": "uint64_t" }
], ],
"instance request adapter": [ "instance request adapter": [
{ "name": "instance id", "type": "ObjectId" }, { "name": "instance id", "type": "ObjectId", "id_type": "instance" },
{ "name": "request serial", "type": "uint64_t" }, { "name": "request serial", "type": "uint64_t" },
{ "name": "adapter object handle", "type": "ObjectHandle", "handle_type": "adapter"}, { "name": "adapter object handle", "type": "ObjectHandle", "handle_type": "adapter"},
{ "name": "options", "type": "request adapter options", "annotation": "const*" } { "name": "options", "type": "request adapter options", "annotation": "const*" }
], ],
"adapter request device": [ "adapter request device": [
{ "name": "adapter id", "type": "ObjectId" }, { "name": "adapter id", "type": "ObjectId", "id_type": "adapter" },
{ "name": "request serial", "type": "uint64_t" }, { "name": "request serial", "type": "uint64_t" },
{ "name": "device object handle", "type": "ObjectHandle", "handle_type": "device"}, { "name": "device object handle", "type": "ObjectHandle", "handle_type": "device"},
{ "name": "descriptor", "type": "device descriptor", "annotation": "const*" } { "name": "descriptor", "type": "device descriptor", "annotation": "const*" }

View File

@ -181,6 +181,7 @@ class RecordMember:
self.optional = optional self.optional = optional
self.is_return_value = is_return_value self.is_return_value = is_return_value
self.handle_type = None self.handle_type = None
self.id_type = None
self.default_value = default_value self.default_value = default_value
self.skip_serialize = skip_serialize self.skip_serialize = skip_serialize
@ -188,6 +189,10 @@ class RecordMember:
assert self.type.dict_name == "ObjectHandle" assert self.type.dict_name == "ObjectHandle"
self.handle_type = handle_type self.handle_type = handle_type
def set_id_type(self, id_type):
assert self.type.dict_name == "ObjectId"
self.id_type = id_type
Method = namedtuple( Method = namedtuple(
'Method', ['name', 'return_type', 'arguments', 'autolock', 'json_data']) 'Method', ['name', 'return_type', 'arguments', 'autolock', 'json_data'])
@ -308,6 +313,9 @@ def linked_record_members(json_data, types):
handle_type = m.get('handle_type') handle_type = m.get('handle_type')
if handle_type: if handle_type:
member.set_handle_type(types[handle_type]) member.set_handle_type(types[handle_type])
id_type = m.get('id_type')
if id_type:
member.set_id_type(types[id_type])
members.append(member) members.append(member)
members_by_name[member.name.canonical_case()] = member members_by_name[member.name.canonical_case()] = member
@ -331,6 +339,18 @@ def linked_record_members(json_data, types):
return members return members
def mark_lengths_non_serializable_lpm(record_members):
# Remove member length values from command metadata,
# these are set to the length of the protobuf array.
for record_member in record_members:
lengths = set()
for member in record_member.members:
lengths.add(member.length)
for member in record_member.members:
if member in lengths:
member.skip_serialize = True
############################################################ ############################################################
# PARSE # PARSE
############################################################ ############################################################
@ -376,7 +396,6 @@ def link_function(function, types):
function.arguments = linked_record_members(function.json_data['args'], function.arguments = linked_record_members(function.json_data['args'],
types) types)
# Sort structures so that if struct A has struct B as a member, then B is # Sort structures so that if struct A has struct B as a member, then B is
# listed before A. # listed before A.
# #
@ -601,10 +620,21 @@ def compute_lpm_params(api_and_wire_params, lpm_json):
generated_commands.append(command) generated_commands.append(command)
all_commands.append(command) all_commands.append(command)
# Set all fields that are marked as the "length" of another field to
# skip_serialize. The values passed by libprotobuf-mutator will cause
# an instant crash during serialization if these don't match the length
# of the data they are passing. These values aren't used in
# deserialization.
mark_lengths_non_serializable_lpm(
api_and_wire_params['cmd_records']['command'])
mark_lengths_non_serializable_lpm(
api_and_wire_params['by_category']['structure'])
lpm_params['cmd_records'] = { lpm_params['cmd_records'] = {
'proto_generated_commands': generated_commands, 'proto_generated_commands': generated_commands,
'proto_all_commands': all_commands, 'proto_all_commands': all_commands,
'cpp_generated_commands': generated_commands 'cpp_generated_commands': generated_commands,
'lpm_info': lpm_json.get("lpm_info")
} }
return lpm_params return lpm_params
@ -636,16 +666,28 @@ def as_protobufTypeLPM(member):
return member.type.name.CamelCase() return member.type.name.CamelCase()
# Helper that generates names for protobuf grammars from contents
# of dawn*.json like files. example: membera
def as_protobufNameLPM(*names): def as_protobufNameLPM(*names):
# `descriptor` is a reserved keyword in lpm # `descriptor` is a reserved keyword in lib-protobuf-mutator
if (names[0].concatcase() == "descriptor"): if (names[0].concatcase() == "descriptor"):
return "desc" return "desc"
return as_varName(*names) return as_varName(*names)
# Helper to generate member accesses within C++ of protobuf objects
# example: cmd.membera().memberb()
def as_protobufMemberNameLPM(*names):
# `descriptor` is a reserved keyword in lib-protobuf-mutator
if (names[0].concatcase() == "descriptor"):
return "desc"
return ''.join([name.concatcase().lower() for name in names])
def unreachable_code(): def unreachable_code():
assert False assert False
############################################################# #############################################################
# Generator # Generator
############################################################# #############################################################
@ -1152,8 +1194,10 @@ class MultiGeneratorFromDawnJSON(Generator):
fuzzer_params = compute_lpm_params(api_and_wire_params, lpm_json) fuzzer_params = compute_lpm_params(api_and_wire_params, lpm_json)
lpm_params = [ lpm_params = [
RENDER_PARAMS_BASE, params_dawn_wire, {}, api_and_wire_params, RENDER_PARAMS_BASE, params_dawn_wire, {
fuzzer_params 'as_protobufMemberName': as_protobufMemberNameLPM,
'unreachable_code': unreachable_code
}, api_and_wire_params, fuzzer_params
] ]
renders.append( renders.append(

View File

@ -12,8 +12,20 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <stdint.h>
namespace DawnLPMFuzzer { namespace DawnLPMFuzzer {
static constexpr int kInstanceObjectId = 1; static constexpr int kInstanceObjectId = 1;
static constexpr uint32_t kInvalidObjectId = {{ cmd_records["lpm_info"]["invalid object id"] }};
{% for type in by_category["object"] %}
{% if type.name.get() in cmd_records["lpm_info"]["limits"] %}
static constexpr int k{{ type.name.CamelCase() }}Limit = {{ cmd_records["lpm_info"]["limits"][type.name.get()] }};
{% else %}
static constexpr int k{{ type.name.CamelCase() }}Limit = {{ cmd_records["lpm_info"]["limits"]["default"] }};
{% endif %}
{% endfor %}
} // namespace DawnLPMFuzzer } // namespace DawnLPMFuzzer

View File

@ -12,31 +12,246 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.h" #include "dawn/fuzzers/lpmfuzz/DawnLPMConstants_autogen.h"
#include "dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.h" #include "dawn/fuzzers/lpmfuzz/DawnLPMFuzzer.h"
#include "dawn/fuzzers/lpmfuzz/DawnLPMObjectStore.h"
#include "dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.h"
#include "dawn/webgpu.h"
#include "dawn/wire/BufferConsumer_impl.h"
#include "dawn/wire/ObjectHandle.h"
#include "dawn/wire/Wire.h" #include "dawn/wire/Wire.h"
#include "dawn/wire/WireClient.h" #include "dawn/wire/WireClient.h"
#include "dawn/wire/WireCmd_autogen.h" #include "dawn/wire/WireCmd_autogen.h"
#include "dawn/wire/WireResult.h"
#include "dawn/wire/client/ApiObjects_autogen.h" #include "dawn/wire/client/ApiObjects_autogen.h"
#include "dawn/webgpu.h"
#include "dawn/wire/client/Client.h"
namespace dawn::wire { namespace dawn::wire {
void SerializedData(const fuzzing::Program& program, dawn::wire::ChunkedCommandSerializer serializer) { //* Outputs an rvalue that's the number of elements a pointer member points to.
{% macro member_length(member, proto_accessor) -%}
{%- if member.length == "constant" -%}
{{member.constant_length}}u
{%- else -%}
{{proto_accessor}}().size()
{%- endif -%}
{%- endmacro %}
//* Outputs the type that will be used on the wire for the member
{% macro member_type(member) -%}
{{ assert(as_cType(member.type.name) != "size_t") }}
{{as_cType(member.type.name)}}
{%- endmacro %}
//* Outputs the conversion code to put `in` in `out`
{% macro convert_member(member, in, out, in_access="") %}
{% if member.type in by_category["structure"] %}
{{ convert_structure(member, in, out, in_access) }}
{% elif member.type in by_category["bitmask"] %}
{{ convert_bitmask(member, in, out, in_access) }}
{% elif member.type in by_category["enum"] %}
{{ convert_enum(member, in, out, in_access) }}
{% elif member.type in by_category["object"] %}
{{ convert_object(member, in, out, in_access) }}
{% elif member.type.name.get() == "ObjectId" %}
{{ convert_objectid(member, in, out, access) }}
{% elif member.type.name.get() == "ObjectHandle" %}
{{ convert_objecthandle(member, in, out, in_access) }}
{% else %}
{{out}} = {{in}}({{in_access}});
{% endif %}
{% endmacro %}
//* Helper functions for converting protobufs to specific types
{% macro convert_enum(member, in, out, in_access) %}
{{out}} = static_cast<{{ member_type(member) }}>(
{{in}}({{in_access}})
);
{% endmacro %}
{% macro convert_object(member, in, out, in_access) -%}
{{ out }} = reinterpret_cast<{{ as_cType(member.type.name) }}>(
objectStores[ObjectType::{{ member.type.name.CamelCase() }}].Lookup(
static_cast<ObjectId>(
{{in}}({{in_access}})
)
)
);
{%- endmacro %}
{% macro convert_objectid(member, in, out, in_access) -%}
{{ out }} = objectStores[ObjectType::{{ member.id_type.name.CamelCase() }}].Lookup(
static_cast<ObjectId>(
{{in}}({{ in_access}})
)
);
{%- endmacro %}
{% macro convert_objecthandle(member, in, out, in_access) -%}
if (objectStores[ObjectType::{{ member.handle_type.name.CamelCase() }}].Size() < DawnLPMFuzzer::k{{ member.handle_type.name.CamelCase() }}Limit) {
{{ out }} = objectStores[ObjectType::{{ member.handle_type.name.CamelCase() }}].ReserveHandle();
} else {
{{ out }} = {0, 0};
}
{%- endmacro %}
{% macro convert_bitmask(member, in, out, in_access) -%}
{{ out }} = 0;
for (size_t bm = 0; bm < static_cast<size_t>({{ in }}().size()); bm++) {
{{ out }} |=
static_cast<{{ member_type(member) }}>(
{{ in }}(bm)
);
}
{%- endmacro %}
{% macro convert_structure(member, in, out, in_access) -%}
// Serializing a Structure Recursively
WIRE_TRY({{member_type(member)}}ProtoConvert({{in}}({{in_access}}), &{{out}}, serializeBuffer, objectStores));
{%- endmacro %}
{% macro write_record_conversion_helpers(record, name, members, is_cmd) %}
{% set overrides = cmd_records["lpm_info"]["overrides"] %}
{% set overrides_key = record.name.canonical_case() %}
{% set name = record.name.CamelCase() %}
{% set Cmd = "Cmd" if is_cmd else "" %}
{% set WGPU = "WGPU" if not is_cmd else "" %}
WireResult {{WGPU}}{{name}}ProtoConvert(fuzzing::{{ name }} proto_record, {{WGPU}}{{ name }}{{ Cmd }} const *record, SerializeBuffer* serializeBuffer, PerObjectType<DawnLPMObjectStore> &objectStores) {
{{WGPU}}{{ name }}{{ Cmd }} *mutable_record = const_cast<{{WGPU}}{{ name }}{{ Cmd }} *>(record);
//* Some commands don't set any members.
DAWN_UNUSED(mutable_record);
//* Zero out any non-serializable values as they won't be set
{% for member in members if member.skip_serialize %}
{% set memberName = as_varName(member.name) %}
memset(&mutable_record->{{ memberName }}, 0, sizeof(mutable_record->{{ memberName }}));
{% endfor %}
//* Pass by Value handling. This mirrors WireCmd with some differences between
//* convert_member and serialize_member
{% for member in members if member.annotation == "value" if not member.skip_serialize %}
{% set memberName = as_varName(member.name) %}
{% set protoMember = as_protobufMemberName(member.name) %}
//* Major WireCmd Divergence: Some member values are hardcoded in dawn_lpm.json
{% if overrides_key in overrides and member.name.canonical_case() in overrides[overrides_key] %}
mutable_record->{{ memberName }} = {{ overrides[overrides_key][member.name.canonical_case()] }};
{% else %}
{{ convert_member(member, 'proto_record.' + protoMember, "mutable_record->" + memberName) }}
{% endif %}
{% endfor %}
//* Chained structures are currently not supported.
{% if record.extensible %}
mutable_record->nextInChain = nullptr;
{% endif %}
//* Special handling for strings for now.
{% for member in members if member.length == "strlen" %}
{% set memberName = as_varName(member.name) %}
{
mutable_record->{{ memberName }} = "main";
}
{% endfor %}
//* Pass by Pointer handling. This mirrors WireCmd with some divergences when handling
//* byte arrays.
{% for member in members if member.annotation != "value" and member.length != "strlen" and not member.skip_serialize %}
{% set memberName = as_varName(member.name) %}
{% set protoMember = as_protobufMemberName(member.name) %}
{% set protoAccess = "i" if member.length != "constant" or member.constant_length > 1 else "" %}
//* Major WireCmd Divergence: DawnLPM handles raw byte arrays uniquely
//* as they don't lead to new coverage, lead to OOMs when allocated with
//* an arbitrary size, and are difficult to work with in protobuf.
{% if member.type.name.get() == 'uint8_t' %}
{
const size_t kDataBufferLength = 128;
auto memberLength = kDataBufferLength;
{{member_type(member)}}* memberBuffer;
WIRE_TRY(serializeBuffer->NextN(memberLength, &memberBuffer));
memset(memberBuffer, 0, kDataBufferLength);
mutable_record->{{ memberName }} = memberBuffer;
{% if member.length != "constant" -%}
mutable_record->{{ member.length.name.camelCase() }} = memberLength;
{%- endif %}
}
{% else %}
{
auto memberLength = static_cast<unsigned int>({{member_length(member, "proto_record." + protoMember)}});
//* Needed for the edge cases in "external texture descriptor"
//* where we want to fuzzer to fill the fixed-length float arrays
//* with values, but the length of the protobuf buffer might not
//* be large enough for "src transfer function parameters".
{% if member.length == "constant" and member.constant_length > 1 %}
memberLength = std::min(memberLength, static_cast<unsigned int>({{"proto_record." + protoMember}}().size()));
{% endif %}
{{member_type(member)}}* memberBuffer;
WIRE_TRY(serializeBuffer->NextN(memberLength, &memberBuffer));
for (decltype(memberLength) i = 0; i < memberLength; ++i) {
{{convert_member(member, "proto_record." + protoMember, "memberBuffer[i]", protoAccess )}}
}
mutable_record->{{ memberName }} = memberBuffer;
//* Major WireCmd Divergence: Within the serializer the length member is
//* set by using record.length. Here we aren't receiving any data
//* and set it to the number of protobuf objects in proto_record.
{% if member.length != "constant" -%}
mutable_record->{{ member.length.name.camelCase() }} = memberLength;
{%- endif %}
}
{% endif %}
{% endfor %}
return WireResult::Success;
}
{% endmacro %}
//* Output structure conversion first because it is used by commands.
{% for type in by_category["structure"] %}
{% set name = as_cType(type.name) %}
{% if type.name.CamelCase() not in client_side_structures %}
{{ write_record_conversion_helpers(type, name, type.members, False) }}
{% endif %}
{% endfor %}
//* Output command conversion functions.
{% for command in cmd_records["cpp_generated_commands"] %}
{% set name = command.name.CamelCase() %}
{{ write_record_conversion_helpers(command, name, command.members, True) }}
{% endfor %}
WireResult SerializedData(const fuzzing::Program& program, dawn::wire::ChunkedCommandSerializer serializer) {
DawnLPMObjectIdProvider provider; DawnLPMObjectIdProvider provider;
PerObjectType<DawnLPMObjectStore> objectStores, &objectStoresRef = objectStores;
// Allocate a scoped buffer allocation
const size_t kMaxSerializeBufferSize = 65536;
std::unique_ptr<char[]> allocatedBuffer(
new char[kMaxSerializeBufferSize]()
);
for (const fuzzing::Command& command : program.commands()) { for (const fuzzing::Command& command : program.commands()) {
switch (command.command_case()) { switch (command.command_case()) {
{% for command in cmd_records["cpp_generated_commands"] %} {% for command in cmd_records["cpp_generated_commands"] %}
case fuzzing::Command::k{{command.name.CamelCase()}}: { {% set name = command.name.CamelCase() %}
{{ command.name.CamelCase() }}Cmd {{ 'cmd' }}; case fuzzing::Command::k{{name}}: {
// TODO(chromium:1374747): Populate command buffer with serialized code from generated SerializeBuffer serializeBuffer(allocatedBuffer.get(), kMaxSerializeBufferSize);
// protobuf structures. Currently, this will nullptr-deref constantly. {{ name }}Cmd *cmd = nullptr;
memset(&{{ 'cmd' }}, 0, sizeof({{ command.name.CamelCase() }}Cmd)); WIRE_TRY(serializeBuffer.Next(&cmd));
serializer.SerializeCommand(cmd, provider);
WIRE_TRY({{name}}ProtoConvert(command.{{ command.name.concatcase() }}(), cmd, &serializeBuffer, objectStoresRef));
serializer.SerializeCommand(*cmd, provider);
break; break;
} }
{% endfor %} {% endfor %}
@ -45,6 +260,8 @@ void SerializedData(const fuzzing::Program& program, dawn::wire::ChunkedCommandS
} }
} }
} }
return WireResult::Success;
} }
} // namespace dawn::wire } // namespace dawn::wire

View File

@ -18,6 +18,7 @@
#include "dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.pb.h" #include "dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.pb.h"
#include "dawn/wire/ChunkedCommandSerializer.h" #include "dawn/wire/ChunkedCommandSerializer.h"
#include "dawn/wire/WireCmd_autogen.h" #include "dawn/wire/WireCmd_autogen.h"
#include "dawn/wire/WireResult.h"
namespace dawn::wire { namespace dawn::wire {
@ -38,7 +39,7 @@ class DawnLPMObjectIdProvider : public ObjectIdProvider {
}; };
void SerializedData(const fuzzing::Program& program, WireResult SerializedData(const fuzzing::Program& program,
dawn::wire::ChunkedCommandSerializer serializer); dawn::wire::ChunkedCommandSerializer serializer);
} // namespace dawn::wire } // namespace dawn::wire

View File

@ -17,6 +17,25 @@ package fuzzing;
import "third_party/dawn/src/dawn/fuzzers/lpmfuzz/dawn_custom_lpm.proto"; import "third_party/dawn/src/dawn/fuzzers/lpmfuzz/dawn_custom_lpm.proto";
// These are hardcoded limits for Dawn Object allocations based on type to help
// guide the fuzzer towards reusing existing objects.
{% for type in by_category["object"] %}
{% set type_key = type.name.canonical_case() %}
enum {{ type.name.CamelCase() }}Id {
{% if type_key in cmd_records["lpm_info"]["limits"] %}
{% for n in range(cmd_records["lpm_info"]["limits"][type_key]) %}
{{ type.name.SNAKE_CASE() }}_{{ loop.index }} = {{ loop.index }};
{% endfor %}
{% else %}
{% for n in range(cmd_records["lpm_info"]["limits"]["default"]) %}
{{ type.name.SNAKE_CASE() }}_{{ loop.index }} = {{ loop.index }};
{% endfor %}
{% endif %}
INVALID_{{ type.name.SNAKE_CASE() }} = {{ cmd_records["lpm_info"]["invalid object id"] }};
};
{% endfor %}
{% for type in by_category["enum"] %} {% for type in by_category["enum"] %}
enum {{as_cppType(type.name)}} { enum {{as_cppType(type.name)}} {
{% for value in type.values %} {% for value in type.values %}
@ -38,23 +57,33 @@ import "third_party/dawn/src/dawn/fuzzers/lpmfuzz/dawn_custom_lpm.proto";
{% macro lift_string_proto_member(member, count) -%} {% macro lift_string_proto_member(member, count) -%}
required string {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; required string {{ as_protobufNameLPM(member.name) }} = {{ count.value }};
{% set count.value = count.value + 1 %} {% set count.value = count.value + 1 %}
{%- endmacro %} {%- endmacro %}
{% macro lift_float_array_proto_member(member, count) -%} {% macro lift_float_array_proto_member(member, count) -%}
repeated float {{ as_varName(member.name) }} = {{ count.value }}; repeated float {{ as_varName(member.name) }} = {{ count.value }};
{% set count.value = count.value + 1 %} {% set count.value = count.value + 1 %}
{%- endmacro %} {%- endmacro %}
{% macro lift_object_member(member, count) %}
{{ member.type.name.CamelCase() }}Id {{ as_protobufNameLPM(member.name) }}
{% endmacro %}
{% macro lift_objectid_member(member, count) %}
{{ member.id_type.name.CamelCase() }}Id {{ as_protobufNameLPM(member.name) }}
{% endmacro %}
{% macro lift_varlength_proto_member(member, count) -%} {% macro lift_varlength_proto_member(member, count) -%}
{% if member.type in by_category["object"] %} {% if member.type in by_category["object"] %}
repeated uint32 {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; repeated {{ lift_object_member(member, count) }} = {{ count.value }};
{% set count.value = count.value + 1 %} {% set count.value = count.value + 1 %}
{% elif member.type.name.get() == "object id" %} {% elif member.type.name.get() == "object id" %}
repeated uint32 {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; repeated {{ lift_objectid_member(member, count) }} = {{ count.value }};
{% set count.value = count.value + 1 %} {% set count.value = count.value + 1 %}
{% elif member.type.name.get() == "uint8_t" %}
// Skip over byte arrays in protobuf, handled by DawnSerializer
{% else %} {% else %}
repeated {{ as_protobufTypeLPM(member) }} {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; repeated {{ as_protobufTypeLPM(member) }} {{ as_protobufNameLPM(member.name) }} = {{ count.value }};
{% set count.value = count.value + 1 %} {% set count.value = count.value + 1 %}
@ -73,10 +102,10 @@ import "third_party/dawn/src/dawn/fuzzers/lpmfuzz/dawn_custom_lpm.proto";
required {{ as_protobufTypeLPM(member) }} {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; required {{ as_protobufTypeLPM(member) }} {{ as_protobufNameLPM(member.name) }} = {{ count.value }};
{% set count.value = count.value + 1 %} {% set count.value = count.value + 1 %}
{% elif member.type in by_category["object"] %} {% elif member.type in by_category["object"] %}
required uint32 {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; required {{ lift_object_member(member, count) }} = {{ count.value }};
{% set count.value = count.value + 1 %} {% set count.value = count.value + 1 %}
{% elif member.type.name.get() == "ObjectId" %} {% elif member.type.name.get() == "ObjectId" %}
required uint32 {{ as_protobufNameLPM(member.name) }} = {{ count.value }}; required {{ lift_objectid_member(member, count) }} = {{ count.value }};
{% set count.value = count.value + 1 %} {% set count.value = count.value + 1 %}
{% elif member.type.name.get() == "ObjectHandle" %} {% elif member.type.name.get() == "ObjectHandle" %}
// Skips object handles while lifting dawn.json to protobuf because // Skips object handles while lifting dawn.json to protobuf because
@ -105,7 +134,7 @@ import "third_party/dawn/src/dawn/fuzzers/lpmfuzz/dawn_custom_lpm.proto";
{% else %} {% else %}
// There shouldn't be any other pass-by-pointer types in // There shouldn't be any other pass-by-pointer types in
// dawn*.json, if any are added we would like to know at compile time // dawn*.json, if any are added we would like to know at compile time
{{ unreachable_code }} {{ unreachable_code() }}
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}

View File

@ -19,4 +19,4 @@ enum ObjectType {
{% for type in by_category["object"] %} {% for type in by_category["object"] %}
{{ type.name.CamelCase() }} = {{ loop.index - 1}}; {{ type.name.CamelCase() }} = {{ loop.index - 1}};
{% endfor %} {% endfor %}
}; };

View File

@ -175,6 +175,8 @@ if (is_dawn_lpm_fuzzer && build_with_chromium && dawn_use_swiftshader) {
"lpmfuzz/DawnLPMFuzzer.cpp", "lpmfuzz/DawnLPMFuzzer.cpp",
"lpmfuzz/DawnLPMFuzzer.h", "lpmfuzz/DawnLPMFuzzer.h",
"lpmfuzz/DawnLPMFuzzerAndVulkanBackend.cpp", "lpmfuzz/DawnLPMFuzzerAndVulkanBackend.cpp",
"lpmfuzz/DawnLPMObjectStore.cpp",
"lpmfuzz/DawnLPMObjectStore.h",
] ]
deps = [ deps = [

View File

@ -30,6 +30,7 @@
#include "dawn/webgpu_cpp.h" #include "dawn/webgpu_cpp.h"
#include "dawn/wire/ChunkedCommandSerializer.h" #include "dawn/wire/ChunkedCommandSerializer.h"
#include "dawn/wire/WireClient.h" #include "dawn/wire/WireClient.h"
#include "dawn/wire/WireResult.h"
#include "dawn/wire/WireServer.h" #include "dawn/wire/WireServer.h"
#include "testing/libfuzzer/libfuzzer_exports.h" #include "testing/libfuzzer/libfuzzer_exports.h"
@ -106,14 +107,14 @@ int Run(const fuzzing::Program& program, bool (*AdapterSupported)(const dawn::na
dawn::wire::ChunkedCommandSerializer(mCommandBuffer); dawn::wire::ChunkedCommandSerializer(mCommandBuffer);
mCommandBuffer->SetHandler(wireServer.get()); mCommandBuffer->SetHandler(wireServer.get());
dawn::wire::SerializedData(program, mSerializer); dawn::wire::WireResult result = dawn::wire::SerializedData(program, mSerializer);
mCommandBuffer->Flush(); mCommandBuffer->Flush();
// Note: Deleting the server will release all created objects. // Note: Deleting the server will release all created objects.
// Deleted devices will wait for idle on destruction. // Deleted devices will wait for idle on destruction.
wireServer = nullptr; wireServer = nullptr;
return 0; return result == dawn::wire::WireResult::FatalError;
} }
} // namespace DawnLPMFuzzer } // namespace DawnLPMFuzzer

View File

@ -0,0 +1,100 @@
// 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 <algorithm>
#include <functional>
#include <limits>
#include <utility>
#include "dawn/fuzzers/lpmfuzz/DawnLPMConstants_autogen.h"
#include "dawn/fuzzers/lpmfuzz/DawnLPMObjectStore.h"
#include "dawn/fuzzers/lpmfuzz/DawnLPMSerializer_autogen.h"
#include "dawn/wire/ObjectHandle.h"
namespace dawn::wire {
DawnLPMObjectStore::DawnLPMObjectStore() {
mCurrentId = 1;
}
ObjectHandle DawnLPMObjectStore::ReserveHandle() {
if (mFreeHandles.empty()) {
Insert(mCurrentId);
return {mCurrentId++, 0};
}
ObjectHandle handle = mFreeHandles.back();
mFreeHandles.pop_back();
Insert(handle.id);
return handle;
}
void DawnLPMObjectStore::Insert(ObjectId id) {
std::vector<ObjectId>::iterator iter =
std::lower_bound(mObjects.begin(), mObjects.end(), id, std::greater<ObjectId>());
mObjects.insert(iter, id);
}
void DawnLPMObjectStore::Free(ObjectId id) {
if (id == DawnLPMFuzzer::kInvalidObjectId) {
return;
}
for (size_t i = 0; i < mObjects.size(); i++) {
if (mObjects[i] == id) {
mFreeHandles.push_back({id, 0});
mObjects.erase(mObjects.begin() + i);
}
}
}
/*
* Consistent hashing inspired map for fuzzer state.
* If we store the Dawn objects in a hash table mapping FuzzInt -> ObjectId
* then it would be highly unlikely that any subsequence DestroyObject command
* would come up with an ID that would correspond to a valid ObjectId in the
* hash table.
*
* One solution is to modulo the FuzzInt with the length of the hash table, but
* it does not work well with libfuzzer's minimization techniques because
* deleting a single ObjectId from the hash table changes the index of every
* entry from then on.
*
* So we use consistent hashing. we take the entry in the table that
* has the next highest id (wrapping when there is no higher entry).
*/
ObjectId DawnLPMObjectStore::Lookup(uint32_t id) const {
// CreateBindGroup relies on sending invalid object ids
if (id == DawnLPMFuzzer::kInvalidObjectId) {
return 0;
}
auto iter = std::lower_bound(mObjects.begin(), mObjects.end(), id, std::greater<ObjectId>());
if (iter != mObjects.end()) {
return *iter;
}
// Wrap to 0
iter = std::lower_bound(mObjects.begin(), mObjects.end(), 0, std::greater<ObjectId>());
if (iter != mObjects.end()) {
return *iter;
}
return 0;
}
size_t DawnLPMObjectStore::Size() const {
return mObjects.size();
}
} // namespace dawn::wire

View File

@ -0,0 +1,43 @@
// 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_WIRE_CLIENT_DAWNLPMOBJECTSTORE_H_
#define SRC_DAWN_WIRE_CLIENT_DAWNLPMOBJECTSTORE_H_
#include <vector>
#include "dawn/wire/client/ObjectBase.h"
namespace dawn::wire {
class DawnLPMObjectStore {
public:
DawnLPMObjectStore();
ObjectHandle ReserveHandle();
void Free(ObjectId handle);
void Insert(ObjectId handle);
ObjectId Lookup(ObjectId id) const;
size_t Size() const;
uint32_t mCurrentId;
std::vector<ObjectHandle> mFreeHandles;
// TODO(tiszka): refactor into a vector of ObjectHandles
std::vector<ObjectId> mObjects;
};
} // namespace dawn::wire
#endif // SRC_DAWN_WIRE_CLIENT_DAWNLPMOBJECTSTORE_H_

View File

@ -23,10 +23,26 @@
], ],
"blocklisted_cmds": [ "blocklisted_cmds": [
"device create render pipeline",
"device create render pipeline async",
"surface descriptor from windows core window", "surface descriptor from windows core window",
"surface descriptor from windows swap chain panel", "surface descriptor from windows swap chain panel",
"surface descriptor from canvas html selector" "surface descriptor from canvas html selector"
] ],
"lpm_info": {
"overrides": {
"instance request adapter": {
"instance id": 1
}
},
"limits": {
"adapter": 2,
"bind group": 512,
"bind group layout": 512,
"device": 2,
"default": 16
},
"invalid object id": 2147483647
}
} }