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": {
"buffer map async": [
{ "name": "buffer id", "type": "ObjectId" },
{ "name": "buffer id", "type": "ObjectId", "id_type": "buffer" },
{ "name": "request serial", "type": "uint64_t" },
{ "name": "mode", "type": "map mode" },
{ "name": "offset", "type": "uint64_t"},
{ "name": "size", "type": "uint64_t"}
],
"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", "type": "uint8_t", "annotation": "const*", "length": "write data update info length", "skip_serialize": true},
{ "name": "offset", "type": "uint64_t"},
{ "name": "size", "type": "uint64_t"}
],
"device create buffer": [
{ "name": "device id", "type": "ObjectId" },
{ "name": "device id", "type": "ObjectId", "id_type": "device" },
{ "name": "descriptor", "type": "buffer descriptor", "annotation": "const*" },
{ "name": "result", "type": "ObjectHandle", "handle_type": "buffer" },
{ "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}
],
"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": "pipeline object handle", "type": "ObjectHandle", "handle_type": "compute pipeline"},
{ "name": "descriptor", "type": "compute pipeline descriptor", "annotation": "const*"}
],
"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": "pipeline object handle", "type": "ObjectHandle", "handle_type": "render pipeline"},
{ "name": "descriptor", "type": "render pipeline descriptor", "annotation": "const*"}
],
"device pop error scope": [
{ "name": "device id", "type": "ObjectId" },
{ "name": "device id", "type": "ObjectId", "id_type": "device" },
{ "name": "request serial", "type": "uint64_t" }
],
"destroy object": [
@ -62,19 +62,19 @@
{ "name": "object id", "type": "ObjectId" }
],
"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": "request serial", "type": "uint64_t" }
],
"queue write buffer": [
{"name": "queue id", "type": "ObjectId" },
{"name": "buffer id", "type": "ObjectId" },
{"name": "queue id", "type": "ObjectId", "id_type": "queue" },
{"name": "buffer id", "type": "ObjectId", "id_type": "buffer" },
{"name": "buffer offset", "type": "uint64_t"},
{"name": "data", "type": "uint8_t", "annotation": "const*", "length": "size", "wire_is_data_only": true},
{"name": "size", "type": "uint64_t"}
],
"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": "data", "type": "uint8_t", "annotation": "const*", "length": "data size", "wire_is_data_only": true},
{"name": "data size", "type": "uint64_t"},
@ -82,17 +82,17 @@
{"name": "writeSize", "type": "extent 3D", "annotation": "const*"}
],
"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" }
],
"instance request adapter": [
{ "name": "instance id", "type": "ObjectId" },
{ "name": "instance id", "type": "ObjectId", "id_type": "instance" },
{ "name": "request serial", "type": "uint64_t" },
{ "name": "adapter object handle", "type": "ObjectHandle", "handle_type": "adapter"},
{ "name": "options", "type": "request adapter options", "annotation": "const*" }
],
"adapter request device": [
{ "name": "adapter id", "type": "ObjectId" },
{ "name": "adapter id", "type": "ObjectId", "id_type": "adapter" },
{ "name": "request serial", "type": "uint64_t" },
{ "name": "device object handle", "type": "ObjectHandle", "handle_type": "device"},
{ "name": "descriptor", "type": "device descriptor", "annotation": "const*" }

View File

@ -181,6 +181,7 @@ class RecordMember:
self.optional = optional
self.is_return_value = is_return_value
self.handle_type = None
self.id_type = None
self.default_value = default_value
self.skip_serialize = skip_serialize
@ -188,6 +189,10 @@ class RecordMember:
assert self.type.dict_name == "ObjectHandle"
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', ['name', 'return_type', 'arguments', 'autolock', 'json_data'])
@ -308,6 +313,9 @@ def linked_record_members(json_data, types):
handle_type = m.get('handle_type')
if 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_by_name[member.name.canonical_case()] = member
@ -331,6 +339,18 @@ def linked_record_members(json_data, types):
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
############################################################
@ -376,7 +396,6 @@ def link_function(function, types):
function.arguments = linked_record_members(function.json_data['args'],
types)
# Sort structures so that if struct A has struct B as a member, then B is
# listed before A.
#
@ -601,10 +620,21 @@ def compute_lpm_params(api_and_wire_params, lpm_json):
generated_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'] = {
'proto_generated_commands': generated_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
@ -636,16 +666,28 @@ def as_protobufTypeLPM(member):
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):
# `descriptor` is a reserved keyword in lpm
# `descriptor` is a reserved keyword in lib-protobuf-mutator
if (names[0].concatcase() == "descriptor"):
return "desc"
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():
assert False
#############################################################
# Generator
#############################################################
@ -1152,8 +1194,10 @@ class MultiGeneratorFromDawnJSON(Generator):
fuzzer_params = compute_lpm_params(api_and_wire_params, lpm_json)
lpm_params = [
RENDER_PARAMS_BASE, params_dawn_wire, {}, api_and_wire_params,
fuzzer_params
RENDER_PARAMS_BASE, params_dawn_wire, {
'as_protobufMemberName': as_protobufMemberNameLPM,
'unreachable_code': unreachable_code
}, api_and_wire_params, fuzzer_params
]
renders.append(

View File

@ -12,8 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdint.h>
namespace DawnLPMFuzzer {
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

View File

@ -12,31 +12,246 @@
// 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/DawnLPMConstants_autogen.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/WireClient.h"
#include "dawn/wire/WireCmd_autogen.h"
#include "dawn/wire/WireResult.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) {
//* 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;
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()) {
switch (command.command_case()) {
{% for command in cmd_records["cpp_generated_commands"] %}
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);
{% set name = command.name.CamelCase() %}
case fuzzing::Command::k{{name}}: {
SerializeBuffer serializeBuffer(allocatedBuffer.get(), kMaxSerializeBufferSize);
{{ name }}Cmd *cmd = nullptr;
WIRE_TRY(serializeBuffer.Next(&cmd));
WIRE_TRY({{name}}ProtoConvert(command.{{ command.name.concatcase() }}(), cmd, &serializeBuffer, objectStoresRef));
serializer.SerializeCommand(*cmd, provider);
break;
}
{% endfor %}
@ -45,6 +260,8 @@ void SerializedData(const fuzzing::Program& program, dawn::wire::ChunkedCommandS
}
}
}
return WireResult::Success;
}
} // namespace dawn::wire

