CVE-2026-13970
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmedia/base/audio_block_fifo.cc |
modified | |
TEST_Fmedia/base/audio_block_fifo_unittest.cc |
modified | |
formedia/base/audio_block_fifo_unittest.cc |
modified |
Files Changed
media/base/audio_block_fifo.ccmedia/base/audio_block_fifo_unittest.cc
Patch
From 9571dcb0d0d73f902179cc612fa6f672965b8b2f Mon Sep 17 00:00:00 2001
From: Thomas Guilbert <tguilbert@chromium.org>
Date: Thu, 21 May 2026 09:28:07 -0700
Subject: [PATCH] Fix IncreaseCapacity when FIFO is full
This CL fixes an edge case in `IncreaseCapacity()` which is only reached
when the FIFO is full. At full capacity, the read and write indices are
identical. After rotating the new blocks, the write index is correctly
pointing to the newly inserted blocks, and should not be updated.
Bug: 513779283
Change-Id: I96199d193e7e8c704f64168a1567bcb3caca14c7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7866205
Auto-Submit: Thomas Guilbert <tguilbert@chromium.org>
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1634345}
---
diff --git a/media/base/audio_block_fifo.cc b/media/base/audio_block_fifo.cc
index d98c4096..c736a676 100644
--- a/media/base/audio_block_fifo.cc
+++ b/media/base/audio_block_fifo.cc
@@ -81,9 +81,13 @@
std::rotate(audio_blocks_.begin() + read_block_,
audio_blocks_.begin() + original_size, audio_blocks_.end());
- // Update the write pointer if it is on top of the new inserted blocks.
- if (write_block_ >= read_block_)
+ // Update `write_block_` if the block it pointed to was shifted.
+ // If the FIFO was full, keep `write_block_` pointing to the new empty blocks.
+ const bool write_block_moved = write_block_ >= read_block_;
+ const bool fifo_was_full = available_blocks_ == original_size;
+ if (write_block_moved && !fifo_was_full) {
write_block_ += blocks;
+ }
// Update the read pointers correspondingly.
read_block_ += blocks;
diff --git a/media/base/audio_block_fifo_unittest.cc b/media/base/audio_block_fifo_unittest.cc
index 45d312a0..b581b74 100644
--- a/media/base/audio_block_fifo_unittest.cc
+++ b/media/base/audio_block_fifo_unittest.cc
@@ -46,12 +46,13 @@
void Push(AudioBlockFifo* fifo,
int frames_to_push,
int channels,
- SampleFormat format = kSampleFormatS16) {
+ SampleFormat format = kSampleFormatS16,
+ uint8_t fill_value = 1) {
DCHECK_LE(frames_to_push, fifo->GetUnfilledFrames());
const size_t data_byte_size =
SampleFormatToBytesPerChannel(format) * channels * frames_to_push;
auto data = base::HeapArray<uint8_t>::Uninit(data_byte_size);
- std::ranges::fill(data, 1);
+ std::ranges::fill(data, fill_value);
fifo->Push(data, frames_to_push, format);
}
@@ -275,4 +276,46 @@
ConsumeAndVerify(&fifo, max_frames, 0);
}
+TEST_F(AudioBlockFifoTest, IncreaseCapacityWhenFull) {
+ constexpr int kFrames = 4;
+ constexpr int kOriginalBlocks = 2;
+ AudioBlockFifo fifo(kChannels, kFrames, kOriginalBlocks);
+
+ // Using U8 format allows exact float representations:
+ // - U8 value 64 maps to -0.5f.
+ // - U8 value 96 maps to -0.25f.
+ constexpr float kOriginalValue = -0.5f;
+ constexpr float kAdditionalValue = -0.25f;
+
+ // Fill the FIFO.
+ Push(&fifo, kFrames, kChannels, kSampleFormatU8, 64);
+ Push(&fifo, kFrames, kChannels, kSampleFormatU8, 64);
+ EXPECT_EQ(0, fifo.GetUnfilledFrames());
+ EXPECT_EQ(kOriginalBlocks, fifo.available_blocks());
+
+ // Increase capacity.
+ fifo.IncreaseCapacity(1);
+ EXPECT_EQ(kFrames, fifo.GetUnfilledFrames());
+ EXPECT_EQ(kOriginalBlocks, fifo.available_blocks());
+
+ // Push another block.
+ Push(&fifo, kFrames, kChannels, kSampleFormatU8, 96);
+
+ EXPECT_EQ(0, fifo.GetUnfilledFrames());
+ EXPECT_EQ(kOriginalBlocks + 1, fifo.available_blocks());
+
+ auto verify_bus = [](const AudioBus* bus, float expected_value) {
+ for (auto channel : bus->AllChannels()) {
+ for (float sample : channel) {
+ EXPECT_NEAR(sample, expected_value, 0.0001);
+ }
+ }
+ };
+
+ // Consume and verify.
+ verify_bus(fifo.Consume(), kOriginalValue);
+ verify_bus(fifo.Consume(), kOriginalValue);
+ verify_bus(fifo.Consume(), kAdditionalValue);
+}
+
} // namespace media
Regression Test / PoC
diff --git a/media/base/audio_block_fifo_unittest.cc b/media/base/audio_block_fifo_unittest.cc
index 45d312a0..b581b74 100644
--- a/media/base/audio_block_fifo_unittest.cc
+++ b/media/base/audio_block_fifo_unittest.cc
@@ -46,12 +46,13 @@
void Push(AudioBlockFifo* fifo,
int frames_to_push,
int channels,
- SampleFormat format = kSampleFormatS16) {
+ SampleFormat format = kSampleFormatS16,
+ uint8_t fill_value = 1) {
DCHECK_LE(frames_to_push, fifo->GetUnfilledFrames());
const size_t data_byte_size =
SampleFormatToBytesPerChannel(format) * channels * frames_to_push;
auto data = base::HeapArray<uint8_t>::Uninit(data_byte_size);
- std::ranges::fill(data, 1);
+ std::ranges::fill(data, fill_value);
fifo->Push(data, frames_to_push, format);
}
@@ -275,4 +276,46 @@
ConsumeAndVerify(&fifo, max_frames, 0);
}
+TEST_F(AudioBlockFifoTest, IncreaseCapacityWhenFull) {
+ constexpr int kFrames = 4;
+ constexpr int kOriginalBlocks = 2;
+ AudioBlockFifo fifo(kChannels, kFrames, kOriginalBlocks);
+
+ // Using U8 format allows exact float representations:
+ // - U8 value 64 maps to -0.5f.
+ // - U8 value 96 maps to -0.25f.
+ constexpr float kOriginalValue = -0.5f;
+ constexpr float kAdditionalValue = -0.25f;
+
+ // Fill the FIFO.
+ Push(&fifo, kFrames, kChannels, kSampleFormatU8, 64);
+ Push(&fifo, kFrames, kChannels, kSampleFormatU8, 64);
+ EXPECT_EQ(0, fifo.GetUnfilledFrames());
+ EXPECT_EQ(kOriginalBlocks, fifo.available_blocks());
+
+ // Increase capacity.
+ fifo.IncreaseCapacity(1);
+ EXPECT_EQ(kFrames, fifo.GetUnfilledFrames());
+ EXPECT_EQ(kOriginalBlocks, fifo.available_blocks());
+
+ // Push another block.
+ Push(&fifo, kFrames, kChannels, kSampleFormatU8, 96);
+
+ EXPECT_EQ(0, fifo.GetUnfilledFrames());
+ EXPECT_EQ(kOriginalBlocks + 1, fifo.available_blocks());
+
+ auto verify_bus = [](const AudioBus* bus, float expected_value) {
+ for (auto channel : bus->AllChannels()) {
+ for (float sample : channel) {
+ EXPECT_NEAR(sample, expected_value, 0.0001);
+ }
+ }
+ };
+
+ // Consume and verify.
+ verify_bus(fifo.Consume(), kOriginalValue);
+ verify_bus(fifo.Consume(), kOriginalValue);
+ verify_bus(fifo.Consume(), kAdditionalValue);
+}
+
} // namespace media
Original Bug Report
Potential uninitialized memory leak in AudioBlockFifo::IncreaseCapacity
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A logic error in media::AudioBlockFifo::IncreaseCapacity incorrectly updates internal pointers when resizing a full ring buffer. This leads to new buffer blocks being skipped during write operations, causing uninitialized heap memory to be leaked to the renderer process.
Affected files:
media/base/audio_block_fifo.ccmedia/audio/pulse/pulse_input.ccservices/audio/input_sync_writer.cc
Estimated timestamp from git blame: 2014-09-19
Background
media::AudioBlockFifo is a ring buffer used to manage AudioBus objects for audio capture and playback. It supports dynamic capacity expansion via the IncreaseCapacity() method. Pointers read_block_ and write_block_ track the next block to be consumed and written, respectively. When the FIFO is full, read_block_ == write_block_.
Root Cause Analysis
The vulnerability exists in media/base/audio_block_fifo.cc within the IncreaseCapacity() function:
void AudioBlockFifo::IncreaseCapacity(int blocks) {
// ... (New uninitialized blocks are appended to audio_blocks_)
const int original_size = audio_blocks_.size();
// ...
std::rotate(audio_blocks_.begin() + read_block_,
audio_blocks_.begin() + original_size, audio_blocks_.end());
// Update the write pointer if it is on top of the new inserted blocks.
if (write_block_ >= read_block_)
write_block_ += blocks;
// Update the read pointers correspondingly.
read_block_ += blocks;
// ...
}
When IncreaseCapacity() is called on a full FIFO, write_block_ == read_block_. The std::rotate call moves existing data blocks to the end of the vector and places the newly allocated, uninitialized blocks starting at the original read_block_ index.
However, because write_block_ >= read_block_ evaluates to true, write_block_ is incremented by blocks. This makes write_block_ point to the same index as the new read_block_ (the start of the old data), effectively skipping over the newly inserted uninitialized blocks. Subsequent Push() calls will overwrite the oldest existing data rather than filling the new blocks.
Since available_blocks_ is correctly incremented based on the amount of data pushed, the FIFO eventually allows Consume() to return the unwritten blocks. These blocks contain uninitialized heap residue because they were created using AudioBus::Create, which uses base::AlignedUninit<float> internally.
Potential Exploit Path
An attacker with control over a renderer process could potentially trigger this leak in the Audio Service (a utility process on Linux and macOS) as follows:
- Open an audio input capture stream (e.g., via WebAudio or MediaDevices) with a small buffer size.
- Induce scheduling jitter or pause the renderer’s audio consumption to fill the Audio Service’s FIFO.
- Send a large burst of audio data that forces the Audio Service to dynamically increase the FIFO capacity while it is full (reachable via
PulseAudioInputStream::ReadDataon Linux orAudioLowLatencyInputApple::Captureon macOS). - The miscalculated pointers cause uninitialized blocks to be passed to
InputSyncWriter::WriteDataToCurrentSegment. InputSyncWritercopies this uninitialized data into a shared memory region mapped by the renderer, allowing the attacker to read raw heap bytes from the Audio Service.
Impact
This is a potential high-severity information leak. A compromised renderer can recover sensitive heap data (including pointers or residue from other origins) from the Audio Service process. On Linux, this can facilitate ASLR bypass for further attacks against the sandboxed Audio Service.
Suggested Fix
The write pointer should only be incremented if it is strictly greater than the read pointer, or the pointer logic should be redesigned to explicitly account for the full-buffer state. A simple change to if (write_block_ > read_block_) in IncreaseCapacity() may resolve the immediate issue.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.