Low chrome Input Validation 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in WebAudio
DescriptionImproper input validation in WebAudio
ComponentWebAudio
Bug ClassInput Validation
Tracker519211890
Fix commit08625f7ba49b (chromium/src) +117/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
TEST_F
content/browser/media/webaudio/audio_context_manager_impl_unittest.cc
modified

Files Changed

  • content/browser/media/webaudio/audio_context_manager_impl_unittest.cc
  • content/browser/media/webaudio/audio_context_manager_impl.cc
From 08625f7ba49b76f2d093197c9077145514686342 Mon Sep 17 00:00:00 2001
From: Michael Wilson <mjwilson@chromium.org>
Date: Wed, 09 Sep 2026 14:03:40 -0700
Subject: [PATCH] Validate audio stream, context ID in AudioContextAudiblePlaybackStarted

This CL adds browser-side validation:
1. Rejects AudioContextAudiblePlaybackStarted calls if audio_context_id
   is not present in concurrent_audio_context_ids_.
2. Rejects AudioContextAudiblePlaybackStarted calls if the associated
   RenderFrameHostImpl has no active kPlayingAudioStream media stream.
3. Hardens AudioContextAudiblePlaybackStopped and AudioContextClosed
   against unstarted context IDs, and flushes pending audible durations
   and playback state upon context closure.
4. Adds unit tests verifying that audibility notifications for
   unregistered context IDs or frames without active streams are
   ignored.