View File

@ -18,6 +18,7 @@
#include "dawn/fuzzers/lpmfuzz/dawn_lpm_autogen.pb.h"
#include "dawn/wire/ChunkedCommandSerializer.h"
#include "dawn/wire/WireCmd_autogen.h"
#include "dawn/wire/WireResult.h"
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);
} // namespace dawn::wire

View File

@ -17,6 +17,25 @@ package fuzzing;
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"] %}
enum {{as_cppType(type.name)}} {
{% 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) -%}
required string {{ as_protobufNameLPM(member.name) }} = {{ count.value }};
{% set count.value = count.value + 1 %}
{%- endmacro %}
{% macro lift_float_array_proto_member(member, count) -%}
repeated float {{ as_varName(member.name) }} = {{ count.value }};
{% set count.value = count.value + 1 %}
{%- 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) -%}
{% 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 %}
{% 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 %}
{% elif member.type.name.get() == "uint8_t" %}
// Skip over byte arrays in protobuf, handled by DawnSerializer
{% else %}
repeated {{ as_protobufTypeLPM(member) }} {{ as_protobufNameLPM(member.name) }} = {{ count.value }};
{% 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 }};
{% set count.value = count.value + 1 %}
{% 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 %}
{% 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 %}
{% elif member.type.name.get() == "ObjectHandle" %}
// 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 %}
// 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
{{ unreachable_code }}
{{ unreachable_code() }}
{% endif %}
{% endmacro %}

View File

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

View File

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

View File

@ -30,6 +30,7 @@
#include "dawn/webgpu_cpp.h"
#include "dawn/wire/ChunkedCommandSerializer.h"
#include "dawn/wire/WireClient.h"
#include "dawn/wire/WireResult.h"
#include "dawn/wire/WireServer.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);
mCommandBuffer->SetHandler(wireServer.get());
dawn::wire::SerializedData(program, mSerializer);
dawn::wire::WireResult result = 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;
return result == dawn::wire::WireResult::FatalError;
}
} // 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": [
"device create render pipeline",
"device create render pipeline async",
"surface descriptor from windows core window",
"surface descriptor from windows swap chain panel",
"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
}
}