diff options
8 files changed, 142 insertions, 17 deletions
diff --git a/chrome/browser/extensions/app_window_overrides_browsertest.cc b/chrome/browser/extensions/app_window_overrides_browsertest.cc new file mode 100644 index 0000000..3f7a8d8 --- /dev/null +++ b/chrome/browser/extensions/app_window_overrides_browsertest.cc @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/extension_browsertest.h" +#include "extensions/test/result_catcher.h" + +namespace extensions { + +using AppWindowRestrictedApisBrowserTest = ExtensionBrowserTest; + +// Test that the window events like onbeforeunload event are correctly +// clobbered. +IN_PROC_BROWSER_TEST_F(AppWindowRestrictedApisBrowserTest, UnloadEvents) { + ResultCatcher catcher; + ASSERT_TRUE(LoadExtension( + test_data_dir_.AppendASCII("app_forbidden_apis/onbeforeunload"))); + ASSERT_TRUE(catcher.GetNextResult()); +} + +// Test that Document apis like document.write are correctly clobbered. +IN_PROC_BROWSER_TEST_F(AppWindowRestrictedApisBrowserTest, DocumentApis) { + ResultCatcher catcher; + ASSERT_TRUE(LoadExtension( + test_data_dir_.AppendASCII("app_forbidden_apis/document_apis"))); + ASSERT_TRUE(catcher.GetNextResult()); +} + +} // namespace extensions diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index ea7dddf..8b20c08 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -213,6 +213,7 @@ 'browser/extensions/api/webstore_private/webstore_private_apitest.cc', 'browser/extensions/app_background_page_apitest.cc', 'browser/extensions/app_process_apitest.cc', + 'browser/extensions/app_window_overrides_browsertest.cc', 'browser/extensions/background_app_browsertest.cc', 'browser/extensions/background_page_apitest.cc', 'browser/extensions/background_scripts_apitest.cc', diff --git a/chrome/test/data/extensions/app_forbidden_apis/document_apis/background.js b/chrome/test/data/extensions/app_forbidden_apis/document_apis/background.js new file mode 100644 index 0000000..06c4e90 --- /dev/null +++ b/chrome/test/data/extensions/app_forbidden_apis/document_apis/background.js @@ -0,0 +1,17 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +var writeSuccess = true; +try { + Document.prototype.write.call(document, 'Hello, world'); +} catch (e) { + writeSuccess = false; +} + +if (writeSuccess) + chrome.test.fail(); +else + chrome.test.succeed(); diff --git a/chrome/test/data/extensions/app_forbidden_apis/document_apis/manifest.json b/chrome/test/data/extensions/app_forbidden_apis/document_apis/manifest.json new file mode 100644 index 0000000..ae7374d --- /dev/null +++ b/chrome/test/data/extensions/app_forbidden_apis/document_apis/manifest.json @@ -0,0 +1,9 @@ +{ + "name": "Using restricted APIs", + "description": "An app using forbidden window apis", + "manifest_version": 2, + "version": "1", + "app": { + "background": { "scripts": ["background.js"] } + } +} diff --git a/chrome/test/data/extensions/app_forbidden_apis/onbeforeunload/background.js b/chrome/test/data/extensions/app_forbidden_apis/onbeforeunload/background.js new file mode 100644 index 0000000..0f0d75c --- /dev/null +++ b/chrome/test/data/extensions/app_forbidden_apis/onbeforeunload/background.js @@ -0,0 +1,37 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +var didRun = sessionStorage.didRun; + +function beforeUnload() { + chrome.test.fail(); +} + +try { + delete window.onbeforeunload; + window.onbeforeunload = beforeUnload; +} catch (e) {} + +try { + window.addEventListener('beforeunload', beforeUnload); +} catch (e) {} + +try { + var beforeUnloadTricky = { + toString: function() { + beforeUnloadTricky.toString = function() { return 'beforeunload'; }; + return 'something not beforeunload'; + } + }; + window.addEventListener(beforeUnloadTricky, beforeUnload); +} catch (e) {} + +if (!didRun) { + sessionStorage.didRun = true; + location.reload(); +} else { + chrome.test.succeed(); +} diff --git a/chrome/test/data/extensions/app_forbidden_apis/onbeforeunload/manifest.json b/chrome/test/data/extensions/app_forbidden_apis/onbeforeunload/manifest.json new file mode 100644 index 0000000..ae7374d --- /dev/null +++ b/chrome/test/data/extensions/app_forbidden_apis/onbeforeunload/manifest.json @@ -0,0 +1,9 @@ +{ + "name": "Using restricted APIs", + "description": "An app using forbidden window apis", + "manifest_version": 2, + "version": "1", + "app": { + "background": { "scripts": ["background.js"] } + } +} diff --git a/chrome/test/data/extensions/platform_apps/restrictions/main.js b/chrome/test/data/extensions/platform_apps/restrictions/main.js index cd7ddd5..856b0c8 100644 --- a/chrome/test/data/extensions/platform_apps/restrictions/main.js +++ b/chrome/test/data/extensions/platform_apps/restrictions/main.js @@ -74,28 +74,28 @@ chrome.test.runTests([ }, function testWindowFind() { - assertEq('undefined', typeof(Window.prototype.find('needle'))); + assertEq('undefined', typeof(Window.prototype.find)); assertEq('undefined', typeof(window.find('needle'))); assertEq('undefined', typeof(find('needle'))); succeed(); }, function testWindowAlert() { - assertEq('undefined', typeof(Window.prototype.alert())); + assertEq('undefined', typeof(Window.prototype.alert)); assertEq('undefined', typeof(window.alert())); assertEq('undefined', typeof(alert())); succeed(); }, function testWindowConfirm() { - assertEq('undefined', typeof(Window.prototype.confirm('Failed'))); + assertEq('undefined', typeof(Window.prototype.confirm)); assertEq('undefined', typeof(window.confirm('Failed'))); assertEq('undefined', typeof(confirm('Failed'))); succeed(); }, function testWindowPrompt() { - assertEq('undefined', typeof(Window.prototype.prompt('Failed'))); + assertEq('undefined', typeof(Window.prototype.prompt)); assertEq('undefined', typeof(window.prompt('Failed'))); assertEq('undefined', typeof(prompt('Failed'))); succeed(); diff --git a/extensions/renderer/resources/platform_app.js b/extensions/renderer/resources/platform_app.js index 21263d3..9f386d7 100644 --- a/extensions/renderer/resources/platform_app.js +++ b/extensions/renderer/resources/platform_app.js @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +var logging = requireNative('logging'); + /** * Returns a function that logs a 'not available' error to the console and * returns undefined. @@ -65,10 +67,16 @@ function generateThrowingMethodStub(messagePrefix, opt_messageSuffix) { */ function disableMethods(object, objectName, methodNames, useThrowingStubs) { $Array.forEach(methodNames, function(methodName) { + logging.DCHECK($Object.getOwnPropertyDescriptor(object, methodName), + objectName + ': ' + methodName); var messagePrefix = objectName + '.' + methodName + '()'; - object[methodName] = useThrowingStubs ? - generateThrowingMethodStub(messagePrefix) : - generateDisabledMethodStub(messagePrefix); + $Object.defineProperty(object, methodName, { + configurable: false, + enumerable: false, + value: useThrowingStubs ? + generateThrowingMethodStub(messagePrefix) : + generateDisabledMethodStub(messagePrefix) + }); }); } @@ -85,9 +93,16 @@ function disableMethods(object, objectName, methodNames, useThrowingStubs) { * referred to by web developers, e.g. "document" instead of * "HTMLDocument"). * @param {Array<string>} propertyNames names of properties to disable. + * @param {?string=} opt_messageSuffix An optional suffix for the message. + * @param {boolean=} opt_ignoreMissingProperty True if we allow disabling + * getters for non-existent properties. */ -function disableGetters(object, objectName, propertyNames, opt_messageSuffix) { +function disableGetters(object, objectName, propertyNames, opt_messageSuffix, + opt_ignoreMissingProperty) { $Array.forEach(propertyNames, function(propertyName) { + logging.DCHECK(opt_ignoreMissingProperty || + $Object.getOwnPropertyDescriptor(object, propertyName), + objectName + ': ' + propertyName); var stub = generateDisabledMethodStub(objectName + '.' + propertyName, opt_messageSuffix); stub._is_platform_app_disabled_getter = true; @@ -130,10 +145,12 @@ function disableGetters(object, objectName, propertyNames, opt_messageSuffix) { */ function disableSetters(object, objectName, propertyNames, opt_messageSuffix) { $Array.forEach(propertyNames, function(propertyName) { + logging.DCHECK($Object.getOwnPropertyDescriptor(object, propertyName), + objectName + ': ' + propertyName); var stub = generateDisabledMethodStub(objectName + '.' + propertyName, opt_messageSuffix); $Object.defineProperty(object, propertyName, { - configurable: true, + configurable: false, enumerable: false, get: function() { return; @@ -144,24 +161,26 @@ function disableSetters(object, objectName, propertyNames, opt_messageSuffix) { } // Disable benign Document methods. -disableMethods(HTMLDocument.prototype, 'document', ['open', 'clear', 'close']); +disableMethods(Document.prototype, 'document', ['open', 'close']); +disableMethods(HTMLDocument.prototype, 'document', ['clear']); // Replace evil Document methods with exception-throwing stubs. -disableMethods(HTMLDocument.prototype, 'document', ['write', 'writeln'], true); +disableMethods(Document.prototype, 'document', ['write', 'writeln'], true); // Disable history. Object.defineProperty(window, "history", { value: {} }); +// Note: we just blew away the history object, so we need to ignore the fact +// that these properties aren't defined on the object. disableGetters(window.history, 'history', - ['back', 'forward', 'go', 'length', 'pushState', 'replaceState', 'state']); + ['back', 'forward', 'go', 'length', 'pushState', 'replaceState', 'state'], + null, true); // Disable find. disableMethods(window, 'window', ['find']); -disableMethods(Window.prototype, 'window', ['find']); // Disable modal dialogs. Shell windows disable these anyway, but it's nice to // warn. disableMethods(window, 'window', ['alert', 'confirm', 'prompt']); -disableMethods(Window.prototype, 'window', ['alert', 'confirm', 'prompt']); // Disable window.*bar. disableGetters(window, 'window', @@ -194,16 +213,20 @@ window.addEventListener('readystatechange', function(event) { // it first to 'undefined' to avoid this. document.all = undefined; disableGetters(document, 'document', - ['alinkColor', 'all', 'bgColor', 'fgColor', 'linkColor', 'vlinkColor']); + ['alinkColor', 'all', 'bgColor', 'fgColor', 'linkColor', 'vlinkColor'], + null, true); }, true); // Disable onunload, onbeforeunload. disableSetters(window, 'window', ['onbeforeunload', 'onunload']); -disableSetters(Window.prototype, 'window', ['onbeforeunload', 'onunload']); var eventTargetAddEventListener = EventTarget.prototype.addEventListener; EventTarget.prototype.addEventListener = function(type) { + var args = $Array.slice(arguments); + // Note: Force conversion to a string in order to catch any funny attempts + // to pass in something that evals to 'unload' but wouldn't === 'unload'. + var type = (args[0] += ''); if (type === 'unload' || type === 'beforeunload') generateDisabledMethodStub(type)(); else - return $Function.apply(eventTargetAddEventListener, this, arguments); + return $Function.apply(eventTargetAddEventListener, this, args); }; |