Bug: 519211890
Change-Id: Icd0e2d4cf4287a06155b58f6f69861e23fd47136
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8372615
Reviewed-by: Hongchan Choi <hongchan@chromium.org>
Commit-Queue: Michael Wilson <mjwilson@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1694857}
---
diff --git a/content/browser/media/webaudio/audio_context_manager_impl.cc b/content/browser/media/webaudio/audio_context_manager_impl.cc
index 003f81d1..6afa1bc 100644
--- a/content/browser/media/webaudio/audio_context_manager_impl.cc
+++ b/content/browser/media/webaudio/audio_context_manager_impl.cc
@@ -67,6 +67,21 @@
 
 void AudioContextManagerImpl::AudioContextAudiblePlaybackStarted(
     uint32_t audio_context_id) {
+  // Reject audibility assertions for contexts that were not created by this
+  // frame (or have already been closed).
+  if (!concurrent_audio_context_ids_.contains(audio_context_id)) {
+    return;
+  }
+
+  // Reject audibility assertions if the frame has no active audio output
+  // streams.
+  auto& render_frame_host_impl =
+      static_cast<RenderFrameHostImpl&>(render_frame_host());
+  if (!render_frame_host_impl.HasMediaStreams(
+          RenderFrameHostImpl::MediaStreamType::kPlayingAudioStream)) {
+    return;
+  }
+
   if (!pending_audible_durations_[audio_context_id].is_null()) {
     mojo::ReportBadMessage(
         "AudioContextAudiblePlaybackStarted() called more than once with the "
@@ -77,26 +92,30 @@
   // Keeps track of the start audible time for this context.
   pending_audible_durations_[audio_context_id] = clock_->NowTicks();
 
-  static_cast<RenderFrameHostImpl&>(render_frame_host())
-      .AudioContextPlaybackStarted(audio_context_id);
+  render_frame_host_impl.AudioContextPlaybackStarted(audio_context_id);
 }
 
 void AudioContextManagerImpl::AudioContextAudiblePlaybackStopped(
     uint32_t audio_context_id) {
-  base::TimeTicks then = pending_audible_durations_[audio_context_id];
-  DCHECK(!then.is_null());
+  auto it = pending_audible_durations_.find(audio_context_id);
+  if (it == pending_audible_durations_.end() || it->second.is_null()) {
+    return;
+  }
 
+  base::TimeTicks then = it->second;
   RecordAudibleTime(clock_->NowTicks() - then);
 
   // Resets the context slot because the context is not audible.
-  pending_audible_durations_[audio_context_id] = base::TimeTicks();
+  it->second = base::TimeTicks();
 
   static_cast<RenderFrameHostImpl&>(render_frame_host())
       .AudioContextPlaybackStopped(audio_context_id);
 }
 
 void AudioContextManagerImpl::RecordAudibleTime(base::TimeDelta audible_time) {
-  DCHECK(!audible_time.is_zero());
+  if (!audible_time.is_positive()) {
+    return;
+  }
 
   ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
   DCHECK(ukm_recorder);
@@ -123,6 +142,15 @@
 
 void AudioContextManagerImpl::AudioContextClosed(uint32_t audio_context_id) {
   concurrent_audio_context_ids_.erase(audio_context_id);
+  auto it = pending_audible_durations_.find(audio_context_id);
+  if (it != pending_audible_durations_.end()) {
+    if (!it->second.is_null()) {
+      RecordAudibleTime(clock_->NowTicks() - it->second);
+      static_cast<RenderFrameHostImpl&>(render_frame_host())
+          .AudioContextPlaybackStopped(audio_context_id);
+    }
+    pending_audible_durations_.erase(it);
+  }
 }
 
 }  // namespace content
diff --git a/content/browser/media/webaudio/audio_context_manager_impl_unittest.cc b/content/browser/media/webaudio/audio_context_manager_impl_unittest.cc
index b7ac5c2..61c3923 100644
--- a/content/browser/media/webaudio/audio_context_manager_impl_unittest.cc
+++ b/content/browser/media/webaudio/audio_context_manager_impl_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "components/ukm/test_ukm_recorder.h"
+#include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/public/test/test_renderer_host.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -25,6 +26,8 @@
 
     clock_.SetNowTicks(base::TimeTicks::Now());
 
+    SimulateAudioStreamStarted();
+
     mojo::Remote<blink::mojom::AudioContextManager> service_remote;
     audio_context_manager_ = &AudioContextManagerImpl::CreateForTesting(
         *main_rfh(), service_remote.BindNewPipeAndPassReceiver());
@@ -32,10 +35,28 @@
   }
 
   void TearDown() override {
+    if (web_contents() && main_rfh() &&
+        static_cast<RenderFrameHostImpl*>(main_rfh())
+            ->HasMediaStreams(
+                RenderFrameHostImpl::MediaStreamType::kPlayingAudioStream)) {
+      SimulateAudioStreamStopped();
+    }
     audio_context_manager_ = nullptr;
     RenderViewHostTestHarness::TearDown();
   }
 
+  void SimulateAudioStreamStarted() {
+    static_cast<RenderFrameHostImpl*>(main_rfh())
+        ->OnMediaStreamAdded(
+            RenderFrameHostImpl::MediaStreamType::kPlayingAudioStream);
+  }
+
+  void SimulateAudioStreamStopped() {
+    static_cast<RenderFrameHostImpl*>(main_rfh())
+        ->OnMediaStreamRemoved(
+            RenderFrameHostImpl::MediaStreamType::kPlayingAudioStream);
+  }
+
   AudioContextManagerImpl* audio_context_manager() {
     return audio_context_manager_;
   }
@@ -53,6 +74,7 @@
 };
 
 TEST_F(AudioContextManagerImplTest, TimeBelow10SecondsIsRaw) {
+  audio_context_manager()->AudioContextCreated(0);
   // Entry for 42 milliseconds.
   audio_context_manager()->AudioContextAudiblePlaybackStarted(0);
   clock().Advance(base::Milliseconds(42));
@@ -79,6 +101,7 @@
 }
 
 TEST_F(AudioContextManagerImplTest, TimeGreater10SecondsIsRoundedDown) {
+  audio_context_manager()->AudioContextCreated(0);
   // Entry for 42 seconds.
   audio_context_manager()->AudioContextAudiblePlaybackStarted(0);
   clock().Advance(base::Seconds(42));
@@ -165,4 +188,64 @@
       /*expected_bucket_count=*/1);
 }
 
