1 /*
2 * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include <algorithm>
12 #include <array>
13 #include <cmath>
14 #include <limits>
15
16 #include "modules/audio_processing/include/audio_processing.h"
17 #include "modules/audio_processing/test/audio_processing_builder_for_testing.h"
18 #include "rtc_base/checks.h"
19 #include "test/fuzzers/fuzz_data_helper.h"
20
21 namespace webrtc {
22 namespace {
23 constexpr int kMaxNumChannels = 2;
24 // APM supported max rate is 384000 Hz, using a limit slightly above lets the
25 // fuzzer exercise the handling of too high rates.
26 constexpr int kMaxSampleRateHz = 400000;
27 constexpr int kMaxSamplesPerChannel = kMaxSampleRateHz / 100;
28
GenerateFloatFrame(test::FuzzDataHelper & fuzz_data,int input_rate,int num_channels,float * const * float_frames)29 void GenerateFloatFrame(test::FuzzDataHelper& fuzz_data,
30 int input_rate,
31 int num_channels,
32 float* const* float_frames) {
33 const int samples_per_input_channel =
34 AudioProcessing::GetFrameSize(input_rate);
35 RTC_DCHECK_LE(samples_per_input_channel, kMaxSamplesPerChannel);
36 for (int i = 0; i < num_channels; ++i) {
37 float channel_value;
38 fuzz_data.CopyTo<float>(&channel_value);
39 std::fill(float_frames[i], float_frames[i] + samples_per_input_channel,
40 channel_value);
41 }
42 }
43
GenerateFixedFrame(test::FuzzDataHelper & fuzz_data,int input_rate,int num_channels,int16_t * fixed_frames)44 void GenerateFixedFrame(test::FuzzDataHelper& fuzz_data,
45 int input_rate,
46 int num_channels,
47 int16_t* fixed_frames) {
48 const int samples_per_input_channel =
49 AudioProcessing::GetFrameSize(input_rate);
50 RTC_DCHECK_LE(samples_per_input_channel, kMaxSamplesPerChannel);
51 // Write interleaved samples.
52 for (int ch = 0; ch < num_channels; ++ch) {
53 const int16_t channel_value = fuzz_data.ReadOrDefaultValue<int16_t>(0);
54 for (int i = ch; i < samples_per_input_channel * num_channels;
55 i += num_channels) {
56 fixed_frames[i] = channel_value;
57 }
58 }
59 }
60
61 // No-op processor used to influence APM input/output pipeline decisions based
62 // on what submodules are present.
63 class NoopCustomProcessing : public CustomProcessing {
64 public:
NoopCustomProcessing()65 NoopCustomProcessing() {}
~NoopCustomProcessing()66 ~NoopCustomProcessing() override {}
Initialize(int sample_rate_hz,int num_channels)67 void Initialize(int sample_rate_hz, int num_channels) override {}
Process(AudioBuffer * audio)68 void Process(AudioBuffer* audio) override {}
ToString() const69 std::string ToString() const override { return ""; }
SetRuntimeSetting(AudioProcessing::RuntimeSetting setting)70 void SetRuntimeSetting(AudioProcessing::RuntimeSetting setting) override {}
71 };
72 } // namespace
73
74 // This fuzzer is directed at fuzzing unexpected input and output sample rates
75 // of APM. For example, the sample rate 22050 Hz is processed by APM in frames
76 // of floor(22050/100) = 220 samples. This is not exactly 10 ms of audio
77 // content, and may break assumptions commonly made on the APM frame size.
FuzzOneInput(const uint8_t * data,size_t size)78 void FuzzOneInput(const uint8_t* data, size_t size) {
79 if (size > 100) {
80 return;
81 }
82 test::FuzzDataHelper fuzz_data(rtc::ArrayView<const uint8_t>(data, size));
83
84 std::unique_ptr<CustomProcessing> capture_processor =
85 fuzz_data.ReadOrDefaultValue(true)
86 ? std::make_unique<NoopCustomProcessing>()
87 : nullptr;
88 std::unique_ptr<CustomProcessing> render_processor =
89 fuzz_data.ReadOrDefaultValue(true)
90 ? std::make_unique<NoopCustomProcessing>()
91 : nullptr;
92 rtc::scoped_refptr<AudioProcessing> apm =
93 AudioProcessingBuilderForTesting()
94 .SetConfig({.pipeline = {.multi_channel_render = true,
95 .multi_channel_capture = true}})
96 .SetCapturePostProcessing(std::move(capture_processor))
97 .SetRenderPreProcessing(std::move(render_processor))
98 .Create();
99 RTC_DCHECK(apm);
100
101 std::array<int16_t, kMaxSamplesPerChannel * kMaxNumChannels> fixed_frame;
102 std::array<std::array<float, kMaxSamplesPerChannel>, kMaxNumChannels>
103 float_frames;
104 std::array<float*, kMaxNumChannels> float_frame_ptrs;
105 for (int i = 0; i < kMaxNumChannels; ++i) {
106 float_frame_ptrs[i] = float_frames[i].data();
107 }
108 float* const* ptr_to_float_frames = &float_frame_ptrs[0];
109
110 // Choose whether to fuzz the float or int16_t interfaces of APM.
111 const bool is_float = fuzz_data.ReadOrDefaultValue(true);
112
113 // We may run out of fuzz data in the middle of a loop iteration. In
114 // that case, default values will be used for the rest of that
115 // iteration.
116 while (fuzz_data.CanReadBytes(1)) {
117 // Decide input/output rate for this iteration.
118 const int input_rate = static_cast<int>(
119 fuzz_data.ReadOrDefaultValue<size_t>(8000) % kMaxSampleRateHz);
120 const int output_rate = static_cast<int>(
121 fuzz_data.ReadOrDefaultValue<size_t>(8000) % kMaxSampleRateHz);
122 const int num_channels = fuzz_data.ReadOrDefaultValue(true) ? 2 : 1;
123
124 // Since render and capture calls have slightly different reinitialization
125 // procedures, we let the fuzzer choose the order.
126 const bool is_capture = fuzz_data.ReadOrDefaultValue(true);
127
128 int apm_return_code = AudioProcessing::Error::kNoError;
129 if (is_float) {
130 GenerateFloatFrame(fuzz_data, input_rate, num_channels,
131 ptr_to_float_frames);
132
133 if (is_capture) {
134 apm_return_code = apm->ProcessStream(
135 ptr_to_float_frames, StreamConfig(input_rate, num_channels),
136 StreamConfig(output_rate, num_channels), ptr_to_float_frames);
137 } else {
138 apm_return_code = apm->ProcessReverseStream(
139 ptr_to_float_frames, StreamConfig(input_rate, num_channels),
140 StreamConfig(output_rate, num_channels), ptr_to_float_frames);
141 }
142 } else {
143 GenerateFixedFrame(fuzz_data, input_rate, num_channels,
144 fixed_frame.data());
145
146 if (is_capture) {
147 apm_return_code = apm->ProcessStream(
148 fixed_frame.data(), StreamConfig(input_rate, num_channels),
149 StreamConfig(output_rate, num_channels), fixed_frame.data());
150 } else {
151 apm_return_code = apm->ProcessReverseStream(
152 fixed_frame.data(), StreamConfig(input_rate, num_channels),
153 StreamConfig(output_rate, num_channels), fixed_frame.data());
154 }
155 }
156 // APM may flag an error on unsupported audio formats, but should not crash.
157 RTC_DCHECK(apm_return_code == AudioProcessing::kNoError ||
158 apm_return_code == AudioProcessing::kBadSampleRateError);
159 }
160 }
161
162 } // namespace webrtc
163