Bug 1407879 - Check password field url against the local whitelist. r=francois

MozReview-Commit-ID: 1OqpeWeU1s1

--HG--
extra : rebase_source : e201e13e8a8d9cc2b4d25700ea6e44aa6e4a850b
This commit is contained in:
DimiL 2017-12-19 14:16:23 +08:00
Родитель ce5a43b7a1
Коммит 92cdac8eef
7 изменённых файлов: 426 добавлений и 37 удалений

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

@ -1114,11 +1114,6 @@ BrowserGlue.prototype = {
// early, so we use a maximum timeout for it.
Services.tm.idleDispatchToMainThread(() => {
SafeBrowsing.init();
// Login reputation depends on the Safe Browsing API.
let reputationService = Cc["@mozilla.org/reputationservice/login-reputation-service;1"]
.getService(Ci.nsILoginReputationService);
reputationService.init();
}, 5000);
if (AppConstants.MOZ_CRASHREPORTER) {

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

@ -5394,7 +5394,7 @@ pref("urlclassifier.trackingTable", "test-track-simple,base-track-digest256");
pref("urlclassifier.trackingWhitelistTable", "test-trackwhite-simple,mozstd-trackwhite-digest256");
// These tables will never trigger a gethash call.
pref("urlclassifier.disallow_completions", "test-malware-simple,test-harmful-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,except-flashinfobar-digest256");
pref("urlclassifier.disallow_completions", "test-malware-simple,test-harmful-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,block-flash-digest256,except-flash-digest256,allow-flashallow-digest256,except-flashallow-digest256,block-flashsubdoc-digest256,except-flashsubdoc-digest256,except-flashinfobar-digest256,goog-passwordwhite-proto");
// Number of random entries to send with a gethash request
pref("urlclassifier.gethashnoise", 4);

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

@ -5,6 +5,9 @@
#include "LoginReputation.h"
#include "nsIDOMHTMLInputElement.h"
#include "nsThreadUtils.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/ipc/URIUtils.h"
@ -12,7 +15,9 @@
using namespace mozilla;
using namespace mozilla::dom;
#define PREF_PP_ENABLED "browser.safebrowsing.passwords.enabled"
#define PREF_PP_ENABLED "browser.safebrowsing.passwords.enabled"
#define PREF_PASSWORD_ALLOW_TABLE "urlclassifier.passwordAllowTable"
static bool sPasswordProtectionEnabled = false;
// MOZ_LOG=LoginReputation:5
@ -20,6 +25,12 @@ LazyLogModule gLoginReputationLogModule("LoginReputation");
#define LR_LOG(args) MOZ_LOG(gLoginReputationLogModule, mozilla::LogLevel::Debug, args)
#define LR_LOG_ENABLED() MOZ_LOG_TEST(gLoginReputationLogModule, mozilla::LogLevel::Debug)
static Atomic<bool> gShuttingDown(false);
static const char* kObservedPrefs[] = {
PREF_PASSWORD_ALLOW_TABLE,
};
// -------------------------------------------------------------------------
// ReputationQueryParam
//
@ -51,15 +62,146 @@ ReputationQueryParam::GetFormURI(nsIURI** aURI)
return NS_OK;
}
// -------------------------------------------------------------------------
// LoginWhitelist
//
// This class is a wrapper that encapsulate asynchronous callback API provided
// by DBService into a MozPromise callback.
//
class LoginWhitelist final : public nsIURIClassifierCallback
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIURICLASSIFIERCALLBACK
RefPtr<ReputationPromise> QueryLoginWhitelist(nsILoginReputationQuery* aParam);
LoginWhitelist() = default;
nsresult Init();
nsresult Uninit();
void UpdateWhitelistTables();
private:
~LoginWhitelist() = default;
nsCString mTables;
// Queries that are waiting for callback from ::AsyncClassifyLocalWithTables.
nsTArray<UniquePtr<MozPromiseHolder<ReputationPromise>>> mQueryPromises;
};
NS_IMPL_ISUPPORTS(LoginWhitelist, nsIURIClassifierCallback)
nsresult
LoginWhitelist::Init()
{
UpdateWhitelistTables();
return NS_OK;
}
nsresult
LoginWhitelist::Uninit()
{
// Reject all query promise before releasing.
for (uint8_t i = 0; i < mQueryPromises.Length(); i++) {
mQueryPromises[i]->Reject(NS_ERROR_ABORT, __func__);
}
mQueryPromises.Clear();
return NS_OK;
}
RefPtr<ReputationPromise>
LoginWhitelist::QueryLoginWhitelist(nsILoginReputationQuery* aParam)
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv;
UniquePtr<MozPromiseHolder<ReputationPromise>> holder =
MakeUnique<MozPromiseHolder<ReputationPromise>>();
RefPtr<ReputationPromise> p = holder->Ensure(__func__);
// Return rejected promise while there is an error.
auto fail = MakeScopeExit([&] () {
holder->Reject(rv, __func__);
});
nsCOMPtr<nsIURI> uri;
rv = aParam->GetFormURI(getter_AddRefs(uri));
if (NS_WARN_IF(NS_FAILED(rv) || !uri)) {
return p;
}
nsCOMPtr<nsIURIClassifier> uriClassifier =
do_GetService(NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return p;
}
// AsyncClassifyLocalWithTables API won't trigger a gethash request on
// a full-length match, so this API call should only include local operation.
rv = uriClassifier->AsyncClassifyLocalWithTables(uri, mTables, this);
if (NS_FAILED(rv)) {
return p;
}
fail.release();
mQueryPromises.AppendElement(Move(holder));
return p;
}
nsresult
LoginWhitelist::OnClassifyComplete(nsresult aErrorCode,
const nsACString& aLists,
const nsACString& aProvider,
const nsACString& aFullHash)
{
MOZ_ASSERT(NS_IsMainThread());
if (gShuttingDown) {
return NS_OK;
}
LR_LOG(("OnClassifyComplete : list = %s", aLists.BeginReading()));
UniquePtr<MozPromiseHolder<ReputationPromise>> holder =
Move(mQueryPromises.ElementAt(0));
mQueryPromises.RemoveElementAt(0);
if (NS_FAILED(aErrorCode)) {
// This should not happen
MOZ_ASSERT_UNREACHABLE("unexpected error received in OnClassifyComplete");
holder->Reject(aErrorCode, __func__);
} else if (aLists.IsEmpty()) {
// Reject if we can not find url in white list.
holder->Reject(NS_OK, __func__);
} else {
holder->Resolve(nsILoginReputationVerdictType::SAFE, __func__);
}
return NS_OK;
}
void
LoginWhitelist::UpdateWhitelistTables()
{
Preferences::GetCString(PREF_PASSWORD_ALLOW_TABLE, mTables);
}
// -------------------------------------------------------------------------
// LoginReputationService
//
NS_IMPL_ISUPPORTS(LoginReputationService,
nsILoginReputationService)
nsILoginReputationService,
nsIObserver)
LoginReputationService*
LoginReputationService::gLoginReputationService = nullptr;
// static
already_AddRefed<LoginReputationService>
LoginReputationService::GetSingleton()
{
@ -77,6 +219,10 @@ LoginReputationService::LoginReputationService()
LoginReputationService::~LoginReputationService()
{
LR_LOG(("Login reputation service shutting down"));
MOZ_ASSERT(gLoginReputationService == this);
gLoginReputationService = nullptr;
}
NS_IMETHODIMP
@ -84,19 +230,90 @@ LoginReputationService::Init()
{
MOZ_ASSERT(NS_IsMainThread());
Preferences::AddBoolVarCache(&sPasswordProtectionEnabled, PREF_PP_ENABLED, true);
switch (XRE_GetProcessType()) {
case GeckoProcessType_Default:
LR_LOG(("Init login reputation service in parent"));
break;
case GeckoProcessType_Content:
LR_LOG(("Init login reputation service in child"));
break;
// Login reputation service in child process will only forward request to
// parent, return here to skip unnecessary initialization.
return NS_OK;
default:
// No other process type is supported!
return NS_ERROR_NOT_AVAILABLE;
}
Preferences::AddBoolVarCache(&sPasswordProtectionEnabled, PREF_PP_ENABLED, true);
// The initialization below only happens in parent process.
Preferences::AddStrongObserver(this, PREF_PP_ENABLED);
// Init should only be called once.
MOZ_ASSERT(!mLoginWhitelist);
mLoginWhitelist = new LoginWhitelist();
if (sPasswordProtectionEnabled) {
Enable();
}
return NS_OK;
}
nsresult
LoginReputationService::Enable()
{
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(sPasswordProtectionEnabled);
LR_LOG(("Enable login reputation service"));
nsresult rv = mLoginWhitelist->Init();
Unused << NS_WARN_IF(NS_FAILED(rv));
for (const char* pref : kObservedPrefs) {
Preferences::AddStrongObserver(this, pref);
}
return NS_OK;
}
nsresult
LoginReputationService::Disable()
{
MOZ_ASSERT(XRE_IsParentProcess());
LR_LOG(("Disable login reputation service"));
nsresult rv = mLoginWhitelist->Uninit();
Unused << NS_WARN_IF(NS_FAILED(rv));
mQueryRequests.Clear();
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefs) {
for (const char* pref : kObservedPrefs) {
prefs->RemoveObserver(pref, this);
}
}
return NS_OK;
}
nsresult
LoginReputationService::Shutdown()
{
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(gShuttingDown);
// Disable will wait until worker threads are shutdown.
Disable();
// Disable will only destroy worker thread, it won't null out these classes.
// So we will null these classes in shutdown.
mLoginWhitelist = nullptr;
return NS_OK;
}
@ -156,18 +373,154 @@ NS_IMETHODIMP
LoginReputationService::QueryReputation(nsILoginReputationQuery* aQuery,
nsILoginReputationQueryCallback* aCallback)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG_POINTER(aQuery);
NS_ENSURE_ARG_POINTER(aCallback);
LR_LOG(("QueryReputation() [this=%p]", this));
if (!sPasswordProtectionEnabled) {
return NS_ERROR_FAILURE;
if (gShuttingDown || !sPasswordProtectionEnabled) {
LR_LOG(("QueryReputation() abort [this=%p]", this));
aCallback->OnComplete(NS_ERROR_ABORT, nsILoginReputationVerdictType::UNSPECIFIED);
return NS_OK;
}
// Return SAFE until we add support for the remote service (bug 1413732).
if (aCallback) {
aCallback->OnQueryComplete(nsILoginReputationResult::SAFE);
// mQueryRequests is an array used to maintain the ownership of |QueryRequest|.
// We ensure that |QueryRequest| is always valid until Finish() is
// called or LoginReputationService is shutdown.
auto* request =
mQueryRequests.AppendElement(MakeUnique<QueryRequest>(aQuery, aCallback));
return QueryLoginWhitelist(request->get());
}
nsresult
LoginReputationService::QueryLoginWhitelist(QueryRequest* aRequest)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG_POINTER(aRequest);
if (gShuttingDown) {
return NS_ERROR_ABORT;
}
RefPtr<LoginReputationService> self = this;
mLoginWhitelist->QueryLoginWhitelist(aRequest->mParam)->Then(
GetCurrentThreadSerialEventTarget(), __func__,
[self, aRequest](VerdictType aResolveValue) -> void {
// Promise is resolved if url is found in google-provided whitelist.
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aResolveValue == nsILoginReputationVerdictType::SAFE);
LR_LOG(("Query login whitelist [request = %p, result = SAFE]",
aRequest));
self->Finish(aRequest, NS_OK, nsILoginReputationVerdictType::SAFE);
},
[self, aRequest](nsresult rv) -> void {
// Promise is rejected if url cannot be found in google-provided whitelist.
// or there is an error.
if (LR_LOG_ENABLED()) {
if (NS_FAILED(rv)) {
nsAutoCString errorName;
mozilla::GetErrorName(rv, errorName);
LR_LOG(("Error in QueryLoginWhitelist() [request = %p, rv = %s]",
aRequest, errorName.get()));
} else {
LR_LOG(("Query login whitelist cannot find the URL [request = %p]",
aRequest));
}
}
// Check trust-based whitelisting if we can't find the url in login whitelist
self->Finish(aRequest, rv, nsILoginReputationVerdictType::UNSPECIFIED);
});
return NS_OK;
}
nsresult
LoginReputationService::Finish(const QueryRequest* aRequest,
nsresult aStatus,
VerdictType aVerdict)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG_POINTER(aRequest);
LR_LOG(("Query login reputation end [request = %p, result = %s]",
aRequest, VerdictTypeToString(aVerdict).get()));
// Since we are shutting down, don't bother call back to child process.
if (gShuttingDown) {
return NS_OK;
}
aRequest->mCallback->OnComplete(aStatus, aVerdict);
// QueryRequest may not follow the same order when we queued it in ::QueryReputation
// because one query request may be finished earlier than the other.
uint32_t idx = 0;
for (; idx < mQueryRequests.Length(); idx++) {
if (mQueryRequests[idx].get() == aRequest) {
break;
}
}
if (NS_WARN_IF(idx >= mQueryRequests.Length())) {
return NS_ERROR_FAILURE;
}
mQueryRequests.RemoveElementAt(idx);
return NS_OK;
}
NS_IMETHODIMP
LoginReputationService::Observe(nsISupports *aSubject,
const char *aTopic,
const char16_t *aData)
{
if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
nsDependentString data(aData);
if (data.EqualsLiteral(PREF_PP_ENABLED)) {
nsresult rv = sPasswordProtectionEnabled ? Enable() : Disable();
Unused << NS_WARN_IF(NS_FAILED(rv));
} else if (data.EqualsLiteral(PREF_PASSWORD_ALLOW_TABLE)) {
mLoginWhitelist->UpdateWhitelistTables();
}
} else if (!strcmp(aTopic, "quit-application")) {
// Prepare to shutdown, won't allow any query request after 'gShuttingDown'
// is set.
gShuttingDown = true;
} else if (!strcmp(aTopic, "profile-before-change")) {
gShuttingDown = true;
Shutdown();
} else {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
// static
nsCString
LoginReputationService::VerdictTypeToString(VerdictType aVerdict)
{
switch(aVerdict) {
case nsILoginReputationVerdictType::UNSPECIFIED:
return nsCString("Unspecified");
case nsILoginReputationVerdictType::LOW_REPUTATION:
return nsCString("Low Reputation");
case nsILoginReputationVerdictType::SAFE:
return nsCString("Safe");
case nsILoginReputationVerdictType::PHISHING:
return nsCString("Phishing");
default:
return nsCString("Invalid");
}
}

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

@ -7,13 +7,25 @@
#define LoginReputation_h__
#include "nsILoginReputation.h"
#include "nsIURIClassifier.h"
#include "nsIObserver.h"
#include "mozilla/Logging.h"
#include "mozilla/MozPromise.h"
class LoginReputationService final : public nsILoginReputationService
class LoginWhitelist;
namespace mozilla {
typedef uint32_t VerdictType;
typedef MozPromise<VerdictType, nsresult, false> ReputationPromise;
class LoginReputationService final : public nsILoginReputationService,
public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSILOGINREPUTATIONSERVICE
NS_DECL_NSIOBSERVER
public:
static
@ -22,7 +34,23 @@ public:
static
already_AddRefed<nsILoginReputationQuery> ConstructQueryParam(nsIURI* aURI);
static
nsCString VerdictTypeToString(VerdictType aVerdict);
private:
struct QueryRequest {
QueryRequest(nsILoginReputationQuery* aParam,
nsILoginReputationQueryCallback* aCallback) :
mParam(aParam),
mCallback(aCallback)
{
}
nsCOMPtr<nsILoginReputationQuery> mParam;
nsCOMPtr<nsILoginReputationQueryCallback> mCallback;
};
/**
* Global singleton object for holding this factory service.
*/
@ -30,6 +58,28 @@ private:
LoginReputationService();
~LoginReputationService();
nsresult Enable();
nsresult Disable();
nsresult QueryLoginWhitelist(QueryRequest* aRequest);
// Called when a query request is finished.
nsresult Finish(const QueryRequest* aRequest,
nsresult aStatus,
VerdictType aVerdict);
// Clear data and join the worker threads.
nsresult Shutdown();
RefPtr<LoginWhitelist> mLoginWhitelist;
// Array that holds ongoing query requests which are added when
// ::QueryReputation is called.
nsTArray<UniquePtr<QueryRequest>> mQueryRequests;
};
} // namespace mozilla
#endif // LoginReputation_h__

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

