[CVE-2019-16074] Brave Browser AdBlock: Arbitrary Out-of-Bounds Read (AdBlockClient::deserialize)

Description

Brave Browser implements a built-in AdBlock component that can parse AdBlock Plus filters (e.g. EasyList). The parser is implemented from Brave in native C++ code and was found to be vulnerable to an out-of-bounds (OOB) read of arbitrary size.
Exploiting this vulnerability enables an adversary to read to an arbitrary memory address from Chrome’s privileged process since the AdBlock initialization is executed from the main process before delegating to sandboxed workers. This means one could use this vulnerability to perform information disclosure chain this with other vulnerabilities to perform code execution.

CVSS 3.0 Base Score

6.5 (AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N)

Researcher

xen1thlabs software labs

POC

The vulnerability was identified via fuzzing of a standalone AdBlock parser implementation and was reproduced in Brave version 1.0.77 and 1.0.94.The crash was initially triaged with Clang’s Address Sanitizer (ASan), which produced the following report.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
=================================================================
==22248==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7fa1330f673f at pc 0x7fa13705a935
bp 0x7ffedf96f210 sp 0x7ffedf96e9b8
READ of size 150001 at 0x7fa1330f673f thread T0
#0 0x7fa13705a934 in __asan_memcpy (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x8c934)
#1 0x423783 in BloomFilter::BloomFilter(
char
const*,
int, HashFn*,
int) ../../../node_modules/bloom-filter-cpp/BloomFilter.cpp:31
#2 0x40bce9 in AdBlockClient::initBloomFilter(BloomFilter**,
char const*,int) ../../../ad_block_client.cc:1005
#3 0x413ff7 in AdBlockClient::deserialize(
char*) ../../../ad_block_client.cc:1768
#4 0x4034df in checkUrls(
char
const*, std::vector<std::__cxx11::basic_string<
char, std::char_traits<
char>, std::allocator<
char> >, std::allocator<std::__cxx11::basic_string<
char, std::char_traits<
char>, std::allocator<
char> > > >
const&) ../../../main.cc:97
#5 0x40394b in main ../../../main.cc:125
#6 0x7fa13668c82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#7 0x4026c8 in _start (/home/dmuser/brave/sample_asan+0x4026c8)0x7fa1330f673f is located 0 bytes
to the right of 2694975-
byte region [0x7fa132e64800,0x7fa1330f673f)
allocated by thread T0 here:
#0 0x7fa137067532 in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x99532)
#1 0x7fa136d5eeec (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0x112eec)
#2 0x7fa136cfc19d in std::__basic_file<
char>::close() (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xb019d)
#3 0x7ffedf96f48f (<unknown module>)SUMMARY: AddressSanitizer: heap-buffer-overflow ??:0 __asan_memcpy
Shadow bytes around the buggy address:
0x0ff4a6616c90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ff4a6616ca0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ff4a6616cb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ff4a6616cc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ff4a6616cd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0ff4a6616ce0: 00 00 00 00 00 00 00[07]fa fa fa fa fa fa fa fa
0x0ff4a6616cf0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff4a6616d00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff4a6616d10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff4a6616d20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff4a6616d30: 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
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
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
==22248==ABORTING

The top frame indicates an OOB (Out-Of-Bands) read in line 31, with 150001 bytes being read beyond the boundaries of the buffer heap allocated buffer.

1
2
3
4
5
6
7
8
9
10
11
12
13
File: bloom-filter-cpp/BloomFilter.cpp
21 // Constructs a BloomFilter by copying the specified buffer and number of bytes
22 BloomFilter::BloomFilter(const char *buffer, int byteBufferSize,
23 HashFn *hashFns, int numHashFns) :
24 hashFns(nullptr), numHashFns(0), byteBufferSize(0), buffer(nullptr) {
25 this->hashFns = hashFns;
26 this->numHashFns = numHashFns;
27 lastHashes = new uint64_t[numHashFns];
28 this->byteBufferSize = byteBufferSize;
29 bitBufferSize = byteBufferSize * 8;
30 this->buffer = new char[byteBufferSize];
31 memcpy(this->buffer, buffer, byteBufferSize);
32 }

BloomFilter::BloomFilter is invoked from AdBlockClient::initBloomFilter (line 1005), as indicated in the second frame of the stack trace.

1
2
3
4
5
6
7
8
9
10
File: ad-block/ad_block_client.cc 
999 void AdBlockClient::initBloomFilter(BloomFilter **pp,
1000 const char *buffer, int len) {
1001 if (*pp) {
1002 delete *pp;
1003 }
1004 if (len > 0) {
1005 *pp = new BloomFilter(buffer, len);
1006 }
1007 }

