CVE-2024-27850
Overview
Background
- Audio fingerprinting
- Deriving a stable device/OS identifier from subtle differences in Web Audio processing output.
- Noise injection (Private Browsing)
- WebKit perturbs audio readback with random noise so the output cannot be used as a stable fingerprint.
- Averaging attack
- Repeating a computation and averaging the noised results to cancel zero-mean noise and recover the true value; looping a sample is one way to repeat cheaply.
- Normally-distributed vs uniform noise
- Normal (Gaussian) noise resists simple min/max averaging far better than uniform noise, raising the cost of the attack.
Root Cause Analysis
This hardens Web Audio anti-fingerprinting noise injection in Private Browsing against an averaging attack. To defeat audio fingerprinting, WebKit injects noise into audio readback (e.g. via OfflineAudioContext) in Private Browsing. The pre-patch scheme used a fixed, uniformly-distributed noise level (0.001) gated by a single boolean flag (m_needsAdditionalNoise); an attacker could negate it by looping one small audio sample many times within a single audio buffer and averaging the repeated readbacks, converging on the true (un-noised) value and recovering a stable fingerprint.
The fix implements several mitigations. It replaces the boolean m_needsAdditionalNoise with an m_noiseInjectionMultiplier (the standard deviation of the injected noise) so noise magnitude can vary. It switches from uniform to NORMALLY-distributed noise, which does not converge under simple min/max averaging and requires far more iterations to average out. It caches previously-generated random values and reapplies the same noise to values encountered repeatedly, so recomputing the same value many times in one buffer yields no new information. And it lets node types known to expose hardware/OS differences inject more noise than the 0.1% baseline: DynamicsCompressorNode and OscillatorNode raise the level, and crucially AudioBufferSourceNode amplifies noise with loop count — for a source looping more than 200 times it boosts noise by a factor of 0.005 per loop, so looping a tiny sample back-to-back in a large buffer (the exact averaging technique) produces a massive amount of noise instead of a recoverable average. New plumbing (AudioNode::noiseInjectionMultiplier subclassing hook, AudioNodeOutput::forEachInputNode) propagates the multiplier through the processing graph.
The restored invariant is that averaging/looping cannot cancel the injected noise, so audio fingerprinting stays defeated in Private Browsing.
Attack Path
- Build a looping audio graph In Private Browsing, use OfflineAudioContext with an AudioBufferSourceNode that loops a tiny sample many times in a large buffer.
- Read back repeatedly Read the rendered audio buffer, obtaining many noised copies of the same underlying value.
- Average out the noise Average the repeated readbacks; with uniform fixed noise the mean converges on the true value.
- Recover the fingerprint Reconstruct the un-noised audio characteristics to fingerprint the device/OS despite the anti-fingerprinting noise.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
AudioBuffer::applyNoiseIfNeeded / copyToChannel / copyTo / zero / releaseMemorySource/WebCore/Modules/webaudio/AudioBuffer.cpp |
modified | Uses normally-distributed noise scaled by m_noiseInjectionMultiplier (replacing the fixed-level boolean), and caches/reapplies random values so repeated values get identical noise. |
AudioBuffer noise multiplier accessorsSource/WebCore/Modules/webaudio/AudioBuffer.h |
modified | Replaces setNeedsAdditionalNoise with increaseNoiseInjectionMultiplier / noiseInjectionMultiplier (the noise standard deviation). |
AudioBufferSourceNode::noiseInjectionMultiplierSource/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp |
modified | Boosts noise for a source looping >200 times (factor 0.005 per loop), so looping a tiny sample in a large buffer yields massive noise instead of an averageable value. |
AudioNode::noiseInjectionMultiplier / AudioNodeOutput::forEachInputNodeSource/WebCore/Modules/webaudio/AudioNode.h |
modified | Adds a subclassing hook and graph-traversal helper so specific node types (DynamicsCompressorNode, OscillatorNode) can inject extra noise propagated through the processing graph. |
Files Changed
Source/WebCore/Modules/webaudio/AudioBasicProcessorNode.hSource/WebCore/Modules/webaudio/AudioBuffer.cppSource/WebCore/Modules/webaudio/AudioBuffer.hSource/WebCore/Modules/webaudio/AudioBufferSourceNode.cppSource/WebCore/Modules/webaudio/AudioBufferSourceNode.hSource/WebCore/Modules/webaudio/AudioNode.hSource/WebCore/Modules/webaudio/AudioNodeOutput.cppSource/WebCore/Modules/webaudio/AudioNodeOutput.hSource/WebCore/Modules/webaudio/AudioWorkletNode.cppSource/WebCore/Modules/webaudio/BaseAudioContext.hSource/WebCore/Modules/webaudio/DynamicsCompressorNode.hSource/WebCore/Modules/webaudio/OfflineAudioContext.cppSource/WebCore/Modules/webaudio/OfflineAudioContext.hSource/WebCore/Modules/webaudio/OscillatorNode.hSource/WebCore/platform/audio/AudioUtilities.cppSource/WebCore/platform/audio/AudioUtilities.hTools/TestWebKitAPI/Tests/WebKit/AdvancedPrivacyProtections.mmTools/TestWebKitAPI/Tests/WebKitCocoa/audio-fingerprinting.html
Audit Directions
- Same module: noise coverageAudit AudioBuffer/AudioNode readback paths (worklets, OfflineAudioContext render, copyTo) to confirm every readback applies the multiplier-scaled noise and reuses cached randoms for repeated values.
- Other averageable anti-fingerprinting noiseReview canvas/WebGL/other fingerprinting mitigations that add fixed, independent noise per read, which a repeat-and-average attack could similarly cancel.
Patch
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index 30328cf6e845..c83f5658a6fb 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,13 @@
+2020-12-14 Carlos Garcia Campos <cgarcia@igalia.com>
+
+ [WPE][GTK] Should enable WebProcessCache
+ https://bugs.webkit.org/show_bug.cgi?id=219689
+
+ Reviewed by Adrian Perez de Castro.
+
+ * UIProcess/API/glib/WebKitWebContext.cpp:
+ (webkitWebContextConstructed):
+
2020-12-13 Yusuke Suzuki <ysuzuki@apple.com>
[JSC] Introduce vmEntryCustomAccessor and vmEntryHostFunction for JITCage
diff --git a/Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp b/Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp
index f7faa5880bf0..acf9ccdb6d12 100644
--- a/Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp
+++ b/Source/WebKit/UIProcess/API/glib/WebKitWebContext.cpp
@@ -391,6 +391,7 @@ static void webkitWebContextConstructed(GObject* object)
API::ProcessPoolConfiguration configuration;
configuration.setInjectedBundlePath(FileSystem::stringFromFileSystemRepresentation(bundleFilename.get()));
+ configuration.setUsesWebProcessCache(true);
#if PLATFORM(GTK)
configuration.setProcessSwapsOnNavigation(priv->psonEnabled);
#if !USE(GTK4)