Bug 1108009 - Make synchronous interface nsIURIClassifier.ClassifyLocal. r=gcp

This commit is contained in:
Monica Chew 2014-12-18 10:18:09 -08:00
parent 5c9ad68afc
commit a312dcf860
7 changed files with 200 additions and 63 deletions

View File

@ -30,7 +30,7 @@ interface nsIURIClassifierCallback : nsISupports
* The URI classifier service checks a URI against lists of phishing * The URI classifier service checks a URI against lists of phishing
* and malware sites. * and malware sites.
*/ */
[scriptable, uuid(de4f03cd-1a28-4f51-906b-c54b47a533c5)] [scriptable, uuid(03d26681-0ef5-4718-9777-33c9e1ee3e32)]
interface nsIURIClassifier : nsISupports interface nsIURIClassifier : nsISupports
{ {
/** /**
@ -54,4 +54,12 @@ interface nsIURIClassifier : nsISupports
boolean classify(in nsIPrincipal aPrincipal, boolean classify(in nsIPrincipal aPrincipal,
in boolean aTrackingProtectionEnabled, in boolean aTrackingProtectionEnabled,
in nsIURIClassifierCallback aCallback); in nsIURIClassifierCallback aCallback);
/**
* Synchronously classify a Principal locally using its URI. This does not
* make network requests. The result is an error code with which the channel
* should be cancelled, or NS_OK if no result was found.
*/
nsresult classifyLocal(in nsIPrincipal aPrincipal,
in boolean aTrackingProtectionEnabled);
}; };

View File

