Commit e423572a authored by Ulan Degenbaev's avatar Ulan Degenbaev Committed by Commit Bot

Reveal only the initial frame URL in performance.measureMemory

This changes the reporting in performance.measureMemory to reveal only
the URL at the frame creation time for cross-origin iframes and
thus prevents URL information leak if the iframe navigates after loading.

This is done by saving the first cross-origin URL loaded in LocalFrame
in LocalFrame::DidAttachDocument and using that URL for reporting.

The patch also adds a new test for redirecting iframe and fixes
the existing tests to wait until child iframes are loaded.

Bug: 1049093
Change-Id: I0162efb82061f036450586dea0b191cacdce23f2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2098722
Commit-Queue: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#749663}
parent 4ebe8f60
...@@ -577,6 +577,13 @@ void LocalFrame::DidAttachDocument() { ...@@ -577,6 +577,13 @@ void LocalFrame::DidAttachDocument() {
GetInputMethodController().DidAttachDocument(document); GetInputMethodController().DidAttachDocument(document);
GetSpellChecker().DidAttachDocument(document); GetSpellChecker().DidAttachDocument(document);
GetTextSuggestionController().DidAttachDocument(document); GetTextSuggestionController().DidAttachDocument(document);
if (IsCrossOriginToParentFrame() && !first_url_cross_origin_to_parent_) {
first_url_cross_origin_to_parent_ = GetDocument()->Url().GetString();
}
}
base::Optional<String> LocalFrame::FirstUrlCrossOriginToParent() const {
return first_url_cross_origin_to_parent_;
} }
void LocalFrame::Reload(WebFrameLoadType load_type) { void LocalFrame::Reload(WebFrameLoadType load_type) {
......
...@@ -546,6 +546,10 @@ class CORE_EXPORT LocalFrame final : public Frame, ...@@ -546,6 +546,10 @@ class CORE_EXPORT LocalFrame final : public Frame,
// Indicate that this frame was attached as a MainFrame. // Indicate that this frame was attached as a MainFrame.
void WasAttachedAsLocalMainFrame(); void WasAttachedAsLocalMainFrame();
// Returns the first URL loaded in this frame that is cross-origin to the
// parent frame.
base::Optional<String> FirstUrlCrossOriginToParent() const;
private: private:
friend class FrameNavigationDisabler; friend class FrameNavigationDisabler;
...@@ -701,6 +705,14 @@ class CORE_EXPORT LocalFrame final : public Frame, ...@@ -701,6 +705,14 @@ class CORE_EXPORT LocalFrame final : public Frame,
Member<SystemClipboard> system_clipboard_; Member<SystemClipboard> system_clipboard_;
// Access to the global raw/unsanitized system clipboard // Access to the global raw/unsanitized system clipboard
Member<RawSystemClipboard> raw_system_clipboard_; Member<RawSystemClipboard> raw_system_clipboard_;
// Stores the first URL that was loaded in this frame that is cross-origin
// to the parent frame. For this URL we know that the parent origin provided
// it by either setting the |src| attribute or by navigating the iframe.
// Thus we can safely reveal it to the parent origin in Web APIs.
// TODO(ulan): Move this to the browser process once performance.measureMemory
// starts using Performance Manager to support cross-site iframes.
base::Optional<String> first_url_cross_origin_to_parent_;
}; };
inline FrameLoader& LocalFrame::Loader() const { inline FrameLoader& LocalFrame::Loader() const {
......
...@@ -87,11 +87,17 @@ const Frame* GetFrame(v8::Local<v8::Context> context) { ...@@ -87,11 +87,17 @@ const Frame* GetFrame(v8::Local<v8::Context> context) {
} }
String GetUrl(const Frame* frame) { String GetUrl(const Frame* frame) {
// TODO(ulan): Find a way to return the URL at frames open time. // TODO(ulan): Refactor the rest of the code to make the parameter LocalFrame.
const LocalFrame* local_frame = To<LocalFrame>(frame); const LocalFrame* local_frame = To<LocalFrame>(frame);
ExecutionContext* execution_context = if (local_frame->IsCrossOriginToParentFrame()) {
local_frame->GetDocument()->ToExecutionContext(); // The function must be called only for the first cross-origin iframe on
return execution_context->Url().GetString(); // the path down from the main frame. Thus the parent frame is guaranteed
// to be the same origin as the main frame.
DCHECK(!local_frame->Tree().Parent()->IsCrossOriginToMainFrame());
base::Optional<String> url = local_frame->FirstUrlCrossOriginToParent();
return url ? url.value() : "";
}
return local_frame->GetDocument()->Url().GetString();
} }
// To avoid information leaks cross-origin iframes are considered opaque for // To avoid information leaks cross-origin iframes are considered opaque for
......
...@@ -6270,8 +6270,8 @@ http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test.js [ Skip ] ...@@ -6270,8 +6270,8 @@ http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test.js [ Skip ]
# Memory measurement tests are run as virtual tests. # Memory measurement tests are run as virtual tests.
external/wpt/measure-memory/measure-memory.tentative.any.html [ Skip ] external/wpt/measure-memory/measure-memory.tentative.any.html [ Skip ]
external/wpt/measure-memory/measure-memory.tentative.any.worker.html [ Skip ]
external/wpt/measure-memory/measure-memory-cross-origin-iframe.tentative.window.html [ Skip ] external/wpt/measure-memory/measure-memory-cross-origin-iframe.tentative.window.html [ Skip ]
external/wpt/measure-memory/measure-memory-cross-origin-redirecting-iframe.tentative.window.html [ Skip ]
external/wpt/measure-memory/measure-memory-same-origin-iframe.tentative.window.html [ Skip ] external/wpt/measure-memory/measure-memory-same-origin-iframe.tentative.window.html [ Skip ]
# Assertion errors need to be fixed # Assertion errors need to be fixed
......
// META: script=/common/get-host-info.sub.js // META: script=/common/get-host-info.sub.js
// META: script=./resources/common.js // META: script=./resources/common.js
// META: timeout=long
'use strict'; 'use strict';
promise_test(async testCase => { promise_test(async testCase => {
const frame = document.createElement("iframe"); const grandchildLoaded = new Promise(resolve => {
const child = getUrl(CROSS_ORIGIN, "resources/child.sub.html"); window.onmessage = function(message) {
const grandchild = getUrl(CROSS_ORIGIN, "resources/grandchild.sub.html"); if (message.data === 'grandchild-loaded') {
resolve(message);
}
}
});
const frame = document.createElement('iframe');
const child = getUrl(CROSS_ORIGIN, 'resources/child.sub.html');
frame.src = child; frame.src = child;
document.body.append(frame); document.body.append(frame);
await grandchildLoaded;
try { try {
let result = await performance.measureMemory(); let result = await performance.measureMemory();
checkMeasureMemory(result, { checkMeasureMemory(result, {
......
// META: script=/common/get-host-info.sub.js
// META: script=./resources/common.js
'use strict';
promise_test(async testCase => {
const grandchildLoaded = new Promise(resolve => {
window.onmessage = function(message) {
if (message.data === 'grandchild-loaded') {
resolve(message);
}
}
});
const frame = document.createElement('iframe');
const redirecting_child = getUrl(CROSS_ORIGIN, 'resources/redirecting-child.sub.html');
frame.src = redirecting_child;
document.body.append(frame);
await grandchildLoaded;
try {
let result = await performance.measureMemory();
checkMeasureMemory(result, {
allowed: [window.location.href, redirecting_child]
});
} catch (error) {
if (!(error instanceof DOMException)) {
throw error;
}
assert_equals(error.name, 'SecurityError');
}
}, 'Well-formed result of performance.measureMemory with cross-origin iframe.');
// META: script=/common/get-host-info.sub.js // META: script=/common/get-host-info.sub.js
// META: script=./resources/common.js // META: script=./resources/common.js
// META: timeout=long
'use strict'; 'use strict';
promise_test(async testCase => { promise_test(async testCase => {
const frame = document.createElement("iframe"); const grandchildLoaded = new Promise(resolve => {
const child = getUrl(SAME_ORIGIN, "resources/child.sub.html"); window.onmessage = function(message) {
const grandchild = getUrl(SAME_ORIGIN, "resources/grandchild.sub.html"); if (message.data === 'grandchild-loaded') {
resolve(message);
}
}
});
const frame = document.createElement('iframe');
const child = getUrl(SAME_ORIGIN, 'resources/child.sub.html');
const grandchild = getUrl(SAME_ORIGIN, 'resources/grandchild.sub.html');
frame.src = child; frame.src = child;
document.body.append(frame); document.body.append(frame);
await grandchildLoaded;
try { try {
let result = await performance.measureMemory(); let result = await performance.measureMemory();
checkMeasureMemory(result, { checkMeasureMemory(result, {
......
<!doctype html> <!doctype html>
<meta charset=utf-8> <meta charset=utf-8>
<html> <html>
<script>
window.onmessage = function (message) {
// Forward the message to the parent.
window.parent.postMessage(message.data, '*');
}
window.onload = function () {
window.parent.postMessage('grandchild-loaded', '*');
}
</script>
<body> <body>
Hello from child iframe. Hello from child iframe.
<iframe src="grandchild.sub.html"></iframe> <iframe src="grandchild.sub.html"></iframe>
......
const SAME_ORIGIN = {origin: get_host_info().HTTPS_ORIGIN, name: "SAME_ORIGIN"}; const SAME_ORIGIN = {origin: get_host_info().HTTP_ORIGIN, name: "SAME_ORIGIN"};
const CROSS_ORIGIN = {origin: get_host_info().HTTPS_NOTSAMESITE_ORIGIN, name: "CROSS_ORIGIN"} const CROSS_ORIGIN = {origin: get_host_info().HTTP_REMOTE_ORIGIN, name: "CROSS_ORIGIN"}
function checkMeasureMemoryBreakdown(breakdown, options) { function checkMeasureMemoryBreakdown(breakdown, options) {
let allowed = new Set(options.allowed); let allowed = new Set(options.allowed);
...@@ -31,5 +31,5 @@ function checkMeasureMemory(result, options) { ...@@ -31,5 +31,5 @@ function checkMeasureMemory(result, options) {
function getUrl(host, relativePath) { function getUrl(host, relativePath) {
const path = new URL(relativePath, window.location).pathname; const path = new URL(relativePath, window.location).pathname;
return `${host.origin}/${path}`; return `${host.origin}${path}`;
} }
\ No newline at end of file
<!doctype html> <!doctype html>
<meta charset=utf-8> <meta charset=utf-8>
<html> <html>
<script>
window.onload = function () {
window.parent.postMessage('grandchild-loaded', '*');
}
</script>
<body> <body>
Hello from grandchild iframe. Hello from grandchild iframe.
</body> </body>
......
<!doctype html>
<meta charset=utf-8>
<html>
<script>
window.onload = function () {
document.location.href = document.location.href.replace('redirecting-child', 'child');
}
</script>
<body>
Hello from child iframe.
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment