| // Copyright 2016 The Cobalt Authors. All Rights Reserved. |
| // |
| // 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 <map> |
| #include <memory> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/time/time.h" |
| #include "cobalt/base/wrap_main.h" |
| #include "cobalt/media/base/bind_to_loop.h" |
| #include "cobalt/media/sandbox/fuzzer_app.h" |
| #include "cobalt/media/sandbox/media_sandbox.h" |
| #include "cobalt/media/sandbox/media_source_demuxer.h" |
| #include "cobalt/media/sandbox/zzuf_fuzzer.h" |
| #include "starboard/memory.h" |
| |
| namespace cobalt { |
| namespace media { |
| namespace sandbox { |
| namespace { |
| |
| using base::Time; |
| using ::media::BindToCurrentLoop; |
| using ::media::DecoderBuffer; |
| using ::media::ShellRawVideoDecoder; |
| using ::media::VideoFrame; |
| |
| class VideoDecoderFuzzer { |
| public: |
| VideoDecoderFuzzer(const std::vector<uint8_t>& au_data, |
| MediaSourceDemuxer* demuxer, ShellRawVideoDecoder* decoder) |
| : au_data_(au_data), |
| demuxer_(demuxer), |
| decoder_(decoder), |
| au_index_(0), |
| error_occured_(false), |
| eos_decoded_(false) {} |
| |
| void Fuzz() { |
| UpdateCurrentAUBuffer(); |
| decoder_->Decode(current_au_buffer_, BindToCurrentLoop(base::Bind( |
| &VideoDecoderFuzzer::FrameDecoded, |
| base::Unretained(this)))); |
| base::RunLoop().RunUntilIdle(); |
| DCHECK(IsEnded()); |
| } |
| |
| private: |
| void UpdateCurrentAUBuffer() { |
| if (au_index_ < demuxer_->GetFrameCount()) { |
| MediaSourceDemuxer::AUDescriptor desc = demuxer_->GetFrame(au_index_); |
| current_au_buffer_ = |
| ::media::ShellBufferFactory::Instance()->AllocateBufferNow( |
| desc.size, desc.is_keyframe); |
| memcpy(current_au_buffer_->GetWritableData(), &au_data_[0] + desc.offset, |
| desc.size); |
| ++au_index_; |
| } else if (!current_au_buffer_->IsEndOfStream()) { |
| current_au_buffer_ = |
| DecoderBuffer::CreateEOSBuffer(::media::kNoTimestamp()); |
| } |
| } |
| void FrameDecoded(ShellRawVideoDecoder::DecodeStatus status, |
| const scoped_refptr<VideoFrame>& frame) { |
| if (frame) { |
| last_frame_decoded_time_ = Time::Now(); |
| if (frame->IsEndOfStream()) { |
| eos_decoded_ = true; |
| } |
| } |
| switch (status) { |
| case ShellRawVideoDecoder::FRAME_DECODED: |
| case ShellRawVideoDecoder::NEED_MORE_DATA: |
| UpdateCurrentAUBuffer(); |
| break; |
| case ShellRawVideoDecoder::FATAL_ERROR: |
| error_occured_ = true; |
| // Even if there is a fatal error, we still want to keep sending the |
| // rest buffers to decoder. |
| UpdateCurrentAUBuffer(); |
| break; |
| case ShellRawVideoDecoder::RETRY_WITH_SAME_BUFFER: |
| if (current_au_buffer_->IsEndOfStream() && |
| (Time::Now() - last_frame_decoded_time_).InMilliseconds() > 500) { |
| error_occured_ = true; |
| } |
| break; |
| } |
| if (!IsEnded()) { |
| decoder_->Decode( |
| current_au_buffer_, |
| BindToCurrentLoop(base::Bind(&VideoDecoderFuzzer::FrameDecoded, |
| base::Unretained(this)))); |
| } |
| } |
| bool IsEnded() const { |
| if (error_occured_) return true; |
| return eos_decoded_ || |
| (error_occured_ && current_au_buffer_->IsEndOfStream()); |
| } |
| |
| const std::vector<uint8_t>& au_data_; |
| MediaSourceDemuxer* demuxer_; |
| ShellRawVideoDecoder* decoder_; |
| size_t au_index_; |
| scoped_refptr<DecoderBuffer> current_au_buffer_; |
| bool error_occured_; |
| bool eos_decoded_; |
| Time last_frame_decoded_time_; |
| }; |
| |
| int CalculateCheckSum(const std::vector<uint8>& data) { |
| int checksum = 0; |
| for (size_t i = 0; i < data.size(); ++i) { |
| checksum += data[i]; |
| } |
| return checksum; |
| } |
| |
| // This function replace the original data inside the original file with the |
| // fuzzed data to created a valid container with fuzzed AUs. |filename| should |
| // contain a file that inside a path readable by the host. |
| // The following statement can be used inside RawVideoDecoderFuzzerApp::Fuzz() |
| // to save the fuzzing content back into its original container format. |
| // DumpFuzzedData(filename, GetFileContent(file_name), *demuxers_[file_name], |
| // fuzzing_content); |
| void DumpFuzzedData(const std::string& filename, std::vector<uint8> container, |
| const MediaSourceDemuxer& demuxer, |
| const std::vector<uint8>& fuzzing_content) { |
| std::vector<uint8>::iterator last_found = container.begin(); |
| for (size_t i = 0; i < demuxer.GetFrameCount(); ++i) { |
| MediaSourceDemuxer::AUDescriptor desc = demuxer.GetFrame(i); |
| std::vector<uint8>::const_iterator begin = |
| demuxer.au_data().begin() + desc.offset; |
| std::vector<uint8>::const_iterator end = begin + desc.size; |
| std::vector<uint8>::iterator offset = |
| std::search(last_found, container.end(), begin, end); |
| std::copy(fuzzing_content.begin() + desc.offset, |
| fuzzing_content.begin() + desc.offset + desc.size, offset); |
| last_found = offset + desc.size + 1; |
| } |
| base::WriteFile(base::FilePath(filename), |
| reinterpret_cast<const char*>(&container[0]), |
| container.size()); |
| } |
| |
| class RawVideoDecoderFuzzerApp : public FuzzerApp { |
| public: |
| explicit RawVideoDecoderFuzzerApp(MediaSandbox* media_sandbox) |
| : media_sandbox_(media_sandbox) {} |
| ~RawVideoDecoderFuzzerApp() { |
| while (!demuxers_.empty()) { |
| delete demuxers_.begin()->second; |
| demuxers_.erase(demuxers_.begin()); |
| } |
| } |
| |
| std::vector<uint8> ParseFileContent( |
| const std::string& file_name, |
| const std::vector<uint8>& file_content) override { |
| std::string ext = base::FilePath(file_name).Extension(); |
| if (ext != ".webm" && ext != ".mp4" && ext != ".ivf") { |
| LOG(ERROR) << "Skip unsupported file " << file_name; |
| return std::vector<uint8>(); |
| } |
| |
| std::unique_ptr<MediaSourceDemuxer> demuxer(new MediaSourceDemuxer( |
| std::vector<uint8>(file_content.begin(), file_content.end()))); |
| if (demuxer->valid() && demuxer->GetFrameCount() > 0) { |
| demuxers_[file_name] = demuxer.release(); |
| return demuxers_[file_name]->au_data(); |
| } |
| LOG(ERROR) << "Failed to demux video: " << file_name; |
| return std::vector<uint8>(); |
| } |
| |
| void Fuzz(const std::string& file_name, |
| const std::vector<uint8>& fuzzing_content) override { |
| DCHECK(demuxers_.find(file_name) != demuxers_.end()); |
| MediaSourceDemuxer* demuxer = demuxers_[file_name]; |
| std::unique_ptr<ShellRawVideoDecoder> decoder = |
| media_sandbox_->GetMediaModule()->GetRawVideoDecoderFactory()->Create( |
| demuxer->config(), NULL, false); |
| if (decoder) { |
| VideoDecoderFuzzer decoder_fuzzer(fuzzing_content, demuxer, |
| decoder.get()); |
| decoder_fuzzer.Fuzz(); |
| } |
| } |
| |
| private: |
| MediaSandbox* media_sandbox_; |
| std::map<std::string, MediaSourceDemuxer*> demuxers_; |
| }; |
| |
| int SandboxMain(int argc, char** argv) { |
| MediaSandbox media_sandbox( |
| argc, argv, |
| base::FilePath(FILE_PATH_LITERAL("raw_video_decoder_fuzzer.json"))); |
| RawVideoDecoderFuzzerApp fuzzer_app(&media_sandbox); |
| |
| if (fuzzer_app.Init(argc, argv)) { |
| fuzzer_app.RunFuzzingLoop(); |
| } |
| |
| return 0; |
| } |
| |
| } // namespace |
| } // namespace sandbox |
| } // namespace media |
| } // namespace cobalt |
| |
| COBALT_WRAP_SIMPLE_MAIN(cobalt::media::sandbox::SandboxMain); |