@ -11,6 +11,7 @@
#include "nsIInputStream.h" #include "nsIInputStream.h"
#include "nsISeekableStream.h" #include "nsISeekableStream.h"
#include "nsIFile.h" #include "nsIFile.h"
#include "nsThreadUtils.h"
#include "mozilla/Telemetry.h" #include "mozilla/Telemetry.h"
#include "prlog.h" #include "prlog.h"
@ -54,7 +55,6 @@ Classifier::SplitTables(const nsACString& str, nsTArray<nsCString>& tables)
} }
Classifier::Classifier() Classifier::Classifier()
: mFreshTime(45 * 60)
{ {
} }
@ -144,6 +144,9 @@ Classifier::Open(nsIFile& aCacheDirectory)
rv = CreateStoreDirectory(); rv = CreateStoreDirectory();
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
// Classifier keeps its own cryptoHash for doing operations on the background
// thread. Callers can optionally pass in an nsICryptoHash for working on the
// main thread.
mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
@ -216,8 +219,15 @@ Classifier::TableRequest(nsACString& aResult)
nsresult nsresult
Classifier::Check(const nsACString& aSpec, Classifier::Check(const nsACString& aSpec,
const nsACString& aTables, const nsACString& aTables,
uint32_t aFreshnessGuarantee,
nsICryptoHash* aCryptoHash,
LookupResultArray& aResults) LookupResultArray& aResults)
{ {
nsCOMPtr<nsICryptoHash> cryptoHash = aCryptoHash;
if (!aCryptoHash) {
MOZ_ASSERT(!NS_IsMainThread(), "mCryptoHash must be used on worker thread");
cryptoHash = mCryptoHash;
}
Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_CL_CHECK_TIME> timer; Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_CL_CHECK_TIME> timer;
// Get the set of fragments based on the url. This is necessary because we // Get the set of fragments based on the url. This is necessary because we
@ -244,11 +254,11 @@ Classifier::Check(const nsACString& aSpec,
// Now check each lookup fragment against the entries in the DB. // Now check each lookup fragment against the entries in the DB.
for (uint32_t i = 0; i < fragments.Length(); i++) { for (uint32_t i = 0; i < fragments.Length(); i++) {
Completion lookupHash; Completion lookupHash;
lookupHash.FromPlaintext(fragments[i], mCryptoHash); lookupHash.FromPlaintext(fragments[i], cryptoHash);
// Get list of host keys to look up // Get list of host keys to look up
Completion hostKey; Completion hostKey;
rv = LookupCache::GetKey(fragments[i], &hostKey, mCryptoHash); rv = LookupCache::GetKey(fragments[i], &hostKey, cryptoHash);
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
// Local host on the network. // Local host on the network.
continue; continue;
@ -288,7 +298,7 @@ Classifier::Check(const nsACString& aSpec,
result->hash.complete = lookupHash; result->hash.complete = lookupHash;
result->mComplete = complete; result->mComplete = complete;
result->mFresh = (age < mFreshTime); result->mFresh = (age < aFreshnessGuarantee);
result->mTableName.Assign(cache->TableName()); result->mTableName.Assign(cache->TableName());
} }
} }

View File

@ -47,6 +47,8 @@ public:
*/ */
nsresult Check(const nsACString& aSpec, nsresult Check(const nsACString& aSpec,
const nsACString& tables, const nsACString& tables,
uint32_t aFreshnessGuarantee,
nsICryptoHash* aCryptoHash,
LookupResultArray& aResults); LookupResultArray& aResults);
/** /**
@ -61,7 +63,6 @@ public:
nsresult MarkSpoiled(nsTArray<nsCString>& aTables); nsresult MarkSpoiled(nsTArray<nsCString>& aTables);
nsresult CacheCompletions(const CacheResultArray& aResults); nsresult CacheCompletions(const CacheResultArray& aResults);
uint32_t GetHashKey(void) { return mHashKey; } uint32_t GetHashKey(void) { return mHashKey; }
void SetFreshTime(uint32_t aTime) { mFreshTime = aTime; }
/* /*
* Get a bunch of extra prefixes to query for completion * Get a bunch of extra prefixes to query for completion
* and mask the real entry being requested * and mask the real entry being requested
@ -102,7 +103,6 @@ private:
uint32_t mHashKey; uint32_t mHashKey;
// Stores the last time a given table was updated (seconds). // Stores the last time a given table was updated (seconds).
nsDataHashtable<nsCStringHashKey, int64_t> mTableFreshness; nsDataHashtable<nsCStringHashKey, int64_t> mTableFreshness;
uint32_t mFreshTime;
}; };
} }

View File

@ -185,9 +185,11 @@ interface nsIUrlClassifierDBService : nsISupports
* Interface for the actual worker thread. Implementations of this need not * Interface for the actual worker thread. Implementations of this need not
* be thread aware and just work on the database. * be thread aware and just work on the database.
*/ */
[scriptable, uuid(abcd7978-c304-4a7d-a44c-33c2ed5441e7)] [scriptable, uuid(b7b505d0-bfa2-44db-abf8-6e2bfc25bbab)]
interface nsIUrlClassifierDBServiceWorker : nsIUrlClassifierDBService interface nsIUrlClassifierDBServiceWorker : nsIUrlClassifierDBService
{ {
// Open the DB connection
void openDb();
// Provide a way to forcibly close the db connection. // Provide a way to forcibly close the db connection.
void closeDb(); void closeDb();

View File

@ -121,6 +121,13 @@ public:
// update operations to prevent lookups from blocking for too long. // update operations to prevent lookups from blocking for too long.
nsresult HandlePendingLookups(); nsresult HandlePendingLookups();
// Perform a blocking classifier lookup for a given url. Can be called on
// either the main thread or the worker thread.
nsresult DoLocalLookup(const nsACString& spec,
const nsACString& tables,
nsICryptoHash* cryptoHash,
LookupResultArray* results);
private: private:
// No subclassing // No subclassing
~nsUrlClassifierDBServiceWorker(); ~nsUrlClassifierDBServiceWorker();
@ -128,8 +135,6 @@ private:
// Disallow copy constructor // Disallow copy constructor
nsUrlClassifierDBServiceWorker(nsUrlClassifierDBServiceWorker&); nsUrlClassifierDBServiceWorker(nsUrlClassifierDBServiceWorker&);
nsresult OpenDb();
// Applies the current transaction and resets the update/working times. // Applies the current transaction and resets the update/working times.
nsresult ApplyUpdate(); nsresult ApplyUpdate();
@ -149,6 +154,7 @@ private:
uint32_t aCount, uint32_t aCount,
LookupResultArray& results); LookupResultArray& results);
// Can only be used on the background thread
nsCOMPtr<nsICryptoHash> mCryptoHash; nsCOMPtr<nsICryptoHash> mCryptoHash;
nsAutoPtr<Classifier> mClassifier; nsAutoPtr<Classifier> mClassifier;
@ -241,6 +247,76 @@ nsUrlClassifierDBServiceWorker::QueueLookup(const nsACString& spec,
return NS_OK; return NS_OK;
} }
nsresult
nsUrlClassifierDBServiceWorker::DoLocalLookup(const nsACString& spec,
const nsACString& tables,
nsICryptoHash* cryptoHash,
LookupResultArray* results)
{
LOG(("nsUrlClassifierDBServiceWorker::DoLocalLookup %s (main=%s) %p",
spec.Data(), NS_IsMainThread() ? "true" : "false", this));
if (!results) {
return NS_ERROR_FAILURE;
}
// Bail if we haven't been initialized on the background thread.
if (!mClassifier) {
return NS_ERROR_NOT_AVAILABLE;
}
// We ignore failures from Check because we'd rather return the
// results that were found than fail.
mClassifier->Check(spec, tables, gFreshnessGuarantee, cryptoHash, *results);
LOG(("Found %d results.", results->Length()));
return NS_OK;
}
static nsresult
TablesToResponse(const nsACString& tables,
bool checkMalware,
bool checkPhishing,
bool checkTracking)
{
if (checkMalware &&
FindInReadable(NS_LITERAL_CSTRING("-malware-"), tables)) {
return NS_ERROR_MALWARE_URI;
}
if (checkPhishing &&
FindInReadable(NS_LITERAL_CSTRING("-phish-"), tables)) {
return NS_ERROR_PHISHING_URI;
}
if (checkTracking &&
FindInReadable(NS_LITERAL_CSTRING("-track-"), tables)) {
return NS_ERROR_TRACKING_URI;
}
return NS_OK;
}
static nsresult
ProcessLookupResults(LookupResultArray* results,
bool checkMalware,
bool checkPhishing,
bool checkTracking)
{
// Build a stringified list of result tables.
nsTArray<nsCString> tables;
for (uint32_t i = 0; i < results->Length(); i++) {
LookupResult& result = results->ElementAt(i);
MOZ_ASSERT(!result.mNoise, "Lookup results should not have noise added");
LOG(("Found result from table %s", result.mTableName.get()));
if (tables.IndexOf(result.mTableName) == nsTArray<nsCString>::NoIndex) {
tables.AppendElement(result.mTableName);
}
}
nsAutoCString tableStr;
for (uint32_t i = 0; i < tables.Length(); i++) {
if (i != 0)
tableStr.Append(',');
tableStr.Append(tables[i]);
}
return TablesToResponse(tableStr, checkMalware, checkPhishing, checkTracking);
}
/** /**
* Lookup up a key in the database is a two step process: * Lookup up a key in the database is a two step process:
* *
@ -262,13 +338,6 @@ nsUrlClassifierDBServiceWorker::DoLookup(const nsACString& spec,
return NS_ERROR_NOT_INITIALIZED; return NS_ERROR_NOT_INITIALIZED;
} }
nsresult rv = OpenDb();
if (NS_FAILED(rv)) {
c->LookupComplete(nullptr);
NS_ERROR("Unable to open SafeBrowsing database.");
return NS_ERROR_FAILURE;
}
#if defined(PR_LOGGING) #if defined(PR_LOGGING)
PRIntervalTime clockStart = 0; PRIntervalTime clockStart = 0;
if (LOG_ENABLED()) { if (LOG_ENABLED()) {
@ -282,10 +351,11 @@ nsUrlClassifierDBServiceWorker::DoLookup(const nsACString& spec,
return NS_ERROR_OUT_OF_MEMORY; return NS_ERROR_OUT_OF_MEMORY;
} }
// we ignore failures from Check because we'd rather return the nsresult rv = DoLocalLookup(spec, tables, nullptr, results);
// results that were found than fail. if (NS_FAILED(rv)) {
mClassifier->SetFreshTime(gFreshnessGuarantee); c->LookupComplete(nullptr);
mClassifier->Check(spec, tables, *results); return rv;
}
LOG(("Found %d results.", results->Length())); LOG(("Found %d results.", results->Length()));
@ -457,6 +527,7 @@ NS_IMETHODIMP
nsUrlClassifierDBServiceWorker::BeginStream(const nsACString &table) nsUrlClassifierDBServiceWorker::BeginStream(const nsACString &table)
{ {
LOG(("nsUrlClassifierDBServiceWorker::BeginStream")); LOG(("nsUrlClassifierDBServiceWorker::BeginStream"));
MOZ_ASSERT(!NS_IsMainThread(), "Streaming must be on the background thread");
if (gShuttingDownThread) if (gShuttingDownThread)
return NS_ERROR_NOT_INITIALIZED; return NS_ERROR_NOT_INITIALIZED;
@ -732,13 +803,12 @@ nsUrlClassifierDBServiceWorker::CacheMisses(PrefixArray *results)
nsresult nsresult
nsUrlClassifierDBServiceWorker::OpenDb() nsUrlClassifierDBServiceWorker::OpenDb()
{ {
MOZ_ASSERT(!NS_IsMainThread(), "Must initialize DB on background thread");
// Connection already open, don't do anything. // Connection already open, don't do anything.
if (mClassifier) { if (mClassifier) {
return NS_OK; return NS_OK;
} }
LOG(("Opening db"));
nsresult rv; nsresult rv;
mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
@ -748,8 +818,6 @@ nsUrlClassifierDBServiceWorker::OpenDb()
return NS_ERROR_OUT_OF_MEMORY; return NS_ERROR_OUT_OF_MEMORY;
} }
classifier->SetFreshTime(gFreshnessGuarantee);
rv = classifier->Open(*mCacheDir); rv = classifier->Open(*mCacheDir);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
@ -1025,25 +1093,9 @@ NS_IMPL_ISUPPORTS(nsUrlClassifierClassifyCallback,
NS_IMETHODIMP NS_IMETHODIMP
nsUrlClassifierClassifyCallback::HandleEvent(const nsACString& tables) nsUrlClassifierClassifyCallback::HandleEvent(const nsACString& tables)
{ {
// XXX: we should probably have the wardens tell the service which table nsresult response = TablesToResponse(tables, mCheckMalware,
// names match with which classification. For now the table names give mCheckPhishing, mCheckTracking);
// enough information.
nsresult response = NS_OK;
if (mCheckMalware &&
FindInReadable(NS_LITERAL_CSTRING("-malware-"), tables)) {
response = NS_ERROR_MALWARE_URI;
} else if (mCheckPhishing &&
FindInReadable(NS_LITERAL_CSTRING("-phish-"), tables)) {
response = NS_ERROR_PHISHING_URI;
} else if (mCheckTracking &&
FindInReadable(NS_LITERAL_CSTRING("-track-"), tables)) {
LOG(("Blocking tracking uri [this=%p]", this));
response = NS_ERROR_TRACKING_URI;
}
mCallback->OnClassifyComplete(response); mCallback->OnClassifyComplete(response);
return NS_OK; return NS_OK;
} }
@ -1141,6 +1193,7 @@ nsUrlClassifierDBService::Init()
if (!gUrlClassifierDbServiceLog) if (!gUrlClassifierDbServiceLog)
gUrlClassifierDbServiceLog = PR_NewLogModule("UrlClassifierDbService"); gUrlClassifierDbServiceLog = PR_NewLogModule("UrlClassifierDbService");
#endif #endif
MOZ_ASSERT(NS_IsMainThread(), "Must initialize DB service on main thread");
// Retrieve all the preferences. // Retrieve all the preferences.
mCheckMalware = Preferences::GetBool(CHECK_MALWARE_PREF, mCheckMalware = Preferences::GetBool(CHECK_MALWARE_PREF,
@ -1170,7 +1223,7 @@ nsUrlClassifierDBService::Init()
// Force PSM loading on main thread // Force PSM loading on main thread
nsresult rv; nsresult rv;
nsCOMPtr<nsICryptoHash> acryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); mCryptoHashMain = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
// Directory providers must also be accessed on the main thread. // Directory providers must also be accessed on the main thread.
@ -1199,6 +1252,10 @@ nsUrlClassifierDBService::Init()
// Proxy for calling the worker on the background thread // Proxy for calling the worker on the background thread
mWorkerProxy = new UrlClassifierDBServiceWorkerProxy(mWorker); mWorkerProxy = new UrlClassifierDBServiceWorkerProxy(mWorker);
rv = mWorkerProxy->OpenDb();
if (NS_FAILED(rv)) {
return rv;
}
// Add an observer for shutdown // Add an observer for shutdown
nsCOMPtr<nsIObserverService> observerService = nsCOMPtr<nsIObserverService> observerService =
@ -1212,6 +1269,28 @@ nsUrlClassifierDBService::Init()
return NS_OK; return NS_OK;
} }
static void BuildTables(bool aTrackingProtectionEnabled, nsCString &tables)
{
nsAutoCString malware;
// LookupURI takes a comma-separated list already.
Preferences::GetCString(MALWARE_TABLE_PREF, &malware);
if (!malware.IsEmpty()) {
tables.Append(malware);
}
nsAutoCString phishing;
Preferences::GetCString(PHISH_TABLE_PREF, &phishing);
if (!phishing.IsEmpty()) {
tables.Append(',');
tables.Append(phishing);
}
nsAutoCString tracking;
Preferences::GetCString(TRACKING_TABLE_PREF, &tracking);
if (aTrackingProtectionEnabled && !tracking.IsEmpty()) {
tables.Append(',');
tables.Append(tracking);
}
}
// nsChannelClassifier is the only consumer of this interface. // nsChannelClassifier is the only consumer of this interface.
NS_IMETHODIMP NS_IMETHODIMP
nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal, nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal,
@ -1233,25 +1312,8 @@ nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal,
if (!callback) return NS_ERROR_OUT_OF_MEMORY; if (!callback) return NS_ERROR_OUT_OF_MEMORY;
nsAutoCString tables; nsAutoCString tables;
nsAutoCString malware; BuildTables(aTrackingProtectionEnabled, tables);
// LookupURI takes a comma-separated list already.
Preferences::GetCString(MALWARE_TABLE_PREF, &malware);
if (!malware.IsEmpty()) {
tables.Append(malware);
}
nsAutoCString phishing;
Preferences::GetCString(PHISH_TABLE_PREF, &phishing);
if (!phishing.IsEmpty()) {
tables.Append(',');
tables.Append(phishing);
}
nsAutoCString tracking;
Preferences::GetCString(TRACKING_TABLE_PREF, &tracking);
if (aTrackingProtectionEnabled && !tracking.IsEmpty()) {
LOG(("Looking up third party in tracking table, [cb=%p]", callback.get()));
tables.Append(',');
tables.Append(tracking);
}
nsresult rv = LookupURI(aPrincipal, tables, callback, false, result); nsresult rv = LookupURI(aPrincipal, tables, callback, false, result);
if (rv == NS_ERROR_MALFORMED_URI) { if (rv == NS_ERROR_MALFORMED_URI) {
*result = false; *result = false;
@ -1263,6 +1325,47 @@ nsUrlClassifierDBService::Classify(nsIPrincipal* aPrincipal,
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
nsUrlClassifierDBService::ClassifyLocal(nsIPrincipal* aPrincipal,
bool aTrackingProtectionEnabled,
nsresult* aResponse)
{
MOZ_ASSERT(NS_IsMainThread(), "ClassifyLocal must be on main thread");
*aResponse = NS_OK;
nsAutoCString tables;
BuildTables(aTrackingProtectionEnabled, tables);
nsCOMPtr<nsIURI> uri;
nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
uri = NS_GetInnermostURI(uri);
NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
nsAutoCString key;
// Canonicalize the url
nsCOMPtr<nsIUrlClassifierUtils> utilsService =
do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
rv = utilsService->GetKeyForURI(uri, key);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoPtr<LookupResultArray> results(new LookupResultArray());
if (!results) {
return NS_ERROR_OUT_OF_MEMORY;
}
// We don't use the proxy, since this is a blocking lookup. In unittests, we
// may not have been initalized, so don't crash.
rv = mWorker->DoLocalLookup(key, tables, mCryptoHashMain, results);
if (NS_SUCCEEDED(rv)) {
rv = ProcessLookupResults(results, mCheckMalware, mCheckPhishing,
mCheckTracking);
*aResponse = rv;
}
return NS_OK;
}
NS_IMETHODIMP NS_IMETHODIMP
nsUrlClassifierDBService::Lookup(nsIPrincipal* aPrincipal, nsUrlClassifierDBService::Lookup(nsIPrincipal* aPrincipal,
const nsACString& tables, const nsACString& tables,

View File

@ -33,6 +33,7 @@
#define COMPLETE_LENGTH 32 #define COMPLETE_LENGTH 32
class nsUrlClassifierDBServiceWorker; class nsUrlClassifierDBServiceWorker;
class nsICryptoHash;
class nsIThread; class nsIThread;
class nsIURI; class nsIURI;
@ -117,6 +118,10 @@ private:
// Thread that we do the updates on. // Thread that we do the updates on.
static nsIThread* gDbBackgroundThread; static nsIThread* gDbBackgroundThread;
// nsICryptoHash for doing hash operations on the main thread. This is only
// used for nsIURIClassifier.ClassifyLocal
nsCOMPtr<nsICryptoHash> mCryptoHashMain;
}; };
NS_DEFINE_STATIC_IID_ACCESSOR(nsUrlClassifierDBService, NS_URLCLASSIFIERDBSERVICE_CID) NS_DEFINE_STATIC_IID_ACCESSOR(nsUrlClassifierDBService, NS_URLCLASSIFIERDBSERVICE_CID)

View File

@ -143,6 +143,15 @@ UrlClassifierDBServiceWorkerProxy::ResetDatabase()
return DispatchToWorkerThread(r); return DispatchToWorkerThread(r);
} }
NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::OpenDb()
{
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(mTarget,
&nsIUrlClassifierDBServiceWorker::OpenDb);
return DispatchToWorkerThread(r);
}
NS_IMETHODIMP NS_IMETHODIMP
UrlClassifierDBServiceWorkerProxy::CloseDb() UrlClassifierDBServiceWorkerProxy::CloseDb()
{ {