Bug 1503984 - Add a pref to block chrome code from accessing content subframes. r=nika

The pref dom.chrome_frame_access.enabled will default to true. When false, it will block various methods that chrome code can use to traverse subframes. The initial list is:
  iframe.contentWindow
  iframe.contentDocument
  window.top
  window.parent
  window.opener
  window.frames[i]
  window.frames.length
  MessageEvent.source

More blocks are likely to be added in the future.

Differential Revision: https://phabricator.services.mozilla.com/D13180

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Felipe Gomes 2018-12-18 18:53:24 +00:00
Родитель ba862815a3
Коммит 72585dea54
14 изменённых файлов: 159 добавлений и 20 удалений

Просмотреть файл

@ -99,7 +99,9 @@ bool WindowNamedPropertiesHandler::getOwnPropDescriptor(
// Grab the DOM window.
nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aProxy);
if (win->Length() > 0) {
if (win->Length(nsContentUtils::IsSystemCaller(aCx)
? CallerType::System
: CallerType::NonSystem) > 0) {
nsCOMPtr<nsPIDOMWindowOuter> childWin = win->GetChildWindow(str);
if (childWin && ShouldExposeChildWindow(str, childWin)) {
// We found a subframe of the right name. Shadowing via |var foo| in

Просмотреть файл

@ -2635,8 +2635,8 @@ nsDOMWindowList* nsGlobalWindowInner::GetFrames() {
}
already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowInner::IndexedGetter(
uint32_t aIndex) {
FORWARD_TO_OUTER(IndexedGetterOuter, (aIndex), nullptr);
JSContext* aCx, uint32_t aIndex) {
FORWARD_TO_OUTER(IndexedGetterOuter, (aCx, aIndex), nullptr);
}
namespace {
@ -2884,6 +2884,17 @@ void nsGlobalWindowInner::GetOwnPropertyNames(JSContext* aCx,
nsContentUtils::GetSystemPrincipal();
}
/* static */ bool nsGlobalWindowInner::AllowChromeFrameAccess(JSContext* aCx,
JSObject* aObj) {
if (StaticPrefs::dom_chrome_frame_access_enabled() ||
!nsContentUtils::IsSystemCaller(aCx)) {
return true;
}
return nsContentUtils::ObjectPrincipal(aObj) ==
nsContentUtils::GetSystemPrincipal();
}
/* static */ bool nsGlobalWindowInner::OfflineCacheAllowedForContext(
JSContext* aCx, JSObject* aObj) {
return IsSecureContextOrObjectIsFromSecureContext(aCx, aObj) ||
@ -3304,7 +3315,9 @@ double nsGlobalWindowInner::GetScrollY(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetScrollYOuter, (), aError, 0);
}
uint32_t nsGlobalWindowInner::Length() { FORWARD_TO_OUTER(Length, (), 0); }
uint32_t nsGlobalWindowInner::Length(mozilla::dom::CallerType aCallerType) {
FORWARD_TO_OUTER(Length, (aCallerType), 0);
}
already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowInner::GetTop(
mozilla::ErrorResult& aError) {

Просмотреть файл

@ -387,10 +387,20 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
NS_DECL_NSIINTERFACEREQUESTOR
// WebIDL interface.
already_AddRefed<nsPIDOMWindowOuter> IndexedGetter(uint32_t aIndex);
already_AddRefed<nsPIDOMWindowOuter> IndexedGetter(JSContext* aCx,
uint32_t aIndex);
static bool IsPrivilegedChromeWindow(JSContext* /* unused */, JSObject* aObj);
// The pref dom.chrome_frame_access.enabled can be used to block
// various methods that chrome code can be used to traverse
// content subframes. This is useful to enforce Fission-compatible
// behavior on code. The current methods blocked are:
// iframe.contentWindow, iframe.contentDocument, window.top,
// window.opener, window.parent, window.frames[i], window.frames.length,
// MessageEvent.source.
static bool AllowChromeFrameAccess(JSContext* aCx, JSObject* aObj);
static bool OfflineCacheAllowedForContext(JSContext* /* unused */,
JSObject* aObj);
@ -622,7 +632,7 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
void Blur(mozilla::ErrorResult& aError);
nsDOMWindowList* GetFrames() final;
already_AddRefed<nsPIDOMWindowOuter> GetFrames(mozilla::ErrorResult& aError);
uint32_t Length();
uint32_t Length(mozilla::dom::CallerType aCallerType);
already_AddRefed<nsPIDOMWindowOuter> GetTop(mozilla::ErrorResult& aError);
protected:

Просмотреть файл

@ -779,12 +779,14 @@ already_AddRefed<nsPIDOMWindowOuter> nsOuterWindowProxy::GetSubframeWindow(
}
nsGlobalWindowOuter* win = GetOuterWindow(proxy);
return win->IndexedGetterOuter(index);
return win->IndexedGetterOuter(cx, index);
}
bool nsOuterWindowProxy::AppendIndexedPropertyNames(
JSContext* cx, JSObject* proxy, JS::AutoIdVector& props) const {
uint32_t length = GetOuterWindow(proxy)->Length();
uint32_t length = GetOuterWindow(proxy)->Length(
nsContentUtils::IsSystemCaller(cx) ? CallerType::System
: CallerType::NonSystem);
MOZ_ASSERT(int32_t(length) >= 0);
if (!props.reserve(props.length() + length)) {
return false;
@ -2837,11 +2839,22 @@ nsDOMWindowList* nsGlobalWindowOuter::GetFrames() {
}
already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::IndexedGetterOuter(
uint32_t aIndex) {
JSContext* aCx, uint32_t aIndex) {
nsDOMWindowList* windows = GetFrames();
NS_ENSURE_TRUE(windows, nullptr);
return windows->IndexedGetter(aIndex);
nsCOMPtr<nsPIDOMWindowOuter> win = windows->IndexedGetter(aIndex);
if (win && !StaticPrefs::dom_chrome_frame_access_enabled()) {
nsGlobalWindowOuter* global = nsGlobalWindowOuter::Cast(win);
if (!nsGlobalWindowInner::AllowChromeFrameAccess(
aCx, global->GetGlobalJSObject())) {
return nullptr;
}
}
return win.forget();
}
nsIControllers* nsGlobalWindowOuter::GetControllersOuter(ErrorResult& aError) {
@ -3544,7 +3557,12 @@ double nsGlobalWindowOuter::GetScrollXOuter() { return GetScrollXY(false).x; }
double nsGlobalWindowOuter::GetScrollYOuter() { return GetScrollXY(false).y; }
uint32_t nsGlobalWindowOuter::Length() {
uint32_t nsGlobalWindowOuter::Length(mozilla::dom::CallerType aCallerType) {
if (!StaticPrefs::dom_chrome_frame_access_enabled() &&
aCallerType == CallerType::System) {
return 0;
}
nsDOMWindowList* windows = GetFrames();
return windows ? windows->GetLength() : 0;

Просмотреть файл

@ -354,7 +354,8 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
// nsIObserver
NS_DECL_NSIOBSERVER
already_AddRefed<nsPIDOMWindowOuter> IndexedGetterOuter(uint32_t aIndex);
already_AddRefed<nsPIDOMWindowOuter> IndexedGetterOuter(JSContext* aCx,
uint32_t aIndex);
already_AddRefed<nsPIDOMWindowOuter> GetTop() override;
nsPIDOMWindowOuter* GetScriptableTop() override;
@ -528,7 +529,7 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
void BlurOuter();
already_AddRefed<nsPIDOMWindowOuter> GetFramesOuter();
nsDOMWindowList* GetFrames() final;
uint32_t Length();
uint32_t Length(mozilla::dom::CallerType aCallerType);
already_AddRefed<nsPIDOMWindowOuter> GetTopOuter();
nsresult GetPrompter(nsIPrompt** aPrompt) override;

Просмотреть файл

@ -18,6 +18,8 @@ support-files =
test_largeAllocationFormSubmit.sjs
helper_largeAllocation.js
helper_localStorage_e10s.js
test_dom_chromeframes_top.html
test_dom_chromeframes_inner.html
!/dom/tests/mochitest/geolocation/network_geolocation.sjs
[browser_allocateGigabyte.js]
@ -44,6 +46,7 @@ support-files =
skip-if = e10s
[browser_ConsoleStorageAPITests.js]
[browser_ConsoleStoragePBTest_perwindowpb.js]
[browser_dom_chrome_frame_access.js]
[browser_focus_steal_from_chrome.js]
[browser_focus_steal_from_chrome_during_mousedown.js]
[browser_frame_elements.js]

Просмотреть файл

@ -0,0 +1,52 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const TEST_URI = "http://example.com/browser/dom/tests/browser/test_dom_chromeframes_top.html";
add_task(async function() {
SpecialPowers.pushPrefEnv({set: [["dom.chrome_frame_access.enabled", true]]})
await testProperties(TEST_URI, true);
SpecialPowers.pushPrefEnv({set: [["dom.chrome_frame_access.enabled", false]]})
await testProperties(TEST_URI, false);
});
async function testProperties(uri, shouldBeDefined) {
await BrowserTestUtils.withNewTab(uri, async function(browser) {
// eslint-disable-next-line no-shadow
await ContentTask.spawn(browser, shouldBeDefined, async function (shouldBeDefined) {
let iframe = content.document.querySelector("iframe");
let rawIframe = ChromeUtils.waiveXrays(iframe);
ok(rawIframe.contentWindow, "Not null");
let messageEvent = await new Promise(resolve => {
content.addEventListener("message",
messageEvent => resolve(messageEvent),
{once: true});
rawIframe.contentWindow.postMessage("syn", "*");
});
ok(messageEvent, "message received");
is(messageEvent.data, "ack", "ack received");
let innerWindow = ChromeUtils.unwaiveXrays(rawIframe.contentWindow);
is(content.frames.length > 0, shouldBeDefined, "Window.frames.length");
is(content.frames[0] !== undefined, shouldBeDefined, "Window.frames - IndexedGetter");
// Ignored test case:
// - privileged -> unprivileged named getter is blocked by default.
// is(content.frames["testframe"] !== undefined, shouldBeDefined, "Window.frames - Named Getter");
is(iframe.contentWindow !== undefined, shouldBeDefined, "HTMLIframeElement.contentWindow");
is(iframe.contentDocument !== undefined, shouldBeDefined, "HTMLIframeElement.contentDocument");
is(innerWindow.parent !== undefined, shouldBeDefined, "Window.parent");
is(innerWindow.top !== undefined, shouldBeDefined, "Window.top");
is(innerWindow.opener !== undefined, shouldBeDefined, "Window.opener");
is(messageEvent.source !== undefined, shouldBeDefined, "MessageEvent.source");
});
});
}

Просмотреть файл

@ -0,0 +1,16 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Test dom.strictChromeFrameAccess.enabled pref inner frame</title>
</head>
<script>
addEventListener("message", function(e) {
if (e.data == "syn") {
window.parent.postMessage("ack", "*");
}
});
</script>
<body>
<p>inner frame</p>
</body>
</html>

Просмотреть файл

@ -0,0 +1,10 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Test dom.strictChromeFrameAccess.enabled pref</title>
</head>
<body>
<p>Test dom.strictChromeFrameAccess.enabled pref</p>
<iframe id="testframe" name="testframe" src="test_dom_chromeframes_inner.html"></iframe>
</body>
</html>

Просмотреть файл

@ -33,8 +33,9 @@ interface HTMLIFrameElement : HTMLElement {
attribute DOMString height;
[CEReactions, SetterThrows, Pure]
attribute DOMString referrerPolicy;
[NeedsSubjectPrincipal]
[NeedsSubjectPrincipal, Func="nsGlobalWindowInner::AllowChromeFrameAccess"]
readonly attribute Document? contentDocument;
[Func="nsGlobalWindowInner::AllowChromeFrameAccess"]
readonly attribute WindowProxy? contentWindow;
};

Просмотреть файл

@ -33,6 +33,7 @@ interface MessageEvent : Event {
/**
* The window or port which originated this event.
*/
[Func="nsGlobalWindowInner::AllowChromeFrameAccess"]
readonly attribute MessageEventSource? source;
[Pure, Cached, Frozen]

Просмотреть файл

@ -55,12 +55,15 @@ typedef OfflineResourceList ApplicationCache;
// other browsing contexts
[Replaceable, Throws, CrossOriginReadable] readonly attribute WindowProxy frames;
[Replaceable, CrossOriginReadable] readonly attribute unsigned long length;
[Replaceable, CrossOriginReadable, NeedsCallerType] readonly attribute unsigned long length;
//[Unforgeable, Throws, CrossOriginReadable] readonly attribute WindowProxy top;
[Unforgeable, Throws, CrossOriginReadable] readonly attribute WindowProxy? top;
[Throws, CrossOriginReadable] attribute any opener;
[Unforgeable, Throws, CrossOriginReadable, Func="nsGlobalWindowInner::AllowChromeFrameAccess"]
readonly attribute WindowProxy? top;
[Throws, CrossOriginReadable, Func="nsGlobalWindowInner::AllowChromeFrameAccess"]
attribute any opener;
//[Throws] readonly attribute WindowProxy parent;
[Replaceable, Throws, CrossOriginReadable] readonly attribute WindowProxy? parent;
[Replaceable, Throws, CrossOriginReadable, Func="nsGlobalWindowInner::AllowChromeFrameAccess"]
readonly attribute WindowProxy? parent;
[Throws, NeedsSubjectPrincipal] readonly attribute Element? frameElement;
//[Throws] WindowProxy? open(optional USVString url = "about:blank", optional DOMString target = "_blank", [TreatNullAs=EmptyString] optional DOMString features = "");
[Throws] WindowProxy? open(optional DOMString url = "", optional DOMString target = "", [TreatNullAs=EmptyString] optional DOMString features = "");

Просмотреть файл

@ -1638,7 +1638,7 @@ bool DOMXrayTraits::resolveOwnProperty(JSContext* cx, HandleObject wrapper,
nsGlobalWindowInner* win = AsWindow(cx, wrapper);
// Note: As() unwraps outer windows to get to the inner window.
if (win) {
nsCOMPtr<nsPIDOMWindowOuter> subframe = win->IndexedGetter(index);
nsCOMPtr<nsPIDOMWindowOuter> subframe = win->IndexedGetter(cx, index);
if (subframe) {
subframe->EnsureInnerWindow();
nsGlobalWindowOuter* global = nsGlobalWindowOuter::Cast(subframe);
@ -1707,7 +1707,9 @@ bool DOMXrayTraits::enumerateNames(JSContext* cx, HandleObject wrapper,
// Put the indexed properties for a window first.
nsGlobalWindowInner* win = AsWindow(cx, wrapper);
if (win) {
uint32_t length = win->Length();
uint32_t length =
win->Length(nsContentUtils::IsSystemCaller(cx) ? CallerType::System
: CallerType::NonSystem);
if (!props.reserve(props.length() + length)) {
return false;
}

Просмотреть файл

@ -287,6 +287,13 @@ VARCACHE_PREF(
RelaxedAtomicBool, false
)
// Allow chrome code to access non-privileged frames.
VARCACHE_PREF(
"dom.chrome_frame_access.enabled",
dom_chrome_frame_access_enabled,
bool, true
)
// Enable printing performance marks/measures to log
VARCACHE_PREF(
"dom.performance.enable_user_timing_logging",