CVE-2026-14149
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmedia/audio/pulse/pulse_util.cc |
modified |
Files Changed
media/audio/pulse/pulse_util.cc
Patch
From dae1470416c15677552bead4d30615dcc9a575fa Mon Sep 17 00:00:00 2001
From: Thomas Guilbert <tguilbert@chromium.org>
Date: Thu, 21 May 2026 20:09:18 -0700
Subject: [PATCH] Fix pulse operation leak/double free
This CL fixes two issues:
- Double frees of `pa_operation`s which the
`WaitForOperationCompletion()` helper already frees
- A missing free of a mute `pa_operation` which mirrors the free of the
unmute operation.
Fixed: 515427046
Change-Id: I185fe03a6fca2d79197b279744dd15aafc3fff3a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7870179
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Auto-Submit: Thomas Guilbert <tguilbert@chromium.org>
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: Thomas Guilbert <tguilbert@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1634699}
---
diff --git a/media/audio/pulse/pulse_util.cc b/media/audio/pulse/pulse_util.cc
index beaf508..fd1fea63 100644
--- a/media/audio/pulse/pulse_util.cc
+++ b/media/audio/pulse/pulse_util.cc
@@ -668,19 +668,18 @@
std::string* exclude_sink_name = static_cast<std::string*>(userdata);
// Check if current sink's name matches the exclude_sink_name
if (i->name != *exclude_sink_name) {
- pa_context_set_sink_mute_by_index(
+ pa_operation* mute_op = pa_context_set_sink_mute_by_index(
c, i->index, 1, /*callback=*/nullptr,
/*userdata=*/nullptr); // Mute the sink
+ if (mute_op) {
+ pa_operation_unref(mute_op);
+ }
}
}
},
(void*)&exclude_sink_name);
WaitForOperationCompletion(mainloop, op, context);
- // Clean up the operation after completion
- if (op) {
- pa_operation_unref(op);
- }
}
// Unmutes all audio output sinks in the system.
@@ -715,11 +714,8 @@
mainloop // Pass mainloop as userdata
);
- WaitForOperationCompletion(mainloop, op, context);
// Wait for the operation to complete to ensure all sinks are unmuted.
- if (op) {
- pa_operation_unref(op);
- }
+ WaitForOperationCompletion(mainloop, op, context);
}
std::string GetBusOfInput(pa_threaded_mainloop* mainloop,
Original Bug Report
Double-Free in Unsandboxed Linux Audio Service via PulseAudio Helpers
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/audio/pulse/pulse_util.cc results in a redundant call to pa_operation_unref, causing a potential double-free or use-after-free of PulseAudio operation objects. This occurs within the Audio Service process, which on Linux is currently unsandboxed by default. The vulnerability is potentially reachable through the getDisplayMedia API when specific audio loopback features are enabled.
Affected files:
media/audio/pulse/pulse_util.ccmedia/audio/pulse/pulse_loopback.ccmedia/audio/pulse/pulse_loopback_manager.ccmedia/audio/pulse/audio_manager_pulse.cc
Estimated timestamp from git blame: 2024-09-04
Vulnerability Details
A potential memory safety vulnerability exists in the PulseAudio utility implementation within the Chrome audio stack on Linux. The issue is a double-unref (which leads to a double-free) of pa_operation objects in media/audio/pulse/pulse_util.cc.
The helper function WaitForOperationCompletion is designed to wait for a PulseAudio operation to finish and then release the caller’s reference. As seen in media/audio/pulse/pulse_util.cc:
390: bool WaitForOperationCompletion(pa_threaded_mainloop* mainloop,
391: pa_operation* operation,
...
425: pa_operation_unref(operation);
426: return true;
427: }
However, two functions added for PulseAudio system-loopback support incorrectly call pa_operation_unref a second time after WaitForOperationCompletion returns, despite the reference already being consumed:
-
MuteAllSinksExcept (lines 679-682):
679: WaitForOperationCompletion(mainloop, op, context); 680: // Clean up the operation after completion 681: if (op) { 682: pa_operation_unref(op); 683: } -
UnmuteAllSinks (lines 718-721):
718: WaitForOperationCompletion(mainloop, op, context); 719: // Wait for the operation to complete to ensure all sinks are unmuted. 720: if (op) { 721: pa_operation_unref(op); 722: }
Since WaitForOperationCompletion already calls pa_operation_unref on the provided pointer, the subsequent call in the parent function results in a double-free of the pa_operation object. This can lead to heap corruption within the PulseAudio library or the process’s allocator.
Impact and Reachability
On Linux, the Audio Service typically runs as a separate utility process where the sandbox is disabled by default (the kAudioServiceSandbox feature is base::FEATURE_DISABLED_BY_DEFAULT for Linux in content/public/common/content_features.cc). Memory corruption in this process could lead to arbitrary code execution with the privileges of the user running Chromium.
This code path is reachable when the kPulseaudioLoopbackForScreenShare feature flag is enabled. An attacker could potentially trigger this by invoking the getDisplayMedia API with the suppressLocalAudioPlayback: true constraint. This configuration routes the request through AudioManagerPulse and PulseLoopbackManager, eventually calling the vulnerable functions in pulse_util.cc.
Potential Trigger Steps
Note: These steps are based on code analysis; our tooling cannot yet execute code to verify a proof-of-concept.
- The user must have the experimental flag enabled (e.g., via
chrome://flags/#pulseaudio-loopback-for-screen-share). - An attacker-controlled page calls
navigator.mediaDevices.getDisplayMedia({ audio: { suppressLocalAudioPlayback: true } }). - The user is prompted to share their screen and accepts.
- This triggers
MuteAllSinksExceptin the Audio Service, causing the potential double-free of the PulseAudio operation object. - Closing the shared stream triggers
UnmuteAllSinks, causing a second potential double-free.
Suggested Fix
Remove the redundant calls to pa_operation_unref(op) in MuteAllSinksExcept and UnmuteAllSinks (lines 682 and 721 in media/audio/pulse/pulse_util.cc). The WaitForOperationCompletion helper already correctly handles the reference release.
Additionally, MuteAllSinksExcept should be updated to properly unref the operation returned by pa_context_set_sink_mute_by_index (lines 671-673) to prevent a memory leak, similar to the corrected logic seen in other parts of the PulseAudio utility.
Evaluated with Chrome root at commit: 29093e11cf509e3593f6229e4b1b075cca356049
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.