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:
parent
8e42cfa7ea
commit
27521c6b91
|
@ -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*" }
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue