зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
ba862815a3
Коммит
72585dea54
|
@ -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",
|
||||
|
|
Загрузка…
Ссылка в новой задаче