mirror of
https://github.com/ManchildProductions/UXP-Fixed.git
synced 2026-06-22 02:09:28 +00:00
32e6b0f471
Rationale: This was a Mozilla oversight and/or error.
This change has caused harm and is causing users to switch back to
Chrome, Safari or Edge for their WebStart needs.
JNLP is not an executable and should not be treated as such. JNLP should
be treated the same as any (e.g. Word) document and allowed to be opened
with the designated program.
A JNLP file will not cause execution on a system unless it has a valid
signature, and the user explicitly authorizes the launching based on
information provided by the signature. Moreover, there will even be a
check by the Java environment to see if the Java runtime (if there is
one) is current, and prompt the user to update if required.
This reverts commit 21495c5897 and
modifies ApplicationReputation.cpp to keep the list in sync (was a
discrepancy before).
1593 lines
51 KiB
C++
1593 lines
51 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=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/. */
|
|
// See
|
|
// https://wiki.mozilla.org/Security/Features/Application_Reputation_Design_Doc
|
|
// for a description of Chrome's implementation of this feature.
|
|
#include "ApplicationReputation.h"
|
|
#include "chrome/common/safe_browsing/csd.pb.h"
|
|
|
|
#include "nsIArray.h"
|
|
#include "nsIApplicationReputation.h"
|
|
#include "nsIChannel.h"
|
|
#include "nsICryptoHash.h"
|
|
#include "nsIHttpChannel.h"
|
|
#include "nsIIOService.h"
|
|
#include "nsIPrefService.h"
|
|
#include "nsISimpleEnumerator.h"
|
|
#include "nsIStreamListener.h"
|
|
#include "nsIStringStream.h"
|
|
#include "nsITimer.h"
|
|
#include "nsIUploadChannel2.h"
|
|
#include "nsIURI.h"
|
|
#include "nsIURL.h"
|
|
#include "nsIUrlClassifierDBService.h"
|
|
#include "nsIX509Cert.h"
|
|
#include "nsIX509CertDB.h"
|
|
#include "nsIX509CertList.h"
|
|
|
|
#include "mozilla/ArrayUtils.h"
|
|
#include "mozilla/BasePrincipal.h"
|
|
#include "mozilla/ErrorNames.h"
|
|
#include "mozilla/LoadContext.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
|
|
#include "nsAutoPtr.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsDebug.h"
|
|
#include "nsError.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "nsString.h"
|
|
#include "nsTArray.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsXPCOMStrings.h"
|
|
|
|
#include "nsIContentPolicy.h"
|
|
#include "nsILoadInfo.h"
|
|
#include "nsContentUtils.h"
|
|
|
|
using mozilla::ArrayLength;
|
|
using mozilla::BasePrincipal;
|
|
using mozilla::DocShellOriginAttributes;
|
|
using mozilla::PrincipalOriginAttributes;
|
|
using mozilla::Preferences;
|
|
using mozilla::TimeStamp;
|
|
using safe_browsing::ClientDownloadRequest;
|
|
using safe_browsing::ClientDownloadRequest_CertificateChain;
|
|
using safe_browsing::ClientDownloadRequest_Resource;
|
|
using safe_browsing::ClientDownloadRequest_SignatureInfo;
|
|
|
|
// Preferences that we need to initialize the query.
|
|
#define PREF_SB_APP_REP_URL "browser.safebrowsing.downloads.remote.url"
|
|
#define PREF_SB_MALWARE_ENABLED "browser.safebrowsing.malware.enabled"
|
|
#define PREF_SB_DOWNLOADS_ENABLED "browser.safebrowsing.downloads.enabled"
|
|
#define PREF_SB_DOWNLOADS_REMOTE_ENABLED "browser.safebrowsing.downloads.remote.enabled"
|
|
#define PREF_SB_DOWNLOADS_REMOTE_TIMEOUT "browser.safebrowsing.downloads.remote.timeout_ms"
|
|
#define PREF_GENERAL_LOCALE "general.useragent.locale"
|
|
#define PREF_DOWNLOAD_BLOCK_TABLE "urlclassifier.downloadBlockTable"
|
|
#define PREF_DOWNLOAD_ALLOW_TABLE "urlclassifier.downloadAllowTable"
|
|
|
|
// Preferences that are needed to action the verdict.
|
|
#define PREF_BLOCK_DANGEROUS "browser.safebrowsing.downloads.remote.block_dangerous"
|
|
#define PREF_BLOCK_DANGEROUS_HOST "browser.safebrowsing.downloads.remote.block_dangerous_host"
|
|
#define PREF_BLOCK_POTENTIALLY_UNWANTED "browser.safebrowsing.downloads.remote.block_potentially_unwanted"
|
|
#define PREF_BLOCK_UNCOMMON "browser.safebrowsing.downloads.remote.block_uncommon"
|
|
|
|
// MOZ_LOG=ApplicationReputation:5
|
|
mozilla::LazyLogModule ApplicationReputationService::prlog("ApplicationReputation");
|
|
#define LOG(args) MOZ_LOG(ApplicationReputationService::prlog, mozilla::LogLevel::Debug, args)
|
|
#define LOG_ENABLED() MOZ_LOG_TEST(ApplicationReputationService::prlog, mozilla::LogLevel::Debug)
|
|
|
|
class PendingDBLookup;
|
|
|
|
// A single use class private to ApplicationReputationService encapsulating an
|
|
// nsIApplicationReputationQuery and an nsIApplicationReputationCallback. Once
|
|
// created by ApplicationReputationService, it is guaranteed to call mCallback.
|
|
// This class is private to ApplicationReputationService.
|
|
class PendingLookup final : public nsIStreamListener,
|
|
public nsITimerCallback,
|
|
public nsIObserver
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIREQUESTOBSERVER
|
|
NS_DECL_NSISTREAMLISTENER
|
|
NS_DECL_NSITIMERCALLBACK
|
|
NS_DECL_NSIOBSERVER
|
|
|
|
// Constructor and destructor.
|
|
PendingLookup(nsIApplicationReputationQuery* aQuery,
|
|
nsIApplicationReputationCallback* aCallback);
|
|
|
|
// Start the lookup. The lookup may have 2 parts: local and remote. In the
|
|
// local lookup, PendingDBLookups are created to query the local allow and
|
|
// blocklists for various URIs associated with this downloaded file. In the
|
|
// event that no results are found, a remote lookup is sent to the Application
|
|
// Reputation server.
|
|
nsresult StartLookup();
|
|
|
|
private:
|
|
~PendingLookup();
|
|
|
|
friend class PendingDBLookup;
|
|
|
|
// Number of blocklist and allowlist hits we have seen.
|
|
uint32_t mBlocklistCount;
|
|
uint32_t mAllowlistCount;
|
|
|
|
// The query containing metadata about the downloaded file.
|
|
nsCOMPtr<nsIApplicationReputationQuery> mQuery;
|
|
|
|
// The callback with which to report the verdict.
|
|
nsCOMPtr<nsIApplicationReputationCallback> mCallback;
|
|
|
|
// An array of strings created from certificate information used to whitelist
|
|
// the downloaded file.
|
|
nsTArray<nsCString> mAllowlistSpecs;
|
|
// The source URI of the download, the referrer and possibly any redirects.
|
|
nsTArray<nsCString> mAnylistSpecs;
|
|
|
|
// When we started this query
|
|
TimeStamp mStartTime;
|
|
|
|
// The channel used to talk to the remote lookup server
|
|
nsCOMPtr<nsIChannel> mChannel;
|
|
|
|
// Timer to abort this lookup if it takes too long
|
|
nsCOMPtr<nsITimer> mTimeoutTimer;
|
|
|
|
// A protocol buffer for storing things we need in the remote request. We
|
|
// store the resource chain (redirect information) as well as signature
|
|
// information extracted using the Windows Authenticode API, if the binary is
|
|
// signed.
|
|
ClientDownloadRequest mRequest;
|
|
|
|
// The response from the application reputation query. This is read in chunks
|
|
// as part of our nsIStreamListener implementation and may contain embedded
|
|
// NULLs.
|
|
nsCString mResponse;
|
|
|
|
// Returns true if the file is likely to be binary.
|
|
bool IsBinaryFile();
|
|
|
|
// Returns the type of download binary for the file.
|
|
ClientDownloadRequest::DownloadType GetDownloadType(const nsAString& aFilename);
|
|
|
|
// Clean up and call the callback. PendingLookup must not be used after this
|
|
// function is called.
|
|
nsresult OnComplete(bool shouldBlock, nsresult rv,
|
|
uint32_t verdict = nsIApplicationReputationService::VERDICT_SAFE);
|
|
|
|
// Wrapper function for nsIStreamListener.onStopRequest to make it easy to
|
|
// guarantee calling the callback
|
|
nsresult OnStopRequestInternal(nsIRequest *aRequest,
|
|
nsISupports *aContext,
|
|
nsresult aResult,
|
|
bool* aShouldBlock,
|
|
uint32_t* aVerdict);
|
|
|
|
// Return the hex-encoded hash of the whole URI.
|
|
nsresult GetSpecHash(nsACString& aSpec, nsACString& hexEncodedHash);
|
|
|
|
// Strip url parameters, fragments, and user@pass fields from the URI spec
|
|
// using nsIURL. Hash data URIs and return blob URIs unfiltered.
|
|
nsresult GetStrippedSpec(nsIURI* aUri, nsACString& spec);
|
|
|
|
// Escape '/' and '%' in certificate attribute values.
|
|
nsCString EscapeCertificateAttribute(const nsACString& aAttribute);
|
|
|
|
// Escape ':' in fingerprint values.
|
|
nsCString EscapeFingerprint(const nsACString& aAttribute);
|
|
|
|
// Generate whitelist strings for the given certificate pair from the same
|
|
// certificate chain.
|
|
nsresult GenerateWhitelistStringsForPair(
|
|
nsIX509Cert* certificate, nsIX509Cert* issuer);
|
|
|
|
// Generate whitelist strings for the given certificate chain, which starts
|
|
// with the signer and may go all the way to the root cert.
|
|
nsresult GenerateWhitelistStringsForChain(
|
|
const ClientDownloadRequest_CertificateChain& aChain);
|
|
|
|
// For signed binaries, generate strings of the form:
|
|
// http://sb-ssl.google.com/safebrowsing/csd/certificate/
|
|
// <issuer_cert_sha1_fingerprint>[/CN=<cn>][/O=<org>][/OU=<unit>]
|
|
// for each (cert, issuer) pair in each chain of certificates that is
|
|
// associated with the binary.
|
|
nsresult GenerateWhitelistStrings();
|
|
|
|
// Parse the XPCOM certificate lists and stick them into the protocol buffer
|
|
// version.
|
|
nsresult ParseCertificates(nsIArray* aSigArray);
|
|
|
|
// Adds the redirects to mAnylistSpecs to be looked up.
|
|
nsresult AddRedirects(nsIArray* aRedirects);
|
|
|
|
// Helper function to ensure that we call PendingLookup::LookupNext or
|
|
// PendingLookup::OnComplete.
|
|
nsresult DoLookupInternal();
|
|
|
|
// Looks up all the URIs that may be responsible for allowlisting or
|
|
// blocklisting the downloaded file. These URIs may include whitelist strings
|
|
// generated by certificates verifying the binary as well as the target URI
|
|
// from which the file was downloaded.
|
|
nsresult LookupNext();
|
|
|
|
// Sends a query to the remote application reputation service. Returns NS_OK
|
|
// on success.
|
|
nsresult SendRemoteQuery();
|
|
|
|
// Helper function to ensure that we always call the callback.
|
|
nsresult SendRemoteQueryInternal();
|
|
};
|
|
|
|
// A single-use class for looking up a single URI in the safebrowsing DB. This
|
|
// class is private to PendingLookup.
|
|
class PendingDBLookup final : public nsIUrlClassifierCallback
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIURLCLASSIFIERCALLBACK
|
|
|
|
// Constructor and destructor
|
|
explicit PendingDBLookup(PendingLookup* aPendingLookup);
|
|
|
|
// Look up the given URI in the safebrowsing DBs, optionally on both the allow
|
|
// list and the blocklist. If there is a match, call
|
|
// PendingLookup::OnComplete. Otherwise, call PendingLookup::LookupNext.
|
|
nsresult LookupSpec(const nsACString& aSpec, bool aAllowlistOnly);
|
|
|
|
private:
|
|
~PendingDBLookup();
|
|
|
|
// The download appeared on the allowlist, blocklist, or no list (and thus
|
|
// could trigger a remote query.
|
|
enum LIST_TYPES {
|
|
ALLOW_LIST = 0,
|
|
BLOCK_LIST = 1,
|
|
NO_LIST = 2,
|
|
};
|
|
|
|
nsCString mSpec;
|
|
bool mAllowlistOnly;
|
|
RefPtr<PendingLookup> mPendingLookup;
|
|
nsresult LookupSpecInternal(const nsACString& aSpec);
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(PendingDBLookup,
|
|
nsIUrlClassifierCallback)
|
|
|
|
PendingDBLookup::PendingDBLookup(PendingLookup* aPendingLookup) :
|
|
mAllowlistOnly(false),
|
|
mPendingLookup(aPendingLookup)
|
|
{
|
|
LOG(("Created pending DB lookup [this = %p]", this));
|
|
}
|
|
|
|
PendingDBLookup::~PendingDBLookup()
|
|
{
|
|
LOG(("Destroying pending DB lookup [this = %p]", this));
|
|
mPendingLookup = nullptr;
|
|
}
|
|
|
|
nsresult
|
|
PendingDBLookup::LookupSpec(const nsACString& aSpec,
|
|
bool aAllowlistOnly)
|
|
{
|
|
LOG(("Checking principal %s [this=%p]", aSpec.Data(), this));
|
|
mSpec = aSpec;
|
|
mAllowlistOnly = aAllowlistOnly;
|
|
nsresult rv = LookupSpecInternal(aSpec);
|
|
if (NS_FAILED(rv)) {
|
|
nsAutoCString errorName;
|
|
mozilla::GetErrorName(rv, errorName);
|
|
LOG(("Error in LookupSpecInternal() [rv = %s, this = %p]",
|
|
errorName.get(), this));
|
|
return mPendingLookup->LookupNext(); // ignore this lookup and move to next
|
|
}
|
|
// LookupSpecInternal has called nsIUrlClassifierCallback.lookup, which is
|
|
// guaranteed to call HandleEvent.
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
PendingDBLookup::LookupSpecInternal(const nsACString& aSpec)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
|
|
rv = ios->NewURI(aSpec, nullptr, nullptr, getter_AddRefs(uri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
PrincipalOriginAttributes attrs;
|
|
nsCOMPtr<nsIPrincipal> principal =
|
|
BasePrincipal::CreateCodebasePrincipal(uri, attrs);
|
|
if (!principal) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// Check local lists to see if the URI has already been whitelisted or
|
|
// blacklisted.
|
|
LOG(("Checking DB service for principal %s [this = %p]", mSpec.get(), this));
|
|
nsCOMPtr<nsIUrlClassifierDBService> dbService =
|
|
do_GetService(NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsAutoCString tables;
|
|
nsAutoCString allowlist;
|
|
Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allowlist);
|
|
if (!allowlist.IsEmpty()) {
|
|
tables.Append(allowlist);
|
|
}
|
|
nsAutoCString blocklist;
|
|
Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blocklist);
|
|
if (!mAllowlistOnly && !blocklist.IsEmpty()) {
|
|
tables.Append(',');
|
|
tables.Append(blocklist);
|
|
}
|
|
return dbService->Lookup(principal, tables, this);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PendingDBLookup::HandleEvent(const nsACString& tables)
|
|
{
|
|
// HandleEvent is guaranteed to call either:
|
|
// 1) PendingLookup::OnComplete if the URL matches the blocklist, or
|
|
// 2) PendingLookup::LookupNext if the URL does not match the blocklist.
|
|
// Blocklisting trumps allowlisting.
|
|
nsAutoCString blockList;
|
|
Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blockList);
|
|
if (!mAllowlistOnly && FindInReadable(blockList, tables)) {
|
|
mPendingLookup->mBlocklistCount++;
|
|
LOG(("Found principal %s on blocklist [this = %p]", mSpec.get(), this));
|
|
return mPendingLookup->OnComplete(true, NS_OK,
|
|
nsIApplicationReputationService::VERDICT_DANGEROUS);
|
|
}
|
|
|
|
nsAutoCString allowList;
|
|
Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allowList);
|
|
if (FindInReadable(allowList, tables)) {
|
|
mPendingLookup->mAllowlistCount++;
|
|
LOG(("Found principal %s on allowlist [this = %p]", mSpec.get(), this));
|
|
// Don't call onComplete, since blocklisting trumps allowlisting
|
|
} else {
|
|
LOG(("Didn't find principal %s on any list [this = %p]", mSpec.get(),
|
|
this));
|
|
}
|
|
return mPendingLookup->LookupNext();
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(PendingLookup,
|
|
nsIStreamListener,
|
|
nsIRequestObserver,
|
|
nsIObserver)
|
|
|
|
PendingLookup::PendingLookup(nsIApplicationReputationQuery* aQuery,
|
|
nsIApplicationReputationCallback* aCallback) :
|
|
mBlocklistCount(0),
|
|
mAllowlistCount(0),
|
|
mQuery(aQuery),
|
|
mCallback(aCallback)
|
|
{
|
|
LOG(("Created pending lookup [this = %p]", this));
|
|
}
|
|
|
|
PendingLookup::~PendingLookup()
|
|
{
|
|
LOG(("Destroying pending lookup [this = %p]", this));
|
|
}
|
|
|
|
static const char16_t* kBinaryFileExtensions[] = {
|
|
// Extracted from the "File Type Policies" Chrome extension
|
|
//u".001",
|
|
//u".7z",
|
|
//u".ace",
|
|
//u".action", // Mac script
|
|
//u".ad", // Windows
|
|
u".ade", // MS Access
|
|
u".adp", // MS Access
|
|
u".apk", // Android package
|
|
u".app", // Executable application
|
|
u".application", // MS ClickOnce
|
|
u".appref-ms", // MS ClickOnce
|
|
//u".arc",
|
|
//u".arj",
|
|
u".as", // Mac archive
|
|
u".asp", // Windows Server script
|
|
u".asx", // Windows Media Player
|
|
//u".b64",
|
|
//u".balz",
|
|
u".bas", // Basic script
|
|
u".bash", // Linux shell
|
|
u".bat", // Windows shell
|
|
//u".bhx",
|
|
//u".bin",
|
|
u".bz", // Linux archive (bzip)
|
|
u".bz2", // Linux archive (bzip2)
|
|
u".bzip2", // Linux archive (bzip2)
|
|
u".cab", // Windows archive
|
|
u".cdr", // Mac disk image
|
|
u".cfg", // Windows
|
|
u".chi", // Windows Help
|
|
u".chm", // Windows Help
|
|
u".class", // Java
|
|
u".cmd", // Windows executable
|
|
u".com", // Windows executable
|
|
u".command", // Mac script
|
|
u".cpgz", // Mac archive
|
|
//u".cpio",
|
|
u".cpl", // Windows executable
|
|
u".crt", // Windows signed certificate
|
|
u".crx", // Chrome extensions
|
|
u".csh", // Linux shell
|
|
u".dart", // Mac disk image
|
|
u".dc42", // Apple DiskCopy Image
|
|
u".deb", // Linux package
|
|
u".dex", // Android
|
|
u".diskcopy42", // Apple DiskCopy Image
|
|
u".dll", // Windows executable
|
|
u".dmg", // Mac disk image
|
|
u".dmgpart", // Mac disk image
|
|
//u".docb", // MS Office
|
|
//u".docm", // MS Word
|
|
//u".docx", // MS Word
|
|
//u".dotm", // MS Word
|
|
//u".dott", // MS Office
|
|
u".drv", // Windows driver
|
|
u".dvdr", // Mac Disk image
|
|
u".efi", // Firmware
|
|
u".eml", // MS Outlook
|
|
u".exe", // Windows executable
|
|
//u".fat",
|
|
u".fon", // Windows font
|
|
u".fxp", // MS FoxPro
|
|
u".gadget", // Windows
|
|
u".grp", // Windows
|
|
u".gz", // Linux archive (gzip)
|
|
u".gzip", // Linux archive (gzip)
|
|
u".hfs", // Mac disk image
|
|
u".hlp", // Windows Help
|
|
u".hqx", // Mac archive
|
|
u".hta", // HTML trusted application
|
|
u".htt", // MS HTML template
|
|
u".img", // Mac disk image
|
|
u".imgpart", // Mac disk image
|
|
u".inf", // Windows installer
|
|
u".ini", // Generic config file
|
|
u".ins", // IIS config
|
|
//u".inx", // InstallShield
|
|
u".iso", // CD image
|
|
u".isp", // IIS config
|
|
//u".isu", // InstallShield
|
|
u".jar", // Java
|
|
//u".jnlp", // Java
|
|
//u".job", // Windows
|
|
u".js", // JavaScript script
|
|
u".jse", // JScript
|
|
u".ksh", // Linux shell
|
|
//u".lha",
|
|
u".lnk", // Windows
|
|
u".local", // Windows
|
|
//u".lpaq1",
|
|
//u".lpaq5",
|
|
//u".lpaq8",
|
|
//u".lzh",
|
|
//u".lzma",
|
|
u".mad", // MS Access
|
|
u".maf", // MS Access
|
|
u".mag", // MS Access
|
|
u".mam", // MS Access
|
|
u".manifest", // Windows
|
|
u".maq", // MS Access
|
|
u".mar", // MS Access
|
|
u".mas", // MS Access
|
|
u".mat", // MS Access
|
|
u".mau", // Media attachment
|
|
u".mav", // MS Access
|
|
u".maw", // MS Access
|
|
u".mda", // MS Access
|
|
u".mdb", // MS Access
|
|
u".mde", // MS Access
|
|
u".mdt", // MS Access
|
|
u".mdw", // MS Access
|
|
u".mdz", // MS Access
|
|
u".mht", // MS HTML
|
|
u".mhtml", // MS HTML
|
|
u".mim", // MS Mail
|
|
u".mmc", // MS Office
|
|
u".mof", // Windows
|
|
u".mpkg", // Mac installer
|
|
u".msc", // Windows executable
|
|
u".msg", // MS Outlook
|
|
u".msh", // Windows shell
|
|
u".msh1", // Windows shell
|
|
u".msh1xml", // Windows shell
|
|
u".msh2", // Windows shell
|
|
u".msh2xml", // Windows shell
|
|
u".mshxml", // Windows
|
|
u".msi", // Windows installer
|
|
u".msp", // Windows installer
|
|
u".mst", // Windows installer
|
|
u".ndif", // Mac disk image
|
|
//u".ntfs", // 7z
|
|
u".ocx", // ActiveX
|
|
u".ops", // MS Office
|
|
//u".out", // Linux binary
|
|
//u".paf", // PortableApps package
|
|
//u".paq8f",
|
|
//u".paq8jd",
|
|
//u".paq8l",
|
|
//u".paq8o",
|
|
u".partial", // Downloads
|
|
u".pax", // Mac archive
|
|
u".pcd", // Microsoft Visual Test
|
|
u".pdf", // Adobe Acrobat
|
|
//u".pea",
|
|
u".pet", // Linux package
|
|
u".pif", // Windows
|
|
u".pkg", // Mac installer
|
|
u".pl", // Perl script
|
|
u".plg", // MS Visual Studio
|
|
//u".potx", // MS PowerPoint
|
|
//u".ppam", // MS PowerPoint
|
|
//u".ppsx", // MS PowerPoint
|
|
//u".pptm", // MS PowerPoint
|
|
//u".pptx", // MS PowerPoint
|
|
u".prf", // MS Outlook
|
|
u".prg", // Windows
|
|
u".ps1", // Windows shell
|
|
u".ps1xml", // Windows shell
|
|
u".ps2", // Windows shell
|
|
u".ps2xml", // Windows shell
|
|
u".psc1", // Windows shell
|
|
u".psc2", // Windows shell
|
|
u".pst", // MS Outlook
|
|
u".pup", // Linux package
|
|
u".py", // Python script
|
|
u".pyc", // Python binary
|
|
u".pyw", // Python GUI
|
|
//u".quad",
|
|
//u".r00",
|
|
//u".r01",
|
|
//u".r02",
|
|
//u".r03",
|
|
//u".r04",
|
|
//u".r05",
|
|
//u".r06",
|
|
//u".r07",
|
|
//u".r08",
|
|
//u".r09",
|
|
//u".r10",
|
|
//u".r11",
|
|
//u".r12",
|
|
//u".r13",
|
|
//u".r14",
|
|
//u".r15",
|
|
//u".r16",
|
|
//u".r17",
|
|
//u".r18",
|
|
//u".r19",
|
|
//u".r20",
|
|
//u".r21",
|
|
//u".r22",
|
|
//u".r23",
|
|
//u".r24",
|
|
//u".r25",
|
|
//u".r26",
|
|
//u".r27",
|
|
//u".r28",
|
|
//u".r29",
|
|
//u".rar",
|
|
u".rb", // Ruby script
|
|
u".reg", // Windows Registry
|
|
u".rels", // MS Office
|
|
//u".rgs", // Windows Registry
|
|
u".rpm", // Linux package
|
|
//u".rtf", // MS Office
|
|
//u".run", // Linux shell
|
|
u".scf", // Windows shell
|
|
u".scr", // Windows
|
|
u".sct", // Windows shell
|
|
u".search-ms", // Windows
|
|
u".sh", // Linux shell
|
|
u".shar", // Linux shell
|
|
u".shb", // Windows
|
|
u".shs", // Windows shell
|
|
//u".sldm", // MS PowerPoint
|
|
//u".sldx", // MS PowerPoint
|
|
u".slp", // Linux package
|
|
u".smi", // Mac disk image
|
|
u".sparsebundle", // Mac disk image
|
|
u".sparseimage", // Mac disk image
|
|
u".spl", // Adobe Flash
|
|
//u".squashfs",
|
|
u".svg",
|
|
u".swf", // Adobe Flash
|
|
u".swm", // Windows Imaging
|
|
u".sys", // Windows
|
|
u".tar", // Linux archive
|
|
u".taz", // Linux archive (bzip2)
|
|
u".tbz", // Linux archive (bzip2)
|
|
u".tbz2", // Linux archive (bzip2)
|
|
u".tcsh", // Linux shell
|
|
u".tgz", // Linux archive (gzip)
|
|
//u".toast", // Roxio disk image
|
|
//u".torrent", // Bittorrent
|
|
u".tpz", // Linux archive (gzip)
|
|
u".txz", // Linux archive (xz)
|
|
u".tz", // Linux archive (gzip)
|
|
//u".u3p", // U3 Smart Apps
|
|
u".udf", // MS Excel
|
|
u".udif", // Mac disk image
|
|
u".url", // Windows
|
|
//u".uu",
|
|
//u".uue",
|
|
u".vb", // Visual Basic script
|
|
u".vbe", // Visual Basic script
|
|
u".vbs", // Visual Basic script
|
|
//u".vbscript", // Visual Basic script
|
|
u".vhd", // Windows virtual hard drive
|
|
u".vhdx", // Windows virtual hard drive
|
|
u".vmdk", // VMware virtual disk
|
|
u".vsd", // MS Visio
|
|
u".vsmacros", // MS Visual Studio
|
|
u".vss", // MS Visio
|
|
u".vst", // MS Visio
|
|
u".vsw", // MS Visio
|
|
u".website", // Windows
|
|
u".wim", // Windows Imaging
|
|
//u".workflow", // Mac Automator
|
|
//u".wrc", // FreeArc archive
|
|
u".ws", // Windows script
|
|
u".wsc", // Windows script
|
|
u".wsf", // Windows script
|
|
u".wsh", // Windows script
|
|
u".xar", // MS Excel
|
|
u".xbap", // XAML Browser Application
|
|
u".xip", // Mac archive
|
|
//u".xlsm", // MS Excel
|
|
//u".xlsx", // MS Excel
|
|
//u".xltm", // MS Excel
|
|
//u".xltx", // MS Excel
|
|
u".xml",
|
|
u".xnk", // MS Exchange
|
|
u".xrm-ms", // Windows
|
|
u".xsl", // XML Stylesheet
|
|
//u".xxe",
|
|
u".xz", // Linux archive (xz)
|
|
u".z", // InstallShield
|
|
#ifdef XP_WIN // disable on Mac/Linux, see 1167493
|
|
u".zip", // Generic archive
|
|
#endif
|
|
u".zipx", // WinZip
|
|
//u".zpaq",
|
|
};
|
|
|
|
bool
|
|
PendingLookup::IsBinaryFile()
|
|
{
|
|
nsString fileName;
|
|
nsresult rv = mQuery->GetSuggestedFileName(fileName);
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("No suggested filename [this = %p]", this));
|
|
return false;
|
|
}
|
|
LOG(("Suggested filename: %s [this = %p]",
|
|
NS_ConvertUTF16toUTF8(fileName).get(), this));
|
|
|
|
for (size_t i = 0; i < ArrayLength(kBinaryFileExtensions); ++i) {
|
|
if (StringEndsWith(fileName, nsDependentString(kBinaryFileExtensions[i]))) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ClientDownloadRequest::DownloadType
|
|
PendingLookup::GetDownloadType(const nsAString& aFilename) {
|
|
MOZ_ASSERT(IsBinaryFile());
|
|
|
|
// From https://cs.chromium.org/chromium/src/chrome/common/safe_browsing/download_protection_util.cc?l=17
|
|
if (StringEndsWith(aFilename, NS_LITERAL_STRING(".zip"))) {
|
|
return ClientDownloadRequest::ZIPPED_EXECUTABLE;
|
|
} else if (StringEndsWith(aFilename, NS_LITERAL_STRING(".apk"))) {
|
|
return ClientDownloadRequest::ANDROID_APK;
|
|
} else if (StringEndsWith(aFilename, NS_LITERAL_STRING(".app")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".cdr")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".dart")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".dc42")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".diskcopy42")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".dmg")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".dmgpart")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".dvdr")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".img")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".imgpart")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".iso")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".mpkg")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".ndif")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".pkg")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".smi")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".sparsebundle")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".sparseimage")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".toast")) ||
|
|
StringEndsWith(aFilename, NS_LITERAL_STRING(".udif"))) {
|
|
return ClientDownloadRequest::MAC_EXECUTABLE;
|
|
}
|
|
|
|
return ClientDownloadRequest::WIN_EXECUTABLE; // default to Windows binaries
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::LookupNext()
|
|
{
|
|
// We must call LookupNext or SendRemoteQuery upon return.
|
|
// Look up all of the URLs that could allow or block this download.
|
|
// Blocklist first.
|
|
if (mBlocklistCount > 0) {
|
|
return OnComplete(true, NS_OK,
|
|
nsIApplicationReputationService::VERDICT_DANGEROUS);
|
|
}
|
|
int index = mAnylistSpecs.Length() - 1;
|
|
nsCString spec;
|
|
if (index >= 0) {
|
|
// Check the source URI, referrer and redirect chain.
|
|
spec = mAnylistSpecs[index];
|
|
mAnylistSpecs.RemoveElementAt(index);
|
|
RefPtr<PendingDBLookup> lookup(new PendingDBLookup(this));
|
|
return lookup->LookupSpec(spec, false);
|
|
}
|
|
// If any of mAnylistSpecs matched the blocklist, go ahead and block.
|
|
if (mBlocklistCount > 0) {
|
|
return OnComplete(true, NS_OK,
|
|
nsIApplicationReputationService::VERDICT_DANGEROUS);
|
|
}
|
|
// If any of mAnylistSpecs matched the allowlist, go ahead and pass.
|
|
if (mAllowlistCount > 0) {
|
|
return OnComplete(false, NS_OK);
|
|
}
|
|
// Only binary signatures remain.
|
|
index = mAllowlistSpecs.Length() - 1;
|
|
if (index >= 0) {
|
|
spec = mAllowlistSpecs[index];
|
|
LOG(("PendingLookup::LookupNext: checking %s on allowlist", spec.get()));
|
|
mAllowlistSpecs.RemoveElementAt(index);
|
|
RefPtr<PendingDBLookup> lookup(new PendingDBLookup(this));
|
|
return lookup->LookupSpec(spec, true);
|
|
}
|
|
// There are no more URIs to check against local list. If the file is
|
|
// not eligible for remote lookup, bail.
|
|
if (!IsBinaryFile()) {
|
|
LOG(("Not eligible for remote lookups [this=%x]", this));
|
|
return OnComplete(false, NS_OK);
|
|
}
|
|
nsresult rv = SendRemoteQuery();
|
|
if (NS_FAILED(rv)) {
|
|
return OnComplete(false, rv);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCString
|
|
PendingLookup::EscapeCertificateAttribute(const nsACString& aAttribute)
|
|
{
|
|
// Escape '/' because it's a field separator, and '%' because Chrome does
|
|
nsCString escaped;
|
|
escaped.SetCapacity(aAttribute.Length());
|
|
for (unsigned int i = 0; i < aAttribute.Length(); ++i) {
|
|
if (aAttribute.Data()[i] == '%') {
|
|
escaped.AppendLiteral("%25");
|
|
} else if (aAttribute.Data()[i] == '/') {
|
|
escaped.AppendLiteral("%2F");
|
|
} else if (aAttribute.Data()[i] == ' ') {
|
|
escaped.AppendLiteral("%20");
|
|
} else {
|
|
escaped.Append(aAttribute.Data()[i]);
|
|
}
|
|
}
|
|
return escaped;
|
|
}
|
|
|
|
nsCString
|
|
PendingLookup::EscapeFingerprint(const nsACString& aFingerprint)
|
|
{
|
|
// Google's fingerprint doesn't have colons
|
|
nsCString escaped;
|
|
escaped.SetCapacity(aFingerprint.Length());
|
|
for (unsigned int i = 0; i < aFingerprint.Length(); ++i) {
|
|
if (aFingerprint.Data()[i] != ':') {
|
|
escaped.Append(aFingerprint.Data()[i]);
|
|
}
|
|
}
|
|
return escaped;
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::GenerateWhitelistStringsForPair(
|
|
nsIX509Cert* certificate,
|
|
nsIX509Cert* issuer)
|
|
{
|
|
// The whitelist paths have format:
|
|
// http://sb-ssl.google.com/safebrowsing/csd/certificate/<issuer_cert_fingerprint>[/CN=<cn>][/O=<org>][/OU=<unit>]
|
|
// Any of CN, O, or OU may be omitted from the whitelist entry. Unfortunately
|
|
// this is not publicly documented, but the Chrome implementation can be found
|
|
// here:
|
|
// https://code.google.com/p/chromium/codesearch#search/&q=GetCertificateWhitelistStrings
|
|
nsCString whitelistString(
|
|
"http://sb-ssl.google.com/safebrowsing/csd/certificate/");
|
|
|
|
nsString fingerprint;
|
|
nsresult rv = issuer->GetSha1Fingerprint(fingerprint);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
whitelistString.Append(
|
|
EscapeFingerprint(NS_ConvertUTF16toUTF8(fingerprint)));
|
|
|
|
nsString commonName;
|
|
rv = certificate->GetCommonName(commonName);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (!commonName.IsEmpty()) {
|
|
whitelistString.AppendLiteral("/CN=");
|
|
whitelistString.Append(
|
|
EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(commonName)));
|
|
}
|
|
|
|
nsString organization;
|
|
rv = certificate->GetOrganization(organization);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (!organization.IsEmpty()) {
|
|
whitelistString.AppendLiteral("/O=");
|
|
whitelistString.Append(
|
|
EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(organization)));
|
|
}
|
|
|
|
nsString organizationalUnit;
|
|
rv = certificate->GetOrganizationalUnit(organizationalUnit);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (!organizationalUnit.IsEmpty()) {
|
|
whitelistString.AppendLiteral("/OU=");
|
|
whitelistString.Append(
|
|
EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(organizationalUnit)));
|
|
}
|
|
LOG(("Whitelisting %s", whitelistString.get()));
|
|
|
|
mAllowlistSpecs.AppendElement(whitelistString);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::GenerateWhitelistStringsForChain(
|
|
const safe_browsing::ClientDownloadRequest_CertificateChain& aChain)
|
|
{
|
|
// We need a signing certificate and an issuer to construct a whitelist
|
|
// entry.
|
|
if (aChain.element_size() < 2) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Get the signer.
|
|
nsresult rv;
|
|
nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIX509Cert> signer;
|
|
rv = certDB->ConstructX509(
|
|
const_cast<char *>(aChain.element(0).certificate().data()),
|
|
aChain.element(0).certificate().size(), getter_AddRefs(signer));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
for (int i = 1; i < aChain.element_size(); ++i) {
|
|
// Get the issuer.
|
|
nsCOMPtr<nsIX509Cert> issuer;
|
|
rv = certDB->ConstructX509(
|
|
const_cast<char *>(aChain.element(i).certificate().data()),
|
|
aChain.element(i).certificate().size(), getter_AddRefs(issuer));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = GenerateWhitelistStringsForPair(signer, issuer);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::GenerateWhitelistStrings()
|
|
{
|
|
for (int i = 0; i < mRequest.signature().certificate_chain_size(); ++i) {
|
|
nsresult rv = GenerateWhitelistStringsForChain(
|
|
mRequest.signature().certificate_chain(i));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::AddRedirects(nsIArray* aRedirects)
|
|
{
|
|
uint32_t length = 0;
|
|
aRedirects->GetLength(&length);
|
|
LOG(("ApplicationReputation: Got %u redirects", length));
|
|
nsCOMPtr<nsISimpleEnumerator> iter;
|
|
nsresult rv = aRedirects->Enumerate(getter_AddRefs(iter));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool hasMoreRedirects = false;
|
|
rv = iter->HasMoreElements(&hasMoreRedirects);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
while (hasMoreRedirects) {
|
|
nsCOMPtr<nsISupports> supports;
|
|
rv = iter->GetNext(getter_AddRefs(supports));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(supports, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
rv = principal->GetURI(getter_AddRefs(uri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Add the spec to our list of local lookups. The most recent redirect is
|
|
// the last element.
|
|
nsCString spec;
|
|
rv = GetStrippedSpec(uri, spec);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
mAnylistSpecs.AppendElement(spec);
|
|
LOG(("ApplicationReputation: Appending redirect %s\n", spec.get()));
|
|
|
|
// Store the redirect information in the remote request.
|
|
ClientDownloadRequest_Resource* resource = mRequest.add_resources();
|
|
resource->set_url(spec.get());
|
|
resource->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT);
|
|
|
|
rv = iter->HasMoreElements(&hasMoreRedirects);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::StartLookup()
|
|
{
|
|
mStartTime = TimeStamp::Now();
|
|
nsresult rv = DoLookupInternal();
|
|
if (NS_FAILED(rv)) {
|
|
return OnComplete(false, NS_OK);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::GetSpecHash(nsACString& aSpec, nsACString& hexEncodedHash)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsICryptoHash> cryptoHash =
|
|
do_CreateInstance("@mozilla.org/security/hash;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = cryptoHash->Init(nsICryptoHash::SHA256);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = cryptoHash->Update(reinterpret_cast<const uint8_t*>(aSpec.BeginReading()),
|
|
aSpec.Length());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsAutoCString binaryHash;
|
|
rv = cryptoHash->Finish(false, binaryHash);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// This needs to match HexEncode() in Chrome's
|
|
// src/base/strings/string_number_conversions.cc
|
|
static const char* const hex = "0123456789ABCDEF";
|
|
hexEncodedHash.SetCapacity(2 * binaryHash.Length());
|
|
for (size_t i = 0; i < binaryHash.Length(); ++i) {
|
|
auto c = static_cast<unsigned char>(binaryHash[i]);
|
|
hexEncodedHash.Append(hex[(c >> 4) & 0x0F]);
|
|
hexEncodedHash.Append(hex[c & 0x0F]);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::GetStrippedSpec(nsIURI* aUri, nsACString& escaped)
|
|
{
|
|
if (NS_WARN_IF(!aUri)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsresult rv;
|
|
rv = aUri->GetScheme(escaped);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (escaped.EqualsLiteral("blob")) {
|
|
aUri->GetSpec(escaped);
|
|
LOG(("PendingLookup::GetStrippedSpec(): blob URL left unstripped as '%s' [this = %p]",
|
|
PromiseFlatCString(escaped).get(), this));
|
|
return NS_OK;
|
|
|
|
} else if (escaped.EqualsLiteral("data")) {
|
|
// Replace URI with "data:<everything before comma>,SHA256(<whole URI>)"
|
|
aUri->GetSpec(escaped);
|
|
int32_t comma = escaped.FindChar(',');
|
|
if (comma > -1 &&
|
|
static_cast<nsCString::size_type>(comma) < escaped.Length() - 1) {
|
|
MOZ_ASSERT(comma > 4, "Data URIs start with 'data:'");
|
|
nsAutoCString hexEncodedHash;
|
|
rv = GetSpecHash(escaped, hexEncodedHash);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
escaped.Truncate(comma + 1);
|
|
escaped.Append(hexEncodedHash);
|
|
}
|
|
}
|
|
|
|
LOG(("PendingLookup::GetStrippedSpec(): data URL stripped to '%s' [this = %p]",
|
|
PromiseFlatCString(escaped).get(), this));
|
|
return NS_OK;
|
|
}
|
|
|
|
// If aURI is not an nsIURL, we do not want to check the lists or send a
|
|
// remote query.
|
|
nsCOMPtr<nsIURL> url = do_QueryInterface(aUri, &rv);
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("PendingLookup::GetStrippedSpec(): scheme '%s' is not supported [this = %p]",
|
|
PromiseFlatCString(escaped).get(), this));
|
|
return rv;
|
|
}
|
|
|
|
nsCString temp;
|
|
rv = url->GetHostPort(temp);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
escaped.Append("://");
|
|
escaped.Append(temp);
|
|
|
|
rv = url->GetFilePath(temp);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// nsIUrl.filePath starts with '/'
|
|
escaped.Append(temp);
|
|
|
|
LOG(("PendingLookup::GetStrippedSpec(): URL stripped to '%s' [this = %p]",
|
|
PromiseFlatCString(escaped).get(), this));
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::DoLookupInternal()
|
|
{
|
|
// We want to check the target URI, its referrer, and associated redirects
|
|
// against the local lists.
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = mQuery->GetSourceURI(getter_AddRefs(uri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCString sourceSpec;
|
|
rv = GetStrippedSpec(uri, sourceSpec);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mAnylistSpecs.AppendElement(sourceSpec);
|
|
|
|
ClientDownloadRequest_Resource* resource = mRequest.add_resources();
|
|
resource->set_url(sourceSpec.get());
|
|
resource->set_type(ClientDownloadRequest::DOWNLOAD_URL);
|
|
|
|
nsCOMPtr<nsIURI> referrer = nullptr;
|
|
rv = mQuery->GetReferrerURI(getter_AddRefs(referrer));
|
|
if (referrer) {
|
|
nsCString referrerSpec;
|
|
rv = GetStrippedSpec(referrer, referrerSpec);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
mAnylistSpecs.AppendElement(referrerSpec);
|
|
resource->set_referrer(referrerSpec.get());
|
|
}
|
|
nsCOMPtr<nsIArray> redirects;
|
|
rv = mQuery->GetRedirects(getter_AddRefs(redirects));
|
|
if (redirects) {
|
|
AddRedirects(redirects);
|
|
} else {
|
|
LOG(("ApplicationReputation: Got no redirects [this=%p]", this));
|
|
}
|
|
|
|
// Extract the signature and parse certificates so we can use it to check
|
|
// whitelists.
|
|
nsCOMPtr<nsIArray> sigArray;
|
|
rv = mQuery->GetSignatureInfo(getter_AddRefs(sigArray));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (sigArray) {
|
|
rv = ParseCertificates(sigArray);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
rv = GenerateWhitelistStrings();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Start the call chain.
|
|
return LookupNext();
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::OnComplete(bool shouldBlock, nsresult rv, uint32_t verdict)
|
|
{
|
|
MOZ_ASSERT(!shouldBlock ||
|
|
verdict != nsIApplicationReputationService::VERDICT_SAFE);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
nsAutoCString errorName;
|
|
mozilla::GetErrorName(rv, errorName);
|
|
LOG(("Failed sending remote query for application reputation "
|
|
"[rv = %s, this = %p]", errorName.get(), this));
|
|
}
|
|
|
|
if (mTimeoutTimer) {
|
|
mTimeoutTimer->Cancel();
|
|
mTimeoutTimer = nullptr;
|
|
}
|
|
|
|
double t = (TimeStamp::Now() - mStartTime).ToMilliseconds();
|
|
LOG(("Application Reputation verdict is %lu, obtained in %f ms [this = %p]",
|
|
verdict, t, this));
|
|
if (shouldBlock) {
|
|
LOG(("Application Reputation check failed, blocking bad binary [this = %p]",
|
|
this));
|
|
} else {
|
|
LOG(("Application Reputation check passed [this = %p]", this));
|
|
}
|
|
nsresult res = mCallback->OnComplete(shouldBlock, rv, verdict);
|
|
return res;
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::ParseCertificates(nsIArray* aSigArray)
|
|
{
|
|
// If we haven't been set for any reason, bail.
|
|
NS_ENSURE_ARG_POINTER(aSigArray);
|
|
|
|
// Binaries may be signed by multiple chains of certificates. If there are no
|
|
// chains, the binary is unsigned (or we were unable to extract signature
|
|
// information on a non-Windows platform)
|
|
nsCOMPtr<nsISimpleEnumerator> chains;
|
|
nsresult rv = aSigArray->Enumerate(getter_AddRefs(chains));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool hasMoreChains = false;
|
|
rv = chains->HasMoreElements(&hasMoreChains);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
while (hasMoreChains) {
|
|
nsCOMPtr<nsISupports> chainSupports;
|
|
rv = chains->GetNext(getter_AddRefs(chainSupports));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIX509CertList> certList = do_QueryInterface(chainSupports, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
safe_browsing::ClientDownloadRequest_CertificateChain* certChain =
|
|
mRequest.mutable_signature()->add_certificate_chain();
|
|
nsCOMPtr<nsISimpleEnumerator> chainElt;
|
|
rv = certList->GetEnumerator(getter_AddRefs(chainElt));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Each chain may have multiple certificates.
|
|
bool hasMoreCerts = false;
|
|
rv = chainElt->HasMoreElements(&hasMoreCerts);
|
|
while (hasMoreCerts) {
|
|
nsCOMPtr<nsISupports> certSupports;
|
|
rv = chainElt->GetNext(getter_AddRefs(certSupports));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIX509Cert> cert = do_QueryInterface(certSupports, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
uint8_t* data = nullptr;
|
|
uint32_t len = 0;
|
|
rv = cert->GetRawDER(&len, &data);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Add this certificate to the protobuf to send remotely.
|
|
certChain->add_element()->set_certificate(data, len);
|
|
free(data);
|
|
|
|
rv = chainElt->HasMoreElements(&hasMoreCerts);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
rv = chains->HasMoreElements(&hasMoreChains);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
if (mRequest.signature().certificate_chain_size() > 0) {
|
|
mRequest.mutable_signature()->set_trusted(true);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::SendRemoteQuery()
|
|
{
|
|
nsresult rv = SendRemoteQueryInternal();
|
|
if (NS_FAILED(rv)) {
|
|
return OnComplete(false, rv);
|
|
}
|
|
// SendRemoteQueryInternal has fired off the query and we call OnComplete in
|
|
// the nsIStreamListener.onStopRequest.
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::SendRemoteQueryInternal()
|
|
{
|
|
// If we aren't supposed to do remote lookups, bail.
|
|
if (!Preferences::GetBool(PREF_SB_DOWNLOADS_REMOTE_ENABLED, false)) {
|
|
LOG(("Remote lookups are disabled [this = %p]", this));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
// If the remote lookup URL is empty or absent, bail.
|
|
nsCString serviceUrl;
|
|
NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_SB_APP_REP_URL, &serviceUrl),
|
|
NS_ERROR_NOT_AVAILABLE);
|
|
if (serviceUrl.IsEmpty()) {
|
|
LOG(("Remote lookup URL is empty [this = %p]", this));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
// If the blocklist or allowlist is empty (so we couldn't do local lookups),
|
|
// bail
|
|
{
|
|
nsAutoCString table;
|
|
NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE,
|
|
&table),
|
|
NS_ERROR_NOT_AVAILABLE);
|
|
if (table.IsEmpty()) {
|
|
LOG(("Blocklist is empty [this = %p]", this));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
}
|
|
#ifdef XP_WIN
|
|
// The allowlist is only needed to do signature verification on Windows
|
|
{
|
|
nsAutoCString table;
|
|
NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE,
|
|
&table),
|
|
NS_ERROR_NOT_AVAILABLE);
|
|
if (table.IsEmpty()) {
|
|
LOG(("Allowlist is empty [this = %p]", this));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
LOG(("Sending remote query for application reputation [this = %p]",
|
|
this));
|
|
// We did not find a local result, so fire off the query to the
|
|
// application reputation service.
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv;
|
|
rv = mQuery->GetSourceURI(getter_AddRefs(uri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
nsCString spec;
|
|
rv = GetStrippedSpec(uri, spec);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
mRequest.set_url(spec.get());
|
|
|
|
uint32_t fileSize;
|
|
rv = mQuery->GetFileSize(&fileSize);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
mRequest.set_length(fileSize);
|
|
// We have no way of knowing whether or not a user initiated the
|
|
// download. Set it to true to lessen the chance of false positives.
|
|
mRequest.set_user_initiated(true);
|
|
|
|
nsCString locale;
|
|
NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_GENERAL_LOCALE, &locale),
|
|
NS_ERROR_NOT_AVAILABLE);
|
|
mRequest.set_locale(locale.get());
|
|
nsCString sha256Hash;
|
|
rv = mQuery->GetSha256Hash(sha256Hash);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
mRequest.mutable_digests()->set_sha256(sha256Hash.Data());
|
|
nsString fileName;
|
|
rv = mQuery->GetSuggestedFileName(fileName);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
mRequest.set_file_basename(NS_ConvertUTF16toUTF8(fileName).get());
|
|
mRequest.set_download_type(GetDownloadType(fileName));
|
|
|
|
if (mRequest.signature().trusted()) {
|
|
LOG(("Got signed binary for remote application reputation check "
|
|
"[this = %p]", this));
|
|
} else {
|
|
LOG(("Got unsigned binary for remote application reputation check "
|
|
"[this = %p]", this));
|
|
}
|
|
|
|
// Serialize the protocol buffer to a string. This can only fail if we are
|
|
// out of memory, or if the protocol buffer req is missing required fields
|
|
// (only the URL for now).
|
|
std::string serialized;
|
|
if (!mRequest.SerializeToString(&serialized)) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
LOG(("Serialized protocol buffer [this = %p]: (length=%d) %s", this,
|
|
serialized.length(), serialized.c_str()));
|
|
|
|
// Set the input stream to the serialized protocol buffer
|
|
nsCOMPtr<nsIStringInputStream> sstream =
|
|
do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = sstream->SetData(serialized.c_str(), serialized.length());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Set up the channel to transmit the request to the service.
|
|
nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
|
|
rv = ios->NewChannel2(serviceUrl,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr, // aLoadingNode
|
|
nsContentUtils::GetSystemPrincipal(),
|
|
nullptr, // aTriggeringPrincipal
|
|
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
|
|
nsIContentPolicy::TYPE_OTHER,
|
|
getter_AddRefs(mChannel));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
|
|
if (loadInfo) {
|
|
loadInfo->SetOriginAttributes(
|
|
mozilla::NeckoOriginAttributes(NECKO_SAFEBROWSING_APP_ID, false));
|
|
}
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel, &rv));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
mozilla::Unused << httpChannel;
|
|
|
|
// Upload the protobuf to the application reputation service.
|
|
nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(mChannel, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = uploadChannel->ExplicitSetUploadStream(sstream,
|
|
NS_LITERAL_CSTRING("application/octet-stream"), serialized.size(),
|
|
NS_LITERAL_CSTRING("POST"), false);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Set the Safebrowsing cookie jar, so that the regular Google cookie is not
|
|
// sent with this request. See bug 897516.
|
|
DocShellOriginAttributes attrs;
|
|
attrs.mAppId = NECKO_SAFEBROWSING_APP_ID;
|
|
nsCOMPtr<nsIInterfaceRequestor> loadContext = new mozilla::LoadContext(attrs);
|
|
rv = mChannel->SetNotificationCallbacks(loadContext);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
uint32_t timeoutMs = Preferences::GetUint(PREF_SB_DOWNLOADS_REMOTE_TIMEOUT, 10000);
|
|
mTimeoutTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
|
|
mTimeoutTimer->InitWithCallback(this, timeoutMs, nsITimer::TYPE_ONE_SHOT);
|
|
|
|
rv = mChannel->AsyncOpen2(this);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PendingLookup::Notify(nsITimer* aTimer)
|
|
{
|
|
LOG(("Remote lookup timed out [this = %p]", this));
|
|
MOZ_ASSERT(aTimer == mTimeoutTimer);
|
|
mChannel->Cancel(NS_ERROR_NET_TIMEOUT);
|
|
mTimeoutTimer->Cancel();
|
|
return NS_OK;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// nsIObserver implementation
|
|
NS_IMETHODIMP
|
|
PendingLookup::Observe(nsISupports *aSubject, const char *aTopic,
|
|
const char16_t *aData)
|
|
{
|
|
if (!strcmp(aTopic, "quit-application")) {
|
|
if (mTimeoutTimer) {
|
|
mTimeoutTimer->Cancel();
|
|
mTimeoutTimer = nullptr;
|
|
}
|
|
if (mChannel) {
|
|
mChannel->Cancel(NS_ERROR_ABORT);
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//// nsIStreamListener
|
|
static nsresult
|
|
AppendSegmentToString(nsIInputStream* inputStream,
|
|
void *closure,
|
|
const char *rawSegment,
|
|
uint32_t toOffset,
|
|
uint32_t count,
|
|
uint32_t *writeCount) {
|
|
nsAutoCString* decodedData = static_cast<nsAutoCString*>(closure);
|
|
decodedData->Append(rawSegment, count);
|
|
*writeCount = count;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PendingLookup::OnDataAvailable(nsIRequest *aRequest,
|
|
nsISupports *aContext,
|
|
nsIInputStream *aStream,
|
|
uint64_t offset,
|
|
uint32_t count) {
|
|
uint32_t read;
|
|
return aStream->ReadSegments(AppendSegmentToString, &mResponse, count, &read);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PendingLookup::OnStartRequest(nsIRequest *aRequest,
|
|
nsISupports *aContext) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PendingLookup::OnStopRequest(nsIRequest *aRequest,
|
|
nsISupports *aContext,
|
|
nsresult aResult) {
|
|
NS_ENSURE_STATE(mCallback);
|
|
|
|
bool shouldBlock = false;
|
|
uint32_t verdict = nsIApplicationReputationService::VERDICT_SAFE;
|
|
|
|
nsresult rv = OnStopRequestInternal(aRequest, aContext, aResult,
|
|
&shouldBlock, &verdict);
|
|
OnComplete(shouldBlock, rv, verdict);
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
PendingLookup::OnStopRequestInternal(nsIRequest *aRequest,
|
|
nsISupports *aContext,
|
|
nsresult aResult,
|
|
bool* aShouldBlock,
|
|
uint32_t* aVerdict) {
|
|
if (NS_FAILED(aResult)) {
|
|
return aResult;
|
|
}
|
|
|
|
*aShouldBlock = false;
|
|
*aVerdict = nsIApplicationReputationService::VERDICT_SAFE;
|
|
nsresult rv;
|
|
nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
uint32_t status = 0;
|
|
rv = channel->GetResponseStatus(&status);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
if (status != 200) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
std::string buf(mResponse.Data(), mResponse.Length());
|
|
safe_browsing::ClientDownloadResponse response;
|
|
if (!response.ParseFromString(buf)) {
|
|
LOG(("Invalid protocol buffer response [this = %p]: %s", this, buf.c_str()));
|
|
return NS_ERROR_CANNOT_CONVERT_DATA;
|
|
}
|
|
|
|
switch(response.verdict()) {
|
|
case safe_browsing::ClientDownloadResponse::DANGEROUS:
|
|
*aShouldBlock = Preferences::GetBool(PREF_BLOCK_DANGEROUS, true);
|
|
*aVerdict = nsIApplicationReputationService::VERDICT_DANGEROUS;
|
|
break;
|
|
case safe_browsing::ClientDownloadResponse::DANGEROUS_HOST:
|
|
*aShouldBlock = Preferences::GetBool(PREF_BLOCK_DANGEROUS_HOST, true);
|
|
*aVerdict = nsIApplicationReputationService::VERDICT_DANGEROUS_HOST;
|
|
break;
|
|
case safe_browsing::ClientDownloadResponse::POTENTIALLY_UNWANTED:
|
|
*aShouldBlock = Preferences::GetBool(PREF_BLOCK_POTENTIALLY_UNWANTED, false);
|
|
*aVerdict = nsIApplicationReputationService::VERDICT_POTENTIALLY_UNWANTED;
|
|
break;
|
|
case safe_browsing::ClientDownloadResponse::UNCOMMON:
|
|
*aShouldBlock = Preferences::GetBool(PREF_BLOCK_UNCOMMON, false);
|
|
*aVerdict = nsIApplicationReputationService::VERDICT_UNCOMMON;
|
|
break;
|
|
default:
|
|
// Treat everything else as safe
|
|
break;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(ApplicationReputationService,
|
|
nsIApplicationReputationService)
|
|
|
|
ApplicationReputationService*
|
|
ApplicationReputationService::gApplicationReputationService = nullptr;
|
|
|
|
ApplicationReputationService*
|
|
ApplicationReputationService::GetSingleton()
|
|
{
|
|
if (gApplicationReputationService) {
|
|
NS_ADDREF(gApplicationReputationService);
|
|
return gApplicationReputationService;
|
|
}
|
|
|
|
// We're not initialized yet.
|
|
gApplicationReputationService = new ApplicationReputationService();
|
|
if (gApplicationReputationService) {
|
|
NS_ADDREF(gApplicationReputationService);
|
|
}
|
|
|
|
return gApplicationReputationService;
|
|
}
|
|
|
|
ApplicationReputationService::ApplicationReputationService()
|
|
{
|
|
LOG(("Application reputation service started up"));
|
|
}
|
|
|
|
ApplicationReputationService::~ApplicationReputationService() {
|
|
LOG(("Application reputation service shutting down"));
|
|
MOZ_ASSERT(gApplicationReputationService == this);
|
|
gApplicationReputationService = nullptr;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ApplicationReputationService::QueryReputation(
|
|
nsIApplicationReputationQuery* aQuery,
|
|
nsIApplicationReputationCallback* aCallback) {
|
|
LOG(("Starting application reputation check [query=%p]", aQuery));
|
|
NS_ENSURE_ARG_POINTER(aQuery);
|
|
NS_ENSURE_ARG_POINTER(aCallback);
|
|
|
|
nsresult rv = QueryReputationInternal(aQuery, aCallback);
|
|
if (NS_FAILED(rv)) {
|
|
aCallback->OnComplete(false, rv,
|
|
nsIApplicationReputationService::VERDICT_SAFE);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult ApplicationReputationService::QueryReputationInternal(
|
|
nsIApplicationReputationQuery* aQuery,
|
|
nsIApplicationReputationCallback* aCallback) {
|
|
nsresult rv;
|
|
// If malware checks aren't enabled, don't query application reputation.
|
|
if (!Preferences::GetBool(PREF_SB_MALWARE_ENABLED, false)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (!Preferences::GetBool(PREF_SB_DOWNLOADS_ENABLED, false)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
rv = aQuery->GetSourceURI(getter_AddRefs(uri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
// Bail if the URI hasn't been set.
|
|
NS_ENSURE_STATE(uri);
|
|
|
|
// Create a new pending lookup and start the call chain.
|
|
RefPtr<PendingLookup> lookup(new PendingLookup(aQuery, aCallback));
|
|
NS_ENSURE_STATE(lookup);
|
|
|
|
// Add an observer for shutdown
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
mozilla::services::GetObserverService();
|
|
if (!observerService) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
observerService->AddObserver(lookup, "quit-application", false);
|
|
return lookup->StartLookup();
|
|
}
|