mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
Merge inbound to m-c a=merge
This commit is contained in:
commit
ca62a34614
@ -390,6 +390,11 @@ Sanitizer.prototype = {
|
||||
for each (var host in hosts) {
|
||||
pwmgr.setLoginSavingEnabled(host, true);
|
||||
}
|
||||
|
||||
// Clear site security settings
|
||||
var sss = Cc["@mozilla.org/ssservice;1"]
|
||||
.getService(Ci.nsISiteSecurityService);
|
||||
sss.clearAll();
|
||||
},
|
||||
|
||||
get canClear()
|
||||
|
@ -134,6 +134,11 @@ Sanitizer.prototype = {
|
||||
for each (var host in hosts) {
|
||||
pwmgr.setLoginSavingEnabled(host, true);
|
||||
}
|
||||
|
||||
// Clear site security settings
|
||||
var sss = Cc["@mozilla.org/ssservice;1"]
|
||||
.getService(Ci.nsISiteSecurityService);
|
||||
sss.clearAll();
|
||||
},
|
||||
|
||||
get canClear()
|
||||
|
@ -377,9 +377,11 @@ Navigator::GetAppName(nsAString& aAppName)
|
||||
*
|
||||
* An empty array will be returned if there is no valid languages.
|
||||
*/
|
||||
void
|
||||
/* static */ void
|
||||
Navigator::GetAcceptLanguages(nsTArray<nsString>& aLanguages)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// E.g. "de-de, en-us,en".
|
||||
const nsAdoptingString& acceptLang =
|
||||
Preferences::GetLocalizedString("intl.accept_languages");
|
||||
@ -438,7 +440,7 @@ Navigator::GetLanguage(nsAString& aLanguage)
|
||||
aLanguage.Truncate();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -267,7 +267,8 @@ public:
|
||||
void GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& aNames,
|
||||
ErrorResult& aRv);
|
||||
void GetLanguages(nsTArray<nsString>& aLanguages);
|
||||
void GetAcceptLanguages(nsTArray<nsString>& aLanguages);
|
||||
|
||||
static void GetAcceptLanguages(nsTArray<nsString>& aLanguages);
|
||||
|
||||
// WebIDL helper methods
|
||||
static bool HasWakeLockSupport(JSContext* /* unused*/, JSObject* /*unused */);
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsISupportsPrimitives.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsDOMJSUtils.h"
|
||||
#include "nsJSUtils.h"
|
||||
#include "nsIDocShell.h"
|
||||
#include "nsIDocShellTreeItem.h"
|
||||
@ -564,7 +565,13 @@ NS_ScriptErrorReporter(JSContext *cx,
|
||||
::JS_ClearPendingException(cx);
|
||||
|
||||
MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext());
|
||||
nsCOMPtr<nsIGlobalObject> globalObject = GetEntryGlobal();
|
||||
nsCOMPtr<nsIGlobalObject> globalObject;
|
||||
if (nsIScriptContext* scx = GetScriptContextFromJSContext(cx)) {
|
||||
nsCOMPtr<nsPIDOMWindow> outer = do_QueryInterface(scx->GetGlobalObject());
|
||||
if (outer) {
|
||||
globalObject = static_cast<nsGlobalWindow*>(outer->GetCurrentInnerWindow());
|
||||
}
|
||||
}
|
||||
if (globalObject) {
|
||||
|
||||
nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(globalObject);
|
||||
|
@ -50,7 +50,7 @@ interface NavigatorID {
|
||||
boolean taintEnabled(); // constant false
|
||||
};
|
||||
|
||||
[NoInterfaceObject]
|
||||
[NoInterfaceObject, Exposed=(Window,Worker)]
|
||||
interface NavigatorLanguage {
|
||||
readonly attribute DOMString? language;
|
||||
[Pure, Cached, Frozen]
|
||||
|
@ -8,5 +8,6 @@ interface WorkerNavigator {
|
||||
};
|
||||
|
||||
WorkerNavigator implements NavigatorID;
|
||||
WorkerNavigator implements NavigatorLanguage;
|
||||
WorkerNavigator implements NavigatorOnLine;
|
||||
WorkerNavigator implements NavigatorDataStore;
|
||||
|
@ -38,7 +38,7 @@ WorkerNavigator::Create(bool aOnLine)
|
||||
nsRefPtr<WorkerNavigator> navigator =
|
||||
new WorkerNavigator(properties.mAppName, properties.mAppVersion,
|
||||
properties.mPlatform, properties.mUserAgent,
|
||||
aOnLine);
|
||||
properties.mLanguages, aOnLine);
|
||||
|
||||
return navigator.forget();
|
||||
}
|
||||
@ -278,4 +278,11 @@ WorkerNavigator::GetDataStores(JSContext* aCx,
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
void
|
||||
WorkerNavigator::SetLanguages(const nsTArray<nsString>& aLanguages)
|
||||
{
|
||||
WorkerNavigatorBinding_workers::ClearCachedLanguagesValue(this);
|
||||
mLanguages = aLanguages;
|
||||
}
|
||||
|
||||
END_WORKERS_NAMESPACE
|
||||
|
@ -28,17 +28,20 @@ class WorkerNavigator MOZ_FINAL : public nsWrapperCache
|
||||
nsString mAppVersion;
|
||||
nsString mPlatform;
|
||||
nsString mUserAgent;
|
||||
nsTArray<nsString> mLanguages;
|
||||
bool mOnline;
|
||||
|
||||
WorkerNavigator(const nsAString& aAppName,
|
||||
const nsAString& aAppVersion,
|
||||
const nsAString& aPlatform,
|
||||
const nsAString& aUserAgent,
|
||||
const nsTArray<nsString>& aLanguages,
|
||||
bool aOnline)
|
||||
: mAppName(aAppName)
|
||||
, mAppVersion(aAppVersion)
|
||||
, mPlatform(aPlatform)
|
||||
, mUserAgent(aUserAgent)
|
||||
, mLanguages(aLanguages)
|
||||
, mOnline(aOnline)
|
||||
{
|
||||
MOZ_COUNT_CTOR(WorkerNavigator);
|
||||
@ -92,6 +95,20 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
void GetLanguage(nsString& aLanguage) const
|
||||
{
|
||||
if (mLanguages.Length() >= 1) {
|
||||
aLanguage.Assign(mLanguages[0]);
|
||||
} else {
|
||||
aLanguage.Truncate();
|
||||
}
|
||||
}
|
||||
|
||||
void GetLanguages(nsTArray<nsString>& aLanguages) const
|
||||
{
|
||||
aLanguages = mLanguages;
|
||||
}
|
||||
|
||||
void GetUserAgent(nsString& aUserAgent) const
|
||||
{
|
||||
aUserAgent = mUserAgent;
|
||||
@ -108,6 +125,8 @@ public:
|
||||
mOnline = aOnline;
|
||||
}
|
||||
|
||||
void SetLanguages(const nsTArray<nsString>& aLanguages);
|
||||
|
||||
already_AddRefed<Promise> GetDataStores(JSContext* aCx,
|
||||
const nsAString& aName,
|
||||
const nsAString& aOwner,
|
||||
|
@ -156,6 +156,7 @@ static_assert(MAX_WORKERS_PER_DOMAIN >= 1,
|
||||
|
||||
#define PREF_DOM_FETCH_ENABLED "dom.fetch.enabled"
|
||||
#define PREF_WORKERS_LATEST_JS_VERSION "dom.workers.latestJSVersion"
|
||||
#define PREF_INTL_ACCEPT_LANGUAGES "intl.accept_languages"
|
||||
|
||||
namespace {
|
||||
|
||||
@ -1025,6 +1026,20 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
PrefLanguagesChanged(const char* /* aPrefName */, void* /* aClosure */)
|
||||
{
|
||||
AssertIsOnMainThread();
|
||||
|
||||
nsTArray<nsString> languages;
|
||||
Navigator::GetAcceptLanguages(languages);
|
||||
|
||||
RuntimeService* runtime = RuntimeService::GetService();
|
||||
if (runtime) {
|
||||
runtime->UpdateAllWorkerLanguages(languages);
|
||||
}
|
||||
}
|
||||
|
||||
} /* anonymous namespace */
|
||||
|
||||
class RuntimeService::WorkerThread MOZ_FINAL : public nsThread
|
||||
@ -1456,6 +1471,7 @@ RuntimeService::RegisterWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
||||
return false;
|
||||
}
|
||||
|
||||
Navigator::GetAcceptLanguages(mNavigatorProperties.mLanguages);
|
||||
mNavigatorPropertiesLoaded = true;
|
||||
}
|
||||
|
||||
@ -1795,6 +1811,9 @@ RuntimeService::Init()
|
||||
LoadRuntimeOptions,
|
||||
PREF_WORKERS_OPTIONS_PREFIX,
|
||||
nullptr)) ||
|
||||
NS_FAILED(Preferences::RegisterCallbackAndCall(PrefLanguagesChanged,
|
||||
PREF_INTL_ACCEPT_LANGUAGES,
|
||||
nullptr)) ||
|
||||
NS_FAILED(Preferences::RegisterCallbackAndCall(
|
||||
JSVersionChanged,
|
||||
PREF_WORKERS_LATEST_JS_VERSION,
|
||||
@ -1941,6 +1960,9 @@ RuntimeService::Cleanup()
|
||||
if (NS_FAILED(Preferences::UnregisterCallback(JSVersionChanged,
|
||||
PREF_WORKERS_LATEST_JS_VERSION,
|
||||
nullptr)) ||
|
||||
NS_FAILED(Preferences::UnregisterCallback(PrefLanguagesChanged,
|
||||
PREF_INTL_ACCEPT_LANGUAGES,
|
||||
nullptr)) ||
|
||||
NS_FAILED(Preferences::UnregisterCallback(LoadRuntimeOptions,
|
||||
PREF_JS_OPTIONS_PREFIX,
|
||||
nullptr)) ||
|
||||
@ -2407,6 +2429,15 @@ RuntimeService::UpdateAllWorkerPreference(WorkerPreference aPref, bool aValue)
|
||||
BROADCAST_ALL_WORKERS(UpdatePreference, aPref, aValue);
|
||||
}
|
||||
|
||||
void
|
||||
RuntimeService::UpdateAllWorkerLanguages(const nsTArray<nsString>& aLanguages)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
mNavigatorProperties.mLanguages = aLanguages;
|
||||
BROADCAST_ALL_WORKERS(UpdateLanguages, aLanguages);
|
||||
}
|
||||
|
||||
void
|
||||
RuntimeService::UpdateAllWorkerMemoryParameter(JSGCParamKey aKey,
|
||||
uint32_t aValue)
|
||||
|
@ -108,6 +108,7 @@ public:
|
||||
nsString mAppVersion;
|
||||
nsString mPlatform;
|
||||
nsString mUserAgent;
|
||||
nsTArray<nsString> mLanguages;
|
||||
};
|
||||
|
||||
private:
|
||||
@ -202,6 +203,9 @@ public:
|
||||
void
|
||||
UpdateAllWorkerRuntimeOptions();
|
||||
|
||||
void
|
||||
UpdateAllWorkerLanguages(const nsTArray<nsString>& aLanguages);
|
||||
|
||||
void
|
||||
UpdateAllWorkerPreference(WorkerPreference aPref, bool aValue);
|
||||
|
||||
|
@ -1564,6 +1564,25 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class UpdateLanguagesRunnable MOZ_FINAL : public WorkerRunnable
|
||||
{
|
||||
nsTArray<nsString> mLanguages;
|
||||
|
||||
public:
|
||||
UpdateLanguagesRunnable(WorkerPrivate* aWorkerPrivate,
|
||||
const nsTArray<nsString>& aLanguages)
|
||||
: WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
|
||||
mLanguages(aLanguages)
|
||||
{ }
|
||||
|
||||
virtual bool
|
||||
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
|
||||
{
|
||||
aWorkerPrivate->UpdateLanguagesInternal(aCx, mLanguages);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class UpdateJSWorkerMemoryParameterRunnable MOZ_FINAL :
|
||||
public WorkerControlRunnable
|
||||
{
|
||||
@ -2891,6 +2910,21 @@ WorkerPrivateParent<Derived>::UpdatePreference(JSContext* aCx, WorkerPreference
|
||||
}
|
||||
}
|
||||
|
||||
template <class Derived>
|
||||
void
|
||||
WorkerPrivateParent<Derived>::UpdateLanguages(JSContext* aCx,
|
||||
const nsTArray<nsString>& aLanguages)
|
||||
{
|
||||
AssertIsOnParentThread();
|
||||
|
||||
nsRefPtr<UpdateLanguagesRunnable> runnable =
|
||||
new UpdateLanguagesRunnable(ParentAsWorkerPrivate(), aLanguages);
|
||||
if (!runnable->Dispatch(aCx)) {
|
||||
NS_WARNING("Failed to update worker languages!");
|
||||
JS_ClearPendingException(aCx);
|
||||
}
|
||||
}
|
||||
|
||||
template <class Derived>
|
||||
void
|
||||
WorkerPrivateParent<Derived>::UpdateJSWorkerMemoryParameter(JSContext* aCx,
|
||||
@ -5527,6 +5561,23 @@ WorkerPrivate::UpdateRuntimeOptionsInternal(
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WorkerPrivate::UpdateLanguagesInternal(JSContext* aCx,
|
||||
const nsTArray<nsString>& aLanguages)
|
||||
{
|
||||
WorkerGlobalScope* globalScope = GlobalScope();
|
||||
if (globalScope) {
|
||||
nsRefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
|
||||
if (nav) {
|
||||
nav->SetLanguages(aLanguages);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
|
||||
mChildWorkers[index]->UpdateLanguages(aCx, aLanguages);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WorkerPrivate::UpdatePreferenceInternal(JSContext* aCx, WorkerPreference aPref, bool aValue)
|
||||
{
|
||||
|
@ -397,6 +397,9 @@ public:
|
||||
UpdateRuntimeOptions(JSContext* aCx,
|
||||
const JS::RuntimeOptions& aRuntimeOptions);
|
||||
|
||||
void
|
||||
UpdateLanguages(JSContext* aCx, const nsTArray<nsString>& aLanguages);
|
||||
|
||||
void
|
||||
UpdatePreference(JSContext* aCx, WorkerPreference aPref, bool aValue);
|
||||
|
||||
@ -935,6 +938,9 @@ public:
|
||||
void
|
||||
UpdateRuntimeOptionsInternal(JSContext* aCx, const JS::RuntimeOptions& aRuntimeOptions);
|
||||
|
||||
void
|
||||
UpdateLanguagesInternal(JSContext* aCx, const nsTArray<nsString>& aLanguages);
|
||||
|
||||
void
|
||||
UpdatePreferenceInternal(JSContext* aCx, WorkerPreference aPref, bool aValue);
|
||||
|
||||
|
@ -36,6 +36,7 @@ support-files =
|
||||
longThread_worker.js
|
||||
multi_sharedWorker_frame.html
|
||||
multi_sharedWorker_sharedWorker.js
|
||||
navigator_languages_worker.js
|
||||
navigator_worker.js
|
||||
newError_worker.js
|
||||
onLine_worker.js
|
||||
@ -126,6 +127,7 @@ skip-if = (toolkit == 'gonk' && debug) #debug-only failure
|
||||
[test_multi_sharedWorker.html]
|
||||
[test_multi_sharedWorker_lifetimes.html]
|
||||
[test_navigator.html]
|
||||
[test_navigator_languages.html]
|
||||
skip-if = buildapp == 'mulet'
|
||||
[test_newError.html]
|
||||
[test_onLine.html]
|
||||
|
11
dom/workers/test/navigator_languages_worker.js
Normal file
11
dom/workers/test/navigator_languages_worker.js
Normal file
@ -0,0 +1,11 @@
|
||||
var active = true;
|
||||
onmessage = function(e) {
|
||||
if (e.data == 'finish') {
|
||||
active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (active) {
|
||||
postMessage(navigator.languages);
|
||||
}
|
||||
}
|
@ -13,7 +13,9 @@ var supportedProps = [
|
||||
"product",
|
||||
"taintEnabled",
|
||||
"userAgent",
|
||||
"onLine"
|
||||
"onLine",
|
||||
"language",
|
||||
"languages",
|
||||
];
|
||||
|
||||
var isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
|
||||
|
@ -46,6 +46,11 @@ Tests of DOM Worker Navigator
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.name === "languages") {
|
||||
is(navigator.languages.toString(), args.value.toString(), "languages matches");
|
||||
return;
|
||||
}
|
||||
|
||||
is(navigator[args.name], args.value,
|
||||
"Mismatched navigator string for " + args.name + "!");
|
||||
};
|
||||
|
53
dom/workers/test/test_navigator_languages.html
Normal file
53
dom/workers/test/test_navigator_languages.html
Normal file
@ -0,0 +1,53 @@
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Tests of DOM Worker Navigator
|
||||
-->
|
||||
<head>
|
||||
<title>Test for DOM Worker Navigator.languages</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" language="javascript">
|
||||
|
||||
var tests = [ 'en,it', 'it,en,fr', '', 'en' ];
|
||||
var expectedLanguages;
|
||||
function runTests() {
|
||||
if (!tests.length) {
|
||||
worker.postMessage('finish');
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
expectedLanguages = tests.shift();
|
||||
SpecialPowers.pushPrefEnv({"set": [["intl.accept_languages", expectedLanguages]]}, function() {
|
||||
worker.postMessage(true);
|
||||
});
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var worker = new Worker("navigator_languages_worker.js");
|
||||
|
||||
worker.onmessage = function(event) {
|
||||
is(event.data.toString(), navigator.languages.toString(), "The languages mach.");
|
||||
is(event.data.toString(), expectedLanguages, "This is the correct result.");
|
||||
runTests();
|
||||
}
|
||||
|
||||
runTests();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -56,12 +56,15 @@ js::CreateRegExpMatchResult(JSContext *cx, HandleString input, const MatchPairs
|
||||
if (pair.isUndefined()) {
|
||||
JS_ASSERT(i != 0); /* Since we had a match, first pair must be present. */
|
||||
arr->setDenseInitializedLength(i + 1);
|
||||
arr->initDenseElement(i, UndefinedValue());
|
||||
arr->initDenseElementWithType(cx, i, UndefinedValue());
|
||||
} else {
|
||||
JSLinearString *str = NewDependentString(cx, input, pair.start, pair.length());
|
||||
if (!str)
|
||||
return false;
|
||||
arr->setDenseInitializedLength(i + 1);
|
||||
|
||||
// We don't have to update type information here, since the match
|
||||
// result template is already known to have string elements.
|
||||
arr->initDenseElement(i, StringValue(str));
|
||||
}
|
||||
}
|
||||
|
9
js/src/jit-test/tests/basic/regexp-undefined-match.js
Normal file
9
js/src/jit-test/tests/basic/regexp-undefined-match.js
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
function f(str) {
|
||||
for (var i=0; i<10; i++) {
|
||||
arr = /foo(ba(r))?/.exec(str);
|
||||
var x = arr[0] + " " + arr[1] + " " + arr[2];
|
||||
}
|
||||
}
|
||||
f("foobar");
|
||||
f("foo");
|
@ -891,6 +891,10 @@ jit::FinishDiscardBaselineScript(FreeOp *fop, JSScript *script)
|
||||
// Reset |active| flag so that we don't need a separate script
|
||||
// iteration to unmark them.
|
||||
script->baselineScript()->resetActive();
|
||||
|
||||
// The baseline caches have been wiped out, so the script will need to
|
||||
// warm back up before it can be inlined during Ion compilation.
|
||||
script->baselineScript()->clearIonCompiledOrInlined();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,12 @@ struct BaselineScript
|
||||
|
||||
// Flag set when compiled for use for debug mode. Handles various
|
||||
// Debugger hooks and compiles toggled calls for traps.
|
||||
DEBUG_MODE = 1 << 3
|
||||
DEBUG_MODE = 1 << 3,
|
||||
|
||||
// Flag set if this script has ever been Ion compiled, either directly
|
||||
// or inlined into another script. This is cleared when the script's
|
||||
// type information or caches are cleared.
|
||||
ION_COMPILED_OR_INLINED = 1 << 4
|
||||
};
|
||||
|
||||
private:
|
||||
@ -229,6 +234,16 @@ struct BaselineScript
|
||||
return flags_ & DEBUG_MODE;
|
||||
}
|
||||
|
||||
void setIonCompiledOrInlined() {
|
||||
flags_ |= ION_COMPILED_OR_INLINED;
|
||||
}
|
||||
void clearIonCompiledOrInlined() {
|
||||
flags_ &= ~ION_COMPILED_OR_INLINED;
|
||||
}
|
||||
bool ionCompiledOrInlined() const {
|
||||
return flags_ & ION_COMPILED_OR_INLINED;
|
||||
}
|
||||
|
||||
uint32_t prologueOffset() const {
|
||||
return prologueOffset_;
|
||||
}
|
||||
|
@ -148,6 +148,9 @@ IonBuilder::IonBuilder(JSContext *analysisContext, CompileCompartment *comp,
|
||||
|
||||
JS_ASSERT(script()->hasBaselineScript() == (info->executionMode() != ArgumentsUsageAnalysis));
|
||||
JS_ASSERT(!!analysisContext == (info->executionMode() == DefinitePropertiesAnalysis));
|
||||
|
||||
if (!info->executionModeIsAnalysis())
|
||||
script()->baselineScript()->setIonCompiledOrInlined();
|
||||
}
|
||||
|
||||
void
|
||||
@ -4202,6 +4205,7 @@ IonBuilder::makeInliningDecision(JSFunction *target, CallInfo &callInfo)
|
||||
// type information, except for definite properties analysis,
|
||||
// as the caller has not run yet.
|
||||
if (targetScript->getUseCount() < optimizationInfo().usesBeforeInlining() &&
|
||||
!targetScript->baselineScript()->ionCompiledOrInlined() &&
|
||||
info().executionMode() != DefinitePropertiesAnalysis)
|
||||
{
|
||||
return DontInline(targetScript, "Vetoed: callee is insufficiently hot.");
|
||||
|
@ -816,7 +816,7 @@ HandleExecutionInterrupt(JSContext *cx);
|
||||
* break out of its loop. This happens if, for example, the user clicks "Stop
|
||||
* script" on the slow script dialog; treat it as an uncatchable error.
|
||||
*/
|
||||
inline bool
|
||||
MOZ_ALWAYS_INLINE bool
|
||||
CheckForInterrupt(JSContext *cx)
|
||||
{
|
||||
MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
|
||||
|
@ -708,6 +708,12 @@ RegExpCompartment::createMatchResultTemplateObject(JSContext *cx)
|
||||
if (!templateObject)
|
||||
return matchResultTemplateObject_; // = nullptr
|
||||
|
||||
// Create a new type for the template.
|
||||
Rooted<TaggedProto> proto(cx, templateObject->getTaggedProto());
|
||||
types::TypeObject *type =
|
||||
cx->compartment()->types.newTypeObject(cx, templateObject->getClass(), proto);
|
||||
templateObject->setType(type);
|
||||
|
||||
/* Set dummy index property */
|
||||
RootedValue index(cx, Int32Value(0));
|
||||
if (!baseops::DefineProperty(cx, templateObject, cx->names().index, index,
|
||||
@ -727,6 +733,10 @@ RegExpCompartment::createMatchResultTemplateObject(JSContext *cx)
|
||||
JS_ASSERT(shape->slot() == 1 &&
|
||||
shape->propidRef() == NameToId(cx->names().input));
|
||||
|
||||
// Make sure type information reflects the indexed properties which might
|
||||
// be added.
|
||||
types::AddTypePropertyId(cx, templateObject, JSID_VOID, types::Type::StringType());
|
||||
|
||||
matchResultTemplateObject_.set(templateObject);
|
||||
|
||||
return matchResultTemplateObject_;
|
||||
|
@ -120,6 +120,11 @@ Sanitizer.prototype = {
|
||||
Services.logins.setLoginSavingEnabled(host, true);
|
||||
}
|
||||
|
||||
// Clear site security settings
|
||||
var sss = Cc["@mozilla.org/ssservice;1"]
|
||||
.getService(Ci.nsISiteSecurityService);
|
||||
sss.clearAll();
|
||||
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
|
@ -8,7 +8,7 @@ interface nsIURI;
|
||||
interface nsIObserver;
|
||||
interface nsIHttpChannel;
|
||||
|
||||
[scriptable, uuid(b20a9242-5732-45bc-9fa0-a178154f2721)]
|
||||
[scriptable, uuid(e7da4bd1-7c38-4d73-843d-c1d6af9b3c85)]
|
||||
interface nsISiteSecurityService : nsISupports
|
||||
{
|
||||
const uint32_t HEADER_HSTS = 0;
|
||||
@ -90,11 +90,12 @@ interface nsISiteSecurityService : nsISupports
|
||||
*/
|
||||
boolean isSecureURI(in uint32_t aType, in nsIURI aURI, in uint32_t aFlags);
|
||||
|
||||
/**
|
||||
* Removes all security state by resetting to factory-original settings.
|
||||
*/
|
||||
void clearAll();
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define NS_SSSERVICE_CONTRACTID "@mozilla.org/ssservice;1"
|
||||
|
||||
#define STS_PERMISSION "sts/use"
|
||||
#define STS_SUBDOMAIN_PERMISSION "sts/subd"
|
||||
%}
|
||||
|
793
security/manager/boot/src/DataStorage.cpp
Normal file
793
security/manager/boot/src/DataStorage.cpp
Normal file
@ -0,0 +1,793 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "DataStorage.h"
|
||||
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/unused.h"
|
||||
#include "nsAppDirectoryServiceDefs.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "prprf.h"
|
||||
|
||||
// NB: Read DataStorage.h first.
|
||||
|
||||
// The default time between data changing and a write, in milliseconds.
|
||||
static const uint32_t sDataStorageDefaultTimerDelay = 5u * 60u * 1000u;
|
||||
// The maximum score an entry can have (prevents overflow)
|
||||
static const uint32_t sMaxScore = UINT32_MAX;
|
||||
// The maximum number of entries per type of data (limits resource use)
|
||||
static const uint32_t sMaxDataEntries = 1024;
|
||||
static const int64_t sOneDayInMicroseconds = int64_t(24 * 60 * 60) *
|
||||
PR_USEC_PER_SEC;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMPL_ISUPPORTS(DataStorage,
|
||||
nsIObserver)
|
||||
|
||||
DataStorage::DataStorage(const nsString& aFilename)
|
||||
: mMutex("DataStorage::mMutex")
|
||||
, mPendingWrite(false)
|
||||
, mShuttingDown(false)
|
||||
, mReadyMonitor("DataStorage::mReadyMonitor")
|
||||
, mReady(false)
|
||||
, mFilename(aFilename)
|
||||
{
|
||||
}
|
||||
|
||||
DataStorage::~DataStorage()
|
||||
{
|
||||
}
|
||||
|
||||
nsresult
|
||||
DataStorage::Init(bool& aDataWillPersist)
|
||||
{
|
||||
// Don't access the observer service or preferences off the main thread.
|
||||
if (!NS_IsMainThread()) {
|
||||
NS_NOTREACHED("DataStorage::Init called off main thread");
|
||||
return NS_ERROR_NOT_SAME_THREAD;
|
||||
}
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
nsresult rv;
|
||||
rv = NS_NewThread(getter_AddRefs(mWorkerThread));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = AsyncReadData(aDataWillPersist, lock);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (NS_WARN_IF(!os)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
// Clear private data as appropriate.
|
||||
os->AddObserver(this, "last-pb-context-exited", false);
|
||||
// Observe shutdown; save data and prevent any further writes.
|
||||
os->AddObserver(this, "profile-before-change", false);
|
||||
|
||||
// For test purposes, we can set the write timer to be very fast.
|
||||
mTimerDelay = Preferences::GetInt("test.datastorage.write_timer_ms",
|
||||
sDataStorageDefaultTimerDelay);
|
||||
rv = Preferences::AddStrongObserver(this, "test.datastorage.write_timer_ms");
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
class DataStorage::Reader : public nsRunnable
|
||||
{
|
||||
public:
|
||||
explicit Reader(DataStorage* aDataStorage)
|
||||
: mDataStorage(aDataStorage)
|
||||
{
|
||||
}
|
||||
~Reader();
|
||||
|
||||
private:
|
||||
NS_DECL_NSIRUNNABLE
|
||||
|
||||
static nsresult ParseLine(nsDependentCSubstring& aLine, nsCString& aKeyOut,
|
||||
Entry& aEntryOut);
|
||||
|
||||
nsRefPtr<DataStorage> mDataStorage;
|
||||
};
|
||||
|
||||
DataStorage::Reader::~Reader()
|
||||
{
|
||||
// Notify that calls to Get can proceed.
|
||||
{
|
||||
MonitorAutoLock readyLock(mDataStorage->mReadyMonitor);
|
||||
mDataStorage->mReady = true;
|
||||
nsresult rv = mDataStorage->mReadyMonitor.NotifyAll();
|
||||
unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
}
|
||||
|
||||
// This is for tests.
|
||||
nsCOMPtr<nsIRunnable> job =
|
||||
NS_NewRunnableMethodWithArg<const char*>(mDataStorage,
|
||||
&DataStorage::NotifyObservers,
|
||||
"data-storage-ready");
|
||||
nsresult rv = NS_DispatchToMainThread(job, NS_DISPATCH_NORMAL);
|
||||
unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
DataStorage::Reader::Run()
|
||||
{
|
||||
nsresult rv;
|
||||
// Concurrent operations on nsIFile objects are not guaranteed to be safe,
|
||||
// so we clone the file while holding the lock and then release the lock.
|
||||
// At that point, we can safely operate on the clone.
|
||||
nsCOMPtr<nsIFile> file;
|
||||
{
|
||||
MutexAutoLock lock(mDataStorage->mMutex);
|
||||
// If we don't have a profile, bail.
|
||||
if (!mDataStorage->mBackingFile) {
|
||||
return NS_OK;
|
||||
}
|
||||
rv = mDataStorage->mBackingFile->Clone(getter_AddRefs(file));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
nsCOMPtr<nsIInputStream> fileInputStream;
|
||||
rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), file);
|
||||
// If we failed for some reason other than the file doesn't exist, bail.
|
||||
if (NS_WARN_IF(NS_FAILED(rv) &&
|
||||
rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && // on Unix
|
||||
rv != NS_ERROR_FILE_NOT_FOUND)) { // on Windows
|
||||
return rv;
|
||||
}
|
||||
|
||||
// If there is a file with data in it, read it. If there isn't,
|
||||
// we'll essentially fall through to notifying that we're good to go.
|
||||
nsCString data;
|
||||
if (fileInputStream) {
|
||||
// Limit to 2MB of data, but only store sMaxDataEntries entries.
|
||||
rv = NS_ConsumeStream(fileInputStream, 1u << 21, data);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
// Atomically parse the data and insert the entries read.
|
||||
// Don't clear existing entries - they may have been inserted between when
|
||||
// this read was kicked-off and when it was run.
|
||||
{
|
||||
MutexAutoLock lock(mDataStorage->mMutex);
|
||||
// The backing file consists of a list of
|
||||
// <key>\t<score>\t<last accessed time>\t<value>\n
|
||||
// The final \n is not optional; if it is not present the line is assumed
|
||||
// to be corrupt.
|
||||
int32_t currentIndex = 0;
|
||||
int32_t newlineIndex = 0;
|
||||
do {
|
||||
newlineIndex = data.FindChar('\n', currentIndex);
|
||||
// If there are no more newlines or the data table has too many
|
||||
// entries, we are done.
|
||||
if (newlineIndex < 0 ||
|
||||
mDataStorage->mPersistentDataTable.Count() >= sMaxDataEntries) {
|
||||
break;
|
||||
}
|
||||
|
||||
nsDependentCSubstring line(data, currentIndex,
|
||||
newlineIndex - currentIndex);
|
||||
currentIndex = newlineIndex + 1;
|
||||
nsCString key;
|
||||
Entry entry;
|
||||
nsresult parseRV = ParseLine(line, key, entry);
|
||||
if (NS_SUCCEEDED(parseRV)) {
|
||||
// It could be the case that a newer entry was added before
|
||||
// we got around to reading the file. Don't overwrite new entries.
|
||||
Entry newerEntry;
|
||||
bool present = mDataStorage->mPersistentDataTable.Get(key, &newerEntry);
|
||||
if (!present) {
|
||||
mDataStorage->mPersistentDataTable.Put(key, entry);
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// The key must be a non-empty string containing no instances of '\t' or '\n',
|
||||
// and must have a length no more than 256.
|
||||
// The value must not contain '\n' and must have a length no more than 1024.
|
||||
// The length limits are to prevent unbounded memory and disk usage.
|
||||
/* static */
|
||||
nsresult
|
||||
DataStorage::ValidateKeyAndValue(const nsCString& aKey, const nsCString& aValue)
|
||||
{
|
||||
if (aKey.IsEmpty()) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (aKey.Length() > 256) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
int32_t delimiterIndex = aKey.FindChar('\t', 0);
|
||||
if (delimiterIndex >= 0) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
delimiterIndex = aKey.FindChar('\n', 0);
|
||||
if (delimiterIndex >= 0) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
delimiterIndex = aValue.FindChar('\n', 0);
|
||||
if (delimiterIndex >= 0) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (aValue.Length() > 1024) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Each line is: <key>\t<score>\t<last accessed time>\t<value>
|
||||
// Where <score> is a uint32_t as a string, <last accessed time> is a
|
||||
// int32_t as a string, and the rest are strings.
|
||||
// <value> can contain anything but a newline.
|
||||
// Returns a successful status if the line can be decoded into a key and entry.
|
||||
// Otherwise, an error status is returned and the values assigned to the
|
||||
// output parameters are in an undefined state.
|
||||
/* static */
|
||||
nsresult
|
||||
DataStorage::Reader::ParseLine(nsDependentCSubstring& aLine, nsCString& aKeyOut,
|
||||
Entry& aEntryOut)
|
||||
{
|
||||
// First find the indices to each part of the line.
|
||||
int32_t scoreIndex;
|
||||
scoreIndex = aLine.FindChar('\t', 0) + 1;
|
||||
if (scoreIndex <= 0) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
int32_t accessedIndex = aLine.FindChar('\t', scoreIndex) + 1;
|
||||
if (accessedIndex <= 0) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
int32_t valueIndex = aLine.FindChar('\t', accessedIndex) + 1;
|
||||
if (valueIndex <= 0) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
// Now make substrings based on where each part is.
|
||||
nsDependentCSubstring keyPart(aLine, 0, scoreIndex - 1);
|
||||
nsDependentCSubstring scorePart(aLine, scoreIndex,
|
||||
accessedIndex - scoreIndex - 1);
|
||||
nsDependentCSubstring accessedPart(aLine, accessedIndex,
|
||||
valueIndex - accessedIndex - 1);
|
||||
nsDependentCSubstring valuePart(aLine, valueIndex);
|
||||
|
||||
nsresult rv;
|
||||
rv = DataStorage::ValidateKeyAndValue(nsCString(keyPart),
|
||||
nsCString(valuePart));
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
// Now attempt to decode the score part as a uint32_t.
|
||||
// XXX nsDependentCSubstring doesn't support ToInteger
|
||||
int32_t integer = nsCString(scorePart).ToInteger(&rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
if (integer < 0) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
aEntryOut.mScore = (uint32_t)integer;
|
||||
|
||||
integer = nsCString(accessedPart).ToInteger(&rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
if (integer < 0) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
aEntryOut.mLastAccessed = integer;
|
||||
|
||||
// Now set the key and value.
|
||||
aKeyOut.Assign(keyPart);
|
||||
aEntryOut.mValue.Assign(valuePart);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DataStorage::AsyncReadData(bool& aHaveProfileDir,
|
||||
const MutexAutoLock& /*aProofOfLock*/)
|
||||
{
|
||||
aHaveProfileDir = false;
|
||||
// Allocate a Reader so that even if it isn't dispatched,
|
||||
// the data-storage-ready notification will be fired and Get
|
||||
// will be able to proceed (this happens in its destructor).
|
||||
nsRefPtr<Reader> job(new Reader(this));
|
||||
nsresult rv;
|
||||
// If we don't have a profile directory, this will fail.
|
||||
// That's okay - it just means there is no persistent state.
|
||||
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
|
||||
getter_AddRefs(mBackingFile));
|
||||
if (NS_FAILED(rv)) {
|
||||
mBackingFile = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
rv = mBackingFile->Append(mFilename);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mWorkerThread->Dispatch(job, NS_DISPATCH_NORMAL);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
aHaveProfileDir = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
DataStorage::WaitForReady()
|
||||
{
|
||||
MonitorAutoLock readyLock(mReadyMonitor);
|
||||
while (!mReady) {
|
||||
nsresult rv = readyLock.Wait();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(mReady);
|
||||
}
|
||||
|
||||
nsCString
|
||||
DataStorage::Get(const nsCString& aKey, DataStorageType aType)
|
||||
{
|
||||
WaitForReady();
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
Entry entry;
|
||||
bool foundValue = GetInternal(aKey, &entry, aType, lock);
|
||||
if (!foundValue) {
|
||||
return EmptyCString();
|
||||
}
|
||||
|
||||
// If we're here, we found a value. Maybe update its score.
|
||||
if (entry.UpdateScore()) {
|
||||
PutInternal(aKey, entry, aType, lock);
|
||||
}
|
||||
|
||||
return entry.mValue;
|
||||
}
|
||||
|
||||
bool
|
||||
DataStorage::GetInternal(const nsCString& aKey, Entry* aEntry,
|
||||
DataStorageType aType,
|
||||
const MutexAutoLock& aProofOfLock)
|
||||
{
|
||||
DataStorageTable& table = GetTableForType(aType, aProofOfLock);
|
||||
bool foundValue = table.Get(aKey, aEntry);
|
||||
return foundValue;
|
||||
}
|
||||
|
||||
DataStorage::DataStorageTable&
|
||||
DataStorage::GetTableForType(DataStorageType aType,
|
||||
const MutexAutoLock& /*aProofOfLock*/)
|
||||
{
|
||||
switch (aType) {
|
||||
case DataStorage_Persistent:
|
||||
return mPersistentDataTable;
|
||||
case DataStorage_Temporary:
|
||||
return mTemporaryDataTable;
|
||||
case DataStorage_Private:
|
||||
return mPrivateDataTable;
|
||||
}
|
||||
|
||||
MOZ_CRASH("given bad DataStorage storage type");
|
||||
}
|
||||
|
||||
// NB: The lock must be held when calling this function.
|
||||
/* static */
|
||||
PLDHashOperator
|
||||
DataStorage::EvictCallback(const nsACString& aKey, Entry aEntry, void* aArg)
|
||||
{
|
||||
KeyAndEntry* toEvict = (KeyAndEntry*)aArg;
|
||||
if (aEntry.mScore < toEvict->mEntry.mScore) {
|
||||
toEvict->mKey = aKey;
|
||||
toEvict->mEntry = aEntry;
|
||||
}
|
||||
return PLDHashOperator::PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
// Limit the number of entries per table. This is to prevent unbounded
|
||||
// resource use. The eviction strategy is as follows:
|
||||
// - An entry's score is incremented once for every day it is accessed.
|
||||
// - Evict an entry with score no more than any other entry in the table
|
||||
// (this is the same as saying evict the entry with the lowest score,
|
||||
// except for when there are multiple entries with the lowest score,
|
||||
// in which case one of them is evicted - which one is not specified).
|
||||
void
|
||||
DataStorage::MaybeEvictOneEntry(DataStorageType aType,
|
||||
const MutexAutoLock& aProofOfLock)
|
||||
{
|
||||
DataStorageTable& table = GetTableForType(aType, aProofOfLock);
|
||||
if (table.Count() >= sMaxDataEntries) {
|
||||
KeyAndEntry toEvict;
|
||||
// If all entries have score sMaxScore, this won't actually remove
|
||||
// anything. This will never happen, however, because having that high
|
||||
// a score either means someone tampered with the backing file or every
|
||||
// entry has been accessed once a day for ~4 billion days.
|
||||
// The worst that will happen is there will be 1025 entries in the
|
||||
// persistent data table, with the 1025th entry being replaced every time
|
||||
// data with a new key is inserted into the table. This is bad but
|
||||
// ultimately not that concerning, considering that if an attacker can
|
||||
// modify data in the profile, they can cause much worse harm.
|
||||
toEvict.mEntry.mScore = sMaxScore;
|
||||
table.EnumerateRead(EvictCallback, (void*)&toEvict);
|
||||
table.Remove(toEvict.mKey);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
DataStorage::Put(const nsCString& aKey, const nsCString& aValue,
|
||||
DataStorageType aType)
|
||||
{
|
||||
WaitForReady();
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
nsresult rv;
|
||||
rv = ValidateKeyAndValue(aKey, aValue);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
Entry entry;
|
||||
bool exists = GetInternal(aKey, &entry, aType, lock);
|
||||
if (exists) {
|
||||
entry.UpdateScore();
|
||||
} else {
|
||||
MaybeEvictOneEntry(aType, lock);
|
||||
}
|
||||
entry.mValue = aValue;
|
||||
rv = PutInternal(aKey, entry, aType, lock);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DataStorage::PutInternal(const nsCString& aKey, Entry& aEntry,
|
||||
DataStorageType aType,
|
||||
const MutexAutoLock& aProofOfLock)
|
||||
{
|
||||
DataStorageTable& table = GetTableForType(aType, aProofOfLock);
|
||||
table.Put(aKey, aEntry);
|
||||
|
||||
if (aType == DataStorage_Persistent && !mPendingWrite) {
|
||||
return AsyncSetTimer(aProofOfLock);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
DataStorage::Remove(const nsCString& aKey, DataStorageType aType)
|
||||
{
|
||||
WaitForReady();
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
DataStorageTable& table = GetTableForType(aType, lock);
|
||||
table.Remove(aKey);
|
||||
|
||||
if (aType == DataStorage_Persistent && !mPendingWrite) {
|
||||
unused << AsyncSetTimer(lock);
|
||||
}
|
||||
}
|
||||
|
||||
class DataStorage::Writer : public nsRunnable
|
||||
{
|
||||
public:
|
||||
Writer(nsCString& aData, DataStorage* aDataStorage)
|
||||
: mData(aData)
|
||||
, mDataStorage(aDataStorage)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
NS_DECL_NSIRUNNABLE
|
||||
|
||||
nsCString mData;
|
||||
nsRefPtr<DataStorage> mDataStorage;
|
||||
};
|
||||
|
||||
NS_IMETHODIMP
|
||||
DataStorage::Writer::Run()
|
||||
{
|
||||
nsresult rv;
|
||||
// Concurrent operations on nsIFile objects are not guaranteed to be safe,
|
||||
// so we clone the file while holding the lock and then release the lock.
|
||||
// At that point, we can safely operate on the clone.
|
||||
nsCOMPtr<nsIFile> file;
|
||||
{
|
||||
MutexAutoLock lock(mDataStorage->mMutex);
|
||||
// If we don't have a profile, bail.
|
||||
if (!mDataStorage->mBackingFile) {
|
||||
return NS_OK;
|
||||
}
|
||||
rv = mDataStorage->mBackingFile->Clone(getter_AddRefs(file));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIOutputStream> outputStream;
|
||||
rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file,
|
||||
PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
const char* ptr = mData.get();
|
||||
int32_t remaining = mData.Length();
|
||||
uint32_t written = 0;
|
||||
while (remaining > 0) {
|
||||
rv = outputStream->Write(ptr, remaining, &written);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
remaining -= written;
|
||||
ptr += written;
|
||||
}
|
||||
|
||||
// Observed by tests.
|
||||
nsCOMPtr<nsIRunnable> job =
|
||||
NS_NewRunnableMethodWithArg<const char*>(mDataStorage,
|
||||
&DataStorage::NotifyObservers,
|
||||
"data-storage-written");
|
||||
rv = NS_DispatchToMainThread(job, NS_DISPATCH_NORMAL);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// NB: The lock must be held when calling this function.
|
||||
/* static */
|
||||
PLDHashOperator
|
||||
DataStorage::WriteDataCallback(const nsACString& aKey, Entry aEntry, void* aArg)
|
||||
{
|
||||
nsCString* output = (nsCString*)aArg;
|
||||
output->Append(aKey);
|
||||
output->Append('\t');
|
||||
output->AppendInt(aEntry.mScore);
|
||||
output->Append('\t');
|
||||
output->AppendInt(aEntry.mLastAccessed);
|
||||
output->Append('\t');
|
||||
output->Append(aEntry.mValue);
|
||||
output->Append('\n');
|
||||
return PLDHashOperator::PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DataStorage::AsyncWriteData(const MutexAutoLock& /*aProofOfLock*/)
|
||||
{
|
||||
if (mShuttingDown || !mBackingFile) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCString output;
|
||||
mPersistentDataTable.EnumerateRead(WriteDataCallback, (void*)&output);
|
||||
|
||||
nsRefPtr<Writer> job(new Writer(output, this));
|
||||
nsresult rv = mWorkerThread->Dispatch(job, NS_DISPATCH_NORMAL);
|
||||
mPendingWrite = false;
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
DataStorage::Clear()
|
||||
{
|
||||
WaitForReady();
|
||||
MutexAutoLock lock(mMutex);
|
||||
mPersistentDataTable.Clear();
|
||||
mTemporaryDataTable.Clear();
|
||||
mPrivateDataTable.Clear();
|
||||
|
||||
// Asynchronously clear the file. This is similar to the permission manager
|
||||
// in that it doesn't wait to synchronously remove the data from its backing
|
||||
// storage either.
|
||||
nsresult rv = AsyncWriteData(lock);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */
|
||||
void
|
||||
DataStorage::TimerCallback(nsITimer* aTimer, void* aClosure)
|
||||
{
|
||||
nsRefPtr<DataStorage> aDataStorage = (DataStorage*)aClosure;
|
||||
MutexAutoLock lock(aDataStorage->mMutex);
|
||||
unused << aDataStorage->AsyncWriteData(lock);
|
||||
}
|
||||
|
||||
// We only initialize the timer on the worker thread because it's not safe
|
||||
// to mix what threads are operating on the timer.
|
||||
nsresult
|
||||
DataStorage::AsyncSetTimer(const MutexAutoLock& /*aProofOfLock*/)
|
||||
{
|
||||
if (mShuttingDown) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mPendingWrite = true;
|
||||
nsCOMPtr<nsIRunnable> job =
|
||||
NS_NewRunnableMethod(this, &DataStorage::SetTimer);
|
||||
nsresult rv = mWorkerThread->Dispatch(job, NS_DISPATCH_NORMAL);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
DataStorage::SetTimer()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
nsresult rv;
|
||||
if (!mTimer) {
|
||||
mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rv = mTimer->InitWithFuncCallback(TimerCallback, this,
|
||||
mTimerDelay, nsITimer::TYPE_ONE_SHOT);
|
||||
unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
}
|
||||
|
||||
void
|
||||
DataStorage::NotifyObservers(const char* aTopic)
|
||||
{
|
||||
// Don't access the observer service off the main thread.
|
||||
if (!NS_IsMainThread()) {
|
||||
NS_NOTREACHED("DataStorage::NotifyObservers called off main thread");
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
||||
if (os) {
|
||||
os->NotifyObservers(nullptr, aTopic, mFilename.get());
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
DataStorage::DispatchShutdownTimer(const MutexAutoLock& /*aProofOfLock*/)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> job =
|
||||
NS_NewRunnableMethod(this, &DataStorage::ShutdownTimer);
|
||||
nsresult rv = mWorkerThread->Dispatch(job, NS_DISPATCH_NORMAL);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
DataStorage::ShutdownTimer()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MutexAutoLock lock(mMutex);
|
||||
nsresult rv = mTimer->Cancel();
|
||||
unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
mTimer = nullptr;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// DataStorage::nsIObserver
|
||||
//------------------------------------------------------------
|
||||
|
||||
NS_IMETHODIMP
|
||||
DataStorage::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
const char16_t* aData)
|
||||
{
|
||||
// Don't access preferences off the main thread.
|
||||
if (!NS_IsMainThread()) {
|
||||
NS_NOTREACHED("DataStorage::Observe called off main thread");
|
||||
return NS_ERROR_NOT_SAME_THREAD;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
if (strcmp(aTopic, "last-pb-context-exited") == 0) {
|
||||
MutexAutoLock lock(mMutex);
|
||||
mPrivateDataTable.Clear();
|
||||
} else if (strcmp(aTopic, "profile-before-change") == 0) {
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
rv = AsyncWriteData(lock);
|
||||
mShuttingDown = true;
|
||||
unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
if (mTimer) {
|
||||
rv = DispatchShutdownTimer(lock);
|
||||
unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
}
|
||||
}
|
||||
// Run the thread to completion and prevent any further events
|
||||
// being scheduled to it. The thread may need the lock, so we can't
|
||||
// hold it here.
|
||||
rv = mWorkerThread->Shutdown();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
} else if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
|
||||
MutexAutoLock lock(mMutex);
|
||||
mTimerDelay = Preferences::GetInt("test.datastorage.write_timer_ms",
|
||||
sDataStorageDefaultTimerDelay);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
DataStorage::Entry::Entry()
|
||||
: mScore(0)
|
||||
, mLastAccessed((int32_t)(PR_Now() / sOneDayInMicroseconds))
|
||||
{
|
||||
}
|
||||
|
||||
// Updates this entry's score. Returns true if the score has actually changed.
|
||||
// If it's been less than a day since this entry has been accessed, the score
|
||||
// does not change. Otherwise, the score increases by 1.
|
||||
// The default score is 0. The maximum score is the maximum value that can
|
||||
// be represented by an unsigned 32 bit integer.
|
||||
// This is to handle evictions from our tables, which in turn is to prevent
|
||||
// unbounded resource use.
|
||||
bool
|
||||
DataStorage::Entry::UpdateScore()
|
||||
{
|
||||
|
||||
int32_t nowInDays = (int32_t)(PR_Now() / sOneDayInMicroseconds);
|
||||
int32_t daysSinceAccessed = (nowInDays - mLastAccessed);
|
||||
|
||||
// Update the last accessed time.
|
||||
mLastAccessed = nowInDays;
|
||||
|
||||
// If it's been less than a day since we've been accessed,
|
||||
// the score isn't updated.
|
||||
if (daysSinceAccessed < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, increment the score (but don't overflow).
|
||||
if (mScore < sMaxScore) {
|
||||
mScore++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
185
security/manager/boot/src/DataStorage.h
Normal file
185
security/manager/boot/src/DataStorage.h
Normal file
@ -0,0 +1,185 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_DataStorage_h
|
||||
#define mozilla_DataStorage_h
|
||||
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIThread.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsString.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* DataStorage is a threadsafe, generic, narrow string-based hash map that
|
||||
* persists data on disk and additionally handles temporary and private data.
|
||||
* However, if used in a context where there is no profile directory, data
|
||||
* will not be persisted.
|
||||
*
|
||||
* Its lifecycle is as follows:
|
||||
* - Allocate with a filename (this is or will eventually be a file in the
|
||||
* profile directory, if the profile exists).
|
||||
* - Call Init() from the main thread. This spins off an asynchronous read
|
||||
* of the backing file.
|
||||
* - Eventually observers of the topic "data-storage-ready" will be notified
|
||||
* with the backing filename as the data in the notification when this
|
||||
* has completed.
|
||||
* - Should the profile directory not be available, (e.g. in xpcshell),
|
||||
* DataStorage will not initially read any persistent data. The
|
||||
* "data-storage-ready" event will still be emitted. This follows semantics
|
||||
* similar to the permission manager and allows tests that test
|
||||
* unrelated components to proceed without a profile.
|
||||
* - When any persistent data changes, a timer is initialized that will
|
||||
* eventually asynchronously write all persistent data to the backing file.
|
||||
* When this happens, observers will be notified with the topic
|
||||
* "data-storage-written" and the backing filename as the data.
|
||||
* It is possible to receive a "data-storage-written" event while there exist
|
||||
* pending persistent data changes. However, those changes will cause the
|
||||
* timer to be reinitialized and another "data-storage-written" event will
|
||||
* be sent.
|
||||
* - When DataStorage observes the topic "profile-before-change" in
|
||||
* anticipation of shutdown, all persistent data is synchronously written to
|
||||
* the backing file. The worker thread responsible for these writes is then
|
||||
* disabled to prevent further writes to that file (the delayed-write timer
|
||||
* is cancelled when this happens).
|
||||
* - For testing purposes, the preference "test.datastorage.write_timer_ms" can
|
||||
* be set to cause the asynchronous writing of data to happen more quickly.
|
||||
* - To prevent unbounded memory and disk use, the number of entries in each
|
||||
* table is limited to 1024. Evictions are handled in by a modified LRU scheme
|
||||
* (see implementation comments).
|
||||
* - NB: Instances of DataStorage have long lifetimes because they are strong
|
||||
* observers of events and won't go away until the observer service does.
|
||||
*
|
||||
* For each key/value:
|
||||
* - The key must be a non-empty string containing no instances of '\t' or '\n'
|
||||
* (this is a limitation of how the data is stored and will be addressed in
|
||||
* the future).
|
||||
* - The key must have a length no more than 256.
|
||||
* - The value must not contain '\n' and must have a length no more than 1024.
|
||||
* (the length limits are to prevent unbounded disk and memory usage)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that is DataStorage_Persistent is saved on disk. DataStorage_Temporary
|
||||
* and DataStorage_Private are not saved. DataStorage_Private is meant to
|
||||
* only be set and accessed from private contexts. It will be cleared upon
|
||||
* observing the event "last-pb-context-exited".
|
||||
*/
|
||||
enum DataStorageType {
|
||||
DataStorage_Persistent,
|
||||
DataStorage_Temporary,
|
||||
DataStorage_Private
|
||||
};
|
||||
|
||||
class DataStorage : public nsIObserver
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
// If there is a profile directory, there is or will eventually be a file
|
||||
// by the name specified by aFilename there.
|
||||
explicit DataStorage(const nsString& aFilename);
|
||||
|
||||
// Initializes the DataStorage. Must be called before using.
|
||||
// aDataWillPersist returns whether or not data can be persistently saved.
|
||||
nsresult Init(/*out*/bool& aDataWillPersist);
|
||||
// Given a key and a type of data, returns a value. Returns an empty string if
|
||||
// the key is not present for that type of data. If Get is called before the
|
||||
// "data-storage-ready" event is observed, it will block. NB: It is not
|
||||
// currently possible to differentiate between missing data and data that is
|
||||
// the empty string.
|
||||
nsCString Get(const nsCString& aKey, DataStorageType aType);
|
||||
// Give a key, value, and type of data, adds an entry as appropriate.
|
||||
// Updates existing entries.
|
||||
nsresult Put(const nsCString& aKey, const nsCString& aValue,
|
||||
DataStorageType aType);
|
||||
// Given a key and type of data, removes an entry if present.
|
||||
void Remove(const nsCString& aKey, DataStorageType aType);
|
||||
// Removes all entries of all types of data.
|
||||
nsresult Clear();
|
||||
|
||||
private:
|
||||
virtual ~DataStorage();
|
||||
|
||||
class Writer;
|
||||
class Reader;
|
||||
|
||||
class Entry
|
||||
{
|
||||
public:
|
||||
Entry();
|
||||
bool UpdateScore();
|
||||
|
||||
uint32_t mScore;
|
||||
int32_t mLastAccessed; // the last accessed time in days since the epoch
|
||||
nsCString mValue;
|
||||
};
|
||||
|
||||
// Utility class for scanning tables for an entry to evict.
|
||||
class KeyAndEntry
|
||||
{
|
||||
public:
|
||||
nsCString mKey;
|
||||
Entry mEntry;
|
||||
};
|
||||
|
||||
typedef nsDataHashtable<nsCStringHashKey, Entry> DataStorageTable;
|
||||
|
||||
void WaitForReady();
|
||||
nsresult AsyncWriteData(const MutexAutoLock& aProofOfLock);
|
||||
nsresult AsyncReadData(bool& aHaveProfileDir,
|
||||
const MutexAutoLock& aProofOfLock);
|
||||
nsresult AsyncSetTimer(const MutexAutoLock& aProofOfLock);
|
||||
nsresult DispatchShutdownTimer(const MutexAutoLock& aProofOfLock);
|
||||
|
||||
static nsresult ValidateKeyAndValue(const nsCString& aKey,
|
||||
const nsCString& aValue);
|
||||
static void TimerCallback(nsITimer* aTimer, void* aClosure);
|
||||
static PLDHashOperator WriteDataCallback(const nsACString& aKey, Entry aEntry,
|
||||
void* aArg);
|
||||
static PLDHashOperator EvictCallback(const nsACString& aKey, Entry aEntry,
|
||||
void* aArg);
|
||||
void SetTimer();
|
||||
void ShutdownTimer();
|
||||
void NotifyObservers(const char* aTopic);
|
||||
|
||||
bool GetInternal(const nsCString& aKey, Entry* aEntry, DataStorageType aType,
|
||||
const MutexAutoLock& aProofOfLock);
|
||||
nsresult PutInternal(const nsCString& aKey, Entry& aEntry,
|
||||
DataStorageType aType,
|
||||
const MutexAutoLock& aProofOfLock);
|
||||
void MaybeEvictOneEntry(DataStorageType aType,
|
||||
const MutexAutoLock& aProofOfLock);
|
||||
DataStorageTable& GetTableForType(DataStorageType aType,
|
||||
const MutexAutoLock& aProofOfLock);
|
||||
|
||||
Mutex mMutex; // This mutex protects access to the following members:
|
||||
DataStorageTable mPersistentDataTable;
|
||||
DataStorageTable mTemporaryDataTable;
|
||||
DataStorageTable mPrivateDataTable;
|
||||
nsCOMPtr<nsIThread> mWorkerThread;
|
||||
nsCOMPtr<nsIFile> mBackingFile;
|
||||
nsCOMPtr<nsITimer> mTimer; // All uses after init must be on the worker thread
|
||||
uint32_t mTimerDelay; // in milliseconds
|
||||
bool mPendingWrite; // true if a write is needed but hasn't been dispatched
|
||||
bool mShuttingDown;
|
||||
// (End list of members protected by mMutex)
|
||||
|
||||
Monitor mReadyMonitor; // Do not acquire this at the same time as mMutex.
|
||||
bool mReady; // Indicates that saved data has been read and Get can proceed.
|
||||
|
||||
const nsString mFilename;
|
||||
};
|
||||
|
||||
}; // namespace mozilla
|
||||
|
||||
#endif // mozilla_DataStorage_h
|
@ -4,7 +4,12 @@
|
||||
# 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/.
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'DataStorage.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'DataStorage.cpp',
|
||||
'nsBOOTModule.cpp',
|
||||
'nsEntropyCollector.cpp',
|
||||
'nsSecurityHeaderParser.cpp',
|
||||
|
@ -2,40 +2,34 @@
|
||||
* 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/. */
|
||||
|
||||
#include "plstr.h"
|
||||
#include "prlog.h"
|
||||
#include "prprf.h"
|
||||
#include "prnetdb.h"
|
||||
#include "nsSiteSecurityService.h"
|
||||
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsCRTGlue.h"
|
||||
#include "nsIPermissionManager.h"
|
||||
#include "nsISSLStatus.h"
|
||||
#include "nsISSLStatusProvider.h"
|
||||
#include "nsSiteSecurityService.h"
|
||||
#include "nsISocketProvider.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
#include "nsISocketProvider.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "nsSecurityHeaderParser.h"
|
||||
#include "nsString.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "plstr.h"
|
||||
#include "prlog.h"
|
||||
#include "prnetdb.h"
|
||||
#include "prprf.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
// A note about the preload list:
|
||||
// When a site specifically disables sts by sending a header with
|
||||
// When a site specifically disables HSTS by sending a header with
|
||||
// 'max-age: 0', we keep a "knockout" value that means "we have no information
|
||||
// regarding the sts state of this host" (any ancestor of "this host" can still
|
||||
// influence its sts status via include subdomains, however).
|
||||
// regarding the HSTS state of this host" (any ancestor of "this host" can still
|
||||
// influence its HSTS status via include subdomains, however).
|
||||
// This prevents the preload list from overriding the site's current
|
||||
// desired sts status. Knockout values are indicated by permission values of
|
||||
// STS_KNOCKOUT.
|
||||
// desired HSTS status.
|
||||
#include "nsSTSPreloadList.inc"
|
||||
|
||||
#define STS_SET (nsIPermissionManager::ALLOW_ACTION)
|
||||
#define STS_UNSET (nsIPermissionManager::UNKNOWN_ACTION)
|
||||
#define STS_KNOCKOUT (nsIPermissionManager::DENY_ACTION)
|
||||
|
||||
#if defined(PR_LOGGING)
|
||||
static PRLogModuleInfo *
|
||||
GetSSSLog()
|
||||
@ -51,29 +45,58 @@ GetSSSLog()
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
nsSSSHostEntry::nsSSSHostEntry(const char* aHost)
|
||||
: mHost(aHost)
|
||||
, mExpireTime(0)
|
||||
, mStsPermission(STS_UNSET)
|
||||
, mExpired(false)
|
||||
, mIncludeSubdomains(false)
|
||||
SiteSecurityState::SiteSecurityState(nsCString& aStateString)
|
||||
: mHSTSExpireTime(0)
|
||||
, mHSTSState(SecurityPropertyUnset)
|
||||
, mHSTSIncludeSubdomains(false)
|
||||
{
|
||||
uint32_t hstsState = 0;
|
||||
uint32_t hstsIncludeSubdomains = 0; // PR_sscanf doesn't handle bools.
|
||||
int32_t matches = PR_sscanf(aStateString.get(), "%lld,%lu,%lu",
|
||||
&mHSTSExpireTime, &hstsState,
|
||||
&hstsIncludeSubdomains);
|
||||
bool valid = (matches == 3 &&
|
||||
(hstsIncludeSubdomains == 0 || hstsIncludeSubdomains == 1) &&
|
||||
((SecurityPropertyState)hstsState == SecurityPropertyUnset ||
|
||||
(SecurityPropertyState)hstsState == SecurityPropertySet ||
|
||||
(SecurityPropertyState)hstsState == SecurityPropertyKnockout));
|
||||
if (valid) {
|
||||
mHSTSState = (SecurityPropertyState)hstsState;
|
||||
mHSTSIncludeSubdomains = (hstsIncludeSubdomains == 1);
|
||||
} else {
|
||||
SSSLOG(("%s is not a valid SiteSecurityState", aStateString.get()));
|
||||
mHSTSExpireTime = 0;
|
||||
mHSTSState = SecurityPropertyUnset;
|
||||
mHSTSIncludeSubdomains = false;
|
||||
}
|
||||
}
|
||||
|
||||
SiteSecurityState::SiteSecurityState(PRTime aHSTSExpireTime,
|
||||
SecurityPropertyState aHSTSState,
|
||||
bool aHSTSIncludeSubdomains)
|
||||
|
||||
: mHSTSExpireTime(aHSTSExpireTime)
|
||||
, mHSTSState(aHSTSState)
|
||||
, mHSTSIncludeSubdomains(aHSTSIncludeSubdomains)
|
||||
{
|
||||
}
|
||||
|
||||
nsSSSHostEntry::nsSSSHostEntry(const nsSSSHostEntry& toCopy)
|
||||
: mHost(toCopy.mHost)
|
||||
, mExpireTime(toCopy.mExpireTime)
|
||||
, mStsPermission(toCopy.mStsPermission)
|
||||
, mExpired(toCopy.mExpired)
|
||||
, mIncludeSubdomains(toCopy.mIncludeSubdomains)
|
||||
void
|
||||
SiteSecurityState::ToString(nsCString& aString)
|
||||
{
|
||||
aString.Truncate();
|
||||
aString.AppendInt(mHSTSExpireTime);
|
||||
aString.Append(',');
|
||||
aString.AppendInt(mHSTSState);
|
||||
aString.Append(',');
|
||||
aString.AppendInt(static_cast<uint32_t>(mHSTSIncludeSubdomains));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
nsSiteSecurityService::nsSiteSecurityService()
|
||||
: mUsePreloadList(true)
|
||||
, mPreloadListTimeOffset(0)
|
||||
{
|
||||
}
|
||||
|
||||
@ -93,18 +116,35 @@ nsSiteSecurityService::Init()
|
||||
MOZ_CRASH("Child process: no direct access to nsSiteSecurityService");
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
// Don't access Preferences off the main thread.
|
||||
if (!NS_IsMainThread()) {
|
||||
NS_NOTREACHED("nsSiteSecurityService initialized off main thread");
|
||||
return NS_ERROR_NOT_SAME_THREAD;
|
||||
}
|
||||
|
||||
mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mUsePreloadList = mozilla::Preferences::GetBool(
|
||||
"network.stricttransportsecurity.preloadlist", true);
|
||||
mozilla::Preferences::AddStrongObserver(this,
|
||||
"network.stricttransportsecurity.preloadlist");
|
||||
mPreloadListTimeOffset = mozilla::Preferences::GetInt(
|
||||
"test.currentTimeOffsetSeconds", 0);
|
||||
mozilla::Preferences::AddStrongObserver(this,
|
||||
"test.currentTimeOffsetSeconds");
|
||||
mSiteStateStorage =
|
||||
new mozilla::DataStorage(NS_LITERAL_STRING("SiteSecurityServiceState.txt"));
|
||||
bool storageWillPersist = false;
|
||||
nsresult rv = mSiteStateStorage->Init(storageWillPersist);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
// This is not fatal. There are some cases where there won't be a
|
||||
// profile directory (e.g. running xpcshell). There isn't the
|
||||
// expectation that site information will be presisted in those cases.
|
||||
if (!storageWillPersist) {
|
||||
NS_WARNING("site security information will not be persisted");
|
||||
}
|
||||
|
||||
mUsePreloadList = mozilla::Preferences::GetBool("network.stricttransportsecurity.preloadlist", true);
|
||||
mozilla::Preferences::AddStrongObserver(this, "network.stricttransportsecurity.preloadlist");
|
||||
mObserverService = mozilla::services::GetObserverService();
|
||||
if (mObserverService)
|
||||
mObserverService->AddObserver(this, "last-pb-context-exited", false);
|
||||
|
||||
return NS_OK;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -121,29 +161,6 @@ nsSiteSecurityService::GetHost(nsIURI *aURI, nsACString &aResult)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsSiteSecurityService::GetPrincipalForURI(nsIURI* aURI,
|
||||
nsIPrincipal** aPrincipal)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIScriptSecurityManager> securityManager =
|
||||
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// We have to normalize the scheme of the URIs we're using, so just use https.
|
||||
// HSTS information is shared across all ports for a given host.
|
||||
nsAutoCString host;
|
||||
rv = GetHost(aURI, host);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://") + host);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// We want all apps to share HSTS state, so this is one of the few places
|
||||
// where we do not silo persistent state by extended origin.
|
||||
return securityManager->GetNoAppCodebasePrincipal(uri, aPrincipal);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsSiteSecurityService::SetState(uint32_t aType,
|
||||
nsIURI* aSourceURI,
|
||||
@ -152,7 +169,7 @@ nsSiteSecurityService::SetState(uint32_t aType,
|
||||
uint32_t flags)
|
||||
{
|
||||
// If max-age is zero, that's an indication to immediately remove the
|
||||
// permissions, so here's a shortcut.
|
||||
// security state, so here's a shortcut.
|
||||
if (!maxage) {
|
||||
return RemoveState(aType, aSourceURI, flags);
|
||||
}
|
||||
@ -162,46 +179,28 @@ nsSiteSecurityService::SetState(uint32_t aType,
|
||||
int64_t expiretime = (PR_Now() / PR_USEC_PER_MSEC) +
|
||||
(maxage * PR_MSEC_PER_SEC);
|
||||
|
||||
SiteSecurityState siteState(expiretime, SecurityPropertySet,
|
||||
includeSubdomains);
|
||||
nsAutoCString stateString;
|
||||
siteState.ToString(stateString);
|
||||
nsAutoCString hostname;
|
||||
nsresult rv = GetHost(aSourceURI, hostname);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
SSSLOG(("SSS: setting state for %s", hostname.get()));
|
||||
bool isPrivate = flags & nsISocketProvider::NO_PERMANENT_STORAGE;
|
||||
|
||||
// record entry for this host with max-age in the permissions manager
|
||||
SSSLOG(("SSS: maxage permission SET, adding permission\n"));
|
||||
nsresult rv = AddPermission(aSourceURI,
|
||||
STS_PERMISSION,
|
||||
(uint32_t) STS_SET,
|
||||
(uint32_t) nsIPermissionManager::EXPIRE_TIME,
|
||||
expiretime,
|
||||
isPrivate);
|
||||
mozilla::DataStorageType storageType = isPrivate
|
||||
? mozilla::DataStorage_Private
|
||||
: mozilla::DataStorage_Persistent;
|
||||
rv = mSiteStateStorage->Put(hostname, stateString, storageType);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (includeSubdomains) {
|
||||
// record entry for this host with include subdomains in the permissions manager
|
||||
SSSLOG(("SSS: subdomains permission SET, adding permission\n"));
|
||||
rv = AddPermission(aSourceURI,
|
||||
STS_SUBDOMAIN_PERMISSION,
|
||||
(uint32_t) STS_SET,
|
||||
(uint32_t) nsIPermissionManager::EXPIRE_TIME,
|
||||
expiretime,
|
||||
isPrivate);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else { // !includeSubdomains
|
||||
nsAutoCString hostname;
|
||||
rv = GetHost(aSourceURI, hostname);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
SSSLOG(("SSS: subdomains permission UNSET, removing any existing ones\n"));
|
||||
rv = RemovePermission(hostname, STS_SUBDOMAIN_PERMISSION, isPrivate);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI, uint32_t aFlags)
|
||||
nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
// Should be called on the main thread (or via proxy) since the permission
|
||||
// manager is used and it's not threadsafe.
|
||||
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
|
||||
// Only HSTS is supported at the moment.
|
||||
NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
|
||||
NS_ERROR_NOT_IMPLEMENTED);
|
||||
@ -211,14 +210,21 @@ nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI, uint32_t aFlags
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
|
||||
|
||||
rv = RemovePermission(hostname, STS_PERMISSION, isPrivate);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
SSSLOG(("SSS: deleted maxage permission\n"));
|
||||
|
||||
rv = RemovePermission(hostname, STS_SUBDOMAIN_PERMISSION, isPrivate);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
SSSLOG(("SSS: deleted subdomains permission\n"));
|
||||
mozilla::DataStorageType storageType = isPrivate
|
||||
? mozilla::DataStorage_Private
|
||||
: mozilla::DataStorage_Persistent;
|
||||
// If this host is in the preload list, we have to store a knockout entry.
|
||||
if (GetPreloadListEntry(hostname.get())) {
|
||||
SSSLOG(("SSS: storing knockout entry for %s", hostname.get()));
|
||||
SiteSecurityState siteState(0, SecurityPropertyKnockout, false);
|
||||
nsAutoCString stateString;
|
||||
siteState.ToString(stateString);
|
||||
rv = mSiteStateStorage->Put(hostname, stateString, storageType);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
SSSLOG(("SSS: removing entry for %s", hostname.get()));
|
||||
mSiteStateStorage->Remove(hostname, storageType);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@ -238,9 +244,6 @@ nsSiteSecurityService::ProcessHeader(uint32_t aType,
|
||||
uint64_t *aMaxAge,
|
||||
bool *aIncludeSubdomains)
|
||||
{
|
||||
// Should be called on the main thread (or via proxy) since the permission
|
||||
// manager is used and it's not threadsafe.
|
||||
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
|
||||
// Only HSTS is supported at the moment.
|
||||
NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
|
||||
NS_ERROR_NOT_IMPLEMENTED);
|
||||
@ -354,11 +357,13 @@ nsSiteSecurityService::ProcessHeaderMutating(uint32_t aType,
|
||||
foundIncludeSubdomains = true;
|
||||
|
||||
if (directive->mValue.Length() != 0) {
|
||||
SSSLOG(("SSS: includeSubdomains directive unexpectedly had value '%s'", directive->mValue.get()));
|
||||
SSSLOG(("SSS: includeSubdomains directive unexpectedly had value '%s'",
|
||||
directive->mValue.get()));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
} else {
|
||||
SSSLOG(("SSS: ignoring unrecognized directive '%s'", directive->mName.get()));
|
||||
SSSLOG(("SSS: ignoring unrecognized directive '%s'",
|
||||
directive->mName.get()));
|
||||
foundUnrecognizedDirective = true;
|
||||
}
|
||||
}
|
||||
@ -381,34 +386,28 @@ nsSiteSecurityService::ProcessHeaderMutating(uint32_t aType,
|
||||
*aIncludeSubdomains = foundIncludeSubdomains;
|
||||
}
|
||||
|
||||
return foundUnrecognizedDirective ?
|
||||
NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA :
|
||||
NS_OK;
|
||||
return foundUnrecognizedDirective ? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
|
||||
: NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSiteSecurityService::IsSecureHost(uint32_t aType, const char* aHost,
|
||||
uint32_t aFlags, bool* aResult)
|
||||
nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI,
|
||||
uint32_t aFlags, bool* aResult)
|
||||
{
|
||||
// Should be called on the main thread (or via proxy) since the permission
|
||||
// manager is used and it's not threadsafe.
|
||||
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
|
||||
// Only HSTS is supported at the moment.
|
||||
NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
|
||||
NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
nsAutoCString hostname;
|
||||
nsresult rv = GetHost(aURI, hostname);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
/* An IP address never qualifies as a secure URI. */
|
||||
if (HostIsIPAddress(aHost)) {
|
||||
if (HostIsIPAddress(hostname.get())) {
|
||||
*aResult = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsDependentCString hostString(aHost);
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri),
|
||||
NS_LITERAL_CSTRING("https://") + hostString);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return IsSecureURI(aType, uri, aFlags, aResult);
|
||||
return IsSecureHost(aType, hostname.get(), aFlags, aResult);
|
||||
}
|
||||
|
||||
int STSPreloadCompare(const void *key, const void *entry)
|
||||
@ -424,14 +423,7 @@ int STSPreloadCompare(const void *key, const void *entry)
|
||||
const nsSTSPreload *
|
||||
nsSiteSecurityService::GetPreloadListEntry(const char *aHost)
|
||||
{
|
||||
PRTime currentTime = PR_Now();
|
||||
int32_t timeOffset = 0;
|
||||
nsresult rv = mozilla::Preferences::GetInt("test.currentTimeOffsetSeconds",
|
||||
&timeOffset);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
currentTime += (PRTime(timeOffset) * PR_USEC_PER_SEC);
|
||||
}
|
||||
|
||||
PRTime currentTime = PR_Now() + (mPreloadListTimeOffset * PR_USEC_PER_SEC);
|
||||
if (mUsePreloadList && currentTime < gPreloadListExpirationTime) {
|
||||
return (const nsSTSPreload *) bsearch(aHost,
|
||||
kSTSPreloadList,
|
||||
@ -444,12 +436,9 @@ nsSiteSecurityService::GetPreloadListEntry(const char *aHost)
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI,
|
||||
uint32_t aFlags, bool* aResult)
|
||||
nsSiteSecurityService::IsSecureHost(uint32_t aType, const char* aHost,
|
||||
uint32_t aFlags, bool* aResult)
|
||||
{
|
||||
// Should be called on the main thread (or via proxy) since the permission
|
||||
// manager is used and it's not threadsafe.
|
||||
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
|
||||
// Only HSTS is supported at the moment.
|
||||
NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
|
||||
NS_ERROR_NOT_IMPLEMENTED);
|
||||
@ -457,75 +446,58 @@ nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI,
|
||||
// set default in case if we can't find any STS information
|
||||
*aResult = false;
|
||||
|
||||
nsAutoCString host;
|
||||
nsresult rv = GetHost(aURI, host);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
/* An IP address never qualifies as a secure URI. */
|
||||
if (HostIsIPAddress(host.BeginReading())) {
|
||||
if (HostIsIPAddress(aHost)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Holepunch chart.apis.google.com and subdomains.
|
||||
nsAutoCString host(aHost);
|
||||
ToLowerCase(host);
|
||||
if (host.EqualsLiteral("chart.apis.google.com") ||
|
||||
StringEndsWith(host, NS_LITERAL_CSTRING(".chart.apis.google.com"))) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
const nsSTSPreload *preload = nullptr;
|
||||
nsSSSHostEntry *pbEntry = nullptr;
|
||||
|
||||
bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
|
||||
if (isPrivate) {
|
||||
pbEntry = mPrivateModeHostTable.GetEntry(host.get());
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
rv = GetPrincipalForURI(aURI, getter_AddRefs(principal));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
uint32_t permMgrPermission;
|
||||
rv = mPermMgr->TestExactPermissionFromPrincipal(principal, STS_PERMISSION,
|
||||
&permMgrPermission);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// First check the exact host. This involves first checking for an entry in
|
||||
// the private browsing table. If that entry exists, we don't want to check
|
||||
// in either the permission manager or the preload list. We only want to use
|
||||
// the stored permission if it is not a knockout entry, however.
|
||||
// site security storage. If that entry exists, we don't want to check
|
||||
// in the preload list. We only want to use the stored value if it is not a
|
||||
// knockout entry, however.
|
||||
// Additionally, if it is a knockout entry, we want to stop looking for data
|
||||
// on the host, because the knockout entry indicates "we have no information
|
||||
// regarding the sts status of this host".
|
||||
if (pbEntry && pbEntry->mStsPermission != STS_UNSET) {
|
||||
SSSLOG(("Found private browsing table entry for %s", host.get()));
|
||||
if (!pbEntry->IsExpired() && pbEntry->mStsPermission == STS_SET) {
|
||||
// regarding the security status of this host".
|
||||
bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
|
||||
mozilla::DataStorageType storageType = isPrivate
|
||||
? mozilla::DataStorage_Private
|
||||
: mozilla::DataStorage_Persistent;
|
||||
nsCString value = mSiteStateStorage->Get(host, storageType);
|
||||
SiteSecurityState siteState(value);
|
||||
if (siteState.mHSTSState != SecurityPropertyUnset) {
|
||||
SSSLOG(("Found entry for %s", host.get()));
|
||||
bool expired = siteState.IsExpired(aType);
|
||||
if (!expired && siteState.mHSTSState == SecurityPropertySet) {
|
||||
*aResult = true;
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
// Next we look in the permission manager. Same story here regarding
|
||||
// knockout entries.
|
||||
else if (permMgrPermission != STS_UNSET) {
|
||||
SSSLOG(("Found permission manager entry for %s", host.get()));
|
||||
if (permMgrPermission == STS_SET) {
|
||||
*aResult = true;
|
||||
return NS_OK;
|
||||
|
||||
// If the entry is expired and not in the preload list, we can remove it.
|
||||
if (expired && !GetPreloadListEntry(host.get())) {
|
||||
mSiteStateStorage->Remove(host, storageType);
|
||||
}
|
||||
}
|
||||
// Finally look in the preloaded list. This is the exact host,
|
||||
// so if an entry exists at all, this host is sts.
|
||||
// so if an entry exists at all, this host is HSTS.
|
||||
else if (GetPreloadListEntry(host.get())) {
|
||||
SSSLOG(("%s is a preloaded STS host", host.get()));
|
||||
*aResult = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Used for testing permissions as we walk up the domain tree.
|
||||
nsCOMPtr<nsIURI> domainWalkURI;
|
||||
nsCOMPtr<nsIPrincipal> domainWalkPrincipal;
|
||||
SSSLOG(("no HSTS data for %s found, walking up domain", host.get()));
|
||||
const char *subdomain;
|
||||
|
||||
SSSLOG(("no HSTS data for %s found, walking up domain", host.get()));
|
||||
uint32_t offset = 0;
|
||||
for (offset = host.FindChar('.', offset) + 1;
|
||||
offset > 0;
|
||||
@ -538,46 +510,26 @@ nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI,
|
||||
break;
|
||||
}
|
||||
|
||||
if (isPrivate) {
|
||||
pbEntry = mPrivateModeHostTable.GetEntry(subdomain);
|
||||
}
|
||||
|
||||
// normalize all URIs with https://
|
||||
rv = NS_NewURI(getter_AddRefs(domainWalkURI),
|
||||
NS_LITERAL_CSTRING("https://") + Substring(host, offset));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = GetPrincipalForURI(domainWalkURI, getter_AddRefs(domainWalkPrincipal));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = mPermMgr->TestExactPermissionFromPrincipal(domainWalkPrincipal,
|
||||
STS_PERMISSION,
|
||||
&permMgrPermission);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Do the same thing as with the exact host, except now we're looking at
|
||||
// ancestor domains of the original host. So, we have to look at the
|
||||
// include subdomains permissions (although we still have to check for the
|
||||
// STS_PERMISSION first to check that this is an sts host and not a
|
||||
// knockout entry - and again, if it is a knockout entry, we stop looking
|
||||
// for data on it and skip to the next higher up ancestor domain).
|
||||
if (pbEntry && pbEntry->mStsPermission != STS_UNSET) {
|
||||
SSSLOG(("Found private browsing table entry for %s", subdomain));
|
||||
if (!pbEntry->IsExpired() && pbEntry->mStsPermission == STS_SET) {
|
||||
*aResult = pbEntry->mIncludeSubdomains;
|
||||
// include subdomains flag (although we still have to check for a
|
||||
// SecurityPropertySet flag first to check that this is a secure host and
|
||||
// not a knockout entry - and again, if it is a knockout entry, we stop
|
||||
// looking for data on it and skip to the next higher up ancestor domain).
|
||||
nsCString subdomainString(subdomain);
|
||||
value = mSiteStateStorage->Get(subdomainString, storageType);
|
||||
SiteSecurityState siteState(value);
|
||||
if (siteState.mHSTSState != SecurityPropertyUnset) {
|
||||
SSSLOG(("Found entry for %s", subdomain));
|
||||
bool expired = siteState.IsExpired(aType);
|
||||
if (!expired && siteState.mHSTSState == SecurityPropertySet) {
|
||||
*aResult = siteState.mHSTSIncludeSubdomains;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (permMgrPermission != STS_UNSET) {
|
||||
SSSLOG(("Found permission manager entry for %s", subdomain));
|
||||
if (permMgrPermission == STS_SET) {
|
||||
uint32_t subdomainPermission;
|
||||
rv = mPermMgr->TestExactPermissionFromPrincipal(domainWalkPrincipal,
|
||||
STS_SUBDOMAIN_PERMISSION,
|
||||
&subdomainPermission);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
*aResult = (subdomainPermission == STS_SET);
|
||||
break;
|
||||
|
||||
// If the entry is expired and not in the preload list, we can remove it.
|
||||
if (expired && !GetPreloadListEntry(subdomain)) {
|
||||
mSiteStateStorage->Remove(subdomainString, storageType);
|
||||
}
|
||||
}
|
||||
// This is an ancestor, so if we get a match, we have to check if the
|
||||
@ -630,6 +582,12 @@ nsSiteSecurityService::ShouldIgnoreHeaders(nsISupports* aSecurityInfo,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSiteSecurityService::ClearAll()
|
||||
{
|
||||
return mSiteStateStorage->Clear();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// nsSiteSecurityService::nsIObserver
|
||||
//------------------------------------------------------------
|
||||
@ -639,123 +597,18 @@ nsSiteSecurityService::Observe(nsISupports *subject,
|
||||
const char *topic,
|
||||
const char16_t *data)
|
||||
{
|
||||
if (strcmp(topic, "last-pb-context-exited") == 0) {
|
||||
mPrivateModeHostTable.Clear();
|
||||
// Don't access Preferences off the main thread.
|
||||
if (!NS_IsMainThread()) {
|
||||
NS_NOTREACHED("Preferences accessed off main thread");
|
||||
return NS_ERROR_NOT_SAME_THREAD;
|
||||
}
|
||||
else if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
|
||||
mUsePreloadList = mozilla::Preferences::GetBool("network.stricttransportsecurity.preloadlist", true);
|
||||
|
||||
if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
|
||||
mUsePreloadList = mozilla::Preferences::GetBool(
|
||||
"network.stricttransportsecurity.preloadlist", true);
|
||||
mPreloadListTimeOffset =
|
||||
mozilla::Preferences::GetInt("test.currentTimeOffsetSeconds", 0);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Functions to overlay the permission manager calls in case
|
||||
// we're in private browsing mode.
|
||||
//------------------------------------------------------------
|
||||
nsresult
|
||||
nsSiteSecurityService::AddPermission(nsIURI *aURI,
|
||||
const char *aType,
|
||||
uint32_t aPermission,
|
||||
uint32_t aExpireType,
|
||||
int64_t aExpireTime,
|
||||
bool aIsPrivate)
|
||||
{
|
||||
// Private mode doesn't address user-set (EXPIRE_NEVER) permissions: let
|
||||
// those be stored persistently.
|
||||
if (!aIsPrivate || aExpireType == nsIPermissionManager::EXPIRE_NEVER) {
|
||||
// Not in private mode, or manually-set permission
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
nsresult rv = GetPrincipalForURI(aURI, getter_AddRefs(principal));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return mPermMgr->AddFromPrincipal(principal, aType, aPermission,
|
||||
aExpireType, aExpireTime);
|
||||
}
|
||||
|
||||
nsAutoCString host;
|
||||
nsresult rv = GetHost(aURI, host);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
SSSLOG(("AddPermission for entry for %s", host.get()));
|
||||
|
||||
// Update in mPrivateModeHostTable only, so any changes will be rolled
|
||||
// back when exiting private mode.
|
||||
|
||||
// Note: EXPIRE_NEVER permissions should trump anything that shows up in
|
||||
// the HTTP header, so if there's an EXPIRE_NEVER permission already
|
||||
// don't store anything new.
|
||||
// Currently there's no way to get the type of expiry out of the
|
||||
// permission manager, but that's okay since there's nothing that stores
|
||||
// EXPIRE_NEVER permissions.
|
||||
|
||||
// PutEntry returns an existing entry if there already is one, or it
|
||||
// creates a new one if there isn't.
|
||||
nsSSSHostEntry* entry = mPrivateModeHostTable.PutEntry(host.get());
|
||||
if (!entry) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
SSSLOG(("Created private mode entry for %s", host.get()));
|
||||
|
||||
// AddPermission() will be called twice if the STS header encountered has
|
||||
// includeSubdomains (first for the main permission and second for the
|
||||
// subdomains permission). If AddPermission() gets called a second time
|
||||
// with the STS_SUBDOMAIN_PERMISSION, we just have to flip that bit in
|
||||
// the nsSSSHostEntry.
|
||||
if (strcmp(aType, STS_SUBDOMAIN_PERMISSION) == 0) {
|
||||
entry->mIncludeSubdomains = true;
|
||||
}
|
||||
else if (strcmp(aType, STS_PERMISSION) == 0) {
|
||||
entry->mStsPermission = aPermission;
|
||||
}
|
||||
|
||||
// Also refresh the expiration time.
|
||||
entry->SetExpireTime(aExpireTime);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsSiteSecurityService::RemovePermission(const nsCString &aHost,
|
||||
const char *aType,
|
||||
bool aIsPrivate)
|
||||
{
|
||||
// Build up a principal for use with the permission manager.
|
||||
// normalize all URIs with https://
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri),
|
||||
NS_LITERAL_CSTRING("https://") + aHost);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
rv = GetPrincipalForURI(uri, getter_AddRefs(principal));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!aIsPrivate) {
|
||||
// Not in private mode: remove permissions persistently.
|
||||
// This means setting the permission to STS_KNOCKOUT in case
|
||||
// this host is on the preload list (so we can override it).
|
||||
return mPermMgr->AddFromPrincipal(principal, aType,
|
||||
STS_KNOCKOUT,
|
||||
nsIPermissionManager::EXPIRE_NEVER, 0);
|
||||
}
|
||||
|
||||
// Make changes in mPrivateModeHostTable only, so any changes will be
|
||||
// rolled back when exiting private mode.
|
||||
nsSSSHostEntry* entry = mPrivateModeHostTable.GetEntry(aHost.get());
|
||||
|
||||
if (!entry) {
|
||||
entry = mPrivateModeHostTable.PutEntry(aHost.get());
|
||||
if (!entry) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
SSSLOG(("Created private mode deleted mask for %s", aHost.get()));
|
||||
}
|
||||
|
||||
if (strcmp(aType, STS_PERMISSION) == 0) {
|
||||
entry->mStsPermission = STS_KNOCKOUT;
|
||||
}
|
||||
else if (strcmp(aType, STS_SUBDOMAIN_PERMISSION) == 0) {
|
||||
entry->mIncludeSubdomains = false;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -2,122 +2,75 @@
|
||||
* 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/. */
|
||||
|
||||
/**
|
||||
* This wraps nsSimpleURI so that all calls to it are done on the main thread.
|
||||
*/
|
||||
|
||||
#ifndef __nsSiteSecurityService_h__
|
||||
#define __nsSiteSecurityService_h__
|
||||
|
||||
#include "nsISiteSecurityService.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIPermissionManager.h"
|
||||
#include "mozilla/DataStorage.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsISiteSecurityService.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTHashtable.h"
|
||||
#include "prtime.h"
|
||||
|
||||
class nsIURI;
|
||||
|
||||
// {16955eee-6c48-4152-9309-c42a465138a1}
|
||||
#define NS_SITE_SECURITY_SERVICE_CID \
|
||||
{0x16955eee, 0x6c48, 0x4152, \
|
||||
{0x93, 0x09, 0xc4, 0x2a, 0x46, 0x51, 0x38, 0xa1} }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsSSSHostEntry - similar to the nsHostEntry class in
|
||||
// nsPermissionManager.cpp, but specific to private-mode caching of STS
|
||||
// permissions.
|
||||
//
|
||||
// Each nsSSSHostEntry contains:
|
||||
// - Expiry time (PRTime, milliseconds)
|
||||
// - Expired flag (bool, default false)
|
||||
// - STS permission (uint32_t, default STS_UNSET)
|
||||
// - Include subdomains flag (bool, default false)
|
||||
//
|
||||
// Note: the subdomains flag has no meaning if the STS permission is STS_UNSET.
|
||||
//
|
||||
// The existence of the nsSSSHostEntry implies STS state is set for the given
|
||||
// host -- unless the expired flag is set, in which case not only is the STS
|
||||
// state not set for the host, but any permission actually present in the
|
||||
// permission manager should be ignored.
|
||||
//
|
||||
// Note: Only one expiry time is stored since the subdomains and STS
|
||||
// permissions are both encountered at the same time in the HTTP header; if the
|
||||
// includeSubdomains directive isn't present in the header, it means to delete
|
||||
// the permission, so the subdomains flag in the nsSSSHostEntry means both that
|
||||
// the permission doesn't exist and any permission in the real permission
|
||||
// manager should be ignored since newer information about it has been
|
||||
// encountered in private browsing mode.
|
||||
//
|
||||
// Note: If there's a permission set by the user (EXPIRE_NEVER), STS is not set
|
||||
// for the host (including the subdomains permission) when the header is
|
||||
// encountered. Furthermore, any user-set permissions are stored persistently
|
||||
// and can't be shadowed.
|
||||
|
||||
class nsSSSHostEntry : public PLDHashEntryHdr
|
||||
{
|
||||
public:
|
||||
explicit nsSSSHostEntry(const char* aHost);
|
||||
explicit nsSSSHostEntry(const nsSSSHostEntry& toCopy);
|
||||
|
||||
nsCString mHost;
|
||||
PRTime mExpireTime;
|
||||
uint32_t mStsPermission;
|
||||
bool mExpired;
|
||||
bool mIncludeSubdomains;
|
||||
|
||||
// Hash methods
|
||||
typedef const char* KeyType;
|
||||
typedef const char* KeyTypePointer;
|
||||
|
||||
KeyType GetKey() const
|
||||
{
|
||||
return mHost.get();
|
||||
}
|
||||
|
||||
bool KeyEquals(KeyTypePointer aKey) const
|
||||
{
|
||||
return !strcmp(mHost.get(), aKey);
|
||||
}
|
||||
|
||||
static KeyTypePointer KeyToPointer(KeyType aKey)
|
||||
{
|
||||
return aKey;
|
||||
}
|
||||
|
||||
static PLDHashNumber HashKey(KeyTypePointer aKey)
|
||||
{
|
||||
return PL_DHashStringKey(nullptr, aKey);
|
||||
}
|
||||
|
||||
void SetExpireTime(PRTime aExpireTime)
|
||||
{
|
||||
mExpireTime = aExpireTime;
|
||||
mExpired = false;
|
||||
}
|
||||
|
||||
bool IsExpired()
|
||||
{
|
||||
// If mExpireTime is 0, this entry never expires (this is the case for
|
||||
// knockout entries).
|
||||
// If we've already expired or we never expire, return early.
|
||||
if (mExpired || mExpireTime == 0) {
|
||||
return mExpired;
|
||||
}
|
||||
|
||||
PRTime now = PR_Now() / PR_USEC_PER_MSEC;
|
||||
if (now > mExpireTime) {
|
||||
mExpired = true;
|
||||
}
|
||||
|
||||
return mExpired;
|
||||
}
|
||||
|
||||
// force the hashtable to use the copy constructor.
|
||||
enum { ALLOW_MEMMOVE = false };
|
||||
/**
|
||||
* SecurityPropertyState: A utility enum for representing the different states
|
||||
* a security property can be in.
|
||||
* SecurityPropertySet and SecurityPropertyUnset correspond to indicating
|
||||
* a site has or does not have the security property in question, respectively.
|
||||
* SecurityPropertyKnockout indicates a value on a preloaded list is being
|
||||
* overridden, and the associated site does not have the security property
|
||||
* in question.
|
||||
*/
|
||||
enum SecurityPropertyState {
|
||||
SecurityPropertyUnset = 0,
|
||||
SecurityPropertySet = 1,
|
||||
SecurityPropertyKnockout = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* SiteSecurityState: A utility class that encodes/decodes a string describing
|
||||
* the security state of a site. Currently only handles HSTS.
|
||||
* HSTS state consists of:
|
||||
* - Expiry time (PRTime (aka int64_t) in milliseconds)
|
||||
* - A state flag (SecurityPropertyState, default SecurityPropertyUnset)
|
||||
* - An include subdomains flag (bool, default false)
|
||||
*/
|
||||
class SiteSecurityState
|
||||
{
|
||||
public:
|
||||
SiteSecurityState(nsCString& aStateString);
|
||||
SiteSecurityState(PRTime aHSTSExpireTime, SecurityPropertyState aHSTSState,
|
||||
bool aHSTSIncludeSubdomains);
|
||||
|
||||
PRTime mHSTSExpireTime;
|
||||
SecurityPropertyState mHSTSState;
|
||||
bool mHSTSIncludeSubdomains;
|
||||
|
||||
bool IsExpired(uint32_t aType)
|
||||
{
|
||||
// If mHSTSExpireTime is 0, this entry never expires (this is the case for
|
||||
// knockout entries).
|
||||
if (mHSTSExpireTime == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PRTime now = PR_Now() / PR_USEC_PER_MSEC;
|
||||
if (now > mHSTSExpireTime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ToString(nsCString &aString);
|
||||
};
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class nsSTSPreload;
|
||||
|
||||
@ -137,7 +90,6 @@ protected:
|
||||
|
||||
private:
|
||||
nsresult GetHost(nsIURI *aURI, nsACString &aResult);
|
||||
nsresult GetPrincipalForURI(nsIURI *aURI, nsIPrincipal **aPrincipal);
|
||||
nsresult SetState(uint32_t aType, nsIURI* aSourceURI, int64_t maxage,
|
||||
bool includeSubdomains, uint32_t flags);
|
||||
nsresult ProcessHeaderMutating(uint32_t aType, nsIURI* aSourceURI,
|
||||
@ -145,23 +97,9 @@ private:
|
||||
uint64_t *aMaxAge, bool *aIncludeSubdomains);
|
||||
const nsSTSPreload *GetPreloadListEntry(const char *aHost);
|
||||
|
||||
// private-mode-preserving permission manager overlay functions
|
||||
nsresult AddPermission(nsIURI *aURI,
|
||||
const char *aType,
|
||||
uint32_t aPermission,
|
||||
uint32_t aExpireType,
|
||||
int64_t aExpireTime,
|
||||
bool aIsPrivate);
|
||||
nsresult RemovePermission(const nsCString &aHost,
|
||||
const char *aType,
|
||||
bool aIsPrivate);
|
||||
|
||||
// cached services
|
||||
nsCOMPtr<nsIPermissionManager> mPermMgr;
|
||||
nsCOMPtr<nsIObserverService> mObserverService;
|
||||
|
||||
nsTHashtable<nsSSSHostEntry> mPrivateModeHostTable;
|
||||
bool mUsePreloadList;
|
||||
int64_t mPreloadListTimeOffset;
|
||||
nsRefPtr<mozilla::DataStorage> mSiteStateStorage;
|
||||
};
|
||||
|
||||
#endif // __nsSiteSecurityService_h__
|
||||
|
224
security/manager/ssl/tests/gtest/DataStorageTest.cpp
Normal file
224
security/manager/ssl/tests/gtest/DataStorageTest.cpp
Normal file
@ -0,0 +1,224 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/DataStorage.h"
|
||||
#include "nsAppDirectoryServiceDefs.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsStreamUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
class DataStorageTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
virtual void SetUp()
|
||||
{
|
||||
const ::testing::TestInfo* const testInfo =
|
||||
::testing::UnitTest::GetInstance()->current_test_info();
|
||||
NS_ConvertUTF8toUTF16 testName(testInfo->name());
|
||||
storage = new DataStorage(testName);
|
||||
storage->Init(dataWillPersist);
|
||||
}
|
||||
|
||||
nsRefPtr<DataStorage> storage;
|
||||
bool dataWillPersist;
|
||||
};
|
||||
|
||||
NS_NAMED_LITERAL_CSTRING(testKey, "test");
|
||||
NS_NAMED_LITERAL_CSTRING(testValue, "value");
|
||||
NS_NAMED_LITERAL_CSTRING(privateTestValue, "private");
|
||||
|
||||
TEST_F(DataStorageTest, GetPutRemove)
|
||||
{
|
||||
EXPECT_TRUE(dataWillPersist);
|
||||
|
||||
// Test Put/Get on Persistent data
|
||||
EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
|
||||
// Don't re-use testKey / testValue here, to make sure that this works as
|
||||
// expected with objects that have the same semantic value but are not
|
||||
// literally the same object.
|
||||
nsCString result = storage->Get(NS_LITERAL_CSTRING("test"),
|
||||
DataStorage_Persistent);
|
||||
EXPECT_STREQ("value", result.get());
|
||||
|
||||
// Get on Temporary/Private data with the same key should give nothing
|
||||
result = storage->Get(testKey, DataStorage_Temporary);
|
||||
EXPECT_TRUE(result.IsEmpty());
|
||||
result = storage->Get(testKey, DataStorage_Private);
|
||||
EXPECT_TRUE(result.IsEmpty());
|
||||
|
||||
// Put with Temporary/Private data shouldn't affect Persistent data
|
||||
NS_NAMED_LITERAL_CSTRING(temporaryTestValue, "temporary");
|
||||
EXPECT_EQ(NS_OK, storage->Put(testKey, temporaryTestValue,
|
||||
DataStorage_Temporary));
|
||||
EXPECT_EQ(NS_OK, storage->Put(testKey, privateTestValue,
|
||||
DataStorage_Private));
|
||||
result = storage->Get(testKey, DataStorage_Temporary);
|
||||
EXPECT_STREQ("temporary", result.get());
|
||||
result = storage->Get(testKey, DataStorage_Private);
|
||||
EXPECT_STREQ("private", result.get());
|
||||
result = storage->Get(testKey, DataStorage_Persistent);
|
||||
EXPECT_STREQ("value", result.get());
|
||||
|
||||
// Put of a previously-present key overwrites it (if of the same type)
|
||||
NS_NAMED_LITERAL_CSTRING(newValue, "new");
|
||||
EXPECT_EQ(NS_OK, storage->Put(testKey, newValue, DataStorage_Persistent));
|
||||
result = storage->Get(testKey, DataStorage_Persistent);
|
||||
EXPECT_STREQ("new", result.get());
|
||||
|
||||
// Removal should work
|
||||
storage->Remove(testKey, DataStorage_Temporary);
|
||||
result = storage->Get(testKey, DataStorage_Temporary);
|
||||
EXPECT_TRUE(result.IsEmpty());
|
||||
// But removing one type shouldn't affect the others
|
||||
result = storage->Get(testKey, DataStorage_Private);
|
||||
EXPECT_STREQ("private", result.get());
|
||||
result = storage->Get(testKey, DataStorage_Persistent);
|
||||
EXPECT_STREQ("new", result.get());
|
||||
// Test removing the other types as well
|
||||
storage->Remove(testKey, DataStorage_Private);
|
||||
result = storage->Get(testKey, DataStorage_Private);
|
||||
EXPECT_TRUE(result.IsEmpty());
|
||||
storage->Remove(testKey, DataStorage_Persistent);
|
||||
result = storage->Get(testKey, DataStorage_Persistent);
|
||||
EXPECT_TRUE(result.IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(DataStorageTest, InputValidation)
|
||||
{
|
||||
EXPECT_TRUE(dataWillPersist);
|
||||
|
||||
// Keys may not have tabs or newlines
|
||||
EXPECT_EQ(NS_ERROR_INVALID_ARG,
|
||||
storage->Put(NS_LITERAL_CSTRING("key\thas tab"), testValue,
|
||||
DataStorage_Persistent));
|
||||
nsCString result = storage->Get(NS_LITERAL_CSTRING("key\thas tab"),
|
||||
DataStorage_Persistent);
|
||||
EXPECT_TRUE(result.IsEmpty());
|
||||
EXPECT_EQ(NS_ERROR_INVALID_ARG,
|
||||
storage->Put(NS_LITERAL_CSTRING("key has\nnewline"), testValue,
|
||||
DataStorage_Persistent));
|
||||
result = storage->Get(NS_LITERAL_CSTRING("keyhas\nnewline"),
|
||||
DataStorage_Persistent);
|
||||
EXPECT_TRUE(result.IsEmpty());
|
||||
// Values may not have newlines
|
||||
EXPECT_EQ(NS_ERROR_INVALID_ARG,
|
||||
storage->Put(testKey, NS_LITERAL_CSTRING("value\nhas newline"),
|
||||
DataStorage_Persistent));
|
||||
result = storage->Get(testKey, DataStorage_Persistent);
|
||||
// Values may have tabs
|
||||
EXPECT_TRUE(result.IsEmpty());
|
||||
EXPECT_EQ(NS_OK, storage->Put(testKey,
|
||||
NS_LITERAL_CSTRING("val\thas tab; this is ok"),
|
||||
DataStorage_Persistent));
|
||||
result = storage->Get(testKey, DataStorage_Persistent);
|
||||
EXPECT_STREQ("val\thas tab; this is ok", result.get());
|
||||
|
||||
nsCString longKey("a");
|
||||
for (int i = 0; i < 8; i++) {
|
||||
longKey.Append(longKey);
|
||||
}
|
||||
// A key of length 256 will work
|
||||
EXPECT_EQ(NS_OK, storage->Put(longKey, testValue, DataStorage_Persistent));
|
||||
result = storage->Get(longKey, DataStorage_Persistent);
|
||||
EXPECT_STREQ("value", result.get());
|
||||
longKey.Append("a");
|
||||
// A key longer than that will not work
|
||||
EXPECT_EQ(NS_ERROR_INVALID_ARG,
|
||||
storage->Put(longKey, testValue, DataStorage_Persistent));
|
||||
result = storage->Get(longKey, DataStorage_Persistent);
|
||||
EXPECT_TRUE(result.IsEmpty());
|
||||
|
||||
nsCString longValue("a");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
longValue.Append(longValue);
|
||||
}
|
||||
// A value of length 1024 will work
|
||||
EXPECT_EQ(NS_OK, storage->Put(testKey, longValue, DataStorage_Persistent));
|
||||
result = storage->Get(testKey, DataStorage_Persistent);
|
||||
EXPECT_STREQ(longValue.get(), result.get());
|
||||
longValue.Append("a");
|
||||
// A value longer than that will not work
|
||||
storage->Remove(testKey, DataStorage_Persistent);
|
||||
EXPECT_EQ(NS_ERROR_INVALID_ARG,
|
||||
storage->Put(testKey, longValue, DataStorage_Persistent));
|
||||
result = storage->Get(testKey, DataStorage_Persistent);
|
||||
EXPECT_TRUE(result.IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(DataStorageTest, Eviction)
|
||||
{
|
||||
EXPECT_TRUE(dataWillPersist);
|
||||
|
||||
// Eviction is on a per-table basis. Tables shouldn't affect each other.
|
||||
EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
|
||||
for (int i = 0; i < 1025; i++) {
|
||||
EXPECT_EQ(NS_OK, storage->Put(nsPrintfCString("%d", i),
|
||||
nsPrintfCString("%d", i),
|
||||
DataStorage_Temporary));
|
||||
nsCString result = storage->Get(nsPrintfCString("%d", i),
|
||||
DataStorage_Temporary);
|
||||
EXPECT_STREQ(nsPrintfCString("%d", i).get(), result.get());
|
||||
}
|
||||
// We don't know which entry got evicted, but we can count them.
|
||||
int entries = 0;
|
||||
for (int i = 0; i < 1025; i++) {
|
||||
nsCString result = storage->Get(nsPrintfCString("%d", i),
|
||||
DataStorage_Temporary);
|
||||
if (!result.IsEmpty()) {
|
||||
entries++;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(entries, 1024);
|
||||
nsCString result = storage->Get(testKey, DataStorage_Persistent);
|
||||
EXPECT_STREQ("value", result.get());
|
||||
}
|
||||
|
||||
TEST_F(DataStorageTest, ClearPrivateData)
|
||||
{
|
||||
EXPECT_TRUE(dataWillPersist);
|
||||
|
||||
EXPECT_EQ(NS_OK, storage->Put(testKey, privateTestValue,
|
||||
DataStorage_Private));
|
||||
nsCString result = storage->Get(testKey, DataStorage_Private);
|
||||
EXPECT_STREQ("private", result.get());
|
||||
storage->Observe(nullptr, "last-pb-context-exited", nullptr);
|
||||
result = storage->Get(testKey, DataStorage_Private);
|
||||
EXPECT_TRUE(result.IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(DataStorageTest, Shutdown)
|
||||
{
|
||||
EXPECT_TRUE(dataWillPersist);
|
||||
|
||||
EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
|
||||
nsCString result = storage->Get(testKey, DataStorage_Persistent);
|
||||
EXPECT_STREQ("value", result.get());
|
||||
// Get "now" (in days) close to when the data was last touched, so we won't
|
||||
// get intermittent failures with the day not matching.
|
||||
int64_t microsecondsPerDay = 24 * 60 * 60 * int64_t(PR_USEC_PER_SEC);
|
||||
int32_t nowInDays = int32_t(PR_Now() / microsecondsPerDay);
|
||||
storage->Observe(nullptr, "profile-before-change", nullptr);
|
||||
nsCOMPtr<nsIFile> backingFile;
|
||||
EXPECT_EQ(NS_OK, NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
|
||||
getter_AddRefs(backingFile)));
|
||||
const ::testing::TestInfo* const testInfo =
|
||||
::testing::UnitTest::GetInstance()->current_test_info();
|
||||
NS_ConvertUTF8toUTF16 testName(testInfo->name());
|
||||
EXPECT_EQ(NS_OK, backingFile->Append(testName));
|
||||
nsCOMPtr<nsIInputStream> fileInputStream;
|
||||
EXPECT_EQ(NS_OK, NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
|
||||
backingFile));
|
||||
nsCString data;
|
||||
EXPECT_EQ(NS_OK, NS_ConsumeStream(fileInputStream, UINT32_MAX, data));
|
||||
// The data will be of the form 'test\t0\t<days since the epoch>\tvalue'
|
||||
EXPECT_STREQ(nsPrintfCString("test\t0\t%d\tvalue\n", nowInDays).get(),
|
||||
data.get());
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
SOURCES += [
|
||||
'DataStorageTest.cpp',
|
||||
'OCSPCacheTest.cpp',
|
||||
'TLSIntoleranceTest.cpp',
|
||||
]
|
||||
|
@ -18,6 +18,8 @@ let gIsWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
|
||||
const isDebugBuild = Cc["@mozilla.org/xpcom/debug;1"]
|
||||
.getService(Ci.nsIDebug2).isDebugBuild;
|
||||
|
||||
const SSS_STATE_FILE_NAME = "SiteSecurityServiceState.txt";
|
||||
|
||||
const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
|
||||
const SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
|
||||
const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE;
|
||||
|
75
security/manager/ssl/tests/unit/test_sss_eviction.js
Normal file
75
security/manager/ssl/tests/unit/test_sss_eviction.js
Normal file
@ -0,0 +1,75 @@
|
||||
/* 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/. */
|
||||
|
||||
// The purpose of this test is to check that a frequently visited site
|
||||
// will not be evicted over an infrequently visited site.
|
||||
|
||||
let gSSService = null;
|
||||
let gProfileDir = null;
|
||||
|
||||
function do_state_written(aSubject, aTopic, aData) {
|
||||
do_check_eq(aData, SSS_STATE_FILE_NAME);
|
||||
|
||||
let stateFile = gProfileDir.clone();
|
||||
stateFile.append(SSS_STATE_FILE_NAME);
|
||||
do_check_true(stateFile.exists());
|
||||
let stateFileContents = readFile(stateFile);
|
||||
// the last part is removed because it's the empty string after the final \n
|
||||
let lines = stateFileContents.split('\n').slice(0, -1);
|
||||
// We can receive multiple data-storage-written events. In particular, we
|
||||
// may receive one where DataStorage wrote out data before we were done
|
||||
// processing all of our headers. In this case, the data may not be
|
||||
// as we expect. We only care about the final one being correct, however,
|
||||
// so we return and wait for the next event if things aren't as we expect.
|
||||
// There should be 1024 entries.
|
||||
if (lines.length != 1024) {
|
||||
return;
|
||||
}
|
||||
|
||||
let foundLegitSite = false;
|
||||
for (let line of lines) {
|
||||
if (line.startsWith("frequentlyused.example.com")) {
|
||||
foundLegitSite = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
do_check_true(foundLegitSite);
|
||||
do_test_finished();
|
||||
}
|
||||
|
||||
function do_state_read(aSubject, aTopic, aData) {
|
||||
do_check_eq(aData, SSS_STATE_FILE_NAME);
|
||||
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"frequentlyused.example.com", 0));
|
||||
for (let i = 0; i < 2000; i++) {
|
||||
let uri = Services.io.newURI("http://bad" + i + ".example.com", null, null);
|
||||
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
|
||||
"max-age=1000", 0);
|
||||
}
|
||||
do_test_pending();
|
||||
Services.obs.addObserver(do_state_written, "data-storage-written", false);
|
||||
do_test_finished();
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
Services.prefs.setIntPref("test.datastorage.write_timer_ms", 100);
|
||||
gProfileDir = do_get_profile();
|
||||
let stateFile = gProfileDir.clone();
|
||||
stateFile.append(SSS_STATE_FILE_NAME);
|
||||
// Assuming we're working with a clean slate, the file shouldn't exist
|
||||
// until we create it.
|
||||
do_check_false(stateFile.exists());
|
||||
let outputStream = FileUtils.openFileOutputStream(stateFile);
|
||||
let now = (new Date()).getTime();
|
||||
let line = "frequentlyused.example.com\t4\t0\t" + (now + 100000) + ",1,0\n";
|
||||
outputStream.write(line, line.length);
|
||||
outputStream.close();
|
||||
Services.obs.addObserver(do_state_read, "data-storage-ready", false);
|
||||
do_test_pending();
|
||||
gSSService = Cc["@mozilla.org/ssservice;1"]
|
||||
.getService(Ci.nsISiteSecurityService);
|
||||
do_check_true(gSSService != null);
|
||||
}
|
77
security/manager/ssl/tests/unit/test_sss_readstate.js
Normal file
77
security/manager/ssl/tests/unit/test_sss_readstate.js
Normal file
@ -0,0 +1,77 @@
|
||||
/* 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/. */
|
||||
|
||||
// The purpose of this test is to create a site security service state file
|
||||
// and see that the site security service reads it properly.
|
||||
|
||||
function writeLine(aLine, aOutputStream) {
|
||||
aOutputStream.write(aLine, aLine.length);
|
||||
}
|
||||
|
||||
let gSSService = null;
|
||||
|
||||
function checkStateRead(aSubject, aTopic, aData) {
|
||||
do_check_eq(aData, SSS_STATE_FILE_NAME);
|
||||
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"expired.example.com", 0));
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"notexpired.example.com", 0));
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"bugzilla.mozilla.org", 0));
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"sub.bugzilla.mozilla.org", 0));
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"incsubdomain.example.com", 0));
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"sub.incsubdomain.example.com", 0));
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"login.persona.org", 0));
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"sub.login.persona.org", 0));
|
||||
|
||||
// Clearing the data should make everything go back to default.
|
||||
gSSService.clearAll();
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"expired.example.com", 0));
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"notexpired.example.com", 0));
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"bugzilla.mozilla.org", 0));
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"sub.bugzilla.mozilla.org", 0));
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"incsubdomain.example.com", 0));
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"sub.incsubdomain.example.com", 0));
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"login.persona.org", 0));
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"sub.login.persona.org", 0));
|
||||
do_test_finished();
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let profileDir = do_get_profile();
|
||||
let stateFile = profileDir.clone();
|
||||
stateFile.append(SSS_STATE_FILE_NAME);
|
||||
// Assuming we're working with a clean slate, the file shouldn't exist
|
||||
// until we create it.
|
||||
do_check_false(stateFile.exists());
|
||||
let outputStream = FileUtils.openFileOutputStream(stateFile);
|
||||
let now = (new Date()).getTime();
|
||||
writeLine("expired.example.com\t0\t0\t" + (now - 100000) + ",1,0\n", outputStream);
|
||||
writeLine("notexpired.example.com\t0\t0\t" + (now + 100000) + ",1,0\n", outputStream);
|
||||
// This overrides an entry on the preload list.
|
||||
writeLine("bugzilla.mozilla.org\t0\t0\t" + (now + 100000) + ",1,0\n", outputStream);
|
||||
writeLine("incsubdomain.example.com\t0\t0\t" + (now + 100000) + ",1,1\n", outputStream);
|
||||
// This overrides an entry on the preload list.
|
||||
writeLine("login.persona.org\t0\t0\t0,2,0\n", outputStream);
|
||||
outputStream.close();
|
||||
Services.obs.addObserver(checkStateRead, "data-storage-ready", false);
|
||||
do_test_pending();
|
||||
gSSService = Cc["@mozilla.org/ssservice;1"]
|
||||
.getService(Ci.nsISiteSecurityService);
|
||||
do_check_true(gSSService != null);
|
||||
}
|
40
security/manager/ssl/tests/unit/test_sss_readstate_empty.js
Normal file
40
security/manager/ssl/tests/unit/test_sss_readstate_empty.js
Normal file
@ -0,0 +1,40 @@
|
||||
/* 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/. */
|
||||
|
||||
// The purpose of this test is to create an empty site security service state
|
||||
// file and see that the site security service doesn't fail when reading it.
|
||||
|
||||
let gSSService = null;
|
||||
|
||||
function checkStateRead(aSubject, aTopic, aData) {
|
||||
// nonexistent.example.com should never be an HSTS host
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"nonexistent.example.com", 0));
|
||||
// bugzilla.mozilla.org is preloaded
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"bugzilla.mozilla.org", 0));
|
||||
// notexpired.example.com is an HSTS host in a different test - we
|
||||
// want to make sure that test hasn't interfered with this one.
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"notexpired.example.com", 0));
|
||||
do_test_finished();
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let profileDir = do_get_profile();
|
||||
let stateFile = profileDir.clone();
|
||||
stateFile.append(SSS_STATE_FILE_NAME);
|
||||
// Assuming we're working with a clean slate, the file shouldn't exist
|
||||
// until we create it.
|
||||
do_check_false(stateFile.exists());
|
||||
stateFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0x1a4); // 0x1a4 == 0644
|
||||
do_check_true(stateFile.exists());
|
||||
// Initialize nsISiteSecurityService after do_get_profile() so it
|
||||
// can read the state file.
|
||||
Services.obs.addObserver(checkStateRead, "data-storage-ready", false);
|
||||
do_test_pending();
|
||||
gSSService = Cc["@mozilla.org/ssservice;1"]
|
||||
.getService(Ci.nsISiteSecurityService);
|
||||
do_check_true(gSSService != null);
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* 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/. */
|
||||
|
||||
// The purpose of this test is to create a mostly bogus site security service
|
||||
// state file and see that the site security service handles it properly.
|
||||
|
||||
function writeLine(aLine, aOutputStream) {
|
||||
aOutputStream.write(aLine, aLine.length);
|
||||
}
|
||||
|
||||
let gSSService = null;
|
||||
|
||||
function checkStateRead(aSubject, aTopic, aData) {
|
||||
do_check_eq(aData, SSS_STATE_FILE_NAME);
|
||||
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"example1.example.com", 0));
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"example2.example.com", 0));
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"example.com", 0));
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"example3.example.com", 0));
|
||||
do_test_finished();
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let profileDir = do_get_profile();
|
||||
let stateFile = profileDir.clone();
|
||||
stateFile.append(SSS_STATE_FILE_NAME);
|
||||
// Assuming we're working with a clean slate, the file shouldn't exist
|
||||
// until we create it.
|
||||
do_check_false(stateFile.exists());
|
||||
let outputStream = FileUtils.openFileOutputStream(stateFile);
|
||||
let now = (new Date()).getTime();
|
||||
writeLine("example1.example.com\t0\t0\t" + (now + 100000) + ",1,0\n", outputStream);
|
||||
writeLine("I'm a lumberjack and I'm okay; I work all night and I sleep all day!\n", outputStream);
|
||||
writeLine("This is a totally bogus entry\t\n", outputStream);
|
||||
writeLine("0\t0\t0\t0\t\n", outputStream);
|
||||
writeLine("\t\t\t\t\t\t\t\n", outputStream);
|
||||
writeLine("example.com\t\t\t\t\t\t\t\n", outputStream);
|
||||
writeLine("example3.example.com\t0\t\t\t\t\t\t\n", outputStream);
|
||||
writeLine("example2.example.com\t0\t0\t" + (now + 100000) + ",1,0\n", outputStream);
|
||||
outputStream.close();
|
||||
Services.obs.addObserver(checkStateRead, "data-storage-ready", false);
|
||||
do_test_pending();
|
||||
gSSService = Cc["@mozilla.org/ssservice;1"]
|
||||
.getService(Ci.nsISiteSecurityService);
|
||||
do_check_true(gSSService != null);
|
||||
}
|
55
security/manager/ssl/tests/unit/test_sss_readstate_huge.js
Normal file
55
security/manager/ssl/tests/unit/test_sss_readstate_huge.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* 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/. */
|
||||
|
||||
// The purpose of this test is to create a site security service state file
|
||||
// that is too large and see that the site security service reads it properly
|
||||
// (this means discarding all entries after the 1024th).
|
||||
|
||||
function writeLine(aLine, aOutputStream) {
|
||||
aOutputStream.write(aLine, aLine.length);
|
||||
}
|
||||
|
||||
let gSSService = null;
|
||||
|
||||
function checkStateRead(aSubject, aTopic, aData) {
|
||||
do_check_eq(aData, SSS_STATE_FILE_NAME);
|
||||
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"example0.example.com", 0));
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"example423.example.com", 0));
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"example1023.example.com", 0));
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"example1024.example.com", 0));
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"example1025.example.com", 0));
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"example9000.example.com", 0));
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"example99999.example.com", 0));
|
||||
do_test_finished();
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let profileDir = do_get_profile();
|
||||
let stateFile = profileDir.clone();
|
||||
stateFile.append(SSS_STATE_FILE_NAME);
|
||||
// Assuming we're working with a clean slate, the file shouldn't exist
|
||||
// until we create it.
|
||||
do_check_false(stateFile.exists());
|
||||
let outputStream = FileUtils.openFileOutputStream(stateFile);
|
||||
let now = (new Date()).getTime();
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
// The 0s will all get squashed down into one 0 when they are read.
|
||||
// This is just to make the file size large (>2MB).
|
||||
writeLine("example" + i + ".example.com\t0000000000000000000000000000000000000000000000000\t00000000000000000000000000000000000000\t" + (now + 100000) + ",1,0000000000000000000000000000000000000000000000000000000000000000000000000\n", outputStream);
|
||||
}
|
||||
outputStream.close();
|
||||
Services.obs.addObserver(checkStateRead, "data-storage-ready", false);
|
||||
do_test_pending();
|
||||
gSSService = Cc["@mozilla.org/ssservice;1"]
|
||||
.getService(Ci.nsISiteSecurityService);
|
||||
do_check_true(gSSService != null);
|
||||
}
|
104
security/manager/ssl/tests/unit/test_sss_savestate.js
Normal file
104
security/manager/ssl/tests/unit/test_sss_savestate.js
Normal file
@ -0,0 +1,104 @@
|
||||
/* 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/. */
|
||||
|
||||
// The purpose of this test is to see that the site security service properly
|
||||
// writes its state file.
|
||||
|
||||
const EXPECTED_ENTRIES = 5;
|
||||
const EXPECTED_COLUMNS = 3;
|
||||
let gProfileDir = null;
|
||||
|
||||
// For reference, the format of the state file is a list of:
|
||||
// <domain name> <expiration time in milliseconds>,<sts status>,<includeSubdomains>
|
||||
// separated by newlines ('\n')
|
||||
|
||||
function checkStateWritten(aSubject, aTopic, aData) {
|
||||
do_check_eq(aData, SSS_STATE_FILE_NAME);
|
||||
|
||||
let stateFile = gProfileDir.clone();
|
||||
stateFile.append(SSS_STATE_FILE_NAME);
|
||||
do_check_true(stateFile.exists());
|
||||
let stateFileContents = readFile(stateFile);
|
||||
// the last line is removed because it's just a trailing newline
|
||||
let lines = stateFileContents.split('\n').slice(0, -1);
|
||||
do_check_eq(lines.length, EXPECTED_ENTRIES);
|
||||
let sites = {}; // a map of domain name -> [the entry in the state file]
|
||||
for (let line of lines) {
|
||||
let parts = line.split('\t');
|
||||
let host = parts[0];
|
||||
let score = parts[1];
|
||||
let lastAccessed = parts[2];
|
||||
let entry = parts[3].split(',');
|
||||
do_check_eq(entry.length, EXPECTED_COLUMNS);
|
||||
sites[host] = entry;
|
||||
}
|
||||
|
||||
// We can receive multiple data-storage-written events. In particular, we
|
||||
// may receive one where DataStorage wrote out data before we were done
|
||||
// processing all of our headers. In this case, the data may not be
|
||||
// as we expect. We only care about the final one being correct, however,
|
||||
// so we return and wait for the next event if things aren't as we expect.
|
||||
// sites[url][1] corresponds to SecurityPropertySet (if 1) and
|
||||
// SecurityPropertyUnset (if 0)
|
||||
// sites[url][2] corresponds to includeSubdomains
|
||||
if (sites["bugzilla.mozilla.org"][1] != 1) {
|
||||
return;
|
||||
}
|
||||
if (sites["bugzilla.mozilla.org"][2] != 0) {
|
||||
return;
|
||||
}
|
||||
if (sites["a.example.com"][1] != 1) {
|
||||
return;
|
||||
}
|
||||
if (sites["a.example.com"][2] != 1) {
|
||||
return;
|
||||
}
|
||||
if (sites["b.example.com"][1] != 1) {
|
||||
return;
|
||||
}
|
||||
if (sites["b.example.com"][2] != 0) {
|
||||
return;
|
||||
}
|
||||
if (sites["c.c.example.com"][1] != 1) {
|
||||
return;
|
||||
}
|
||||
if (sites["c.c.example.com"][2] != 1) {
|
||||
return;
|
||||
}
|
||||
if (sites["d.example.com"][1] != 1) {
|
||||
return;
|
||||
}
|
||||
if (sites["d.example.com"][2] != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
do_test_finished();
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
Services.prefs.setIntPref("test.datastorage.write_timer_ms", 100);
|
||||
gProfileDir = do_get_profile();
|
||||
let SSService = Cc["@mozilla.org/ssservice;1"]
|
||||
.getService(Ci.nsISiteSecurityService);
|
||||
|
||||
let uris = [ Services.io.newURI("http://bugzilla.mozilla.org", null, null),
|
||||
Services.io.newURI("http://a.example.com", null, null),
|
||||
Services.io.newURI("http://b.example.com", null, null),
|
||||
Services.io.newURI("http://c.c.example.com", null, null),
|
||||
Services.io.newURI("http://d.example.com", null, null) ];
|
||||
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
let uriIndex = i % uris.length;
|
||||
// vary max-age
|
||||
let maxAge = "max-age=" + (i * 1000);
|
||||
// alternate setting includeSubdomains
|
||||
let includeSubdomains = (i % 2 == 0 ? "; includeSubdomains" : "");
|
||||
SSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
uris[uriIndex], maxAge + includeSubdomains, 0);
|
||||
}
|
||||
|
||||
|
||||
do_test_pending();
|
||||
Services.obs.addObserver(checkStateWritten, "data-storage-written", false);
|
||||
}
|
@ -17,28 +17,9 @@ Observer.prototype = {
|
||||
|
||||
var gObserver = new Observer();
|
||||
|
||||
// nsISiteSecurityService.removeStsState removes a given domain's
|
||||
// HSTS status. This means that a domain on the preload list will be
|
||||
// considered not HSTS if this is called. So, to reset everything to its
|
||||
// original state, we have to reach into the permission manager and clear
|
||||
// any HSTS-related state manually.
|
||||
function clearStsState() {
|
||||
var permissionManager = Cc["@mozilla.org/permissionmanager;1"]
|
||||
.getService(Ci.nsIPermissionManager);
|
||||
// This is a list of every host we call processHeader with
|
||||
// (so we can remove any state added to the sts service)
|
||||
var hosts = ["bugzilla.mozilla.org", "login.persona.org",
|
||||
"subdomain.www.torproject.org",
|
||||
"subdomain.bugzilla.mozilla.org" ];
|
||||
for (var host of hosts) {
|
||||
permissionManager.remove(host, "sts/use");
|
||||
permissionManager.remove(host, "sts/subd");
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
Services.obs.removeObserver(gObserver, "last-pb-context-exited");
|
||||
clearStsState();
|
||||
gSSService.clearAll();
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
@ -103,7 +84,7 @@ function test_part1() {
|
||||
// but this time include subdomains was not set, so test for that
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"subdomain.bugzilla.mozilla.org", 0));
|
||||
clearStsState();
|
||||
gSSService.clearAll();
|
||||
|
||||
// check that processing a header with max-age: 0 from a subdomain of a site
|
||||
// will not remove that (ancestor) site from the list
|
||||
@ -150,14 +131,28 @@ function test_part1() {
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"another.subdomain.bugzilla.mozilla.org", 0));
|
||||
|
||||
// Simulate leaving private browsing mode
|
||||
Services.obs.notifyObservers(null, "last-pb-context-exited", null);
|
||||
// Test that an expired non-private browsing entry results in correctly
|
||||
// identifying a host that is on the preload list as no longer sts.
|
||||
// (This happens when we're in regular browsing mode, we get a header from
|
||||
// a site on the preload list, and that header later expires. We need to
|
||||
// then treat that host as no longer an sts host.)
|
||||
// (sanity check first - this should be in the preload list)
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"login.persona.org", 0));
|
||||
var uri = Services.io.newURI("http://login.persona.org", null, null);
|
||||
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
|
||||
"max-age=1", 0);
|
||||
do_timeout(1250, function() {
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"login.persona.org", 0));
|
||||
run_next_test();
|
||||
});
|
||||
}
|
||||
|
||||
const IS_PRIVATE = Ci.nsISocketProvider.NO_PERMANENT_STORAGE;
|
||||
|
||||
function test_private_browsing1() {
|
||||
clearStsState();
|
||||
gSSService.clearAll();
|
||||
// sanity - bugzilla.mozilla.org is preloaded, includeSubdomains set
|
||||
do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"bugzilla.mozilla.org", IS_PRIVATE));
|
||||
@ -189,9 +184,6 @@ function test_private_browsing1() {
|
||||
do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
|
||||
"subdomain.bugzilla.mozilla.org", IS_PRIVATE));
|
||||
|
||||
// TODO unfortunately we don't have a good way to know when an entry
|
||||
// has expired in the permission manager, so we can't yet extend this test
|
||||
// to that case.
|
||||
// Test that an expired private browsing entry results in correctly
|
||||
// identifying a host that is on the preload list as no longer sts.
|
||||
// (This happens when we're in private browsing mode, we get a header from
|
||||
|
@ -30,6 +30,13 @@ skip-if = buildapp == "b2g" && processor = "arm"
|
||||
[test_sts_holepunch.js]
|
||||
[test_sts_ipv4_ipv6.js]
|
||||
|
||||
[test_sss_eviction.js]
|
||||
[test_sss_readstate.js]
|
||||
[test_sss_readstate_empty.js]
|
||||
[test_sss_readstate_garbage.js]
|
||||
[test_sss_readstate_huge.js]
|
||||
[test_sss_savestate.js]
|
||||
|
||||
[test_certificate_usages.js]
|
||||
[test_ocsp_stapling.js]
|
||||
run-sequentially = hardcoded ports
|
||||
|
@ -1 +1 @@
|
||||
NSS_3_17_1_BETA1
|
||||
NSS_3_17_1_BETA2
|
||||
|
@ -42,7 +42,7 @@ ifndef INTERNAL_TOOLS
|
||||
CROSS_COMPILE = 1
|
||||
endif
|
||||
endif
|
||||
ifeq ($(OS_TEST),ppc64)
|
||||
ifeq (,$(filter-out ppc64 ppc64le,$(OS_TEST)))
|
||||
CPU_ARCH = ppc
|
||||
ifeq ($(USE_64),1)
|
||||
ARCHFLAG = -m64
|
||||
|
@ -30,9 +30,16 @@ else
|
||||
BSDECHO = echo
|
||||
RC = rc.exe
|
||||
MT = mt.exe
|
||||
# Check for clang-cl
|
||||
CLANG_CL := $(shell expr `$(CC) -? 2>&1 | grep -w clang | wc -l` \> 0)
|
||||
# Determine compiler version
|
||||
CC_VERSION := $(shell $(CC) 2>&1 | sed -ne \
|
||||
ifeq ($(CLANG_CL),1)
|
||||
# clang-cl pretends to be MSVC 2012.
|
||||
CC_VERSION := 17.00.00.00
|
||||
else
|
||||
CC_VERSION := $(shell $(CC) 2>&1 | sed -ne \
|
||||
's|.* \([0-9]\+\.[0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).*|\1|p')
|
||||
endif
|
||||
# Change the dots to spaces.
|
||||
_CC_VERSION_WORDS := $(subst ., ,$(CC_VERSION))
|
||||
_CC_VMAJOR := $(word 1,$(_CC_VERSION_WORDS))
|
||||
@ -203,12 +210,18 @@ endif
|
||||
ifeq (,$(filter-out x386 x86_64,$(CPU_ARCH)))
|
||||
ifdef USE_64
|
||||
DEFINES += -D_AMD64_
|
||||
# Use subsystem 5.02 to allow running on Windows XP.
|
||||
ifeq ($(_MSC_VER_GE_11),1)
|
||||
LDFLAGS += -SUBSYSTEM:CONSOLE,5.02
|
||||
endif
|
||||
else
|
||||
DEFINES += -D_X86_
|
||||
# VS2012 defaults to -arch:SSE2. Use -arch:IA32 to avoid requiring
|
||||
# SSE2.
|
||||
# Use subsystem 5.01 to allow running on Windows XP.
|
||||
ifeq ($(_MSC_VER_GE_11),1)
|
||||
OS_CFLAGS += -arch:IA32
|
||||
LDFLAGS += -SUBSYSTEM:CONSOLE,5.01
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
@ -10,3 +10,4 @@
|
||||
*/
|
||||
|
||||
#error "Do not include this header file."
|
||||
|
||||
|
@ -445,11 +445,11 @@ SEC_GetSignatureAlgorithmOidTag(KeyType keyType, SECOidTag hashAlgTag)
|
||||
sigTag = SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION; break;
|
||||
case SEC_OID_MD5:
|
||||
sigTag = SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION; break;
|
||||
case SEC_OID_UNKNOWN: /* default for RSA if not specified */
|
||||
case SEC_OID_SHA1:
|
||||
sigTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; break;
|
||||
case SEC_OID_SHA224:
|
||||
sigTag = SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION; break;
|
||||
case SEC_OID_UNKNOWN: /* default for RSA if not specified */
|
||||
case SEC_OID_SHA256:
|
||||
sigTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; break;
|
||||
case SEC_OID_SHA384:
|
||||
|
@ -141,6 +141,9 @@ else
|
||||
DEFINES += -DUSE_HW_AES -DINTEL_GCM
|
||||
ASFILES += intel-aes-x86-masm.asm intel-gcm-x86-masm.asm
|
||||
EXTRA_SRCS += intel-gcm-wrap.c
|
||||
ifeq ($(CLANG_CL),1)
|
||||
INTEL_GCM_CLANG_CL = 1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
else
|
||||
@ -668,3 +671,10 @@ ifneq (,$(findstring clang,$(shell $(AS) --version)))
|
||||
$(OBJDIR)/$(PROG_PREFIX)intel-gcm$(OBJ_SUFFIX): ASFLAGS += -no-integrated-as
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef INTEL_GCM_CLANG_CL
|
||||
#
|
||||
# clang-cl needs -mssse3
|
||||
#
|
||||
$(OBJDIR)/$(PROG_PREFIX)intel-gcm-wrap$(OBJ_SUFFIX): CFLAGS += -mssse3
|
||||
endif
|
||||
|
@ -1,5 +0,0 @@
|
||||
[001.html]
|
||||
type: testharness
|
||||
[navigator]
|
||||
expected: FAIL
|
||||
|
@ -1,5 +0,0 @@
|
||||
[language.html]
|
||||
type: testharness
|
||||
[navigator.language]
|
||||
expected: FAIL
|
||||
|
@ -30,8 +30,6 @@
|
||||
static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000;
|
||||
char16_t* MakeCommandLine(int argc, char16_t **argv);
|
||||
BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
|
||||
BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
|
||||
LPCWSTR newFileName);
|
||||
|
||||
/*
|
||||
* Read the update.status file and sets isApplying to true if
|
||||
|
@ -3321,11 +3321,17 @@
|
||||
"description": "Updater: The interval in days between the previous and the current background update check when the check was timer initiated"
|
||||
},
|
||||
"UPDATER_STATUS_CODES": {
|
||||
"expires_in_version": "never",
|
||||
"expires_in_version": "35",
|
||||
"kind": "enumerated",
|
||||
"n_values": 50,
|
||||
"description": "Updater: the status of the latest update performed"
|
||||
},
|
||||
"UPDATER_ALL_STATUS_CODES": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 200,
|
||||
"description": "Updater: the status of the latest update performed"
|
||||
},
|
||||
"UPDATER_UPDATES_ENABLED": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
|
@ -69,6 +69,11 @@
|
||||
#define WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID 47
|
||||
#define WRITE_ERROR_SHARING_VIOLATION_NOPID 48
|
||||
|
||||
#define FOTA_FILE_OPERATION_ERROR 49
|
||||
#define FOTA_RECOVERY_ERROR 50
|
||||
|
||||
#define SECURE_LOCATION_UPDATE_ERROR 51
|
||||
|
||||
// The following error codes are only used by updater.exe
|
||||
// when a fallback key exists and XPCShell tests are being run.
|
||||
#define FALLBACKKEY_UNKNOWN_ERROR 100
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include <windows.h>
|
||||
#include <wtsapi32.h>
|
||||
#include <aclapi.h>
|
||||
#include "uachelper.h"
|
||||
#include "updatelogging.h"
|
||||
|
||||
@ -161,11 +162,15 @@ UACHelper::DisableUnneededPrivileges(HANDLE token,
|
||||
BOOL result = TRUE;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (SetPrivilege(token, unneededPrivs[i], FALSE)) {
|
||||
#ifdef UPDATER_LOG_PRIVS
|
||||
LOG(("Disabled unneeded token privilege: %s.",
|
||||
unneededPrivs[i]));
|
||||
#endif
|
||||
} else {
|
||||
#ifdef UPDATER_LOG_PRIVS
|
||||
LOG(("Could not disable token privilege value: %s. (%d)",
|
||||
unneededPrivs[i], GetLastError()));
|
||||
#endif
|
||||
result = FALSE;
|
||||
}
|
||||
}
|
||||
@ -220,3 +225,93 @@ UACHelper::CanUserElevate()
|
||||
|
||||
return canElevate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Denies write access for everyone on the specified path.
|
||||
*
|
||||
* @param path The file path to modify the DACL on
|
||||
* @param originalACL out parameter, set only if successful.
|
||||
* caller must free.
|
||||
* @return true on success
|
||||
*/
|
||||
bool
|
||||
UACHelper::DenyWriteACLOnPath(LPCWSTR path, PACL *originalACL,
|
||||
PSECURITY_DESCRIPTOR *sd)
|
||||
{
|
||||
// Get the old security information on the path.
|
||||
// Note that the actual buffer to be freed is contained in *sd.
|
||||
// originalACL points within *sd's buffer.
|
||||
*originalACL = nullptr;
|
||||
*sd = nullptr;
|
||||
DWORD result =
|
||||
GetNamedSecurityInfoW(path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
|
||||
nullptr, nullptr, originalACL, nullptr, sd);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
*sd = nullptr;
|
||||
*originalACL = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adjust the security for everyone to deny write
|
||||
EXPLICIT_ACCESSW ea;
|
||||
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESSW));
|
||||
ea.grfAccessPermissions = FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES |
|
||||
FILE_WRITE_DATA | FILE_WRITE_EA;
|
||||
ea.grfAccessMode = DENY_ACCESS;
|
||||
ea.grfInheritance = NO_INHERITANCE;
|
||||
ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
|
||||
ea.Trustee.TrusteeType = TRUSTEE_IS_GROUP;
|
||||
ea.Trustee.ptstrName = L"EVERYONE";
|
||||
PACL dacl = nullptr;
|
||||
result = SetEntriesInAclW(1, &ea, *originalACL, &dacl);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
LocalFree(*sd);
|
||||
*originalACL = nullptr;
|
||||
*sd = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the path to have a the new DACL
|
||||
result = SetNamedSecurityInfoW(const_cast<LPWSTR>(path), SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION, nullptr, nullptr,
|
||||
dacl, nullptr);
|
||||
LocalFree(dacl);
|
||||
return result == ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the specified directory only has updater.exe inside of it,
|
||||
* and nothing else.
|
||||
*
|
||||
* @param inputPath the directory path to search
|
||||
* @return true if updater.exe is the only file in the directory
|
||||
*/
|
||||
bool
|
||||
UACHelper::IsDirectorySafe(LPCWSTR inputPath)
|
||||
{
|
||||
WIN32_FIND_DATAW findData;
|
||||
HANDLE findHandle = nullptr;
|
||||
|
||||
WCHAR searchPath[MAX_PATH + 1] = { L'\0' };
|
||||
wsprintfW(searchPath, L"%s\\*.*", inputPath);
|
||||
|
||||
findHandle = FindFirstFileW(searchPath, &findData);
|
||||
if(findHandle == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enumerate the files and if we find anything other than the current
|
||||
// directory, the parent directory, or updater.exe. Then fail.
|
||||
do {
|
||||
if(wcscmp(findData.cFileName, L".") != 0 &&
|
||||
wcscmp(findData.cFileName, L"..") != 0 &&
|
||||
wcscmp(findData.cFileName, L"updater.exe") != 0) {
|
||||
FindClose(findHandle);
|
||||
return false;
|
||||
}
|
||||
} while(FindNextFileW(findHandle, &findData));
|
||||
FindClose(findHandle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -12,12 +12,15 @@ public:
|
||||
static HANDLE OpenLinkedToken(HANDLE token);
|
||||
static BOOL DisablePrivileges(HANDLE token);
|
||||
static bool CanUserElevate();
|
||||
static bool IsDirectorySafe(LPCWSTR inputPath);
|
||||
static bool DenyWriteACLOnPath(LPCWSTR path, PACL *originalACL,
|
||||
PSECURITY_DESCRIPTOR *sd);
|
||||
|
||||
private:
|
||||
static BOOL SetPrivilege(HANDLE token, LPCTSTR privs, BOOL enable);
|
||||
static BOOL DisableUnneededPrivileges(HANDLE token,
|
||||
static BOOL DisableUnneededPrivileges(HANDLE token,
|
||||
LPCTSTR *unneededPrivs, size_t count);
|
||||
static LPCTSTR PrivsToDisable[];
|
||||
static LPCTSTR PrivsToDisable[];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -33,7 +33,6 @@ using mozilla::MakeUnique;
|
||||
using mozilla::UniquePtr;
|
||||
|
||||
WCHAR* MakeCommandLine(int argc, WCHAR **argv);
|
||||
BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
|
||||
|
||||
/**
|
||||
* Obtains the path of a file in the same directory as the specified file.
|
||||
|
@ -17,6 +17,10 @@ BOOL DoesFallbackKeyExist();
|
||||
BOOL IsLocalFile(LPCWSTR file, BOOL &isLocal);
|
||||
DWORD StartServiceCommand(int argc, LPCWSTR* argv);
|
||||
BOOL IsUnpromptedElevation(BOOL &isUnpromptedElevation);
|
||||
BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer,
|
||||
LPCWSTR siblingFilePath,
|
||||
LPCWSTR newFileName);
|
||||
BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
|
||||
|
||||
#define SVC_NAME L"MozillaMaintenance"
|
||||
|
||||
|
@ -162,6 +162,7 @@ const WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID = 47;
|
||||
const WRITE_ERROR_SHARING_VIOLATION_NOPID = 48;
|
||||
const FOTA_FILE_OPERATION_ERROR = 49;
|
||||
const FOTA_RECOVERY_ERROR = 50;
|
||||
const SECURE_LOCATION_UPDATE_ERROR = 51;
|
||||
|
||||
const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100;
|
||||
const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
|
||||
@ -1477,7 +1478,8 @@ function handleUpdateFailure(update, errorCode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (update.errorCode == ELEVATION_CANCELED) {
|
||||
if (update.errorCode == ELEVATION_CANCELED ||
|
||||
update.errorCode == SECURE_LOCATION_UPDATE_ERROR) {
|
||||
writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
|
||||
return true;
|
||||
}
|
||||
@ -2383,7 +2385,7 @@ UpdateService.prototype = {
|
||||
if (parts.length > 1) {
|
||||
result = parseInt(parts[1]) || INVALID_UPDATER_STATUS_CODE;
|
||||
}
|
||||
Services.telemetry.getHistogramById("UPDATER_STATUS_CODES").add(result);
|
||||
Services.telemetry.getHistogramById("UPDATER_ALL_STATUS_CODES").add(result);
|
||||
} catch(e) {
|
||||
// Don't allow any exception to be propagated.
|
||||
Cu.reportError(e);
|
||||
|
@ -1508,15 +1508,18 @@ function runUpdate(aExpectedExitValue, aExpectedStatus, aCallback) {
|
||||
if (gDisableReplaceFallback) {
|
||||
env.set("MOZ_NO_REPLACE_FALLBACK", "1");
|
||||
}
|
||||
env.set("MOZ_EMULATE_ELEVATION_PATH", "1");
|
||||
|
||||
let process = AUS_Cc["@mozilla.org/process/util;1"].
|
||||
createInstance(AUS_Ci.nsIProcess);
|
||||
process.init(updateBin);
|
||||
|
||||
process.run(true, args, args.length);
|
||||
|
||||
if (gDisableReplaceFallback) {
|
||||
env.set("MOZ_NO_REPLACE_FALLBACK", "");
|
||||
}
|
||||
env.set("MOZ_EMULATE_ELEVATION_PATH", "");
|
||||
|
||||
let status = readStatusFile();
|
||||
if (process.exitValue != aExpectedExitValue || status != aExpectedStatus) {
|
||||
|
@ -41,6 +41,8 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
'shlwapi',
|
||||
'crypt32',
|
||||
'advapi32',
|
||||
'ole32',
|
||||
'rpcrt4',
|
||||
]
|
||||
else:
|
||||
USE_LIBS += [
|
||||
|
@ -111,6 +111,7 @@ static bool sUseHardLinks = true;
|
||||
|
||||
#ifdef XP_WIN
|
||||
#include "updatehelper.h"
|
||||
#include <aclapi.h>
|
||||
|
||||
// Closes the handle if valid and if the updater is elevated returns with the
|
||||
// return code specified. This prevents multiple launches of the callback
|
||||
@ -2393,8 +2394,9 @@ int NS_main(int argc, NS_tchar **argv)
|
||||
bool useService = false;
|
||||
bool testOnlyFallbackKeyExists = false;
|
||||
bool noServiceFallback = getenv("MOZ_NO_SERVICE_FALLBACK") != nullptr;
|
||||
bool emulateElevation = getenv("MOZ_EMULATE_ELEVATION_PATH") != nullptr;
|
||||
putenv(const_cast<char*>("MOZ_NO_SERVICE_FALLBACK="));
|
||||
|
||||
putenv(const_cast<char*>("MOZ_EMULATE_ELEVATION_PATH="));
|
||||
// We never want the service to be used unless we build with
|
||||
// the maintenance service.
|
||||
#ifdef MOZ_MAINTENANCE_SERVICE
|
||||
@ -2484,12 +2486,6 @@ int NS_main(int argc, NS_tchar **argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sStagedUpdate) {
|
||||
LOG(("Performing a staged update"));
|
||||
} else if (sReplaceRequest) {
|
||||
LOG(("Performing a replace request"));
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
const char *prioEnv = getenv("MOZ_UPDATER_PRIO");
|
||||
if (prioEnv) {
|
||||
@ -2618,13 +2614,15 @@ int NS_main(int argc, NS_tchar **argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
updateLockFileHandle = CreateFileW(updateLockFilePath,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0,
|
||||
nullptr,
|
||||
OPEN_ALWAYS,
|
||||
FILE_FLAG_DELETE_ON_CLOSE,
|
||||
nullptr);
|
||||
if (!emulateElevation) {
|
||||
updateLockFileHandle = CreateFileW(updateLockFilePath,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0,
|
||||
nullptr,
|
||||
OPEN_ALWAYS,
|
||||
FILE_FLAG_DELETE_ON_CLOSE,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
NS_tsnprintf(elevatedLockFilePath,
|
||||
sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]),
|
||||
@ -2775,7 +2773,8 @@ int NS_main(int argc, NS_tchar **argv)
|
||||
// If the service can't be used when staging and update, make sure that
|
||||
// the UAC prompt is not shown! In this case, just set the status to
|
||||
// pending and the update will be applied during the next startup.
|
||||
if (!useService && sStagedUpdate) {
|
||||
// When emulateElevation is true fall through to the elevation code path.
|
||||
if (!useService && sStagedUpdate && !emulateElevation) {
|
||||
if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(updateLockFileHandle);
|
||||
}
|
||||
@ -2801,35 +2800,145 @@ int NS_main(int argc, NS_tchar **argv)
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't want to use the service at all, or if an update was
|
||||
// already happening, or launching the service command failed, then
|
||||
DWORD returnCode = 0;
|
||||
|
||||
// If we didn't want to use the service at all, or if an update was
|
||||
// already happening, or launching the service command failed, then
|
||||
// launch the elevated updater.exe as we do without the service.
|
||||
// We don't launch the elevated updater in the case that we did have
|
||||
// write access all along because in that case the only reason we're
|
||||
// using the service is because we are testing.
|
||||
if (!useService && !noServiceFallback &&
|
||||
// using the service is because we are testing.
|
||||
if (!useService && !noServiceFallback &&
|
||||
updateLockFileHandle == INVALID_HANDLE_VALUE) {
|
||||
SHELLEXECUTEINFO sinfo;
|
||||
memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
|
||||
sinfo.cbSize = sizeof(SHELLEXECUTEINFO);
|
||||
sinfo.fMask = SEE_MASK_FLAG_NO_UI |
|
||||
SEE_MASK_FLAG_DDEWAIT |
|
||||
SEE_MASK_NOCLOSEPROCESS;
|
||||
sinfo.hwnd = nullptr;
|
||||
sinfo.lpFile = argv[0];
|
||||
sinfo.lpParameters = cmdLine;
|
||||
sinfo.lpVerb = L"runas";
|
||||
sinfo.nShow = SW_SHOWNORMAL;
|
||||
|
||||
bool result = ShellExecuteEx(&sinfo);
|
||||
free(cmdLine);
|
||||
|
||||
if (result) {
|
||||
WaitForSingleObject(sinfo.hProcess, INFINITE);
|
||||
CloseHandle(sinfo.hProcess);
|
||||
// Get a unique directory name to secure
|
||||
RPC_WSTR guidString = RPC_WSTR(L"");
|
||||
GUID guid;
|
||||
HRESULT hr = CoCreateGuid(&guid);
|
||||
BOOL result = TRUE;
|
||||
bool safeToUpdate = true;
|
||||
WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' };
|
||||
WCHAR secureDirPath[MAX_PATH + 1] = { L'\0' };
|
||||
if (SUCCEEDED(hr)) {
|
||||
UuidToString(&guid, &guidString);
|
||||
result = PathGetSiblingFilePath(secureDirPath, argv[0],
|
||||
reinterpret_cast<LPCWSTR>(guidString));
|
||||
RpcStringFree(&guidString);
|
||||
} else {
|
||||
WriteStatusFile(ELEVATION_CANCELED);
|
||||
// This should never happen, but just in case
|
||||
result = PathGetSiblingFilePath(secureDirPath, argv[0], L"tmp_update");
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
fprintf(stderr, "Could not obtain secure update directory path");
|
||||
safeToUpdate = false;
|
||||
}
|
||||
|
||||
// If it's still safe to update, create the directory
|
||||
if (safeToUpdate) {
|
||||
result = CreateDirectoryW(secureDirPath, nullptr);
|
||||
if (!result) {
|
||||
fprintf(stderr, "Could not create secure update directory");
|
||||
safeToUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If it's still safe to update, get the new updater path
|
||||
if (safeToUpdate) {
|
||||
wcsncpy(secureUpdaterPath, secureDirPath, MAX_PATH);
|
||||
result = PathAppendSafe(secureUpdaterPath, L"updater.exe");
|
||||
if (!result) {
|
||||
fprintf(stderr, "Could not obtain secure updater file name");
|
||||
safeToUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If it's still safe to update, copy the file in
|
||||
if (safeToUpdate) {
|
||||
result = CopyFileW(argv[0], secureUpdaterPath, TRUE);
|
||||
if (!result) {
|
||||
fprintf(stderr, "Could not copy updater to secure location");
|
||||
safeToUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If it's still safe to update, restrict access to the directory item
|
||||
// itself so that the directory cannot be deleted and re-created,
|
||||
// nor have its properties modified. Note that this does not disallow
|
||||
// adding items inside the directory.
|
||||
HANDLE handle = INVALID_HANDLE_VALUE;
|
||||
if (safeToUpdate) {
|
||||
handle = CreateFileW(secureDirPath, GENERIC_READ, FILE_SHARE_READ,
|
||||
nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
safeToUpdate = handle != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
// If it's still safe to update, deny write access completely to the
|
||||
// directory.
|
||||
PACL originalACL = nullptr;
|
||||
PSECURITY_DESCRIPTOR sd = nullptr;
|
||||
if (safeToUpdate) {
|
||||
safeToUpdate = UACHelper::DenyWriteACLOnPath(secureDirPath,
|
||||
&originalACL, &sd);
|
||||
}
|
||||
|
||||
// If it's still safe to update, verify that there is only updater.exe
|
||||
// in the directory and nothing else.
|
||||
if (safeToUpdate) {
|
||||
if (!UACHelper::IsDirectorySafe(secureDirPath)) {
|
||||
safeToUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!safeToUpdate) {
|
||||
fprintf(stderr, "Will not proceed to copy secure updater because it "
|
||||
"is not safe to do so.");
|
||||
WriteStatusFile(SECURE_LOCATION_UPDATE_ERROR);
|
||||
} else {
|
||||
SHELLEXECUTEINFO sinfo;
|
||||
memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
|
||||
sinfo.cbSize = sizeof(SHELLEXECUTEINFO);
|
||||
sinfo.fMask = SEE_MASK_FLAG_NO_UI |
|
||||
SEE_MASK_FLAG_DDEWAIT |
|
||||
SEE_MASK_NOCLOSEPROCESS;
|
||||
sinfo.hwnd = nullptr;
|
||||
sinfo.lpFile = secureUpdaterPath;
|
||||
sinfo.lpParameters = cmdLine;
|
||||
sinfo.lpVerb = emulateElevation ? L"open" : L"runas";
|
||||
sinfo.nShow = SW_SHOWNORMAL;
|
||||
|
||||
bool result = ShellExecuteEx(&sinfo);
|
||||
free(cmdLine);
|
||||
|
||||
if (result) {
|
||||
WaitForSingleObject(sinfo.hProcess, INFINITE);
|
||||
// Bubble the elevated updater return code to this updater
|
||||
GetExitCodeProcess(sinfo.hProcess, &returnCode);
|
||||
CloseHandle(sinfo.hProcess);
|
||||
} else {
|
||||
WriteStatusFile(ELEVATION_CANCELED);
|
||||
}
|
||||
}
|
||||
|
||||
// All done, revert back the permissions.
|
||||
if (originalACL) {
|
||||
SetNamedSecurityInfoW(const_cast<LPWSTR>(secureDirPath), SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION, nullptr, nullptr,
|
||||
originalACL, nullptr);
|
||||
}
|
||||
if (sd) {
|
||||
LocalFree(sd);
|
||||
}
|
||||
|
||||
// Done with the directory, no need to lock it.
|
||||
if (INVALID_HANDLE_VALUE != handle) {
|
||||
CloseHandle(handle);
|
||||
}
|
||||
|
||||
// We no longer need the secure updater and directory
|
||||
DeleteFileW(secureUpdaterPath);
|
||||
RemoveDirectoryW(secureDirPath);
|
||||
}
|
||||
|
||||
if (argc > callbackIndex) {
|
||||
@ -2844,7 +2953,7 @@ int NS_main(int argc, NS_tchar **argv)
|
||||
// We didn't use the service and we did run the elevated updater.exe.
|
||||
// The elevated updater.exe is responsible for writing out the
|
||||
// update.status file.
|
||||
return 0;
|
||||
return returnCode;
|
||||
} else if(useService) {
|
||||
// The service command was launched. The service is responsible for
|
||||
// writing out the update.status file.
|
||||
@ -2866,6 +2975,13 @@ int NS_main(int argc, NS_tchar **argv)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (sStagedUpdate) {
|
||||
LOG(("Performing a staged update"));
|
||||
}
|
||||
else if (sReplaceRequest) {
|
||||
LOG(("Performing a replace request"));
|
||||
}
|
||||
|
||||
#if defined(MOZ_WIDGET_GONK)
|
||||
// In gonk, the master b2g process sets its umask to 0027 because
|
||||
// there's no reason for it to ever create world-readable files.
|
||||
@ -3170,7 +3286,7 @@ int NS_main(int argc, NS_tchar **argv)
|
||||
}
|
||||
}
|
||||
}
|
||||
EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
|
||||
EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, gSucceeded ? 0 : 1);
|
||||
#endif /* XP_WIN */
|
||||
#ifdef XP_MACOSX
|
||||
if (gSucceeded) {
|
||||
|
Loading…
Reference in New Issue
Block a user