+TEST_F(AudioContextManagerImplTest,
+       AudioContextAudiblePlaybackStoppedWithoutStartedDoesNotCrash) {
+  // Calling stopped for an unstarted context ID should not crash.
+  audio_context_manager()->AudioContextAudiblePlaybackStopped(42);
+
+  auto ukm_entries = test_ukm_recorder().GetEntriesByName(UkmEntry::kEntryName);
+  EXPECT_EQ(0u, ukm_entries.size());
+}
+
+TEST_F(AudioContextManagerImplTest,
+       AudioContextClosedWithPendingAudiblePlayback) {
+  audio_context_manager()->AudioContextCreated(1);
+  audio_context_manager()->AudioContextAudiblePlaybackStarted(1);
+  clock().Advance(base::Seconds(5));
+
+  // Closing context should flush the pending audible duration to UKM.
+  audio_context_manager()->AudioContextClosed(1);
+
+  auto ukm_entries = test_ukm_recorder().GetEntriesByName(UkmEntry::kEntryName);
+  EXPECT_EQ(1u, ukm_entries.size());
+  EXPECT_EQ(5000, *test_ukm_recorder().GetEntryMetric(
+                      ukm_entries[0], UkmEntry::kAudibleTimeName));
+}
+
+TEST_F(AudioContextManagerImplTest, AudioContextAudiblePlaybackZeroDuration) {
+  audio_context_manager()->AudioContextCreated(1);
+  audio_context_manager()->AudioContextAudiblePlaybackStarted(1);
+  // Stopped in the same clock tick (duration == 0).
+  audio_context_manager()->AudioContextAudiblePlaybackStopped(1);
+
+  // Zero duration should not record to UKM.
+  auto ukm_entries = test_ukm_recorder().GetEntriesByName(UkmEntry::kEntryName);
+  EXPECT_EQ(0u, ukm_entries.size());
+}
+
+TEST_F(AudioContextManagerImplTest,
+       AudioContextAudiblePlaybackStartedIgnoredIfContextNotCreated) {
+  // Started called for a context ID that was never created should be ignored.
+  audio_context_manager()->AudioContextAudiblePlaybackStarted(42);
+  clock().Advance(base::Seconds(5));
+  audio_context_manager()->AudioContextAudiblePlaybackStopped(42);
+
+  auto ukm_entries = test_ukm_recorder().GetEntriesByName(UkmEntry::kEntryName);
+  EXPECT_EQ(0u, ukm_entries.size());
+}
+
+TEST_F(AudioContextManagerImplTest,
+       AudioContextAudiblePlaybackStartedIgnoredIfNoAudioStream) {
+  // Context is created, but the frame has no active audio output stream.
+  SimulateAudioStreamStopped();
+  audio_context_manager()->AudioContextCreated(1);
+
+  audio_context_manager()->AudioContextAudiblePlaybackStarted(1);
+  clock().Advance(base::Seconds(5));
+  audio_context_manager()->AudioContextAudiblePlaybackStopped(1);
+
+  auto ukm_entries = test_ukm_recorder().GetEntriesByName(UkmEntry::kEntryName);
+  EXPECT_EQ(0u, ukm_entries.size());
+}
+
 }  // namespace content
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/media/webaudio/audio_context_manager_impl_unittest.cc b/content/browser/media/webaudio/audio_context_manager_impl_unittest.cc
index b7ac5c2..61c3923 100644
--- a/content/browser/media/webaudio/audio_context_manager_impl_unittest.cc
+++ b/content/browser/media/webaudio/audio_context_manager_impl_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "components/ukm/test_ukm_recorder.h"
+#include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/public/test/test_renderer_host.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -25,6 +26,8 @@
 
     clock_.SetNowTicks(base::TimeTicks::Now());
 
+    SimulateAudioStreamStarted();
+
     mojo::Remote<blink::mojom::AudioContextManager> service_remote;
     audio_context_manager_ = &AudioContextManagerImpl::CreateForTesting(
         *main_rfh(), service_remote.BindNewPipeAndPassReceiver());
@@ -32,10 +35,28 @@
   }
 
   void TearDown() override {
+    if (web_contents() && main_rfh() &&
+        static_cast<RenderFrameHostImpl*>(main_rfh())
+            ->HasMediaStreams(
+                RenderFrameHostImpl::MediaStreamType::kPlayingAudioStream)) {
+      SimulateAudioStreamStopped();
+    }
     audio_context_manager_ = nullptr;
     RenderViewHostTestHarness::TearDown();
   }
 
+  void SimulateAudioStreamStarted() {
+    static_cast<RenderFrameHostImpl*>(main_rfh())
+        ->OnMediaStreamAdded(
+            RenderFrameHostImpl::MediaStreamType::kPlayingAudioStream);
+  }
+
+  void SimulateAudioStreamStopped() {
+    static_cast<RenderFrameHostImpl*>(main_rfh())
+        ->OnMediaStreamRemoved(
+            RenderFrameHostImpl::MediaStreamType::kPlayingAudioStream);
+  }
+
   AudioContextManagerImpl* audio_context_manager() {
     return audio_context_manager_;
   }
