Generalize FuzzDataHelper::Read function To make it easier to use in fuzzers: - change it not to crash when there is not enough fuzz data left. - change it to read any trivial object, not just integers Bug: None Change-Id: I5dfb47d3accc3c856dfc5d4376676354953d3f27 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/411660 Commit-Queue: Danil Chapovalov <danilchap@webrtc.org> Reviewed-by: Sam Zackrisson <saza@webrtc.org> Cr-Commit-Position: refs/heads/main@{#47035}
diff --git a/test/BUILD.gn b/test/BUILD.gn index cf34935..96da3e9 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn
@@ -782,6 +782,7 @@ "../rtc_base/synchronization:mutex", "../rtc_base/system:file_wrapper", "../system_wrappers", + "fuzzers:fuzz_data_helper_unittest", "jitter:jitter_unittests", "pc/e2e:e2e_unittests", "pc/e2e/analyzer/video:video_analyzer_unittests",
diff --git a/test/fuzzers/BUILD.gn b/test/fuzzers/BUILD.gn index f44ec6a..2a78afa 100644 --- a/test/fuzzers/BUILD.gn +++ b/test/fuzzers/BUILD.gn
@@ -33,11 +33,7 @@ rtc_library("fuzz_data_helper") { testonly = true sources = [ "fuzz_data_helper.h" ] - deps = [ - "../../modules/rtp_rtcp:rtp_rtcp_format", - "../../rtc_base:checks", - "//third_party/abseil-cpp/absl/strings:string_view", - ] + deps = [ "//third_party/abseil-cpp/absl/strings:string_view" ] # Only fuzzer targets can depend on this. visibility = [ @@ -46,6 +42,15 @@ ] } +rtc_library("fuzz_data_helper_unittest") { + testonly = true + sources = [ "fuzz_data_helper_unittest.cc" ] + deps = [ + ":fuzz_data_helper", + "..:test_support", + ] +} + set_defaults("webrtc_fuzzer_test") { configs = rtc_add_configs }
diff --git a/test/fuzzers/audio_processing_sample_rate_fuzzer.cc b/test/fuzzers/audio_processing_sample_rate_fuzzer.cc index 8ce3d05..3f7bec7 100644 --- a/test/fuzzers/audio_processing_sample_rate_fuzzer.cc +++ b/test/fuzzers/audio_processing_sample_rate_fuzzer.cc
@@ -39,8 +39,7 @@ AudioProcessing::GetFrameSize(input_rate); RTC_DCHECK_LE(samples_per_input_channel, kMaxSamplesPerChannel); for (int i = 0; i < num_channels; ++i) { - float channel_value; - fuzz_data.CopyTo<float>(&channel_value); + float channel_value = fuzz_data.Read<float>(); std::fill(float_frames[i], float_frames[i] + samples_per_input_channel, channel_value); }
diff --git a/test/fuzzers/fuzz_data_helper.h b/test/fuzzers/fuzz_data_helper.h index 0e64869..d33f233 100644 --- a/test/fuzzers/fuzz_data_helper.h +++ b/test/fuzzers/fuzz_data_helper.h
@@ -19,8 +19,6 @@ #include <type_traits> #include "absl/strings/string_view.h" -#include "modules/rtp_rtcp/source/byte_io.h" -#include "rtc_base/checks.h" namespace webrtc { @@ -35,16 +33,13 @@ // Reads and returns data of type T. template <typename T> - T Read() { - RTC_CHECK(CanReadBytes(sizeof(T))); - T x = ByteReader<T>::ReadLittleEndian(&data_[data_ix_]); - data_ix_ += sizeof(T); - return x; - } + requires(std::is_trivial_v<T>) + T Read(); // Reads and returns data of type T. Returns default_value if not enough // fuzzer input remains to read a T. template <typename T> + requires(std::is_trivial_v<T>) T ReadOrDefaultValue(T default_value) { if (!CanReadBytes(sizeof(T))) { return default_value; @@ -54,8 +49,8 @@ // Like ReadOrDefaultValue, but replaces the value 0 with default_value. template <typename T> + requires(std::is_integral_v<T>) T ReadOrDefaultValueNotZero(T default_value) { - static_assert(std::is_integral<T>::value, ""); T x = ReadOrDefaultValue(default_value); return x == 0 ? default_value : x; } @@ -110,11 +105,15 @@ // If sizeof(T) > BytesLeft then the remaining bytes will be used and the rest // of the object will be zero initialized. template <typename T> - void CopyTo(T* object) { - memset(object, 0, sizeof(T)); + requires(std::is_trivial_v<T>) + void CopyTo(T& object) { + std::span<uint8_t, sizeof(T)> object_memory( + reinterpret_cast<uint8_t*>(&object), sizeof(T)); size_t bytes_to_copy = std::min(BytesLeft(), sizeof(T)); - memcpy(object, data_.data() + data_ix_, bytes_to_copy); + std::ranges::copy(data_.subspan(data_ix_, bytes_to_copy), + object_memory.begin()); + std::ranges::fill(object_memory.subspan(bytes_to_copy), uint8_t{0}); data_ix_ += bytes_to_copy; } @@ -129,6 +128,28 @@ size_t data_ix_ = 0; }; +template <typename T> + requires(std::is_trivial_v<T>) +T FuzzDataHelper::Read() { + if constexpr (sizeof(T) == 1) { + if (BytesLeft() == 0) { + return {}; + } else { + return static_cast<T>(data_[data_ix_++]); + } + } + + T value; + CopyTo(value); + return value; +} + +template <> +inline bool FuzzDataHelper::Read<bool>() { + // Return `true' or 'false' with 50% chance each. + return (Read<uint8_t>() & 0b1) != 0; +} + } // namespace webrtc #endif // TEST_FUZZERS_FUZZ_DATA_HELPER_H_
diff --git a/test/fuzzers/fuzz_data_helper_unittest.cc b/test/fuzzers/fuzz_data_helper_unittest.cc new file mode 100644 index 0000000..b384b6c --- /dev/null +++ b/test/fuzzers/fuzz_data_helper_unittest.cc
@@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026 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/fuzz_data_helper.h" + +#include <cstdint> + +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc::test { +namespace { + +using ::testing::Each; +using ::testing::ElementsAre; + +TEST(FuzzDataHelperTest, ReadsIntegers) { + const uint8_t kData[] = {0xFE, 0x01, 0x02, 0x03, 0x04, 0x05}; + FuzzDataHelper fuzz_data(kData); + ASSERT_EQ(fuzz_data.BytesLeft(), 6u); + + EXPECT_EQ(fuzz_data.Read<int8_t>(), -2); + EXPECT_EQ(fuzz_data.BytesLeft(), 5u); + + EXPECT_EQ(fuzz_data.Read<uint8_t>(), 0x01); + EXPECT_EQ(fuzz_data.BytesLeft(), 4u); + + EXPECT_EQ(fuzz_data.Read<uint16_t>(), 0x0302); + EXPECT_EQ(fuzz_data.BytesLeft(), 2u); + + // Read 4 bytes while only two left in `kData` - expect that succeed and + // returns predictable results (fills missing data with zeros). + EXPECT_EQ(fuzz_data.Read<uint32_t>(), 0x0000'0504u); + EXPECT_EQ(fuzz_data.BytesLeft(), 0u); +} + +struct PlainOldData { + uint32_t value; + uint8_t array[3]; +}; + +TEST(FuzzDataHelperTest, ReadsAnyType) { + const uint8_t kData[] = {0x01, 0x01, 0x02, 0x03, 0x04, 0x05}; + FuzzDataHelper fuzz_data(kData); + + PlainOldData pod = fuzz_data.Read<PlainOldData>(); + EXPECT_EQ(pod.value, 0x03020101u); + EXPECT_THAT(pod.array, ElementsAre(0x04, 0x05, 0)); +} + +TEST(FuzzDataHelperTest, ReadsZerosWhenNoDataLeft) { + const uint8_t kData[] = {0xFE}; + FuzzDataHelper fuzz_data(kData); + ASSERT_EQ(fuzz_data.BytesLeft(), 1u); + fuzz_data.Read<uint8_t>(); + + ASSERT_EQ(fuzz_data.BytesLeft(), 0u); + + EXPECT_EQ(fuzz_data.Read<int8_t>(), 0); + EXPECT_EQ(fuzz_data.Read<uint8_t>(), 0u); + EXPECT_EQ(fuzz_data.Read<uint16_t>(), 0u); + PlainOldData pod = fuzz_data.Read<PlainOldData>(); + EXPECT_EQ(pod.value, 0u); + EXPECT_THAT(pod.array, Each(0u)); +} + +} // namespace +} // namespace webrtc::test
diff --git a/test/fuzzers/residual_echo_detector_fuzzer.cc b/test/fuzzers/residual_echo_detector_fuzzer.cc index b191a64..43845bc 100644 --- a/test/fuzzers/residual_echo_detector_fuzzer.cc +++ b/test/fuzzers/residual_echo_detector_fuzzer.cc
@@ -46,7 +46,7 @@ echo_detector->AnalyzeCaptureAudio(input); for (size_t i = 0; i < 2 * kNrOfUpdates; ++i) { // Convert 4 input bytes to a float. - fuzz_data.CopyTo(&input[0]); + fuzz_data.CopyTo(input[0]); if (!isfinite(input[0]) || fabs(input[0]) > maxFuzzedValue) { // Ignore infinity, nan values and values that are unrealistically large. continue;
diff --git a/test/fuzzers/rtp_frame_reference_finder_fuzzer.cc b/test/fuzzers/rtp_frame_reference_finder_fuzzer.cc index 4e1da60..bbfc288 100644 --- a/test/fuzzers/rtp_frame_reference_finder_fuzzer.cc +++ b/test/fuzzers/rtp_frame_reference_finder_fuzzer.cc
@@ -38,19 +38,14 @@ DataReader(FuzzDataHelper fuzz_data) : data_(fuzz_data) {} template <typename T> - void CopyTo(T* object) { + void CopyTo(T& object) { return data_.CopyTo(object); } template <typename T> T GetNum() { - T t = {}; - if (data_.BytesLeft() >= sizeof(T)) { - CopyTo(&t); - } - return t; + return data_.Read<T>(); } - bool MoreToRead() { return data_.BytesLeft() > 0; } private: @@ -63,7 +58,7 @@ result.packetization_type = reader->GetNum<H264PacketizationTypes>(); int nalus_length = reader->GetNum<uint8_t>(); for (int i = 0; i < nalus_length; ++i) { - reader->CopyTo(&result.nalus.emplace_back()); + reader->CopyTo(result.nalus.emplace_back()); } result.packetization_mode = reader->GetNum<H264PacketizationMode>(); return result; @@ -123,11 +118,11 @@ switch (codec) { case kVideoCodecVP8: reader.CopyTo( - &video_header.video_type_header.emplace<RTPVideoHeaderVP8>()); + video_header.video_type_header.emplace<RTPVideoHeaderVP8>()); break; case kVideoCodecVP9: reader.CopyTo( - &video_header.video_type_header.emplace<RTPVideoHeaderVP9>()); + video_header.video_type_header.emplace<RTPVideoHeaderVP9>()); break; case kVideoCodecH264: video_header.video_type_header = GenerateRTPVideoHeaderH264(&reader);