Add better checks for temporal/spatial bounds in EncoderBitrateAdjuster. Bug: webrtc:514671098 Change-Id: If3cfb55bdc03d57760a5d30fc63ca63ed321fad1 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/474681 Commit-Queue: Erik Språng <sprang@webrtc.org> Reviewed-by: Philip Eliasson <philipel@webrtc.org> Auto-Submit: Erik Språng <sprang@webrtc.org> Commit-Queue: Philip Eliasson <philipel@webrtc.org> Cr-Commit-Position: refs/heads/main@{#47775}
diff --git a/video/encoder_bitrate_adjuster.cc b/video/encoder_bitrate_adjuster.cc index 10c7334..d8aa226 100644 --- a/video/encoder_bitrate_adjuster.cc +++ b/video/encoder_bitrate_adjuster.cc
@@ -76,9 +76,16 @@ if (codec_settings.codecType == VideoCodecType::kVideoCodecAV1 && codec_settings.numberOfSimulcastStreams <= 1 && codec_settings.GetScalabilityMode().has_value()) { - for (int si = 0; si < ScalabilityModeToNumSpatialLayers( - *(codec_settings.GetScalabilityMode())); - ++si) { + const int num_spatial_layers = ScalabilityModeToNumSpatialLayers( + *(codec_settings.GetScalabilityMode())); + for (int si = 0; si < num_spatial_layers; ++si) { + if (si >= static_cast<int>(kMaxSpatialLayers)) { + RTC_LOG(LS_WARNING) + << "AV1 scalability mode specifies " << num_spatial_layers + << " spatial layers, which exceeds kMaxSpatialLayers (" + << kMaxSpatialLayers << ")"; + break; + } if (codec_settings.spatialLayers[si].active) { min_bitrates_bps_[si] = std::max(codec_settings.minBitrate * 1000, @@ -88,6 +95,13 @@ } else if (codec_settings.codecType == VideoCodecType::kVideoCodecVP9 && codec_settings.numberOfSimulcastStreams <= 1) { for (size_t si = 0; si < codec_settings.VP9().numberOfSpatialLayers; ++si) { + if (si >= kMaxSpatialLayers) { + RTC_LOG(LS_WARNING) + << "VP9 specifies " << codec_settings.VP9().numberOfSpatialLayers + << " spatial layers, which exceeds kMaxSpatialLayers (" + << kMaxSpatialLayers << ")"; + break; + } if (codec_settings.spatialLayers[si].active) { min_bitrates_bps_[si] = std::max(codec_settings.minBitrate * 1000, @@ -96,6 +110,13 @@ } } else { for (size_t si = 0; si < codec_settings.numberOfSimulcastStreams; ++si) { + if (si >= kMaxSpatialLayers) { + RTC_LOG(LS_WARNING) + << "Codec specifies " << codec_settings.numberOfSimulcastStreams + << " simulcast streams, which exceeds kMaxSpatialLayers (" + << kMaxSpatialLayers << ")"; + break; + } if (codec_settings.simulcastStream[si].active) { min_bitrates_bps_[si] = std::max(codec_settings.minBitrate * 1000, @@ -372,6 +393,12 @@ // Copy allocation into current state and re-allocate. for (size_t si = 0; si < kMaxSpatialLayers; ++si) { current_fps_allocation_[si] = encoder_info.fps_allocation[si]; + if (current_fps_allocation_[si].size() > kMaxTemporalStreams) { + RTC_LOG(LS_WARNING) << "fps_allocation has more than " + << kMaxTemporalStreams + << " temporal streams. Truncating."; + current_fps_allocation_[si].resize(kMaxTemporalStreams); + } } // Trigger re-allocation so that overshoot detectors have correct targets. @@ -381,6 +408,15 @@ void EncoderBitrateAdjuster::OnEncodedFrame(DataSize size, int stream_index, int temporal_index) { + if (stream_index < 0 || stream_index >= static_cast<int>(kMaxSpatialLayers) || + temporal_index < 0 || + temporal_index >= static_cast<int>(kMaxTemporalStreams)) { + RTC_LOG(LS_WARNING) << "OnEncodedFrame called with invalid layer: " + << "stream_index = " << stream_index + << ", temporal_index = " << temporal_index; + return; + } + ++frames_since_layout_change_; // Detectors may not exist, for instance if ScreenshareLayers is used. auto& detector = overshoot_detectors_[stream_index][temporal_index];
diff --git a/video/encoder_bitrate_adjuster_unittest.cc b/video/encoder_bitrate_adjuster_unittest.cc index 7514405..14bb11e 100644 --- a/video/encoder_bitrate_adjuster_unittest.cc +++ b/video/encoder_bitrate_adjuster_unittest.cc
@@ -581,6 +581,40 @@ ExpectNear(expected_input_allocation, current_adjusted_allocation_, 0.01); } +TEST_P(EncoderBitrateAdjusterTest, OnEncodedFrameInvalidLayers) { + current_input_allocation_.SetBitrate(0, 0, 300000); + target_framerate_fps_ = 30; + SetUpAdjuster(1, 1, false); + + // Call OnEncodedFrame with invalid stream indices and make sure it doesn't + // crash. + adjuster_->OnEncodedFrame(DataSize::Bytes(1000), -1, 0); + adjuster_->OnEncodedFrame(DataSize::Bytes(1000), kMaxSpatialLayers, 0); + + // Call OnEncodedFrame with invalid temporal indices and make sure it doesn't + // crash. + adjuster_->OnEncodedFrame(DataSize::Bytes(1000), 0, -1); + adjuster_->OnEncodedFrame(DataSize::Bytes(1000), 0, kMaxTemporalStreams); +} + +TEST_P(EncoderBitrateAdjusterTest, + OnEncoderInfoTruncatesTooManyTemporalStreams) { + current_input_allocation_.SetBitrate(0, 0, 300000); + target_framerate_fps_ = 30; + SetUpAdjuster(1, 1, false); + + // Create an EncoderInfo with a temporal allocation larger than + // kMaxTemporalStreams. + VideoEncoder::EncoderInfo encoder_info; + encoder_info.fps_allocation[0].resize(kMaxTemporalStreams + 2); + for (size_t ti = 0; ti < kMaxTemporalStreams + 2; ++ti) { + encoder_info.fps_allocation[0][ti] = 255; + } + + // This should truncate to kMaxTemporalStreams and not crash. + adjuster_->OnEncoderInfo(encoder_info); +} + INSTANTIATE_TEST_SUITE_P( AdjustWithHeadroomVariations, EncoderBitrateAdjusterTest,