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:
Andrew McCreight 2016-05-17 07:55:49 -07:00
Родитель d47917df1b
Коммит f9c73e6cf3
6 изменённых файлов: 139 добавлений и 2 удалений

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

@ -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]