@ -40,9 +40,11 @@ LoginReputationParent::QueryReputation(nsIURI* aURI)
}
NS_IMETHODIMP
LoginReputationParent::OnQueryComplete(uint16_t aResult)
LoginReputationParent::OnComplete(nsresult aResult,
VerdictType aVerdict)
{
LR_LOG(("OnQueryComplete() [result=%d]", aResult));
LR_LOG(("OnComplete() [verdict=%s]",
LoginReputationService::VerdictTypeToString(aVerdict).get()));
if (mIPCOpen) {
Unused << Send__delete__(this);

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

@ -10,14 +10,12 @@ interface nsIDOMHTMLInputElement;
interface nsIURI;
[scriptable, uuid(6219f9da-297e-446d-8d47-ccdd8e72a1d5)]
interface nsILoginReputationResult : nsISupports {
interface nsILoginReputationVerdictType : nsISupports {
// These should sync with 'VerdictType' defined in
// LoginReputationClientResponse in csd.proto.
const uint16_t UNSPECIFIED = 0;
const uint16_t SAFE = 1;
const uint16_t LOW_REPUTATION = 2;
const uint16_t PHISHING = 3;
const unsigned long UNSPECIFIED = 0;
const unsigned long SAFE = 1;
const unsigned long LOW_REPUTATION = 2;
const unsigned long PHISHING = 3;
};
[scriptable, uuid(c21ffe59-595f-46c8-9052-fefb639e196e)]
@ -27,9 +25,10 @@ interface nsILoginReputationQuery : nsISupports {
[scriptable, uuid(b527be1e-8fbb-41d9-bee4-267a71236368)]
interface nsILoginReputationQueryCallback : nsISupports {
// aResult should be one of the const value defined in nsILoginReputationResult
// aVerdict should be one of the const value defined in nsILoginReputationResult
// interface.
void onQueryComplete(in uint16_t aResult);
void onComplete(in nsresult aStatus,
in unsigned long aVerdict);
};
[scriptable, uuid(1b3f1dfe-ce3a-486b-953e-ce5ac863eff9)]

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

@ -901,16 +901,6 @@ nsFormFillController::StopSearch()
nsresult
nsFormFillController::StartQueryLoginReputation(nsIDOMHTMLInputElement *aInput)
{
if (!mLoginReputationService) {
mLoginReputationService =
do_GetService(NS_LOGIN_REPUTATION_SERVICE_CONTRACTID);
if (NS_WARN_IF(!mLoginReputationService)) {
return NS_ERROR_FAILURE;
}
}
mLoginReputationService->QueryReputationAsync(aInput, nullptr);
return NS_OK;
}