[M148] Fix TOCTOU race in RtpVideoSender::OnVideoLayersAllocationUpdated RtpVideoSender::OnVideoLayersAllocationUpdated runs on the encoder thread, checks if it's active, and then posts a task to the transport queue to update module status. If SetSending(false) is called on the transport queue before the posted task runs, the task would previously re-register modules in PacketRouter even though active_ was false. This lead to dangling pointers in PacketRouter when the RtpVideoSender was destroyed. This CL adds an IsActive() check inside the posted task to ensure we don't re-register modules if the sender has been deactivated. (cherry picked from commit 77d265670fa7d7f358c86c54704a6e97c27a6005) Bug: chromium:500767595 Test: RtpVideoSenderTest.PostTaskRaceDoesNotLeadToDanglingPointer Change-Id: I18827c34bece7f5ebc41d159d9e19e356afbc404 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/462880 Commit-Queue: Harald Alvestrand <hta@webrtc.org> Reviewed-by: Erik Språng <sprang@webrtc.org> Cr-Original-Commit-Position: refs/heads/main@{#47384} Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/465961 Cr-Commit-Position: refs/branch-heads/7778@{#5} Cr-Branched-From: ca896b7ffef011bbf6957c99d413c5aac602c99f-refs/heads/main@{#47319}
diff --git a/call/BUILD.gn b/call/BUILD.gn index 815d28a..a784332 100644 --- a/call/BUILD.gn +++ b/call/BUILD.gn
@@ -550,6 +550,7 @@ "../api/video:video_codec_constants", "../api/video:video_frame", "../api/video:video_frame_type", + "../api/video:video_layers_allocation", "../api/video:video_rtp_headers", "../api/video_codecs:video_codecs_api", "../audio",
diff --git a/call/rtp_video_sender.cc b/call/rtp_video_sender.cc index 73e8898..7267add 100644 --- a/call/rtp_video_sender.cc +++ b/call/rtp_video_sender.cc
@@ -695,6 +695,11 @@ transport_queue_.PostTask( SafeTask(safety_.flag(), [this, sending = std::move(sending)] { RTC_DCHECK_RUN_ON(&transport_checker_); + // It's possible for another task to be scheduled on the transport + // checker ahead of this call that makes the sender not active. + if (!IsActive()) { + return; + } RTC_CHECK_EQ(sending.size(), rtp_streams_.size()); for (size_t i = 0; i < sending.size(); ++i) { SetModuleIsActive(sending[i], *rtp_streams_[i].rtp_rtcp);
diff --git a/call/rtp_video_sender_unittest.cc b/call/rtp_video_sender_unittest.cc index a979e05..d7d30df 100644 --- a/call/rtp_video_sender_unittest.cc +++ b/call/rtp_video_sender_unittest.cc
@@ -40,6 +40,7 @@ #include "api/video/encoded_image.h" #include "api/video/video_codec_type.h" #include "api/video/video_frame_type.h" +#include "api/video/video_layers_allocation.h" #include "api/video_codecs/video_encoder.h" #include "call/rtp_config.h" #include "call/rtp_transport_config.h" @@ -1607,4 +1608,73 @@ EXPECT_EQ(retransmitted_rtp_sequence_numbers, base_rtp_sequence_numbers); } +TEST(RtpVideoSenderTest, PostTaskRaceDoesNotLeadToDanglingPointer) { + NiceMock<MockTransport> transport; + NiceMock<MockRtcpIntraFrameObserver> encoder_feedback; + + GlobalSimulatedTimeController time_controller(Timestamp::Millis(1000000)); + Environment env = CreateEnvironment(time_controller.GetClock(), + time_controller.CreateTaskQueueFactory()); + + VideoSendStream::Config config(&transport); + config.rtp.ssrcs = {kSsrc1}; + + SendStatisticsProxy stats_proxy( + time_controller.GetClock(), config, + VideoEncoderConfig::ContentType::kRealtimeVideo, env.field_trials()); + + BitrateConstraints bitrate_config = GetBitrateConfig(); + RtpTransportConfig transport_config{.env = env, + .bitrate_config = bitrate_config}; + RtpTransportControllerSend transport_controller(transport_config); + transport_controller.EnsureStarted(); + + RateLimiter retransmission_rate_limiter(time_controller.GetClock(), + kRetransmitWindowSizeMs); + + std::map<uint32_t, RtpState> suspended_ssrcs; + std::map<uint32_t, RtpPayloadState> suspended_payload_states; + + auto router = std::make_unique<RtpVideoSender>( + env, time_controller.GetMainThread(), suspended_ssrcs, + suspended_payload_states, config.rtp, config.rtcp_report_interval_ms, + &transport, + CreateObservers(&encoder_feedback, &stats_proxy, &stats_proxy, + &stats_proxy, nullptr, &stats_proxy), + &transport_controller, &retransmission_rate_limiter, + std::make_unique<FecControllerDefault>(env), nullptr, CryptoOptions{}, + nullptr); + + router->SetSending(true); + // Verify it's registered + EXPECT_TRUE( + transport_controller.packet_router()->SsrcOfFirstSender().has_value()); + + // Trigger race: + // 1. OnVideoLayersAllocationUpdated posts a task to re-register modules + // because it sees active_ is true. + VideoLayersAllocation allocation; + allocation.active_spatial_layers.push_back({.rtp_stream_index = 0}); + router->OnVideoLayersAllocationUpdated(allocation); + + // 2. Immediately set active_ to false. This happens BEFORE the posted task + // runs. + router->SetSending(false); + + // 3. Let the posted task run. With the fix, it should return early and not + // re-register the module because active_ is now false. + time_controller.AdvanceTime(TimeDelta::Zero()); + + // 4. Verify that PacketRouter does NOT have a sender. + EXPECT_FALSE( + transport_controller.packet_router()->SsrcOfFirstSender().has_value()); + + // 5. Destroy the router. + router.reset(); + + // 6. Verify that PacketRouter still does not have a sender. + EXPECT_FALSE( + transport_controller.packet_router()->SsrcOfFirstSender().has_value()); +} + } // namespace webrtc