@@ -53,6 +74,7 @@
 };
 
 TEST_F(AudioContextManagerImplTest, TimeBelow10SecondsIsRaw) {
+  audio_context_manager()->AudioContextCreated(0);
   // Entry for 42 milliseconds.
   audio_context_manager()->AudioContextAudiblePlaybackStarted(0);
   clock().Advance(base::Milliseconds(42));
@@ -79,6 +101,7 @@
 }
 
 TEST_F(AudioContextManagerImplTest, TimeGreater10SecondsIsRoundedDown) {
+  audio_context_manager()->AudioContextCreated(0);
   // Entry for 42 seconds.
   audio_context_manager()->AudioContextAudiblePlaybackStarted(0);
   clock().Advance(base::Seconds(42));
@@ -165,4 +188,64 @@
       /*expected_bucket_count=*/1);
 }
 
+TEST_F(AudioContextManagerImplTest,
+       AudioContextAudiblePlaybackStoppedWithoutStartedDoesNotCrash) {
+  // Calling stopped for an unstarted context ID should not crash.
+  audio_context_manager()->AudioContextAudiblePlaybackStopped(42);
+
+  auto ukm_entries = test_ukm_recorder().GetEntriesByName(UkmEntry::kEntryName);
+  EXPECT_EQ(0u, ukm_entries.size());
+}
+
+TEST_F(AudioContextManagerImplTest,
+       AudioContextClosedWithPendingAudiblePlayback) {
+  audio_context_manager()->AudioContextCreated(1);
+  audio_context_manager()->AudioContextAudiblePlaybackStarted(1);
+  clock().Advance(base::Seconds(5));
+
+  // Closing context should flush the pending audible duration to UKM.
+  audio_context_manager()->AudioContextClosed(1);
+
+  auto ukm_entries = test_ukm_recorder().GetEntriesByName(UkmEntry::kEntryName);
+  EXPECT_EQ(1u, ukm_entries.size());
+  EXPECT_EQ(5000, *test_ukm_recorder().GetEntryMetric(
+                      ukm_entries[0], UkmEntry::kAudibleTimeName));
+}
+
+TEST_F(AudioContextManagerImplTest, AudioContextAudiblePlaybackZeroDuration) {
+  audio_context_manager()->AudioContextCreated(1);
+  audio_context_manager()->AudioContextAudiblePlaybackStarted(1);
+  // Stopped in the same clock tick (duration == 0).
+  audio_context_manager()->AudioContextAudiblePlaybackStopped(1);
+
+  // Zero duration should not record to UKM.
+  auto ukm_entries = test_ukm_recorder().GetEntriesByName(UkmEntry::kEntryName);
+  EXPECT_EQ(0u, ukm_entries.size());
+}
+
+TEST_F(AudioContextManagerImplTest,
+       AudioContextAudiblePlaybackStartedIgnoredIfContextNotCreated) {
+  // Started called for a context ID that was never created should be ignored.
+  audio_context_manager()->AudioContextAudiblePlaybackStarted(42);
+  clock().Advance(base::Seconds(5));
+  audio_context_manager()->AudioContextAudiblePlaybackStopped(42);
+
+  auto ukm_entries = test_ukm_recorder().GetEntriesByName(UkmEntry::kEntryName);
+  EXPECT_EQ(0u, ukm_entries.size());
+}
+
+TEST_F(AudioContextManagerImplTest,
+       AudioContextAudiblePlaybackStartedIgnoredIfNoAudioStream) {
+  // Context is created, but the frame has no active audio output stream.
+  SimulateAudioStreamStopped();
+  audio_context_manager()->AudioContextCreated(1);
+
+  audio_context_manager()->AudioContextAudiblePlaybackStarted(1);
+  clock().Advance(base::Seconds(5));
+  audio_context_manager()->AudioContextAudiblePlaybackStopped(1);
+
+  auto ukm_entries = test_ukm_recorder().GetEntriesByName(UkmEntry::kEntryName);
+  EXPECT_EQ(0u, ukm_entries.size());
+}
+
 }  // namespace content
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.