Bug 879658: don't expose localStorage to FrameWorker for non-whitelisted social providers, r=mixedpuppy

--HG--
extra : rebase_source : 225b3903057e362e180f8ded7e18bf26a63bacb0
This commit is contained in:
Gavin Sharp 2013-06-17 17:25:06 -04:00
parent 7dca0eb71c
commit 72d2cfc005
3 changed files with 84 additions and 40 deletions

View File

@ -30,7 +30,7 @@ var _nextPortId = 1;
// Retrieves a reference to a WorkerHandle associated with a FrameWorker and a
// new ClientPort.
this.getFrameWorkerHandle =
function getFrameWorkerHandle(url, clientWindow, name, origin) {
function getFrameWorkerHandle(url, clientWindow, name, origin, exposeLocalStorage = false) {
// first create the client port we are going to use. Later we will
// message the worker to create the worker port.
let portid = _nextPortId++;
@ -39,7 +39,7 @@ this.getFrameWorkerHandle =
let existingWorker = workerCache[url];
if (!existingWorker) {
// setup the worker and add this connection to the pending queue
let worker = new FrameWorker(url, name, origin);
let worker = new FrameWorker(url, name, origin, exposeLocalStorage);
worker.pendingPorts.push(clientPort);
existingWorker = workerCache[url] = worker;
} else {
@ -69,7 +69,7 @@ this.getFrameWorkerHandle =
* the script does not have a full DOM but is instead run in a sandbox
* that has a select set of methods cloned from the URL's domain.
*/
function FrameWorker(url, name, origin) {
function FrameWorker(url, name, origin, exposeLocalStorage) {
this.url = url;
this.name = name || url;
this.ports = new Map();
@ -78,6 +78,7 @@ function FrameWorker(url, name, origin) {
this.reloading = false;
this.origin = origin;
this._injectController = null;
this.exposeLocalStorage = exposeLocalStorage;
this.frame = makeHiddenFrame();
this.load();
@ -133,11 +134,17 @@ FrameWorker.prototype = {
// copy the window apis onto the sandbox namespace only functions or
// objects that are naturally a part of an iframe, I'm assuming they are
// safe to import this way
let workerAPI = ['WebSocket', 'localStorage', 'atob', 'btoa',
let workerAPI = ['WebSocket', 'atob', 'btoa',
'clearInterval', 'clearTimeout', 'dump',
'setInterval', 'setTimeout', 'XMLHttpRequest',
'FileReader', 'Blob', 'EventSource', 'indexedDB',
'location'];
// Only expose localStorage if the caller opted-in
if (this.exposeLocalStorage) {
workerAPI.push('localStorage');
}
// Bug 798660 - XHR and WebSocket have issues in a sandbox and need
// to be unwrapped to work
let needsWaive = ['XMLHttpRequest', 'WebSocket'];

View File

@ -137,6 +137,42 @@ let SocialServiceInternal = {
}
};
XPCOMUtils.defineLazyGetter(SocialServiceInternal, "providers", function () {
initService();
let providers = {};
for (let manifest of this.manifests) {
try {
if (ActiveProviders.has(manifest.origin)) {
let activationType = getOriginActivationType(manifest.origin);
let blessed = activationType == "builtin" ||
activationType == "whitelist";
let provider = new SocialProvider(manifest, blessed);
providers[provider.origin] = provider;
}
} catch (err) {
Cu.reportError("SocialService: failed to load provider: " + manifest.origin +
", exception: " + err);
}
}
return providers;
});
function getOriginActivationType(origin) {
let prefname = SocialServiceInternal.getManifestPrefname(origin);
if (Services.prefs.getDefaultBranch("social.manifest.").getPrefType(prefname) == Services.prefs.PREF_STRING)
return 'builtin';
let whitelist = Services.prefs.getCharPref("social.whitelist").split(',');
if (whitelist.indexOf(origin) >= 0)
return 'whitelist';
let directories = Services.prefs.getCharPref("social.directories").split(',');
if (directories.indexOf(origin) >= 0)
return 'directory';
return 'foreign';
}
let ActiveProviders = {
get _providers() {
delete this._providers;
@ -304,23 +340,6 @@ function initService() {
MozSocialAPI.enabled = true;
}
XPCOMUtils.defineLazyGetter(SocialServiceInternal, "providers", function () {
initService();
let providers = {};
for (let manifest of this.manifests) {
try {
if (ActiveProviders.has(manifest.origin)) {
let provider = new SocialProvider(manifest);
providers[provider.origin] = provider;
}
} catch (err) {
Cu.reportError("SocialService: failed to load provider: " + manifest.origin +
", exception: " + err);
}
}
return providers;
});
function schedule(callback) {
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
}
@ -444,20 +463,8 @@ this.SocialService = {
SocialServiceInternal.orderedProviders(onDone);
},
getOriginActivationType: function(origin) {
let prefname = SocialServiceInternal.getManifestPrefname(origin);
if (Services.prefs.getDefaultBranch("social.manifest.").getPrefType(prefname) == Services.prefs.PREF_STRING)
return 'builtin';
let whitelist = Services.prefs.getCharPref("social.whitelist").split(',');
if (whitelist.indexOf(origin) >= 0)
return 'whitelist';
let directories = Services.prefs.getCharPref("social.directories").split(',');
if (directories.indexOf(origin) >= 0)
return 'directory';
return 'foreign';
getOriginActivationType: function (origin) {
return getOriginActivationType(origin);
},
_providerListeners: new Map(),
@ -585,7 +592,7 @@ this.SocialService = {
let sourceURI = aDOMDocument.location.href;
let installOrigin = aDOMDocument.nodePrincipal.origin;
let installType = this.getOriginActivationType(installOrigin);
let installType = getOriginActivationType(installOrigin);
let manifest;
if (data) {
// if we get data, we MUST have a valid manifest generated from the data
@ -649,8 +656,9 @@ this.SocialService = {
*
* @constructor
* @param {jsobj} object representing the manifest file describing this provider
* @param {bool} boolean indicating whether this provider is "built in"
*/
function SocialProvider(input) {
function SocialProvider(input, blessed = false) {
if (!input.name)
throw new Error("SocialProvider must be passed a name");
if (!input.origin)
@ -674,6 +682,7 @@ function SocialProvider(input) {
this.ambientNotificationIcons = {};
this.errorState = null;
this.frecency = 0;
this.blessed = blessed;
try {
this.domain = etld.getBaseDomainFromHost(originUri.host);
} catch(e) {
@ -869,8 +878,12 @@ SocialProvider.prototype = {
getWorkerPort: function getWorkerPort(window) {
if (!this.workerURL || !this.enabled)
return null;
return getFrameWorkerHandle(this.workerURL, window,
"SocialProvider:" + this.origin, this.origin).port;
// Only allow localStorage in the frameworker for blessed providers
let allowLocalStorage = this.blessed;
let handle = getFrameWorkerHandle(this.workerURL, window,
"SocialProvider:" + this.origin, this.origin,
allowLocalStorage);
return handle.port;
},
/**

View File

@ -241,7 +241,7 @@ let tests = {
port.postMessage({topic: "done", result: "ok"});
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testLocalStorage");
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testLocalStorage", null, true);
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check the localStorage test worked");
@ -251,6 +251,30 @@ let tests = {
}
},
testNoLocalStorage: function(cbnext) {
let run = function() {
onconnect = function(e) {
let port = e.ports[0];
try {
localStorage.setItem("foo", "1");
} catch(e) {
port.postMessage({topic: "done", result: "ok"});
return;
}
port.postMessage({topic: "done", result: "FAILED because localStorage was exposed" });
}
}
let worker = getFrameWorkerHandle(makeWorkerUrl(run), undefined, "testNoLocalStorage");
worker.port.onmessage = function(e) {
if (e.data.topic == "done") {
is(e.data.result, "ok", "check that retrieving localStorage fails by default");
worker.terminate();
cbnext();
}
}
},
testBase64: function (cbnext) {
let run = function() {
onconnect = function(e) {