зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1272707, part 2 - Limit the size of preference values sent to child processes. r=bsmedberg
Don't send any preferences that have a string value that is longer than MAX_ADVISABLE_PREF_LENGTH. This is intended to mitigate OOM issues, as I've seen a parent process crash trying to create a 100mb message to send to the child. Such users likely cannot use e10s at all. This has a test for all combinations of setting the default and user values of a preference to large or small string values, or not setting them at all. I manually verified that filtering out preferences reduces the size of the IPC::Message that is sent to the child by printing out the size of the reply message in PContentParent::OnMessageReceived().
This commit is contained in:
Родитель
d47917df1b
Коммит
f9c73e6cf3
|
@ -728,7 +728,9 @@ Preferences::GetPreference(PrefSetting* aPref)
|
|||
if (!entry)
|
||||
return;
|
||||
|
||||
pref_GetPrefFromEntry(entry, aPref);
|
||||
if (pref_EntryHasAdvisablySizedValues(entry)) {
|
||||
pref_GetPrefFromEntry(entry, aPref);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -737,6 +739,11 @@ Preferences::GetPreferences(InfallibleTArray<PrefSetting>* aPrefs)
|
|||
aPrefs->SetCapacity(gHashTable->Capacity());
|
||||
for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
|
||||
auto entry = static_cast<PrefHashEntry*>(iter.Get());
|
||||
|
||||
if (!pref_EntryHasAdvisablySizedValues(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dom::PrefSetting *pref = aPrefs->AppendElement();
|
||||
pref_GetPrefFromEntry(entry, pref);
|
||||
}
|
||||
|
|
|
@ -380,7 +380,10 @@ nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName, const ui
|
|||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
nsAutoCString message(nsPrintfCString("Warning: attempting to write %d bytes to preference %s. This is bad for general performance and memory usage. Such an amount of data should rather be written to an external file.",
|
||||
nsAutoCString message(nsPrintfCString("Warning: attempting to write %d bytes to preference %s. This is bad "
|
||||
"for general performance and memory usage. Such an amount of data "
|
||||
"should rather be written to an external file. This preference will "
|
||||
"not be sent to any content processes.",
|
||||
aLength,
|
||||
getPrefName(aPrefName)));
|
||||
rv = console->LogStringMessage(NS_ConvertUTF8toUTF16(message).get());
|
||||
|
|
|
@ -366,6 +366,31 @@ pref_savePrefs(PLDHashTable* aTable)
|
|||
return savedPrefs;
|
||||
}
|
||||
|
||||
bool
|
||||
pref_EntryHasAdvisablySizedValues(PrefHashEntry* aHashEntry)
|
||||
{
|
||||
if (aHashEntry->prefFlags.GetPrefType() != PrefType::String) {
|
||||
return true;
|
||||
}
|
||||
|
||||
char* stringVal;
|
||||
if (aHashEntry->prefFlags.HasDefault()) {
|
||||
stringVal = aHashEntry->defaultPref.stringVal;
|
||||
if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (aHashEntry->prefFlags.HasUserValue()) {
|
||||
stringVal = aHashEntry->userPref.stringVal;
|
||||
if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
GetPrefValueFromEntry(PrefHashEntry *aHashEntry, dom::PrefSetting* aPref,
|
||||
WhichValue aWhich)
|
||||
|
|
|
@ -29,6 +29,9 @@ pref_SetPref(const mozilla::dom::PrefSetting& aPref);
|
|||
int pref_CompareStrings(const void *v1, const void *v2, void* unused);
|
||||
PrefHashEntry* pref_HashTableLookup(const char *key);
|
||||
|
||||
bool
|
||||
pref_EntryHasAdvisablySizedValues(PrefHashEntry* aHashEntry);
|
||||
|
||||
void pref_GetPrefFromEntry(PrefHashEntry *aHashEntry,
|
||||
mozilla::dom::PrefSetting* aPref);
|
||||
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Large preferences should not be set in the child process.
|
||||
// Non-string preferences are not tested here, because their behavior
|
||||
// should not be affected by this filtering.
|
||||
|
||||
var Ci = Components.interfaces;
|
||||
var Cc = Components.classes;
|
||||
|
||||
function isParentProcess() {
|
||||
let appInfo = Cc["@mozilla.org/xre/app-info;1"];
|
||||
return (!appInfo || appInfo.getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT);
|
||||
}
|
||||
|
||||
function makeBuffer(length) {
|
||||
let string = "x";
|
||||
while (string.length < length) {
|
||||
string = string + string;
|
||||
}
|
||||
if (string.length > length) {
|
||||
string = string.substring(length - string.length);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
// from prefapi.h
|
||||
const MAX_ADVISABLE_PREF_LENGTH = 4 * 1024;
|
||||
|
||||
const largeString = makeBuffer(MAX_ADVISABLE_PREF_LENGTH + 1);
|
||||
const smallString = makeBuffer(4);
|
||||
|
||||
const testValues = [
|
||||
{name: "None", value: undefined},
|
||||
{name: "Small", value: smallString},
|
||||
{name: "Large", value: largeString},
|
||||
];
|
||||
|
||||
function prefName(def, user) {
|
||||
return "Test.IPC.default" + def.name + "User" + user.name;
|
||||
}
|
||||
|
||||
function expectedPrefValue(def, user) {
|
||||
if (user.value) {
|
||||
return user.value;
|
||||
}
|
||||
return def.value;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
|
||||
let ps = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);
|
||||
let defaultBranch = ps.getDefaultBranch("");
|
||||
|
||||
let isParent = isParentProcess();
|
||||
if (isParent) {
|
||||
// Set all combinations of none, small and large, for default and user prefs.
|
||||
for (let def of testValues) {
|
||||
for (let user of testValues) {
|
||||
let currPref = prefName(def, user);
|
||||
if (def.value) {
|
||||
defaultBranch.setCharPref(currPref, def.value);
|
||||
}
|
||||
if (user.value) {
|
||||
pb.setCharPref(currPref, user.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run_test_in_child("test_large_pref.js");
|
||||
}
|
||||
|
||||
// Check that each preference is set or not set, as appropriate.
|
||||
for (let def of testValues) {
|
||||
for (let user of testValues) {
|
||||
if (!def.value && !user.value) {
|
||||
continue;
|
||||
}
|
||||
let pref_name = prefName(def, user);
|
||||
if (isParent || (def.name != "Large" && user.name != "Large")) {
|
||||
do_check_eq(pb.getCharPref(pref_name), expectedPrefValue(def, user));
|
||||
} else {
|
||||
// This is the child, and either the default or user value is
|
||||
// large, so the preference should not be set.
|
||||
let prefExists;
|
||||
try {
|
||||
pb.getCharPref(pref_name);
|
||||
prefExists = true;
|
||||
} catch(e) {
|
||||
prefExists = false;
|
||||
}
|
||||
ok(!prefExists,
|
||||
"Pref " + pref_name + " should not be set in the child");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
|
|||
|
||||
[test_existing_prefs.js]
|
||||
[test_initial_prefs.js]
|
||||
[test_large_pref.js]
|
||||
[test_observed_prefs.js]
|
||||
[test_update_prefs.js]
|
||||
[test_user_default_prefs.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче