diff --git a/test/fuzzers/BUILD.gn b/test/fuzzers/BUILD.gn index 99eb4340c2..2cb4b13161 100644 --- a/test/fuzzers/BUILD.gn +++ b/test/fuzzers/BUILD.gn @@ -628,3 +628,27 @@ webrtc_fuzzer_test("ssl_certificate_fuzzer") { "../../modules/rtp_rtcp", ] } + +webrtc_fuzzer_test("vp8_replay_fuzzer") { + sources = [ + "vp8_replay_fuzzer.cc", + ] + deps = [ + "../../rtc_base:rtc_base_approved", + "utils:rtp_replayer", + "//third_party/abseil-cpp/absl/memory", + ] + seed_corpus = "corpora/rtpdump-corpus/vp8" +} + +webrtc_fuzzer_test("vp9_replay_fuzzer") { + sources = [ + "vp9_replay_fuzzer.cc", + ] + deps = [ + "../../rtc_base:rtc_base_approved", + "utils:rtp_replayer", + "//third_party/abseil-cpp/absl/memory", + ] + seed_corpus = "corpora/rtpdump-corpus/vp9" +} diff --git a/test/fuzzers/corpora/rtpdump-corpus/vp8/vp8.rtpdump b/test/fuzzers/corpora/rtpdump-corpus/vp8/vp8.rtpdump new file mode 100644 index 0000000000..328559820d Binary files /dev/null and b/test/fuzzers/corpora/rtpdump-corpus/vp8/vp8.rtpdump differ diff --git a/test/fuzzers/corpora/rtpdump-corpus/vp9/vp9.rtpdump b/test/fuzzers/corpora/rtpdump-corpus/vp9/vp9.rtpdump new file mode 100644 index 0000000000..4dc780cd16 Binary files /dev/null and b/test/fuzzers/corpora/rtpdump-corpus/vp9/vp9.rtpdump differ diff --git a/test/fuzzers/utils/BUILD.gn b/test/fuzzers/utils/BUILD.gn new file mode 100644 index 0000000000..de0347d190 --- /dev/null +++ b/test/fuzzers/utils/BUILD.gn @@ -0,0 +1,40 @@ +# Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +import("../../../webrtc.gni") + +rtc_source_set("rtp_replayer") { + testonly = true + sources = [ + "rtp_replayer.cc", + "rtp_replayer.h", + ] + deps = [ + "../../../api/test/video:function_video_factory", + "../../../api/video_codecs:video_codecs_api", + "../../../call:call_interfaces", + "../../../common_video", + "../../../logging:rtc_event_log_api", + "../../../media:rtc_internal_video_codecs", + "../../../modules/rtp_rtcp", + "../../../rtc_base:checks", + "../../../rtc_base:rtc_base_approved", + "../../../rtc_base:rtc_json", + "../../../system_wrappers", + "../../../test:call_config_utils", + "../../../test:fake_video_codecs", + "../../../test:rtp_test_utils", + "../../../test:run_test", + "../../../test:run_test_interface", + "../../../test:test_common", + "../../../test:test_renderer", + "../../../test:test_support", + "../../../test:video_test_common", + "//third_party/abseil-cpp/absl/memory", + ] +} diff --git a/test/fuzzers/utils/rtp_replayer.cc b/test/fuzzers/utils/rtp_replayer.cc new file mode 100644 index 0000000000..4d53bd3f47 --- /dev/null +++ b/test/fuzzers/utils/rtp_replayer.cc @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "test/fuzzers/utils/rtp_replayer.h" + +#include +#include + +#include "absl/memory/memory.h" +#include "modules/rtp_rtcp/include/rtp_header_parser.h" +#include "rtc_base/strings/json.h" +#include "system_wrappers/include/clock.h" +#include "system_wrappers/include/sleep.h" +#include "test/call_config_utils.h" +#include "test/encoder_settings.h" +#include "test/fake_decoder.h" +#include "test/rtp_file_reader.h" + +namespace webrtc { +namespace test { + +void RtpReplayer::Replay(const std::string& replay_config_filepath, + const uint8_t* rtp_dump_data, + size_t rtp_dump_size) { + auto stream_state = absl::make_unique(); + std::vector receive_stream_configs = + ReadConfigFromFile(replay_config_filepath, &(stream_state->transport)); + return Replay(std::move(stream_state), std::move(receive_stream_configs), + rtp_dump_data, rtp_dump_size); +} + +void RtpReplayer::Replay( + std::unique_ptr stream_state, + std::vector receive_stream_configs, + const uint8_t* rtp_dump_data, + size_t rtp_dump_size) { + // Attempt to create an RtpReader from the input file. + auto rtp_reader = CreateRtpReader(rtp_dump_data, rtp_dump_size); + if (rtp_reader == nullptr) { + RTC_LOG(LS_ERROR) << "Failed to create the rtp_reader"; + return; + } + + // Setup the video streams based on the configuration. + webrtc::RtcEventLogNullImpl event_log; + Call::Config call_config(&event_log); + std::unique_ptr call(Call::Create(call_config)); + SetupVideoStreams(&receive_stream_configs, stream_state.get(), call.get()); + + // Start replaying the provided stream now that it has been configured. + for (const auto& receive_stream : stream_state->receive_streams) { + receive_stream->Start(); + } + + ReplayPackets(call.get(), rtp_reader.get()); + + for (const auto& receive_stream : stream_state->receive_streams) { + call->DestroyVideoReceiveStream(receive_stream); + } +} + +std::vector RtpReplayer::ReadConfigFromFile( + const std::string& replay_config, + Transport* transport) { + Json::Reader json_reader; + Json::Value json_configs; + if (!json_reader.parse(replay_config, json_configs)) { + RTC_LOG(LS_ERROR) + << "Error parsing JSON replay configuration for the fuzzer" + << json_reader.getFormatedErrorMessages(); + return {}; + } + + std::vector receive_stream_configs; + receive_stream_configs.reserve(json_configs.size()); + for (const auto& json : json_configs) { + receive_stream_configs.push_back( + ParseVideoReceiveStreamJsonConfig(transport, json)); + } + return receive_stream_configs; +} + +void RtpReplayer::SetupVideoStreams( + std::vector* receive_stream_configs, + StreamState* stream_state, + Call* call) { + stream_state->decoder_factory = absl::make_unique(); + for (auto& receive_config : *receive_stream_configs) { + // Attach the decoder for the corresponding payload type in the config. + for (auto& decoder : receive_config.decoders) { + decoder = test::CreateMatchingDecoder(decoder.payload_type, + decoder.video_format.name); + decoder.decoder_factory = stream_state->decoder_factory.get(); + } + + // Create the window to display the rendered video. + stream_state->sinks.emplace_back( + test::VideoRenderer::Create("Fuzzing WebRTC Video Config", 640, 480)); + // Create a receive stream for this config. + receive_config.renderer = stream_state->sinks.back().get(); + stream_state->receive_streams.emplace_back( + call->CreateVideoReceiveStream(std::move(receive_config))); + } +} + +std::unique_ptr RtpReplayer::CreateRtpReader( + const uint8_t* rtp_dump_data, + size_t rtp_dump_size) { + std::unique_ptr rtp_reader(test::RtpFileReader::Create( + test::RtpFileReader::kRtpDump, rtp_dump_data, rtp_dump_size, {})); + if (!rtp_reader) { + RTC_LOG(LS_ERROR) << "Unable to open input file with any supported format"; + return nullptr; + } + return rtp_reader; +} + +void RtpReplayer::ReplayPackets(Call* call, test::RtpFileReader* rtp_reader) { + int64_t replay_start_ms = -1; + int num_packets = 0; + std::map unknown_packets; + + while (true) { + int64_t now_ms = rtc::TimeMillis(); + if (replay_start_ms == -1) { + replay_start_ms = now_ms; + } + + test::RtpPacket packet; + if (!rtp_reader->NextPacket(&packet)) { + break; + } + + int64_t deliver_in_ms = replay_start_ms + packet.time_ms - now_ms; + if (deliver_in_ms > 0) { + SleepMs(deliver_in_ms); + } + + ++num_packets; + switch (call->Receiver()->DeliverPacket( + webrtc::MediaType::VIDEO, + rtc::CopyOnWriteBuffer(packet.data, packet.length), + /* packet_time_us */ -1)) { + case PacketReceiver::DELIVERY_OK: + break; + case PacketReceiver::DELIVERY_UNKNOWN_SSRC: { + RTPHeader header; + std::unique_ptr parser(RtpHeaderParser::Create()); + + parser->Parse(packet.data, packet.length, &header); + if (unknown_packets[header.ssrc] == 0) { + RTC_LOG(LS_ERROR) << "Unknown SSRC: " << header.ssrc; + } + ++unknown_packets[header.ssrc]; + break; + } + case PacketReceiver::DELIVERY_PACKET_ERROR: { + RTC_LOG(LS_ERROR) + << "Packet error, corrupt packets or incorrect setup?"; + RTPHeader header; + std::unique_ptr parser(RtpHeaderParser::Create()); + parser->Parse(packet.data, packet.length, &header); + RTC_LOG(LS_ERROR) << "Packet packet_length=" << packet.length + << " payload_type=" << header.payloadType + << " sequence_number=" << header.sequenceNumber + << " time_stamp=" << header.timestamp + << " ssrc=" << header.ssrc; + break; + } + } + } + RTC_LOG(LS_INFO) << "num_packets: " << num_packets; + + for (const auto& unknown_packet : unknown_packets) { + RTC_LOG(LS_ERROR) << "Packets for unknown ssrc " << unknown_packet.first + << ":" << unknown_packet.second; + } +} + +} // namespace test +} // namespace webrtc diff --git a/test/fuzzers/utils/rtp_replayer.h b/test/fuzzers/utils/rtp_replayer.h new file mode 100644 index 0000000000..b77ac231a5 --- /dev/null +++ b/test/fuzzers/utils/rtp_replayer.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef TEST_FUZZERS_UTILS_RTP_REPLAYER_H_ +#define TEST_FUZZERS_UTILS_RTP_REPLAYER_H_ + +#include + +#include +#include +#include +#include + +#include "api/test/video/function_video_decoder_factory.h" +#include "api/video_codecs/video_decoder.h" +#include "call/call.h" +#include "logging/rtc_event_log/rtc_event_log.h" +#include "media/engine/internal_decoder_factory.h" +#include "rtc_base/time_utils.h" +#include "test/null_transport.h" +#include "test/rtp_file_reader.h" +#include "test/test_video_capturer.h" +#include "test/video_renderer.h" + +namespace webrtc { +namespace test { + +// The RtpReplayer is a utility for fuzzing the RTP/RTCP receiver stack in +// WebRTC. It achieves this by accepting a set of Receiver configurations and +// an RtpDump (consisting of both RTP and RTCP packets). The |rtp_dump| is +// passed in as a buffer to allow simple mutation fuzzing directly on the dump. +class RtpReplayer final { + public: + // Holds all the important stream information required to emulate the WebRTC + // rtp receival code path. + struct StreamState { + test::NullTransport transport; + std::vector>> sinks; + std::vector receive_streams; + std::unique_ptr decoder_factory; + }; + + // Construct an RtpReplayer from a JSON replay configuration file. + static void Replay(const std::string& replay_config_filepath, + const uint8_t* rtp_dump_data, + size_t rtp_dump_size); + + // Construct an RtpReplayer from a set of VideoReceiveStream::Configs. Note + // the stream_state.transport must be set for each receiver stream. + static void Replay( + std::unique_ptr stream_state, + std::vector receive_stream_config, + const uint8_t* rtp_dump_data, + size_t rtp_dump_size); + + private: + // Reads the replay configuration from Json. + static std::vector ReadConfigFromFile( + const std::string& replay_config, + Transport* transport); + + // Configures the stream state based on the receiver configurations. + static void SetupVideoStreams( + std::vector* receive_stream_configs, + StreamState* stream_state, + Call* call); + + // Creates a new RtpReader which can read the RtpDump + static std::unique_ptr CreateRtpReader( + const uint8_t* rtp_dump_data, + size_t rtp_dump_size); + + // Replays each packet to from the RtpDump. + static void ReplayPackets(Call* call, test::RtpFileReader* rtp_reader); +}; // class RtpReplayer + +} // namespace test +} // namespace webrtc + +#endif // TEST_FUZZERS_UTILS_RTP_REPLAYER_H_ diff --git a/test/fuzzers/vp8_replay_fuzzer.cc b/test/fuzzers/vp8_replay_fuzzer.cc new file mode 100644 index 0000000000..ff073b1eca --- /dev/null +++ b/test/fuzzers/vp8_replay_fuzzer.cc @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "absl/memory/memory.h" +#include "test/fuzzers/utils/rtp_replayer.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + auto stream_state = absl::make_unique(); + VideoReceiveStream::Config vp8_config(&(stream_state->transport)); + + VideoReceiveStream::Decoder vp8_decoder; + vp8_decoder.video_format = SdpVideoFormat("VP8"); + vp8_decoder.payload_type = 125; + vp8_config.decoders.push_back(std::move(vp8_decoder)); + + vp8_config.rtp.local_ssrc = 7731; + vp8_config.rtp.remote_ssrc = 1337; + vp8_config.rtp.rtx_ssrc = 100; + vp8_config.rtp.transport_cc = true; + vp8_config.rtp.remb = true; + vp8_config.rtp.nack.rtp_history_ms = 1000; + + std::vector replay_configs; + replay_configs.push_back(std::move(vp8_config)); + + test::RtpReplayer::Replay(std::move(stream_state), std::move(replay_configs), + data, size); +} + +} // namespace webrtc diff --git a/test/fuzzers/vp9_replay_fuzzer.cc b/test/fuzzers/vp9_replay_fuzzer.cc new file mode 100644 index 0000000000..ff7c7237e6 --- /dev/null +++ b/test/fuzzers/vp9_replay_fuzzer.cc @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "absl/memory/memory.h" +#include "test/fuzzers/utils/rtp_replayer.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + auto stream_state = absl::make_unique(); + VideoReceiveStream::Config vp9_config(&(stream_state->transport)); + + VideoReceiveStream::Decoder vp9_decoder; + vp9_decoder.video_format = SdpVideoFormat("VP9"); + vp9_decoder.payload_type = 124; + vp9_config.decoders.push_back(std::move(vp9_decoder)); + + vp9_config.rtp.local_ssrc = 7731; + vp9_config.rtp.remote_ssrc = 1337; + vp9_config.rtp.rtx_ssrc = 100; + vp9_config.rtp.transport_cc = true; + vp9_config.rtp.remb = true; + vp9_config.rtp.nack.rtp_history_ms = 1000; + + std::vector replay_configs; + replay_configs.push_back(std::move(vp9_config)); + + test::RtpReplayer::Replay(std::move(stream_state), std::move(replay_configs), + data, size); +} + +} // namespace webrtc diff --git a/test/rtp_file_reader.cc b/test/rtp_file_reader.cc index 294338a043..40a5cff96e 100644 --- a/test/rtp_file_reader.cc +++ b/test/rtp_file_reader.cc @@ -63,30 +63,25 @@ bool ReadUint16(uint16_t* out, FILE* file) { class RtpFileReaderImpl : public RtpFileReader { public: - virtual bool Init(const std::string& filename, - const std::set& ssrc_filter) = 0; + virtual bool Init(FILE* file, const std::set& ssrc_filter) = 0; }; class InterleavedRtpFileReader : public RtpFileReaderImpl { public: ~InterleavedRtpFileReader() override { - if (file_ != NULL) { + if (file_ != nullptr) { fclose(file_); - file_ = NULL; + file_ = nullptr; } } - bool Init(const std::string& filename, - const std::set& ssrc_filter) override { - file_ = fopen(filename.c_str(), "rb"); - if (file_ == NULL) { - printf("ERROR: Can't open file: %s\n", filename.c_str()); - return false; - } + bool Init(FILE* file, const std::set& ssrc_filter) override { + file_ = file; return true; } + bool NextPacket(RtpPacket* packet) override { - assert(file_ != NULL); + assert(file_ != nullptr); packet->length = RtpPacket::kMaxPacketBufferSize; uint32_t len = 0; TRY(ReadUint32(&len, file_)); @@ -107,7 +102,7 @@ class InterleavedRtpFileReader : public RtpFileReaderImpl { } private: - FILE* file_ = NULL; + FILE* file_ = nullptr; int64_t time_ms_ = 0; }; @@ -115,24 +110,19 @@ class InterleavedRtpFileReader : public RtpFileReaderImpl { // http://www.cs.columbia.edu/irt/software/rtptools/ class RtpDumpReader : public RtpFileReaderImpl { public: - RtpDumpReader() : file_(NULL) {} + RtpDumpReader() : file_(nullptr) {} ~RtpDumpReader() override { - if (file_ != NULL) { + if (file_ != nullptr) { fclose(file_); - file_ = NULL; + file_ = nullptr; } } - bool Init(const std::string& filename, - const std::set& ssrc_filter) override { - file_ = fopen(filename.c_str(), "rb"); - if (file_ == NULL) { - printf("ERROR: Can't open file: %s\n", filename.c_str()); - return false; - } + bool Init(FILE* file, const std::set& ssrc_filter) override { + file_ = file; char firstline[kFirstLineLength + 1] = {0}; - if (fgets(firstline, kFirstLineLength, file_) == NULL) { + if (fgets(firstline, kFirstLineLength, file_) == nullptr) { RTC_LOG(LS_INFO) << "Can't read from file"; return false; } @@ -179,10 +169,11 @@ class RtpDumpReader : public RtpFileReaderImpl { // Use 'len' here because a 'plen' of 0 specifies rtcp. len -= kPacketHeaderSize; if (packet->length < len) { - FATAL() << "Packet is too large to fit: " << len << " bytes vs " - << packet->length - << " bytes allocated. Consider increasing the buffer " - "size"; + RTC_LOG(LS_ERROR) << "Packet is too large to fit: " << len << " bytes vs " + << packet->length + << " bytes allocated. Consider increasing the buffer " + "size"; + return false; } if (fread(rtp_data, 1, len, file_) != len) { return false; @@ -242,7 +233,7 @@ const uint32_t kPcapBOMNoSwapOrder = 0xa1b2c3d4UL; class PcapReader : public RtpFileReaderImpl { public: PcapReader() - : file_(NULL), + : file_(nullptr), swap_pcap_byte_order_(false), #ifdef WEBRTC_ARCH_BIG_ENDIAN swap_network_byte_order_(false), @@ -256,24 +247,18 @@ class PcapReader : public RtpFileReaderImpl { } ~PcapReader() override { - if (file_ != NULL) { + if (file_ != nullptr) { fclose(file_); - file_ = NULL; + file_ = nullptr; } } - bool Init(const std::string& filename, - const std::set& ssrc_filter) override { - return Initialize(filename, ssrc_filter) == kResultSuccess; + bool Init(FILE* file, const std::set& ssrc_filter) override { + return Initialize(file, ssrc_filter) == kResultSuccess; } - int Initialize(const std::string& filename, - const std::set& ssrc_filter) { - file_ = fopen(filename.c_str(), "rb"); - if (file_ == NULL) { - printf("ERROR: Can't open file: %s\n", filename.c_str()); - return kResultFail; - } + int Initialize(FILE* file, const std::set& ssrc_filter) { + file_ = file; if (ReadGlobalHeader() < 0) { return kResultFail; @@ -637,24 +622,59 @@ class PcapReader : public RtpFileReaderImpl { RTC_DISALLOW_COPY_AND_ASSIGN(PcapReader); }; -RtpFileReader* RtpFileReader::Create(FileFormat format, - const std::string& filename, - const std::set& ssrc_filter) { - RtpFileReaderImpl* reader = NULL; +RtpFileReaderImpl* CreateReaderForFormat(RtpFileReader::FileFormat format) { + RtpFileReaderImpl* reader = nullptr; switch (format) { - case kPcap: + case RtpFileReader::kPcap: reader = new PcapReader(); break; - case kRtpDump: + case RtpFileReader::kRtpDump: reader = new RtpDumpReader(); break; - case kLengthPacketInterleaved: + case RtpFileReader::kLengthPacketInterleaved: reader = new InterleavedRtpFileReader(); break; } - if (!reader->Init(filename, ssrc_filter)) { + return reader; +} + +RtpFileReader* RtpFileReader::Create(FileFormat format, + const uint8_t* data, + size_t size, + const std::set& ssrc_filter) { + std::unique_ptr reader(CreateReaderForFormat(format)); + + FILE* file = tmpfile(); + if (file == nullptr) { + printf("ERROR: Can't open file from memory buffer\n"); + return nullptr; + } + + if (fwrite(reinterpret_cast(data), sizeof(uint8_t), size, + file) != size) { + return nullptr; + } + rewind(file); + + if (!reader->Init(file, ssrc_filter)) { + return nullptr; + } + return reader.release(); +} + +RtpFileReader* RtpFileReader::Create(FileFormat format, + const std::string& filename, + const std::set& ssrc_filter) { + RtpFileReaderImpl* reader = CreateReaderForFormat(format); + FILE* file = fopen(filename.c_str(), "rb"); + if (file == nullptr) { + printf("ERROR: Can't open file: %s\n", filename.c_str()); + return nullptr; + } + + if (!reader->Init(file, ssrc_filter)) { delete reader; - return NULL; + return nullptr; } return reader; } diff --git a/test/rtp_file_reader.h b/test/rtp_file_reader.h index 805044f52f..1fa6f72f06 100644 --- a/test/rtp_file_reader.h +++ b/test/rtp_file_reader.h @@ -34,11 +34,14 @@ class RtpFileReader { enum FileFormat { kPcap, kRtpDump, kLengthPacketInterleaved }; virtual ~RtpFileReader() {} + static RtpFileReader* Create(FileFormat format, + const uint8_t* data, + size_t size, + const std::set& ssrc_filter); static RtpFileReader* Create(FileFormat format, const std::string& filename); static RtpFileReader* Create(FileFormat format, const std::string& filename, const std::set& ssrc_filter); - virtual bool NextPacket(RtpPacket* packet) = 0; }; } // namespace test