Commit 80429ef7 authored by Chris Cunningham's avatar Chris Cunningham Committed by Commit Bot

WebCodecs: Fuzzing AudioDecoder

Bug: 1120745
Change-Id: Idd2721941fee38a3ad4c6beb30ed1b39ff7759c9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2368284Reviewed-by: default avatarJeremy Roman <jbroman@chromium.org>
Reviewed-by: default avatarThomas Guilbert <tguilbert@chromium.org>
Commit-Queue: Chrome Cunningham <chcunningham@chromium.org>
Auto-Submit: Chrome Cunningham <chcunningham@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800800}
parent 4a53be96
......@@ -572,4 +572,22 @@ if (use_libfuzzer) {
"//third_party/protobuf:protobuf_lite",
]
}
fuzzer_test("webcodecs_audio_decoder_fuzzer") {
sources = [
"webcodecs/audio_decoder_fuzzer.cc",
"webcodecs/fuzzer_utils.cc",
"webcodecs/fuzzer_utils.h",
]
seed_corpus = "webcodecs/fuzzer_seed_corpus/audio_decoder"
deps = [
":modules",
"//third_party/blink/renderer/modules/webcodecs:fuzzer_protos",
"//third_party/blink/renderer/platform:blink_fuzzer_test_support",
"//third_party/libprotobuf-mutator",
"//third_party/protobuf:protobuf_lite",
]
}
}
......@@ -27,6 +27,10 @@ specific_include_rules = {
"+testing/libfuzzer/proto/lpm_interface.h",
"+third_party/protobuf/src/google/protobuf/repeated_field.h",
],
"audio_decoder_fuzzer.cc": [
"+base/run_loop.h",
"+testing/libfuzzer/proto/lpm_interface.h",
],
"video_decoder_fuzzer.cc": [
"+base/run_loop.h",
"+testing/libfuzzer/proto/lpm_interface.h",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/run_loop.h"
#include "testing/libfuzzer/proto/lpm_interface.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_decoder_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_frame_output_callback.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_web_codecs_error_callback.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/modules/webcodecs/audio_decoder.h"
#include "third_party/blink/renderer/modules/webcodecs/encoded_audio_chunk.h"
#include "third_party/blink/renderer/modules/webcodecs/fuzzer_inputs.pb.h"
#include "third_party/blink/renderer/modules/webcodecs/fuzzer_utils.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include <string>
namespace blink {
DEFINE_TEXT_PROTO_FUZZER(
const wc_fuzzer::AudioDecoderApiInvocationSequence& proto) {
static BlinkFuzzerTestSupport test_support = BlinkFuzzerTestSupport();
static DummyPageHolder* page_holder = []() {
auto page_holder = std::make_unique<DummyPageHolder>();
page_holder->GetFrame().GetSettings()->SetScriptEnabled(true);
return page_holder.release();
}();
//
// NOTE: GC objects that need to survive iterations of the loop below
// must be Persistent<>!
//
// GC may be triggered by the RunLoop().RunUntilIdle() below, which will GC
// raw pointers on the stack. This is not required in production code because
// GC typically runs at the top of the stack, or is conservative enough to
// keep stack pointers alive.
//
// Scoping Persistent<> refs so GC can collect these at the end.
{
Persistent<ScriptState> script_state =
ToScriptStateForMainWorld(&page_holder->GetFrame());
ScriptState::Scope scope(script_state);
Persistent<FakeFunction> error_function =
FakeFunction::Create(script_state, "error");
Persistent<V8WebCodecsErrorCallback> error_callback =
V8WebCodecsErrorCallback::Create(error_function->Bind());
Persistent<FakeFunction> output_function =
FakeFunction::Create(script_state, "output");
Persistent<V8AudioFrameOutputCallback> output_callback =
V8AudioFrameOutputCallback::Create(output_function->Bind());
Persistent<AudioDecoderInit> audio_decoder_init =
MakeGarbageCollected<AudioDecoderInit>();
audio_decoder_init->setError(error_callback);
audio_decoder_init->setOutput(output_callback);
Persistent<AudioDecoder> audio_decoder = AudioDecoder::Create(
script_state, audio_decoder_init, IGNORE_EXCEPTION_FOR_TESTING);
for (auto& invocation : proto.invocations()) {
switch (invocation.Api_case()) {
case wc_fuzzer::AudioDecoderApiInvocation::kConfigure:
audio_decoder->configure(
MakeAudioDecoderConfig(invocation.configure()),
IGNORE_EXCEPTION_FOR_TESTING);
break;
case wc_fuzzer::AudioDecoderApiInvocation::kDecode:
audio_decoder->decode(
MakeEncodedAudioChunk(invocation.decode().chunk()),
IGNORE_EXCEPTION_FOR_TESTING);
break;
case wc_fuzzer::AudioDecoderApiInvocation::kFlush: {
// TODO(https://crbug.com/1119253): Fuzz whether to await resolution
// of the flush promise.
audio_decoder->flush(IGNORE_EXCEPTION_FOR_TESTING);
break;
}
case wc_fuzzer::AudioDecoderApiInvocation::kReset:
audio_decoder->reset(IGNORE_EXCEPTION_FOR_TESTING);
break;
case wc_fuzzer::AudioDecoderApiInvocation::kClose:
audio_decoder->close(IGNORE_EXCEPTION_FOR_TESTING);
break;
case wc_fuzzer::AudioDecoderApiInvocation::API_NOT_SET:
break;
}
// Give other tasks a chance to run (e.g. calling our output callback).
base::RunLoop().RunUntilIdle();
}
}
// Request a V8 GC. Oilpan will be invoked by the GC epilogue.
//
// Multiple GCs may be required to ensure everything is collected (due to
// a chain of persistent handles), so some objects may not be collected until
// a subsequent iteration. This is slow enough as is, so we compromise on one
// major GC, as opposed to the 5 used in V8GCController for unit tests.
V8PerIsolateData::MainThreadIsolate()->RequestGarbageCollectionForTesting(
v8::Isolate::kFullGarbageCollection);
}
} // namespace blink
......@@ -13,12 +13,24 @@ message ConfigureVideoDecoder {
optional bytes description = 2;
}
message ConfigureAudioDecoder {
// String describing codec (e.g. "opus")
optional string codec = 1;
optional uint32 sample_rate = 2;
optional uint32 number_of_channels = 3;
optional bytes description = 4;
}
enum EncodedChunkType {
KEY = 0;
DELTA = 1;
}
message EncodedVideoChunk {
enum EncodedVideoChunkType {
KEY = 0;
DELTA = 1;
}
optional EncodedVideoChunkType type = 1;
optional EncodedChunkType type = 1;
optional uint64 timestamp = 2;
......@@ -27,10 +39,22 @@ message EncodedVideoChunk {
optional bytes data = 4;
}
message EncodedAudioChunk {
optional EncodedChunkType type = 1;
optional uint64 timestamp = 2;
optional bytes data = 4;
}
message DecodeVideo {
optional EncodedVideoChunk chunk = 1;
}
message DecodeAudio {
optional EncodedAudioChunk chunk = 1;
}
message Flush {
optional bool wait_for_promise = 1;
}
......@@ -52,3 +76,17 @@ message VideoDecoderApiInvocation {
message VideoDecoderApiInvocationSequence {
repeated VideoDecoderApiInvocation invocations = 1;
}
message AudioDecoderApiInvocation {
oneof Api {
ConfigureAudioDecoder configure = 1;
DecodeAudio decode = 2;
Flush flush = 3;
Reset reset = 4;
Close close = 5;
}
}
message AudioDecoderApiInvocationSequence {
repeated AudioDecoderApiInvocation invocations = 1;
}
# Simulates reasonable usage of AudioDecoder
# configure(aac)
# decode() 2 real frames
# flush(), reset(), re configure(aac)
# decode() a 3rd real frame
# close()
invocations: [
{
configure {
codec: 'mp4a.40.2',
sample_rate: 44100,
number_of_channels: 2
}
},
{
decode {
chunk {
type: KEY,
timestamp: 0,
# aac-44100-packet-0
data: "\377\361P\200.\177\374!\000\005\000\240\033\377\300\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\0007\243\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000{"
}
}
},
{
decode {
chunk {
type: KEY,
timestamp: 23222,
# aac-44100-packet-1
data: "\377\361P\200.\237\374!\020\005\000\240\033\377\300\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\0007\244\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000w"
}
}
},
{
flush {}
},
{
reset {}
},
{
configure {
codec: 'mp4a.40.2',
sample_rate: 44100,
number_of_channels: 2
}
},
{
decode {
chunk {
type: KEY,
timestamp: 46444,
# aac-44100-packet-3
data: "\377\361P\200.\237\374!\020\005\000\240\033\377\300\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\0007\244\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000u"
}
}
},
{
close {}
}
]
\ No newline at end of file
......@@ -34,7 +34,7 @@ ScriptValue FakeFunction::Call(ScriptValue) {
return ScriptValue();
}
EncodedVideoConfig* MakeDecoderConfig(
EncodedVideoConfig* MakeVideoDecoderConfig(
const wc_fuzzer::ConfigureVideoDecoder& proto) {
auto* config = EncodedVideoConfig::Create();
config->setCodec(proto.codec().c_str());
......@@ -45,11 +45,26 @@ EncodedVideoConfig* MakeDecoderConfig(
return config;
}
String ToChunkType(wc_fuzzer::EncodedVideoChunk_EncodedVideoChunkType type) {
EncodedAudioConfig* MakeAudioDecoderConfig(
const wc_fuzzer::ConfigureAudioDecoder& proto) {
auto* config = EncodedAudioConfig::Create();
config->setCodec(proto.codec().c_str());
config->setSampleRate(proto.sample_rate());
config->setNumberOfChannels(proto.number_of_channels());
DOMArrayBuffer* data_copy = DOMArrayBuffer::Create(
proto.description().data(), proto.description().size());
config->setDescription(
ArrayBufferOrArrayBufferView::FromArrayBuffer(data_copy));
return config;
}
String ToChunkType(wc_fuzzer::EncodedChunkType type) {
switch (type) {
case wc_fuzzer::EncodedVideoChunk_EncodedVideoChunkType_KEY:
case wc_fuzzer::EncodedChunkType::KEY:
return "key";
case wc_fuzzer::EncodedVideoChunk_EncodedVideoChunkType_DELTA:
case wc_fuzzer::EncodedChunkType::DELTA:
return "delta";
}
}
......@@ -63,4 +78,13 @@ EncodedVideoChunk* MakeEncodedVideoChunk(
proto.duration(), data_copy);
}
EncodedAudioChunk* MakeEncodedAudioChunk(
const wc_fuzzer::EncodedAudioChunk& proto) {
DOMArrayBuffer* data_copy =
DOMArrayBuffer::Create(proto.data().data(), proto.data().size());
return EncodedAudioChunk::Create(ToChunkType(proto.type()), proto.timestamp(),
data_copy);
}
} // namespace blink
......@@ -7,9 +7,11 @@
#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_init.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/modules/webcodecs/encoded_audio_chunk.h"
#include "third_party/blink/renderer/modules/webcodecs/encoded_video_chunk.h"
#include "third_party/blink/renderer/modules/webcodecs/fuzzer_inputs.pb.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
......@@ -32,13 +34,19 @@ class FakeFunction : public ScriptFunction {
const std::string name_;
};
EncodedVideoConfig* MakeDecoderConfig(
EncodedVideoConfig* MakeVideoDecoderConfig(
const wc_fuzzer::ConfigureVideoDecoder& proto);
EncodedAudioConfig* MakeAudioDecoderConfig(
const wc_fuzzer::ConfigureAudioDecoder& proto);
EncodedVideoChunk* MakeEncodedVideoChunk(
const wc_fuzzer::EncodedVideoChunk& proto);
String ToChunkType(wc_fuzzer::EncodedVideoChunk_EncodedVideoChunkType type);
EncodedAudioChunk* MakeEncodedAudioChunk(
const wc_fuzzer::EncodedAudioChunk& proto);
String ToChunkType(wc_fuzzer::EncodedChunkType type);
} // namespace blink
......
......@@ -73,8 +73,9 @@ DEFINE_TEXT_PROTO_FUZZER(
for (auto& invocation : proto.invocations()) {
switch (invocation.Api_case()) {
case wc_fuzzer::VideoDecoderApiInvocation::kConfigure:
video_decoder->configure(MakeDecoderConfig(invocation.configure()),
IGNORE_EXCEPTION_FOR_TESTING);
video_decoder->configure(
MakeVideoDecoderConfig(invocation.configure()),
IGNORE_EXCEPTION_FOR_TESTING);
break;
case wc_fuzzer::VideoDecoderApiInvocation::kDecode:
video_decoder->decode(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment