Closed Bug 1738237 (CVE-2021-43537) Opened 2 years ago Closed 2 years ago

heap buffer overflow in nsStructuredCloneContainer::GetDataAsBase64 from integer overflow

Categories

(Core :: DOM: Core & HTML, defect, P1)

defect

Tracking

()

VERIFIED FIXED
96 Branch
Tracking Status
firefox-esr91 95+ verified
firefox94 --- wontfix
firefox95 + verified
firefox96 + verified

People

(Reporter: bo13oy, Assigned: smaug)

References

Details

(Keywords: csectype-intoverflow, sec-high, Whiteboard: [reporter-external] [client-bounty-form][sec-survey][adv-main95+][adv-ESR91.4.0+])

Attachments

(4 files)

Attached file poc.zip

Tested Version: Windows 10 1909 x64 memory 16G + firfox 93.0 64-bit.

####Root cause:
In functionn sStructuredCloneContainer::GetDataAsBase64, at the position #1, v10 Force conversion from 64-bit to 32-bit, which leads to Integer Overflow vulnerability. This vulnerability can be easily exploited for Remote Code Execution Vulnerability.
The patch for this vulnerability is simple, you just need to determine if v10 overflows before calling SetLength.

####Code:

__int64 __fastcall nsStructuredCloneContainer::GetDataAsBase64(__int64 a1, __int64 a2)
{
  __int64 v2; // rsi
  __int64 v3; // rbx
  __int64 v4; // rax
  _QWORD *v5; // rcx
  unsigned int v6; // ebp
  _QWORD *v8; // rcx
  __int64 v9; // rax
  unsigned __int64 v10; // rdi
  __int64 v11; // rax
  signed __int64 v12; // r15
  __int64 v13; // r12
  __int64 v14; // rbx
  size_t v15; // rbp
  int v16; // eax
  __int64 v17; // [rsp+0h] [rbp-D8h]
  void *Src[2]; // [rsp+20h] [rbp-B8h]
  __int128 v19; // [rsp+30h] [rbp-A8h]
  char *v20; // [rsp+40h] [rbp-98h]
  __int64 v21; // [rsp+48h] [rbp-90h]
  int v22; // [rsp+50h] [rbp-88h]
  char v23; // [rsp+54h] [rbp-84h]
  unsigned __int64 v24; // [rsp+98h] [rbp-40h]

  v2 = a2;
  v3 = a1;
  v24 = (unsigned __int64)&v17 ^ _security_cookie;
  nsTSubstring_char16_t_::Truncate(a2);
  v4 = *(_QWORD *)(v3 + 240);
  v5 = (_QWORD *)(v4 + 64);
  if ( !v4 )
    v5 = (_QWORD *)(v3 + 168);
  v6 = -2147467259;
  if ( *v5 && !**(_DWORD **)(v3 + 56) && !**(_DWORD **)(v3 + 64) && !**(_DWORD **)(v3 + 80) && !**(_DWORD **)(v3 + 72) )
  {
    v8 = (_QWORD *)(v4 + 8);
    if ( !v4 )
      v8 = (_QWORD *)(v3 + 112);
    v19 = 0i64;
    *(_OWORD *)Src = 0i64;
    if ( v8[2] )
    {
      v9 = v8[1];
      Src[1] = *(void **)v9;
      *(_QWORD *)&v19 = (char *)Src[1] + *(_QWORD *)(v9 + 8);
    }
    v10 = v8[7];
    v20 = &v23;
    v21 = 844497944576000i64;
    v22 = 63;
    v23 = 0;
    v6 = -2147024882;
    if ( nsTSubstring_char_::SetLength((volatile signed __int32 **)&v20, v10) )
...
    if ( (unsigned __int8)nsTSubstring_char_::SetLength(&v20, (unsigned int)v10, &std::nothrow) ) #1
    {
      v11 = *(_QWORD *)(v3 + 240);
      v12 = v11 + 8;
      if ( !v11 )
        v12 = v3 + 112;
      if ( !(unsigned __int8)nsTSubstring_char_::EnsureMutable(&v20, 0xFFFFFFFFi64) )
        NS_ABORT_OOM((unsigned int)v21);
      if ( v10 )
      {
        v13 = (__int64)v20;
        v14 = 0i64;
        do
        {
          if ( Src[1] > (void *)v19 )
          {
            gMozCrashReason = "MOZ_RELEASE_ASSERT(mData <= mDataEnd)";
            __debugbreak();
            MOZ_NoReturn(209i64);
          }
          v15 = v19 - (unsigned __int64)Src[1];
          if ( v10 < (unsigned __int64)v19 - (unsigned __int64)Src[1] )
            v15 = v10;
          if ( !v15 )
            break;
          if ( Src[1] == (void *)v19 )
          {
            gMozCrashReason = "MOZ_RELEASE_ASSERT(!Done())";
            __debugbreak();
            MOZ_NoReturn(196i64);
          }
          memcpy_0((void *)(v13 + v14), Src[1], v15);
          v14 += v15;
          mozilla::BufferList_js::SystemAllocPolicy_::IterImpl::Advance(Src, v12, v15);
          v10 -= v15;
        }
        while ( v10 );
      }
    ....
}

####OOB Write details:

Debugging Details:
------------------

0:000> r
rax=000001d4af2fe808 rbx=00000000000ad000 rcx=000001d4af2fef20
rdx=000001d4b5f16718 rsi=000001d4b57996a0 rdi=00000000fff53430
rip=00007ffd35bd151e rsp=000000036c7fb7f8 rbp=0000000000001000
 r8=00000000000008e8  r9=ffffffffffffffe8 r10=00007ffd35bd0000
r11=000001d665c00000 r12=000001d4af251808 r13=000000036c7fbaa0
r14=000000036c7fb820 r15=000001d4af205828
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
VCRUNTIME140!memcpy+0x22e:
00007ffd`35bd151e c5fd7fa1e0000000 vmovdqa ymmword ptr [rcx+0E0h],ymm4 ds:000001d4`af2ff000=??

0:000> kb
RetAddr           : Args to Child                                                           : Call Site
00007ffd`16446f5b : 00000000`00000000 0000f1a7`6bbca904 000001d4`b5624000 00000003`6c7fb8b0 : VCRUNTIME140!memcpy+0x22e
00007ffd`17083170 : 0000f1a7`6bbca9e4 00000003`6c7fb978 000001d4`b5624088 00000003`6c7fb968 : xul!nsStructuredCloneContainer::GetDataAsBase64+0x18b [/builds/worker/checkouts/gecko/dom/base/nsStructuredCloneContainer.cpp @ 151]
00007ffd`1707f930 : 000001d4`b5750c00 00007ffd`165d8d8c 0000f1a7`6bbca614 fffe02e8`44f009b0 : xul!mozilla::dom::Notification::InitFromJSVal+0x80 [/builds/worker/checkouts/gecko/dom/notification/Notification.cpp @ 1952]
00007ffd`1707f7a0 : 000001d4`b5624068 00000003`6c7fbde8 fff98000`00000000 000001d4`00000c01 : xul!mozilla::dom::Notification::CreateAndShow+0xe0 [/builds/worker/checkouts/gecko/dom/notification/Notification.cpp @ 2234]
00007ffd`165f2a2d : 00000003`6c7fba88 00000000`00000000 00000000`00000002 000001d4`b735c1e0 : xul!mozilla::dom::Notification::Constructor+0x140 [/builds/worker/checkouts/gecko/dom/notification/Notification.cpp @ 779]
00007ffd`157b6066 : 000001d4`b5620001 00000003`6c7fbf00 00000000`00000000 00000000`b5010111 : xul!mozilla::dom::Notification_Binding::_constructor+0x41d [/builds/worker/workspace/obj-build/dom/bindings/NotificationBinding.cpp @ 2212]
00007ffd`159753cb : 00000101`0000fe05 00000003`6c7fc400 00007ffd`18ae8bb0 00000000`00000077 : xul!InternalConstruct+0x296 [/builds/worker/checkouts/gecko/js/src/vm/Interpreter.cpp @ 612]

0:000> dd rcx
000001d4`af2fef20  41414141 41414141 41414141 41414141
000001d4`af2fef30  41414141 41414141 41414141 41414141
000001d4`af2fef40  41414141 41414141 41414141 41414141
000001d4`af2fef50  41414141 41414141 41414141 41414141
000001d4`af2fef60  41414141 41414141 41414141 41414141
000001d4`af2fef70  41414141 41414141 41414141 41414141
000001d4`af2fef80  41414141 41414141 41414141 41414141
000001d4`af2fef90  41414141 41414141 41414141 41414141

#########################################################################################################################################

This vuln is discovered by bo13oy of Cyber Kunlun Lab.

Thanks.

Flags: sec-bounty?

Olli, are you the right person to take a look?

Group: firefox-core-security → dom-core-security
Type: task → defect
Component: Security → DOM: Core & HTML
Flags: needinfo?(bugs)
Product: Firefox → Core
Attached file poc.html

Here's the poc.html from the zip file.

Tested Version: Ubuntu 64-bit memory 16G + linux64-fuzzing-asan-opt(94.0 (64-bit)) => https://firefox-ci-tc.services.mozilla.com/tasks/index/gecko.v2.mozilla-release.latest.firefox/linux64-fuzzing-asan-opt
Loading poc.html with firefox, the crash report is as follows:
###################################################################################################################################################################################
==2203==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61d0000b3480 at pc 0x564bb2d01f6a bp 0x7ffe4d4b27e0 sp 0x7ffe4d4b1fa8
WRITE of size 4096 at 0x61d0000b3480 thread T0 (file:// Content)
#0 0x564bb2d01f69 in __asan_memcpy /builds/worker/fetches/llvm-project/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:22:3
#1 0x7f2520adee09 in mozilla::BufferList<js::SystemAllocPolicy>::ReadBytes(mozilla::BufferList<js::SystemAllocPolicy>::IterImpl&, char*, unsigned long) const /builds/worker/workspace/obj-build/dist/include/mozilla/BufferList.h:492:5
#2 0x7f2520adb6d2 in ReadBytes /builds/worker/workspace/obj-build/dist/include/js/StructuredClone.h:505:21
#3 0x7f2520adb6d2 in nsStructuredCloneContainer::GetDataAsBase64(nsTSubstring<char16_t>&) /builds/worker/checkouts/gecko/dom/base/nsStructuredCloneContainer.cpp:151:32
#4 0x7f2523dfc781 in mozilla::dom::Notification::InitFromJSVal(JSContext*, JS::Handle<JS::Value>, mozilla::ErrorResult&) /builds/worker/checkouts/gecko/dom/notification/Notification.cpp:1952:30
#5 0x7f2523df007d in mozilla::dom::Notification::CreateAndShow(JSContext*, nsIGlobalObject*, nsTSubstring<char16_t> const&, mozilla::dom::NotificationOptions const&, nsTSubstring<char16_t> const&, mozilla::ErrorResult&) /builds/worker/checkouts/gecko/dom/notification/Notification.cpp:2233:17
#6 0x7f2523defb29 in mozilla::dom::Notification::Constructor(mozilla::dom::GlobalObject const&, nsTSubstring<char16_t> const&, mozilla::dom::NotificationOptions const&, mozilla::ErrorResult&) /builds/worker/checkouts/gecko/dom/notification/Notification.cpp:780:7
#7 0x7f25211ae4a2 in mozilla::dom::Notification_Binding::_constructor(JSContext*, unsigned int, JS::Value*) /builds/worker/workspace/obj-build/dom/bindings/NotificationBinding.cpp:2212:58
#8 0x7f25290d357f in CallJSNative /builds/worker/checkouts/gecko/js/src/vm/Interpreter.cpp:385:13
#9 0x7f25290d357f in CallJSNativeConstructor /builds/worker/checkouts/gecko/js/src/vm/Interpreter.cpp:401:8
#10 0x7f25290d357f in InternalConstruct(JSContext*, js::AnyConstructArgs const&) /builds/worker/checkouts/gecko/js/src/vm/Interpreter.cpp:596:10
#11 0x7f2529f47010 in js::jit::DoCallFallback(JSContext*, js::jit::BaselineFrame*, js::jit::ICFallbackStub*, unsigned int, JS::Value*, JS::MutableHandle<JS::Value>) /builds/worker/checkouts/gecko/js/src/jit/BaselineIC.cpp:1571:10
#12 0x7f2492b66de7 (<unknown module>)

0x61d0000b3480 is located 0 bytes to the right of 2048-byte region [0x61d0000b2c80,0x61d0000b3480)
allocated by thread T0 (file:// Content) here:
#0 0x564bb2d02b0d in malloc /builds/worker/fetches/llvm-project/llvm/projects/compiler-rt/lib/asan/asan_malloc_linux.cpp:145:3
#1 0x7f251d047b54 in Alloc /builds/worker/checkouts/gecko/xpcom/string/nsSubstring.cpp:206:42
#2 0x7f251d047b54 in nsTSubstring<char>::StartBulkWriteImpl(unsigned int, unsigned int, bool, unsigned int, unsigned int, unsigned int) /builds/worker/checkouts/gecko/xpcom/string/nsTSubstring.cpp:202:32
#3 0x7f251d050ab1 in nsTSubstring<char>::SetLength(unsigned int, std::nothrow_t const&) /builds/worker/checkouts/gecko/xpcom/string/nsTSubstring.cpp:936:7
#4 0x7f2520adb66a in nsStructuredCloneContainer::GetDataAsBase64(nsTSubstring<char16_t>&) /builds/worker/checkouts/gecko/dom/base/nsStructuredCloneContainer.cpp:147:19
#5 0x7f2523dfc781 in mozilla::dom::Notification::InitFromJSVal(JSContext*, JS::Handle<JS::Value>, mozilla::ErrorResult&) /builds/worker/checkouts/gecko/dom/notification/Notification.cpp:1952:30
#6 0x7f2523df007d in mozilla::dom::Notification::CreateAndShow(JSContext*, nsIGlobalObject*, nsTSubstring<char16_t> const&, mozilla::dom::NotificationOptions const&, nsTSubstring<char16_t> const&, mozilla::ErrorResult&) /builds/worker/checkouts/gecko/dom/notification/Notification.cpp:2233:17
#7 0x7f2523defb29 in mozilla::dom::Notification::Constructor(mozilla::dom::GlobalObject const&, nsTSubstring<char16_t> const&, mozilla::dom::NotificationOptions const&, mozilla::ErrorResult&) /builds/worker/checkouts/gecko/dom/notification/Notification.cpp:780:7
#8 0x7f25211ae4a2 in mozilla::dom::Notification_Binding::_constructor(JSContext*, unsigned int, JS::Value*) /builds/worker/workspace/obj-build/dom/bindings/NotificationBinding.cpp:2212:58
#9 0x7f25290d357f in CallJSNative /builds/worker/checkouts/gecko/js/src/vm/Interpreter.cpp:385:13
#10 0x7f25290d357f in CallJSNativeConstructor /builds/worker/checkouts/gecko/js/src/vm/Interpreter.cpp:401:8
#11 0x7f25290d357f in InternalConstruct(JSContext*, js::AnyConstructArgs const&) /builds/worker/checkouts/gecko/js/src/vm/Interpreter.cpp:596:10
#12 0x7f2529f47010 in js::jit::DoCallFallback(JSContext*, js::jit::BaselineFrame*, js::jit::ICFallbackStub*, unsigned int, JS::Value*, JS::MutableHandle<JS::Value>) /builds/worker/checkouts/gecko/js/src/jit/BaselineIC.cpp:1571:10
#13 0x7f2492b66de7 (<unknown module>)
#14 0x61600006b9af (<unknown module>)
#15 0x7f2492b6456e (<unknown module>)

SUMMARY: AddressSanitizer: heap-buffer-overflow /builds/worker/fetches/llvm-project/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:22:3 in __asan_memcpy
Shadow bytes around the buggy address:
0x0c3a8000e640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c3a8000e650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c3a8000e660: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c3a8000e670: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c3a8000e680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c3a8000e690:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c3a8000e6a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c3a8000e6b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c3a8000e6c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c3a8000e6d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c3a8000e6e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==2203==ABORTING
###################################################################################################################################################################################

Assignee: nobody → bugs
Status: NEW → ASSIGNED
Summary: Firefox nsStructuredCloneContainer::GetDataAsBase64 Integer Overflow Remote Code Execution Vulnerability → heap buffer overflow in nsStructuredCloneContainer::GetDataAsBase64 from integer overflow

Comment on attachment 9248652 [details]
Bug 1738237, don't try to create too large string buffers, r=mccr8

Security Approval Request

  • How easily could an exploit be constructed based on the patch?: Not sure about exploit but the issue is very obvious.
  • Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: No
  • Which older supported branches are affected by this flaw?: All
  • If not all supported branches, which bug introduced the flaw?: None
  • Do you have backports for the affected branches?: Yes
  • If not, how different, hard to create, and risky will they be?: The same patch applies even to esr78 (with a bit --fuzz)
  • How likely is this patch to cause regressions; how much testing does it need?: Should be very safe. It is just checking overflow.
Flags: needinfo?(bugs)
Attachment #9248652 - Flags: sec-approval?

FWIW, DoConsumeStream has a similar explicit check. CopyCocoaStringToXPCOMString has a different sort of check.

Severity: -- → S2
Priority: -- → P1

Comment on attachment 9248652 [details]
Bug 1738237, don't try to create too large string buffers, r=mccr8

Approved to land and request uplift

Attachment #9248652 - Flags: sec-approval? → sec-approval+

Comment on attachment 9248652 [details]
Bug 1738237, don't try to create too large string buffers, r=mccr8

Beta/Release Uplift Approval Request

  • User impact if declined: Crashes, in an unsafe way
  • Is this code covered by automated tests?: No
  • Has the fix been verified in Nightly?: No
  • Needs manual test from QE?: Yes
  • If yes, steps to reproduce: Run the poc.html
  • List of other uplifts needed: None
  • Risk to taking this patch: Low
  • Why is the change risky/not risky? (and alternatives if risky): Requires passing unusually large array to an API which isn't usually used for that
  • String changes made/needed: NA

ESR Uplift Approval Request

  • If this is not a sec:{high,crit} bug, please state case for ESR consideration:
  • User impact if declined:
  • Fix Landed on Version:
  • Risk to taking this patch: Low
  • Why is the change risky/not risky? (and alternatives if risky):
  • String or UUID changes made by this patch:
Attachment #9248652 - Flags: approval-mozilla-esr91?
Attachment #9248652 - Flags: approval-mozilla-beta?
Flags: qe-verify+
Group: dom-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 2 years ago
Resolution: --- → FIXED
Target Milestone: --- → 96 Branch
Flags: sec-bounty? → sec-bounty+
QA Whiteboard: [qa-triaged]

As part of a security bug pattern analysis, we are requesting your help with a high level analysis of this bug. It is our hope to develop static analysis (or potentially runtime/dynamic analysis) in the future to identify classes of bugs.

Please visit this google form to reply.

Flags: needinfo?(bugs)
Whiteboard: [reporter-external] [client-bounty-form] [verif?] → [reporter-external] [client-bounty-form] [verif?][sec-survey]
Flags: needinfo?(bugs)

Reproduced the tab crash on Windows 10x64 using 95.0a1 (20211028223457) and on Ubuntu 18.04 using Firefox 94.0.2 (20211104142158) asan build from comment 3 and the attached test case.
Tab no longer crashes on Ubuntu 18.04 with Firefox 96.0a1 (20211111045525) asan and normal build. Also, the test case was loaded on Windows 10x64 and macOS 10.15 with Firefox 96.0a1 (20211111045525), and the tab is loaded correctly with no crashes.

Maybe worth mentioning that the tab with the attached test case from comment 2 is not loaded instantly and only after ~2-4 sec.

Comment on attachment 9248652 [details]
Bug 1738237, don't try to create too large string buffers, r=mccr8

Approved for 95.0b6

Attachment #9248652 - Flags: approval-mozilla-beta? → approval-mozilla-beta+

Verified that the tab is no longer crashing when loading the attached test case with Firefox 95.0b6 on Windows 10x64, macOS 10.15, and Ubuntu 18.04 and 20.04.

Just to be sure here. Is it ok that the loading time for the attached test case is higher on Firefox than on other browsers? For example, Chrome and Safari load the test case instantly and Firefox loads it in like ~2-5 seconds. Should we open another issue for this? Thank you!

Flags: needinfo?(bugs)

That would be JS perf issue. If you extract that part from the test, filing a new bug sounds reasonable.

Flags: needinfo?(bugs)

(In reply to Olli Pettay [:smaug] from comment #15)

That would be JS perf issue. If you extract that part from the test, filing a new bug sounds reasonable.

Thank you! Marking Firefox 95 as verified per comment 14 and comment 15. Also, bug 1740923 was logged for the remaining issue.

See Also: → 1739219

Comment on attachment 9248652 [details]
Bug 1738237, don't try to create too large string buffers, r=mccr8

Approved for 91.4esr.

Attachment #9248652 - Flags: approval-mozilla-esr91? → approval-mozilla-esr91+

Verified fixed with 91.4.0esr (20211115182036) on Windows 10x64, macOS 10.15, Ubuntu 18.04 and 20.04. Tab no longer crashes when loading the attached test case.

Status: RESOLVED → VERIFIED
Flags: qe-verify+
Whiteboard: [reporter-external] [client-bounty-form] [verif?][sec-survey] → [reporter-external] [client-bounty-form][sec-survey][adv-main95+][adv-ESR91.4.0+]
Alias: CVE-2021-43537
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: