CVE-2026-5864
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forthird_party/blink/renderer/modules/webaudio/audio_param_handler.cc |
modified |
Files Changed
third_party/blink/renderer/modules/webaudio/audio_param_handler.cc
Patch
From 4e9562ca7b4214fe359b5f3b885443e301bc5f74 Mon Sep 17 00:00:00 2001
From: Michael Wilson <mjwilson@chromium.org>
Date: Tue, 10 Mar 2026 10:08:27 -0700
Subject: [PATCH] Replace UNSAFE_BUFFERS in AudioParamHandler with safe operations
This should cause no functional change.
Also do a small optimization in HandleNaNValues for inputs that are
larger than 4 by skipping the first if check, since we expect longer
inputs in general.
Bug: 401184803
Bug: 490642831
Change-Id: I1044267b7e6370af4793fe973fe36224630ced95
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7649387
Reviewed-by: Hongchan Choi <hongchan@chromium.org>
Commit-Queue: Michael Wilson <mjwilson@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1597152}
---
diff --git a/third_party/blink/renderer/modules/webaudio/audio_param_handler.cc b/third_party/blink/renderer/modules/webaudio/audio_param_handler.cc
index b3b95dd..88a8f39 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_param_handler.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_param_handler.cc
@@ -51,40 +51,35 @@
void HandleNaNValues(base::span<float> values, float default_value) {
unsigned k = 0;
#if defined(ARCH_CPU_X86_FAMILY)
- if (values.size() >= 4) {
- __m128 defaults = _mm_set1_ps(default_value);
- for (k = 0; k < values.size(); k += 4) {
- // SAFETY: The for loop condition has been checked k < values.size().
- __m128 v = _mm_loadu_ps(UNSAFE_BUFFERS(values.data() + k));
- // cmpuord returns all 1's if v is NaN for each elmeent of v.
- __m128 isnan = _mm_cmpunord_ps(v, v);
- // Replace NaN parts with default.
- __m128 result = _mm_and_ps(isnan, defaults);
- // Merge in the parts that aren't NaN
- result = _mm_or_ps(_mm_andnot_ps(isnan, v), result);
- // SAFETY: The for loop condition has been checked k < values.size().
- _mm_storeu_ps(UNSAFE_BUFFERS(values.data() + k), result);
- }
+ // Truncate to the next-lowest multiple of 4.
+ const size_t truncated_size = values.size() & ~3;
+ __m128 defaults = _mm_set1_ps(default_value);
+ for (k = 0; k < truncated_size; k += 4) {
+ __m128 v = _mm_loadu_ps(values.subspan(k, 4u).data());
+ // cmpuord returns all 1's if v is NaN for each elmeent of v.
+ __m128 isnan = _mm_cmpunord_ps(v, v);
+ // Replace NaN parts with default.
+ __m128 result = _mm_and_ps(isnan, defaults);
+ // Merge in the parts that aren't NaN
+ result = _mm_or_ps(_mm_andnot_ps(isnan, v), result);
+ _mm_storeu_ps(values.subspan(k, 4u).data(), result);
}
#elif defined(CPU_ARM_NEON)
- if (values.size() >= 4) {
- uint32x4_t defaults =
- reinterpret_cast<uint32x4_t>(vdupq_n_f32(default_value));
- for (k = 0; k < values.size(); k += 4) {
- // SAFETY: The for loop condition has been checked k < values.size().
- float32x4_t v = vld1q_f32(UNSAFE_BUFFERS(values.data() + k));
- // Returns true (all ones) if v is not NaN
- uint32x4_t is_not_nan = vceqq_f32(v, v);
- // Get the parts that are not NaN
- uint32x4_t result =
- vandq_u32(is_not_nan, reinterpret_cast<uint32x4_t>(v));
- // Replace the parts that are NaN with the default and merge with previous
- // result. (Note: vbic_u32(x, y) = x and not y)
- result = vorrq_u32(result, vbicq_u32(defaults, is_not_nan));
- // SAFETY: The for loop condition has been checked k < values.size().
- vst1q_f32(UNSAFE_BUFFERS(values.data() + k),
- reinterpret_cast<float32x4_t>(result));
- }
+ // Truncate to the next-lowest multiple of 4.
+ const size_t truncated_size = values.size() & ~3;
+ uint32x4_t defaults =
+ reinterpret_cast<uint32x4_t>(vdupq_n_f32(default_value));
+ for (k = 0; k < truncated_size; k += 4) {
+ float32x4_t v = vld1q_f32(values.subspan(k, 4u).data());
+ // Returns true (all ones) if v is not NaN
+ uint32x4_t is_not_nan = vceqq_f32(v, v);
+ // Get the parts that are not NaN
+ uint32x4_t result = vandq_u32(is_not_nan, reinterpret_cast<uint32x4_t>(v));
+ // Replace the parts that are NaN with the default and merge with previous
+ // result. (Note: vbic_u32(x, y) = x and not y)
+ result = vorrq_u32(result, vbicq_u32(defaults, is_not_nan));
+ vst1q_f32(values.subspan(k, 4u).data(),
+ reinterpret_cast<float32x4_t>(result));
}
#endif
@@ -1831,10 +1826,7 @@
// Process 4 loop steps.
for (; write_index < fill_to_frame_trunc; write_index += 4) {
- // SAFETY: DCHECK previously checked that `fill_to_frame_trunc <
- // values.size()`. In the for loop, `write_index < fill_to_frame_trunc` so
- // this is safe.
- _mm_storeu_ps(UNSAFE_BUFFERS(values.data() + write_index), v_value);
+ _mm_storeu_ps(values.subspan(write_index, 4u).data(), v_value);
v_value = _mm_add_ps(v_value, v_inc);
}
}
@@ -2020,10 +2012,7 @@
v_value = _mm_set_ps1(value);
v_result = _mm_add_ps(v_value, _mm_mul_ps(v_delta, v_c));
- // SAFETY: DCHECK previously checked that `fill_to_frame_trunc <
- // values.size()`. In the for loop, `write_index < fill_to_frame_trunc`
- // so this is safe.
- _mm_storeu_ps(UNSAFE_BUFFERS(values.data() + write_index), v_result);
+ _mm_storeu_ps(values.subspan(write_index, 4u).data(), v_result);
// Update value for next iteration.
value += delta * c3;
@@ -2184,10 +2173,7 @@
__m128 v_value =
_mm_add_ps(v_c0, _mm_mul_ps(_mm_sub_ps(v_c1, v_c0), v_delta));
- // SAFETY: DCHECK previously checked that `fill_to_frame_trunc <
- // values.size()`. In the for loop, `write_index < fill_to_frame_trunc` so
- // this is safe.
- _mm_storeu_ps(UNSAFE_BUFFERS(values.data() + write_index), v_value);
+ _mm_storeu_ps(values.subspan(write_index, 4u).data(), v_value);
}
// Pass along k to the serial loop.
k = truncated_steps;
Original Bug Report
OOB Read in AudioParamHandler::CalculateFinalValues
Summary
AudioParamHandler::CalculateFinalValues forwards a render-quantum-sized span into HandleNaNValues, but HandleNaNValues processes the span in 4-float SIMD chunks using k < values.size() instead of truncating to a multiple of 4. Once WebAudioConfigurableRenderQuantummakes odd render quanta such as133script-reachable, the last SIMD iteration reads and writes beyond theAudioParam` buffer and produces OOB.
> NOTE: this issue’s root cause is difference with the previous reported issues 487357842, 487357842
Details
The current WebAudio implementation allows script to choose a non-default render quantum through BaseAudioContext.renderQuantumSize and validates that value in AudioContext::Create. That value flows into audio-rate AudioParam processing, where AudioParamHandler::CalculateFinalValues calls `HandleNaNValues(values, DefaultValue()) on a span whose length is the active render quantum.
In HandleNaNValues, the issue is that the SIMD loop advances by four but is bounded only by k < values.size(). For a 133-frame quantum, the final iteration executes with k == 132, so the _mm_loadu_ps/_mm_storeu_pspair touches elements132..135even though only element132is inside the span. After that,values.subspan(k) is evaluated with k == 136, which is also beyond the span length:
void HandleNaNValues(base::span<float> values, float default_value) {
unsigned k = 0;
#if defined(ARCH_CPU_X86_FAMILY)
if (values.size() >= 4) {
__m128 defaults = _mm_set1_ps(default_value);
for (k = 0; k < values.size(); k += 4) {
__m128 v = _mm_loadu_ps(values.data() + k);
__m128 isnan = _mm_cmpunord_ps(v, v);
__m128 result = _mm_and_ps(isnan, defaults);
result = _mm_or_ps(_mm_andnot_ps(isnan, v), result);
_mm_storeu_ps(values.data() + k, result);
}
}
#endif
std::ranges::replace_if(values.subspan(k),
[](float value) { return std::isnan(value); },
default_value);
}
Bisection
This issue is introduced by the commit https://chromium-review.googlesource.com/c/chromium/src/+/6804197, which makes this issue became script-reachable when renderSizeHint is propagated into the CalculateFinalValues and HandleNaNValues.
Reproduction
Download the chromium from https://storage.googleapis.com/chromium-browser-asan/linux-release/asan-linux-release-1595156.zip
Run with
./chrome --no-sandbox --enable-experimental-web-platform-features poc.html
You would observe the OOB crash shown in asan.txt
Suggested Fix
Clamp HandleNaNValues to a SIMD-safe prefix before issuing 4-wide loads and stores. We may compute simd_end = values.size() & ~3u, iterate while k < simd_end, and keep the scalar tail in the `replace_if(values.subspan(k), …) path.
- https://chromium-review.googlesource.com/c/chromium/src/+/6804197
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/webaudio/audio_context.cc;l=484
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/webaudio/audio_param_handler.cc;l=339
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/webaudio/audio_param_handler.cc;l=51
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/webaudio/base_audio_context.idl;l=25
- https://storage.googleapis.com/chromium-browser-asan/linux-release/asan-linux-release-1595156.zip