AdBlockClient::initBloomFilter is invoked from AdBlockClient::deserialize (line 1768), as indicated in the third frame of the stack trace. Notice in line 1721 that the bloomFilterSize variable is directly read from the untrusted input file and used to offset the input buffer without any boundary checks taking place. Therefore, an attacker can read up to max size_t bytes beyond the heap buffer, since the integer variable will be eventually casted to size_t in the memcpy call of the BloomFilter::BloomFilter function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
File: ad-block/ad_block_client.cc
1702 bool AdBlockClient::deserialize(
char *buffer) {
1703 deserializedBuffer = buffer;
1704
int bloomFilterSize = 0, exceptionBloomFilterSize = 0,
[..]
1711 sscanf(buffer + pos,
1712
"%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x",
1713 &numFilters,
1714 &numExceptionFilters, &numCosmeticFilters, &numHtmlFilters,
1715 &numNoFingerprintFilters, &numNoFingerprintExceptionFilters,
1716 &numNoFingerprintDomainOnlyFilters,
1717 &numNoFingerprintAntiDomainOnlyFilters,
1718 &numNoFingerprintDomainOnlyExceptionFilters,
1719 &numNoFingerprintAntiDomainOnlyExceptionFilters,
1720 &numHostAnchoredFilters, &numHostAnchoredExceptionFilters,
1721 &bloomFilterSize, &exceptionBloomFilterSize,
1722 &hostAnchoredHashSetSize, &hostAnchoredExceptionHashSetSize,
1723 &noFingerprintDomainHashSetSize,
1724 &noFingerprintAntiDomainHashSetSize,
1725 &noFingerprintDomainExceptionHashSetSize,
1726 &noFingerprintAntiDomainExceptionHashSetSize);
[..]
1768 initBloomFilter(&bloomFilter, buffer + pos, bloomFilterSize);

Now tracing the place within the application that the AdBlock payload (as downloaded over the Internet from Brave endpoints in AmazonS3) is actually parsed identified the following. The AdBlockClient::deserialize is called in line 197 of the BlockersWorker::InitAdBlock function, which first reads the file contents in line 192 that were downloaded on previous steps (see ADBlockUpdater.UpdateADBlock() invoked early on application launch).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
File: chrome/browser/net/blockers/blockers_worker.cc
184 bool BlockersWorker::InitAdBlock() {
185 base::AssertBlockingAllowed();
186 std::lock_guard<std::mutex> guard(adblock_init_mutex_);
187
188
if (adblock_parser_) {
189
return
true;
190 }
191
192
if (!GetData(std::to_string(DATA_FILE_VERSION), ADBLOCK_DATA_FILE, adblock_buffer_)) {
193
return
false;
194 }
195
196 adblock_parser_ =
new AdBlockClient();
197
if (!adblock_parser_->deserialize((
char*)&adblock_buffer_.front()))

BlockersWorker::InitAdBlock is invoked from ChromeNetworkDelegate::OnBeforeURLRequest_AdBlockFileWork (line 577). The ChromeNetworkDelegate family of callbacks is the central point from within the chrome code to add hooks into the network stack that executed from the main process.

1
2
3
4
5
6
7
8
9
10
11
12
File: chrome/browser/net/chrome_network_delegate.cc
575 void ChromeNetworkDelegate::OnBeforeURLRequest_AdBlockFileWork(std::shared_ptr<OnBeforeURLRequestContext>
ctx) {
576 base::AssertBlockingAllowed();
577 blockers_worker_->InitAdBlock();
578
579
if (ctx->isAdBlockRegionalEnabled &&
580 !blockers_worker_->isAdBlockerRegionalInitialized()) {
581 blockers_worker_->InitAdBlockRegional();
582 }
583 }

As a proof of concept the malformed file generated from the fuzzer was provided as input to the Brave application (served over the web via a Man-in-The-Middle attack by replacing 4ABPFilterParserData.dat file). Upon visiting any URL the main process of the Brave Browser crashed with the following logcat message.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
04-03 08:28:42.933 3641 3659 I cr_ADB : Downloaded 4ABPFilterParserData.dat
04-03 08:28:42.933 3641 3659 I cr_ADB : Creating ABPFilterParserDataDownloaded.dat from
4ABPFilterParserData.dat
04-03 08:28:42.937 3641 3659 I cr_ADB : Created ABPFilterParserDataDownloaded.dat from
4ABPFilterParserData.dat
04-03 08:28:42.950 3641 3659 I cr_ADB : Downloading 6.0httpse.leveldb.zip
04-03 08:28:58.671 3641 3659 I cr_ADB : Downloaded 6.0httpse.leveldb.zip
04-03 08:28:58.894 3641 3659 I cr_ADB : Creating httpse.leveldbDownloaded.zip from
6.0httpse.leveldb
04-03 08:28:58.895 3641 3659 I cr_ADB : Created httpse.leveldbDownloaded.zip from 6.0httpse.leveldb
04-03 08:29:10.352 3641 3641 W cr_AutocompleteEdit: Using spannable model...
04-03 08:29:10.355 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:10.370 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:10.405 3641 3646 I zygote : Do partial code cache collection, code=62KB, data=49KB
04-03 08:29:10.405 3641 3646 I zygote : After code cache collection, code=62KB, data=49KB
04-03 08:29:10.405 3641 3646 I zygote : Increasing code cache capacity to 256KB
04-03 08:29:10.604 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:10.617 3641 3641 I chatty : uid=10081(com.brave.browser) identical 2 lines
04-03 08:29:10.619 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:12.154 3641 3641 W cr_SpanAutocomplete: Did not notify - in batch edit.
04-03 08:29:12.159 3641 3641 I chatty : uid=10081(com.brave.browser) identical 2 lines
04-03 08:29:12.171 3641 3641 W cr_SpanAutocomplete: Did not notify - in batch edit.
04-03 08:29:12.194 3641 3641 W cr_UrlBar: Text change observed, triggering autocomplete.
04-03 08:29:12.194 3641 3641 W cr_Autocomplete: onTextChangedForAutocomplete
04-03 08:29:12.194 3641 3641 W cr_Autocomplete: stopping autocomplete.
04-03 08:29:12.225 3641 3641 W cr_Autocomplete: starting autocomplete controller..[
false][
false]
04-03 08:29:12.229 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:12.234 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:12.351 3641 3641 W cr_SpanAutocomplete: Did not notify - in batch edit.
04-03 08:29:12.354 3641 3641 I chatty : uid=10081(com.brave.browser) identical 2 lines
04-03 08:29:12.372 3641 3641 W cr_SpanAutocomplete: Did not notify - in batch edit.
04-03 08:29:12.374 3641 3641 W cr_UrlBar: Text change observed, triggering autocomplete.
04-03 08:29:12.374 3641 3641 W cr_Autocomplete: onTextChangedForAutocomplete
04-03 08:29:12.374 3641 3641 W cr_Autocomplete: stopping autocomplete.
04-03 08:29:12.406 3641 3641 W cr_Autocomplete: starting autocomplete controller..[
false][false]
04-03 08:29:12.410 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:12.412 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:12.531 3641 3641 W cr_SpanAutocomplete: Did not notify - in batch edit.
04-03 08:29:12.532 3641 3641 I chatty : uid=10081(com.brave.browser) identical 2 lines
04-03 08:29:12.539 3641 3641 W cr_SpanAutocomplete: Did not notify - in batch edit.
04-03 08:29:12.542 3641 3641 W cr_UrlBar: Text change observed, triggering autocomplete.
04-03 08:29:12.542 3641 3641 W cr_Autocomplete: onTextChangedForAutocomplete
04-03 08:29:12.542 3641 3641 W cr_Autocomplete: stopping autocomplete.
04-03 08:29:12.573 3641 3641 W cr_Autocomplete: starting autocomplete controller..[
false][
false]
04-03 08:29:12.577 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:12.578 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:12.735 3641 3641 W cr_SpanAutocomplete: Did not notify - in batch edit.
04-03 08:29:12.737 3641 3641 I chatty : uid=10081(com.brave.browser) identical 2 lines
04-03 08:29:12.740 3641 3641 W cr_SpanAutocomplete: Did not notify - in batch edit.
04-03 08:29:12.743 3641 3641 W cr_UrlBar: Text change observed, triggering autocomplete.
04-03 08:29:12.743 3641 3641 W cr_Autocomplete: onTextChangedForAutocomplete
04-03 08:29:12.743 3641 3641 W cr_Autocomplete: stopping autocomplete.
04-03 08:29:12.774 3641 3641 W cr_Autocomplete: starting autocomplete controller..[
false][04-03 08:29:12.778 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:12.780 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:12.906 3641 3641 W cr_SpanAutocomplete: Did not notify - in batch edit.
04-03 08:29:12.910 3641 3641 I chatty : uid=10081(com.brave.browser) identical 2 lines
04-03 08:29:12.925 3641 3641 W cr_SpanAutocomplete: Did not notify - in batch edit.
04-03 08:29:12.928 3641 3641 W cr_UrlBar: Text change observed, triggering autocomplete.
04-03 08:29:12.928 3641 3641 W cr_Autocomplete: onTextChangedForAutocomplete
04-03 08:29:12.928 3641 3641 W cr_Autocomplete: stopping autocomplete.
04-03 08:29:12.960 3641 3641 W cr_Autocomplete: starting autocomplete controller..[
false][
false]04-03 08:29:12.963 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:12.965 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:13.410 3641 3641 W cr_Autocomplete: stopping autocomplete.
04-03 08:29:13.561 3641 3641 W cr_SpanAutocomplete: Did not notify - ignored.
04-03 08:29:13.568 3641 3641 W cr_Autocomplete: stopping autocomplete.
04-03 08:29:13.569 3641 3641 W cr_Autocomplete: stopping autocomplete.
04-03 08:29:13.570 3641 3641 W cr_Autocomplete: stopping autocomplete.
04-03 08:29:13.571 3641 3641 W cr_Autocomplete: stopping autocomplete.
04-03 08:29:13.571 3641 3641 W cr_SpanAutocomplete: Did not notify - no change.
04-03 08:29:13.578 3641 3689 E cr_ApiBridge: Failed to init handler: Attempt to invoke virtual
method'java.lang.reflect.Constructor java.lang.
Class.getDeclaredConstructor(java.lang.
Class[])' on a
null object reference
04-03 08:29:13.589 3641 3641 W IInputConnectionWrapper: getSelectedText on inactive InputConnection
04-03 08:29:13.592 3641 3641 W IInputConnectionWrapper: requestCursorAnchorInfo on inactive
InputConnection
04-03 08:29:13.592 3641 3641 W IInputConnectionWrapper: getTextBeforeCursor on inactive
InputConnection
04-03 08:29:13.595 3641 3641 W IInputConnectionWrapper: getTextBeforeCursor on inactive
InputConnection
04-03 08:29:13.598 3641 3641 W IInputConnectionWrapper: getTextBeforeCursor on inactive
InputConnection
04-03 08:29:15.852 3641 3641 W cr_SpanAutocomplete: Did not notify - ignored.
04-03 08:29:15.893 3641 3641 I zygote : Deoptimizing void bmi.onMeasure(
int,
int) due to JIT inline cache
04-03 08:29:15.908 3641 3689 F libc : Fatal signal 8 (SIGFPE), code -6, fault addr 0xe39 in tid
3689 (Chrome_IOThread), pid 3641 (m.brave.browser)
04-03 08:29:15.964 3834 3834 I crash_dump32: obtaining output fd from tombstoned, type:
kDebuggerdTombstone
04-03 08:29:15.968 3834 3834 I crash_dump32: performing dump of process 3641 (target tid = 3689)
04-03 08:29:15.968 3834 3834 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
***
04-03 08:29:15.969 3834 3834 F DEBUG : Build fingerprint:
'Android/aosp_sailfish/sailfish:8.1.0/OPM4.171019.021.P1/anesti09272147:userdebug/test-keys'
04-03 08:29:15.969 3834 3834 F DEBUG : Revision:
'0'
04-03 08:29:15.969 3834 3834 F DEBUG : ABI:
'arm'
04-03 08:29:15.969 3834 3834 F DEBUG : pid: 3641, tid: 3689, name: Chrome_IOThread >>>
com.brave.browser <<<
04-03 08:29:15.969 3834 3834 F DEBUG : signal 8 (SIGFPE), code -6 (SI_TKILL), fault addr --------
04-03 08:29:15.969 3834 3834 F DEBUG : r0 00000000 r1 00000e69 r2 00000008 r3 c4a7f344
04-03 08:29:15.969 3834 3834 F DEBUG : r4 c4a7f344 r5 00000000 r6 dc4535e8 r7 0000010c
04-03 08:29:15.969 3834 3834 F DEBUG : r8 0000000e r9 dc46b7ee sl dc46bdd0 fp 0000000e
04-03 08:29:15.969 3834 3834 F DEBUG : ip c4a7f344 sp c4a7f328 lr c64dec00 pc e70eebbc
cpsr 00070010
04-03 08:29:15.970 3834 3834 F DEBUG :
04-03 08:29:15.970 3834 3834 F DEBUG : backtrace:
04-03 08:29:15.970 3834 3834 F DEBUG : #00 pc 0004abbc /system/lib/libc.so (tgkill+12)
04-03 08:29:15.970 3834 3834 F DEBUG : #01 pc 00035bfc /data/app/
com.brave.browser-3sVE2mDMPdmzlOkZCO13Hw==/base.apk (offset 0x16dc000)
04-03 08:29:17.198 626 626 I Zygote :
Process 3641 exited due to signal (8)

POC Attachments

Not released

Disclosure Timelines

  • 19-Jun-2019 - Notified vendor
  • 15-Aug-2019 - Brave browser Android v1.2.0 released which resolves this
Namaste.
You can know about me at my portfolio.
I follow my own Vulnerability Disclosure Policy.
Most of my work is listed here.