diff --git a/remoting/protocol/webrtc_event_log_data_unittest.cc b/remoting/protocol/webrtc_event_log_data_unittest.cc index be43155fe4769a..1098d04e345b3d 100644 --- a/remoting/protocol/webrtc_event_log_data_unittest.cc +++ b/remoting/protocol/webrtc_event_log_data_unittest.cc @@ -4,6 +4,10 @@ #include "remoting/protocol/webrtc_event_log_data.h" +#include +#include + +#include "base/threading/platform_thread.h" #include "testing/gtest/include/gtest/gtest.h" namespace remoting::protocol { @@ -67,4 +71,84 @@ TEST(WebrtcEventLogDataTest, StoreThenClear_IsEmpty) { EXPECT_TRUE(data.empty()); } +// POC: Demonstrates the cross-thread data race between Write() and +// TakeLogData(). In production, Write() is invoked on a base::ThreadPool +// worker (the WebRTC "rtc_event_log" task queue), while TakeLogData() is +// invoked on the CRD network thread when the client opens an +// "rtc-log-transfer-*" data channel and sends a RequestTransfer message. +// There is no synchronization on |sections_|, so the move-construction in +// TakeLogData() races with emplace_back()/pop_front()/back().insert() in +// Write(), producing heap-use-after-free / heap corruption in the +// circular_deque's VectorBuffer or the per-section std::vector backing store. +TEST(WebrtcEventLogDataTest, ConcurrentWriteAndTake_DataRace) { + WebrtcEventLogData event_log; + // Small section size + small section count so that Write() frequently + // touches deque structure (pop_front + emplace_back + reserve), widening + // the race window against TakeLogData()'s std::move(sections_) + Clear(). + event_log.SetMaxSectionSizeForTest(16); + event_log.SetMaxSectionsForTest(4); + + std::atomic stop{false}; + + // Writer thread: simulates RtcEventLogImpl::WriteToOutput() running on the + // "rtc_event_log" base::ThreadPool task queue. + std::thread writer([&]() { + const std::string payload(8, 'A'); + while (!stop.load(std::memory_order_relaxed)) { + event_log.Write(payload); + } + }); + + // Reader: simulates RtcLogFileReader::DoOpen() on the CRD network thread, + // invoked once per "rtc-log-transfer-N" data channel the client opens. The + // attacker can open arbitrarily many such channels. + for (int i = 0; i < 200000; ++i) { + auto data = event_log.TakeLogData(); + // Touch every byte of the taken data, just like RtcLogFileReader does + // when it iterates sections and copies them into the response chunk. + // If the writer has corrupted a moved-from std::vector, ASAN will fire + // here (or earlier, inside circular_deque internals). + size_t total = 0; + for (const auto& section : data) { + for (auto b : section) { + total += b; + } + } + (void)total; + } + + stop.store(true, std::memory_order_relaxed); + writer.join(); +} + +// Same race as above, but with production-default kMaxSections=1000 and +// kMaxSectionSize=102400, to confirm the data race is not an artifact of the +// reduced test limits. +TEST(WebrtcEventLogDataTest, ConcurrentWriteAndTake_DataRace_DefaultLimits) { + WebrtcEventLogData event_log; + + std::atomic stop{false}; + + std::thread writer([&]() { + const std::string payload(64, 'A'); + while (!stop.load(std::memory_order_relaxed)) { + event_log.Write(payload); + } + }); + + for (int i = 0; i < 200000; ++i) { + auto data = event_log.TakeLogData(); + size_t total = 0; + for (const auto& section : data) { + for (auto b : section) { + total += b; + } + } + (void)total; + } + + stop.store(true, std::memory_order_relaxed); + writer.join(); +} + } // namespace remoting::protocol