toolkit/components/downloads/nsDownloadScanner.cpp

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

michael@0 1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: se cin sw=2 ts=2 et : */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "nsDownloadScanner.h"
michael@0 8 #include <comcat.h>
michael@0 9 #include <process.h>
michael@0 10 #include "nsDownloadManager.h"
michael@0 11 #include "nsIXULAppInfo.h"
michael@0 12 #include "nsXULAppAPI.h"
michael@0 13 #include "nsIPrefService.h"
michael@0 14 #include "nsNetUtil.h"
michael@0 15 #include "nsDeque.h"
michael@0 16 #include "nsIFileURL.h"
michael@0 17 #include "nsIPrefBranch.h"
michael@0 18 #include "nsXPCOMCIDInternal.h"
michael@0 19
michael@0 20 /**
michael@0 21 * Code overview
michael@0 22 *
michael@0 23 * Download scanner attempts to make use of one of two different virus
michael@0 24 * scanning interfaces available on Windows - IOfficeAntiVirus (Windows
michael@0 25 * 95/NT 4 and IE 5) and IAttachmentExecute (XPSP2 and up). The latter
michael@0 26 * interface supports calling IOfficeAntiVirus internally, while also
michael@0 27 * adding support for XPSP2+ ADS forks which define security related
michael@0 28 * prompting on downloaded content.
michael@0 29 *
michael@0 30 * Both interfaces are synchronous and can take a while, so it is not a
michael@0 31 * good idea to call either from the main thread. Some antivirus scanners can
michael@0 32 * take a long time to scan or the call might block while the scanner shows
michael@0 33 * its UI so if the user were to download many files that finished around the
michael@0 34 * same time, they would have to wait a while if the scanning were done on
michael@0 35 * exactly one other thread. Since the overhead of creating a thread is
michael@0 36 * relatively small compared to the time it takes to download a file and scan
michael@0 37 * it, a new thread is spawned for each download that is to be scanned. Since
michael@0 38 * most of the mozilla codebase is not threadsafe, all the information needed
michael@0 39 * for the scanner is gathered in the main thread in nsDownloadScanner::Scan::Start.
michael@0 40 * The only function of nsDownloadScanner::Scan which is invoked on another
michael@0 41 * thread is DoScan.
michael@0 42 *
michael@0 43 * Watchdog overview
michael@0 44 *
michael@0 45 * The watchdog is used internally by the scanner. It maintains a queue of
michael@0 46 * current download scans. In a separate thread, it dequeues the oldest scan
michael@0 47 * and waits on that scan's thread with a timeout given by WATCHDOG_TIMEOUT
michael@0 48 * (default is 30 seconds). If the wait times out, then the watchdog notifies
michael@0 49 * the Scan that it has timed out. If the scan really has timed out, then the
michael@0 50 * Scan object will dispatch its run method to the main thread; this will
michael@0 51 * release the watchdog thread's addref on the Scan. If it has not timed out
michael@0 52 * (i.e. the Scan just finished in time), then the watchdog dispatches a
michael@0 53 * ReleaseDispatcher to release its ref of the Scan on the main thread.
michael@0 54 *
michael@0 55 * In order to minimize execution time, there are two events used to notify the
michael@0 56 * watchdog thread of a non-empty queue and a quit event. Every blocking wait
michael@0 57 * that the watchdog thread does waits on the quit event; this lets the thread
michael@0 58 * quickly exit when shutting down. Also, the download scan queue will be empty
michael@0 59 * most of the time; rather than use a spin loop, a simple event is triggered
michael@0 60 * by the main thread when a new scan is added to an empty queue. When the
michael@0 61 * watchdog thread knows that it has run out of elements in the queue, it will
michael@0 62 * wait on the new item event.
michael@0 63 *
michael@0 64 * Memory/resource leaks due to timeout:
michael@0 65 * In the event of a timeout, the thread must remain alive; terminating it may
michael@0 66 * very well cause the antivirus scanner to crash or be put into an
michael@0 67 * inconsistent state; COM resources may also not be cleaned up. The downside
michael@0 68 * is that we need to leave the thread running; suspending it may lead to a
michael@0 69 * deadlock. Because the scan call may be ongoing, it may be dependent on the
michael@0 70 * memory referenced by the MSOAVINFO structure, so we cannot free mName, mPath
michael@0 71 * or mOrigin; this means that we cannot free the Scan object since doing so
michael@0 72 * will deallocate that memory. Note that mDownload is set to null upon timeout
michael@0 73 * or completion, so the download itself is never leaked. If the scan does
michael@0 74 * eventually complete, then the all the memory and resources will be freed.
michael@0 75 * It is possible, however extremely rare, that in the event of a timeout, the
michael@0 76 * mStateSync critical section will leak its event; this will happen only if
michael@0 77 * the scanning thread, watchdog thread or main thread try to enter the
michael@0 78 * critical section when one of the others is already in it.
michael@0 79 *
michael@0 80 * Reasoning for CheckAndSetState - there exists a race condition between the time when
michael@0 81 * either the timeout or normal scan sets the state and when Scan::Run is
michael@0 82 * executed on the main thread. Ex: mStatus could be set by Scan::DoScan* which
michael@0 83 * then queues a dispatch on the main thread. Before that dispatch is executed,
michael@0 84 * the timeout code fires and sets mStatus to AVSCAN_TIMEDOUT which then queues
michael@0 85 * its dispatch to the main thread (the same function as DoScan*). Both
michael@0 86 * dispatches run and both try to set the download state to AVSCAN_TIMEDOUT
michael@0 87 * which is incorrect.
michael@0 88 *
michael@0 89 * There are 5 possible outcomes of the virus scan:
michael@0 90 * AVSCAN_GOOD => the file is clean
michael@0 91 * AVSCAN_BAD => the file has a virus
michael@0 92 * AVSCAN_UGLY => the file had a virus, but it was cleaned
michael@0 93 * AVSCAN_FAILED => something else went wrong with the virus scanner.
michael@0 94 * AVSCAN_TIMEDOUT => the scan (thread setup + execution) took too long
michael@0 95 *
michael@0 96 * Both the good and ugly states leave the user with a benign file, so they
michael@0 97 * transition to the finished state. Bad files are sent to the blocked state.
michael@0 98 * The failed and timedout states transition to finished downloads.
michael@0 99 *
michael@0 100 * Possible Future enhancements:
michael@0 101 * * Create an interface for scanning files in general
michael@0 102 * * Make this a service
michael@0 103 * * Get antivirus scanner status via WMI/registry
michael@0 104 */
michael@0 105
michael@0 106 // IAttachementExecute supports user definable settings for certain
michael@0 107 // security related prompts. This defines a general GUID for use in
michael@0 108 // all projects. Individual projects can define an individual guid
michael@0 109 // if they want to.
michael@0 110 #ifndef MOZ_VIRUS_SCANNER_PROMPT_GUID
michael@0 111 #define MOZ_VIRUS_SCANNER_PROMPT_GUID \
michael@0 112 { 0xb50563d1, 0x16b6, 0x43c2, { 0xa6, 0x6a, 0xfa, 0xe6, 0xd2, 0x11, 0xf2, \
michael@0 113 0xea } }
michael@0 114 #endif
michael@0 115 static const GUID GUID_MozillaVirusScannerPromptGeneric =
michael@0 116 MOZ_VIRUS_SCANNER_PROMPT_GUID;
michael@0 117
michael@0 118 // Initial timeout is 30 seconds
michael@0 119 #define WATCHDOG_TIMEOUT (30*PR_USEC_PER_SEC)
michael@0 120
michael@0 121 // Maximum length for URI's passed into IAE
michael@0 122 #define MAX_IAEURILENGTH 1683
michael@0 123
michael@0 124 class nsDownloadScannerWatchdog
michael@0 125 {
michael@0 126 typedef nsDownloadScanner::Scan Scan;
michael@0 127 public:
michael@0 128 nsDownloadScannerWatchdog();
michael@0 129 ~nsDownloadScannerWatchdog();
michael@0 130
michael@0 131 nsresult Init();
michael@0 132 nsresult Shutdown();
michael@0 133
michael@0 134 void Watch(Scan *scan);
michael@0 135 private:
michael@0 136 static unsigned int __stdcall WatchdogThread(void *p);
michael@0 137 CRITICAL_SECTION mQueueSync;
michael@0 138 nsDeque mScanQueue;
michael@0 139 HANDLE mThread;
michael@0 140 HANDLE mNewItemEvent;
michael@0 141 HANDLE mQuitEvent;
michael@0 142 };
michael@0 143
michael@0 144 nsDownloadScanner::nsDownloadScanner() :
michael@0 145 mAESExists(false)
michael@0 146 {
michael@0 147 }
michael@0 148
michael@0 149 // This destructor appeases the compiler; it would otherwise complain about an
michael@0 150 // incomplete type for nsDownloadWatchdog in the instantiation of
michael@0 151 // nsAutoPtr::~nsAutoPtr
michael@0 152 // Plus, it's a handy location to call nsDownloadScannerWatchdog::Shutdown from
michael@0 153 nsDownloadScanner::~nsDownloadScanner() {
michael@0 154 if (mWatchdog)
michael@0 155 (void)mWatchdog->Shutdown();
michael@0 156 }
michael@0 157
michael@0 158 nsresult
michael@0 159 nsDownloadScanner::Init()
michael@0 160 {
michael@0 161 // This CoInitialize/CoUninitialize pattern seems to be common in the Mozilla
michael@0 162 // codebase. All other COM calls/objects are made on different threads.
michael@0 163 nsresult rv = NS_OK;
michael@0 164 CoInitialize(nullptr);
michael@0 165
michael@0 166 if (!IsAESAvailable()) {
michael@0 167 CoUninitialize();
michael@0 168 return NS_ERROR_NOT_AVAILABLE;
michael@0 169 }
michael@0 170
michael@0 171 mAESExists = true;
michael@0 172
michael@0 173 // Initialize scanning
michael@0 174 mWatchdog = new nsDownloadScannerWatchdog();
michael@0 175 if (mWatchdog) {
michael@0 176 rv = mWatchdog->Init();
michael@0 177 if (FAILED(rv))
michael@0 178 mWatchdog = nullptr;
michael@0 179 } else {
michael@0 180 rv = NS_ERROR_OUT_OF_MEMORY;
michael@0 181 }
michael@0 182
michael@0 183 if (NS_FAILED(rv))
michael@0 184 return rv;
michael@0 185
michael@0 186 return rv;
michael@0 187 }
michael@0 188
michael@0 189 bool
michael@0 190 nsDownloadScanner::IsAESAvailable()
michael@0 191 {
michael@0 192 // Try to instantiate IAE to see if it's available.
michael@0 193 nsRefPtr<IAttachmentExecute> ae;
michael@0 194 HRESULT hr;
michael@0 195 hr = CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_INPROC,
michael@0 196 IID_IAttachmentExecute, getter_AddRefs(ae));
michael@0 197 if (FAILED(hr)) {
michael@0 198 NS_WARNING("Could not instantiate attachment execution service\n");
michael@0 199 return false;
michael@0 200 }
michael@0 201 return true;
michael@0 202 }
michael@0 203
michael@0 204 // If IAttachementExecute is available, use the CheckPolicy call to find out
michael@0 205 // if this download should be prevented due to Security Zone Policy settings.
michael@0 206 AVCheckPolicyState
michael@0 207 nsDownloadScanner::CheckPolicy(nsIURI *aSource, nsIURI *aTarget)
michael@0 208 {
michael@0 209 nsresult rv;
michael@0 210
michael@0 211 if (!mAESExists || !aSource || !aTarget)
michael@0 212 return AVPOLICY_DOWNLOAD;
michael@0 213
michael@0 214 nsAutoCString source;
michael@0 215 rv = aSource->GetSpec(source);
michael@0 216 if (NS_FAILED(rv))
michael@0 217 return AVPOLICY_DOWNLOAD;
michael@0 218
michael@0 219 nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(aTarget));
michael@0 220 if (!fileUrl)
michael@0 221 return AVPOLICY_DOWNLOAD;
michael@0 222
michael@0 223 nsCOMPtr<nsIFile> theFile;
michael@0 224 nsAutoString aFileName;
michael@0 225 if (NS_FAILED(fileUrl->GetFile(getter_AddRefs(theFile))) ||
michael@0 226 NS_FAILED(theFile->GetLeafName(aFileName)))
michael@0 227 return AVPOLICY_DOWNLOAD;
michael@0 228
michael@0 229 // IAttachementExecute prohibits src data: schemes by default but we
michael@0 230 // support them. If this is a data src, skip off doing a policy check.
michael@0 231 // (The file will still be scanned once it lands on the local system.)
michael@0 232 bool isDataScheme(false);
michael@0 233 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aSource);
michael@0 234 if (innerURI)
michael@0 235 (void)innerURI->SchemeIs("data", &isDataScheme);
michael@0 236 if (isDataScheme)
michael@0 237 return AVPOLICY_DOWNLOAD;
michael@0 238
michael@0 239 nsRefPtr<IAttachmentExecute> ae;
michael@0 240 HRESULT hr;
michael@0 241 hr = CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_INPROC,
michael@0 242 IID_IAttachmentExecute, getter_AddRefs(ae));
michael@0 243 if (FAILED(hr))
michael@0 244 return AVPOLICY_DOWNLOAD;
michael@0 245
michael@0 246 (void)ae->SetClientGuid(GUID_MozillaVirusScannerPromptGeneric);
michael@0 247 (void)ae->SetSource(NS_ConvertUTF8toUTF16(source).get());
michael@0 248 (void)ae->SetFileName(aFileName.get());
michael@0 249
michael@0 250 // Any failure means the file download/exec will be blocked by the system.
michael@0 251 // S_OK or S_FALSE imply it's ok.
michael@0 252 hr = ae->CheckPolicy();
michael@0 253
michael@0 254 if (hr == S_OK)
michael@0 255 return AVPOLICY_DOWNLOAD;
michael@0 256
michael@0 257 if (hr == S_FALSE)
michael@0 258 return AVPOLICY_PROMPT;
michael@0 259
michael@0 260 if (hr == E_INVALIDARG)
michael@0 261 return AVPOLICY_PROMPT;
michael@0 262
michael@0 263 return AVPOLICY_BLOCKED;
michael@0 264 }
michael@0 265
michael@0 266 #ifndef THREAD_MODE_BACKGROUND_BEGIN
michael@0 267 #define THREAD_MODE_BACKGROUND_BEGIN 0x00010000
michael@0 268 #endif
michael@0 269
michael@0 270 #ifndef THREAD_MODE_BACKGROUND_END
michael@0 271 #define THREAD_MODE_BACKGROUND_END 0x00020000
michael@0 272 #endif
michael@0 273
michael@0 274 unsigned int __stdcall
michael@0 275 nsDownloadScanner::ScannerThreadFunction(void *p)
michael@0 276 {
michael@0 277 HANDLE currentThread = GetCurrentThread();
michael@0 278 NS_ASSERTION(!NS_IsMainThread(), "Antivirus scan should not be run on the main thread");
michael@0 279 nsDownloadScanner::Scan *scan = static_cast<nsDownloadScanner::Scan*>(p);
michael@0 280 if (!SetThreadPriority(currentThread, THREAD_MODE_BACKGROUND_BEGIN))
michael@0 281 (void)SetThreadPriority(currentThread, THREAD_PRIORITY_IDLE);
michael@0 282 scan->DoScan();
michael@0 283 (void)SetThreadPriority(currentThread, THREAD_MODE_BACKGROUND_END);
michael@0 284 _endthreadex(0);
michael@0 285 return 0;
michael@0 286 }
michael@0 287
michael@0 288 // The sole purpose of this class is to release an object on the main thread
michael@0 289 // It assumes that its creator will addref it and it will release itself on
michael@0 290 // the main thread too
michael@0 291 class ReleaseDispatcher : public nsRunnable {
michael@0 292 public:
michael@0 293 ReleaseDispatcher(nsISupports *ptr)
michael@0 294 : mPtr(ptr) {}
michael@0 295 NS_IMETHOD Run();
michael@0 296 private:
michael@0 297 nsISupports *mPtr;
michael@0 298 };
michael@0 299
michael@0 300 nsresult ReleaseDispatcher::Run() {
michael@0 301 NS_ASSERTION(NS_IsMainThread(), "Antivirus scan release dispatch should be run on the main thread");
michael@0 302 NS_RELEASE(mPtr);
michael@0 303 NS_RELEASE_THIS();
michael@0 304 return NS_OK;
michael@0 305 }
michael@0 306
michael@0 307 nsDownloadScanner::Scan::Scan(nsDownloadScanner *scanner, nsDownload *download)
michael@0 308 : mDLScanner(scanner), mThread(nullptr),
michael@0 309 mDownload(download), mStatus(AVSCAN_NOTSTARTED),
michael@0 310 mSkipSource(false)
michael@0 311 {
michael@0 312 InitializeCriticalSection(&mStateSync);
michael@0 313 }
michael@0 314
michael@0 315 nsDownloadScanner::Scan::~Scan() {
michael@0 316 DeleteCriticalSection(&mStateSync);
michael@0 317 }
michael@0 318
michael@0 319 nsresult
michael@0 320 nsDownloadScanner::Scan::Start()
michael@0 321 {
michael@0 322 mStartTime = PR_Now();
michael@0 323
michael@0 324 mThread = (HANDLE)_beginthreadex(nullptr, 0, ScannerThreadFunction,
michael@0 325 this, CREATE_SUSPENDED, nullptr);
michael@0 326 if (!mThread)
michael@0 327 return NS_ERROR_OUT_OF_MEMORY;
michael@0 328
michael@0 329 nsresult rv = NS_OK;
michael@0 330
michael@0 331 // Get the path to the file on disk
michael@0 332 nsCOMPtr<nsIFile> file;
michael@0 333 rv = mDownload->GetTargetFile(getter_AddRefs(file));
michael@0 334 NS_ENSURE_SUCCESS(rv, rv);
michael@0 335 rv = file->GetPath(mPath);
michael@0 336 NS_ENSURE_SUCCESS(rv, rv);
michael@0 337
michael@0 338 // Grab the app name
michael@0 339 nsCOMPtr<nsIXULAppInfo> appinfo =
michael@0 340 do_GetService(XULAPPINFO_SERVICE_CONTRACTID, &rv);
michael@0 341 NS_ENSURE_SUCCESS(rv, rv);
michael@0 342
michael@0 343 nsAutoCString name;
michael@0 344 rv = appinfo->GetName(name);
michael@0 345 NS_ENSURE_SUCCESS(rv, rv);
michael@0 346 CopyUTF8toUTF16(name, mName);
michael@0 347
michael@0 348 // Get the origin
michael@0 349 nsCOMPtr<nsIURI> uri;
michael@0 350 rv = mDownload->GetSource(getter_AddRefs(uri));
michael@0 351 NS_ENSURE_SUCCESS(rv, rv);
michael@0 352
michael@0 353 nsAutoCString origin;
michael@0 354 rv = uri->GetSpec(origin);
michael@0 355 NS_ENSURE_SUCCESS(rv, rv);
michael@0 356
michael@0 357 // Certain virus interfaces do not like extremely long uris.
michael@0 358 // Chop off the path and cgi data and just pass the base domain.
michael@0 359 if (origin.Length() > MAX_IAEURILENGTH) {
michael@0 360 rv = uri->GetPrePath(origin);
michael@0 361 NS_ENSURE_SUCCESS(rv, rv);
michael@0 362 }
michael@0 363
michael@0 364 CopyUTF8toUTF16(origin, mOrigin);
michael@0 365
michael@0 366 // We count https/ftp/http as an http download
michael@0 367 bool isHttp(false), isFtp(false), isHttps(false);
michael@0 368 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
michael@0 369 if (!innerURI) innerURI = uri;
michael@0 370 (void)innerURI->SchemeIs("http", &isHttp);
michael@0 371 (void)innerURI->SchemeIs("ftp", &isFtp);
michael@0 372 (void)innerURI->SchemeIs("https", &isHttps);
michael@0 373 mIsHttpDownload = isHttp || isFtp || isHttps;
michael@0 374
michael@0 375 // IAttachementExecute prohibits src data: schemes by default but we
michael@0 376 // support them. Mark the download if it's a data scheme, so we
michael@0 377 // can skip off supplying the src to IAttachementExecute when we scan
michael@0 378 // the resulting file.
michael@0 379 (void)innerURI->SchemeIs("data", &mSkipSource);
michael@0 380
michael@0 381 // ResumeThread returns the previous suspend count
michael@0 382 if (1 != ::ResumeThread(mThread)) {
michael@0 383 CloseHandle(mThread);
michael@0 384 return NS_ERROR_UNEXPECTED;
michael@0 385 }
michael@0 386 return NS_OK;
michael@0 387 }
michael@0 388
michael@0 389 nsresult
michael@0 390 nsDownloadScanner::Scan::Run()
michael@0 391 {
michael@0 392 NS_ASSERTION(NS_IsMainThread(), "Antivirus scan dispatch should be run on the main thread");
michael@0 393
michael@0 394 // Cleanup our thread
michael@0 395 if (mStatus != AVSCAN_TIMEDOUT)
michael@0 396 WaitForSingleObject(mThread, INFINITE);
michael@0 397 CloseHandle(mThread);
michael@0 398
michael@0 399 DownloadState downloadState = 0;
michael@0 400 EnterCriticalSection(&mStateSync);
michael@0 401 switch (mStatus) {
michael@0 402 case AVSCAN_BAD:
michael@0 403 downloadState = nsIDownloadManager::DOWNLOAD_DIRTY;
michael@0 404 break;
michael@0 405 default:
michael@0 406 case AVSCAN_FAILED:
michael@0 407 case AVSCAN_GOOD:
michael@0 408 case AVSCAN_UGLY:
michael@0 409 case AVSCAN_TIMEDOUT:
michael@0 410 downloadState = nsIDownloadManager::DOWNLOAD_FINISHED;
michael@0 411 break;
michael@0 412 }
michael@0 413 LeaveCriticalSection(&mStateSync);
michael@0 414 // Download will be null if we already timed out
michael@0 415 if (mDownload)
michael@0 416 (void)mDownload->SetState(downloadState);
michael@0 417
michael@0 418 // Clean up some other variables
michael@0 419 // In the event of a timeout, our destructor won't be called
michael@0 420 mDownload = nullptr;
michael@0 421
michael@0 422 NS_RELEASE_THIS();
michael@0 423 return NS_OK;
michael@0 424 }
michael@0 425
michael@0 426 static DWORD
michael@0 427 ExceptionFilterFunction(DWORD exceptionCode) {
michael@0 428 switch(exceptionCode) {
michael@0 429 case EXCEPTION_ACCESS_VIOLATION:
michael@0 430 case EXCEPTION_ILLEGAL_INSTRUCTION:
michael@0 431 case EXCEPTION_IN_PAGE_ERROR:
michael@0 432 case EXCEPTION_PRIV_INSTRUCTION:
michael@0 433 case EXCEPTION_STACK_OVERFLOW:
michael@0 434 return EXCEPTION_EXECUTE_HANDLER;
michael@0 435 default:
michael@0 436 return EXCEPTION_CONTINUE_SEARCH;
michael@0 437 }
michael@0 438 }
michael@0 439
michael@0 440 bool
michael@0 441 nsDownloadScanner::Scan::DoScanAES()
michael@0 442 {
michael@0 443 // This warning is for the destructor of ae which will not be invoked in the
michael@0 444 // event of a win32 exception
michael@0 445 #pragma warning(disable: 4509)
michael@0 446 HRESULT hr;
michael@0 447 nsRefPtr<IAttachmentExecute> ae;
michael@0 448 MOZ_SEH_TRY {
michael@0 449 hr = CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_ALL,
michael@0 450 IID_IAttachmentExecute, getter_AddRefs(ae));
michael@0 451 } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) {
michael@0 452 return CheckAndSetState(AVSCAN_NOTSTARTED,AVSCAN_FAILED);
michael@0 453 }
michael@0 454
michael@0 455 // If we (somehow) already timed out, then don't bother scanning
michael@0 456 if (CheckAndSetState(AVSCAN_SCANNING, AVSCAN_NOTSTARTED)) {
michael@0 457 AVScanState newState;
michael@0 458 if (SUCCEEDED(hr)) {
michael@0 459 bool gotException = false;
michael@0 460 MOZ_SEH_TRY {
michael@0 461 (void)ae->SetClientGuid(GUID_MozillaVirusScannerPromptGeneric);
michael@0 462 (void)ae->SetLocalPath(mPath.get());
michael@0 463 // Provide the src for everything but data: schemes.
michael@0 464 if (!mSkipSource)
michael@0 465 (void)ae->SetSource(mOrigin.get());
michael@0 466
michael@0 467 // Save() will invoke the scanner
michael@0 468 hr = ae->Save();
michael@0 469 } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) {
michael@0 470 gotException = true;
michael@0 471 }
michael@0 472
michael@0 473 MOZ_SEH_TRY {
michael@0 474 ae = nullptr;
michael@0 475 } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) {
michael@0 476 gotException = true;
michael@0 477 }
michael@0 478
michael@0 479 if(gotException) {
michael@0 480 newState = AVSCAN_FAILED;
michael@0 481 }
michael@0 482 else if (SUCCEEDED(hr)) { // Passed the scan
michael@0 483 newState = AVSCAN_GOOD;
michael@0 484 }
michael@0 485 else if (HRESULT_CODE(hr) == ERROR_FILE_NOT_FOUND) {
michael@0 486 NS_WARNING("Downloaded file disappeared before it could be scanned");
michael@0 487 newState = AVSCAN_FAILED;
michael@0 488 }
michael@0 489 else if (hr == E_INVALIDARG) {
michael@0 490 NS_WARNING("IAttachementExecute returned invalid argument error");
michael@0 491 newState = AVSCAN_FAILED;
michael@0 492 }
michael@0 493 else {
michael@0 494 newState = AVSCAN_UGLY;
michael@0 495 }
michael@0 496 }
michael@0 497 else {
michael@0 498 newState = AVSCAN_FAILED;
michael@0 499 }
michael@0 500 return CheckAndSetState(newState, AVSCAN_SCANNING);
michael@0 501 }
michael@0 502 return false;
michael@0 503 }
michael@0 504 #pragma warning(default: 4509)
michael@0 505
michael@0 506 void
michael@0 507 nsDownloadScanner::Scan::DoScan()
michael@0 508 {
michael@0 509 CoInitialize(nullptr);
michael@0 510
michael@0 511 if (DoScanAES()) {
michael@0 512 // We need to do a few more things on the main thread
michael@0 513 NS_DispatchToMainThread(this);
michael@0 514 } else {
michael@0 515 // We timed out, so just release
michael@0 516 ReleaseDispatcher* releaser = new ReleaseDispatcher(this);
michael@0 517 if(releaser) {
michael@0 518 NS_ADDREF(releaser);
michael@0 519 NS_DispatchToMainThread(releaser);
michael@0 520 }
michael@0 521 }
michael@0 522
michael@0 523 MOZ_SEH_TRY {
michael@0 524 CoUninitialize();
michael@0 525 } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) {
michael@0 526 // Not much we can do at this point...
michael@0 527 }
michael@0 528 }
michael@0 529
michael@0 530 HANDLE
michael@0 531 nsDownloadScanner::Scan::GetWaitableThreadHandle() const
michael@0 532 {
michael@0 533 HANDLE targetHandle = INVALID_HANDLE_VALUE;
michael@0 534 (void)DuplicateHandle(GetCurrentProcess(), mThread,
michael@0 535 GetCurrentProcess(), &targetHandle,
michael@0 536 SYNCHRONIZE, // Only allow clients to wait on this handle
michael@0 537 FALSE, // cannot be inherited by child processes
michael@0 538 0);
michael@0 539 return targetHandle;
michael@0 540 }
michael@0 541
michael@0 542 bool
michael@0 543 nsDownloadScanner::Scan::NotifyTimeout()
michael@0 544 {
michael@0 545 bool didTimeout = CheckAndSetState(AVSCAN_TIMEDOUT, AVSCAN_SCANNING) ||
michael@0 546 CheckAndSetState(AVSCAN_TIMEDOUT, AVSCAN_NOTSTARTED);
michael@0 547 if (didTimeout) {
michael@0 548 // We need to do a few more things on the main thread
michael@0 549 NS_DispatchToMainThread(this);
michael@0 550 }
michael@0 551 return didTimeout;
michael@0 552 }
michael@0 553
michael@0 554 bool
michael@0 555 nsDownloadScanner::Scan::CheckAndSetState(AVScanState newState, AVScanState expectedState) {
michael@0 556 bool gotExpectedState = false;
michael@0 557 EnterCriticalSection(&mStateSync);
michael@0 558 if(gotExpectedState = (mStatus == expectedState))
michael@0 559 mStatus = newState;
michael@0 560 LeaveCriticalSection(&mStateSync);
michael@0 561 return gotExpectedState;
michael@0 562 }
michael@0 563
michael@0 564 nsresult
michael@0 565 nsDownloadScanner::ScanDownload(nsDownload *download)
michael@0 566 {
michael@0 567 if (!mAESExists)
michael@0 568 return NS_ERROR_NOT_AVAILABLE;
michael@0 569
michael@0 570 // No ref ptr, see comment below
michael@0 571 Scan *scan = new Scan(this, download);
michael@0 572 if (!scan)
michael@0 573 return NS_ERROR_OUT_OF_MEMORY;
michael@0 574
michael@0 575 NS_ADDREF(scan);
michael@0 576
michael@0 577 nsresult rv = scan->Start();
michael@0 578
michael@0 579 // Note that we only release upon error. On success, the scan is passed off
michael@0 580 // to a new thread. It is eventually released in Scan::Run on the main thread.
michael@0 581 if (NS_FAILED(rv))
michael@0 582 NS_RELEASE(scan);
michael@0 583 else
michael@0 584 // Notify the watchdog
michael@0 585 mWatchdog->Watch(scan);
michael@0 586
michael@0 587 return rv;
michael@0 588 }
michael@0 589
michael@0 590 nsDownloadScannerWatchdog::nsDownloadScannerWatchdog()
michael@0 591 : mNewItemEvent(nullptr), mQuitEvent(nullptr) {
michael@0 592 InitializeCriticalSection(&mQueueSync);
michael@0 593 }
michael@0 594 nsDownloadScannerWatchdog::~nsDownloadScannerWatchdog() {
michael@0 595 DeleteCriticalSection(&mQueueSync);
michael@0 596 }
michael@0 597
michael@0 598 nsresult
michael@0 599 nsDownloadScannerWatchdog::Init() {
michael@0 600 // Both events are auto-reset
michael@0 601 mNewItemEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
michael@0 602 if (INVALID_HANDLE_VALUE == mNewItemEvent)
michael@0 603 return NS_ERROR_OUT_OF_MEMORY;
michael@0 604 mQuitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
michael@0 605 if (INVALID_HANDLE_VALUE == mQuitEvent) {
michael@0 606 (void)CloseHandle(mNewItemEvent);
michael@0 607 return NS_ERROR_OUT_OF_MEMORY;
michael@0 608 }
michael@0 609
michael@0 610 // This thread is always running, however it will be asleep
michael@0 611 // for most of the dlmgr's lifetime
michael@0 612 mThread = (HANDLE)_beginthreadex(nullptr, 0, WatchdogThread,
michael@0 613 this, 0, nullptr);
michael@0 614 if (!mThread) {
michael@0 615 (void)CloseHandle(mNewItemEvent);
michael@0 616 (void)CloseHandle(mQuitEvent);
michael@0 617 return NS_ERROR_OUT_OF_MEMORY;
michael@0 618 }
michael@0 619
michael@0 620 return NS_OK;
michael@0 621 }
michael@0 622
michael@0 623 nsresult
michael@0 624 nsDownloadScannerWatchdog::Shutdown() {
michael@0 625 // Tell the watchdog thread to quite
michael@0 626 (void)SetEvent(mQuitEvent);
michael@0 627 (void)WaitForSingleObject(mThread, INFINITE);
michael@0 628 (void)CloseHandle(mThread);
michael@0 629 // Manually clear and release the queued scans
michael@0 630 while (mScanQueue.GetSize() != 0) {
michael@0 631 Scan *scan = reinterpret_cast<Scan*>(mScanQueue.Pop());
michael@0 632 NS_RELEASE(scan);
michael@0 633 }
michael@0 634 (void)CloseHandle(mNewItemEvent);
michael@0 635 (void)CloseHandle(mQuitEvent);
michael@0 636 return NS_OK;
michael@0 637 }
michael@0 638
michael@0 639 void
michael@0 640 nsDownloadScannerWatchdog::Watch(Scan *scan) {
michael@0 641 bool wasEmpty;
michael@0 642 // Note that there is no release in this method
michael@0 643 // The scan will be released by the watchdog ALWAYS on the main thread
michael@0 644 // when either the watchdog thread processes the scan or the watchdog
michael@0 645 // is shut down
michael@0 646 NS_ADDREF(scan);
michael@0 647 EnterCriticalSection(&mQueueSync);
michael@0 648 wasEmpty = mScanQueue.GetSize()==0;
michael@0 649 mScanQueue.Push(scan);
michael@0 650 LeaveCriticalSection(&mQueueSync);
michael@0 651 // If the queue was empty, then the watchdog thread is/will be asleep
michael@0 652 if (wasEmpty)
michael@0 653 (void)SetEvent(mNewItemEvent);
michael@0 654 }
michael@0 655
michael@0 656 unsigned int
michael@0 657 __stdcall
michael@0 658 nsDownloadScannerWatchdog::WatchdogThread(void *p) {
michael@0 659 NS_ASSERTION(!NS_IsMainThread(), "Antivirus scan watchdog should not be run on the main thread");
michael@0 660 nsDownloadScannerWatchdog *watchdog = (nsDownloadScannerWatchdog*)p;
michael@0 661 HANDLE waitHandles[3] = {watchdog->mNewItemEvent, watchdog->mQuitEvent, INVALID_HANDLE_VALUE};
michael@0 662 DWORD waitStatus;
michael@0 663 DWORD queueItemsLeft = 0;
michael@0 664 // Loop until quit event or error
michael@0 665 while (0 != queueItemsLeft ||
michael@0 666 (WAIT_OBJECT_0 + 1) !=
michael@0 667 (waitStatus =
michael@0 668 WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE)) &&
michael@0 669 waitStatus != WAIT_FAILED) {
michael@0 670 Scan *scan = nullptr;
michael@0 671 PRTime startTime, expectedEndTime, now;
michael@0 672 DWORD waitTime;
michael@0 673
michael@0 674 // Pop scan from queue
michael@0 675 EnterCriticalSection(&watchdog->mQueueSync);
michael@0 676 scan = reinterpret_cast<Scan*>(watchdog->mScanQueue.Pop());
michael@0 677 queueItemsLeft = watchdog->mScanQueue.GetSize();
michael@0 678 LeaveCriticalSection(&watchdog->mQueueSync);
michael@0 679
michael@0 680 // Calculate expected end time
michael@0 681 startTime = scan->GetStartTime();
michael@0 682 expectedEndTime = WATCHDOG_TIMEOUT + startTime;
michael@0 683 now = PR_Now();
michael@0 684 // PRTime is not guaranteed to be a signed integral type (afaik), but
michael@0 685 // currently it is
michael@0 686 if (now > expectedEndTime) {
michael@0 687 waitTime = 0;
michael@0 688 } else {
michael@0 689 // This is a positive value, and we know that it will not overflow
michael@0 690 // (bounded by WATCHDOG_TIMEOUT)
michael@0 691 // waitTime is in milliseconds, nspr uses microseconds
michael@0 692 waitTime = static_cast<DWORD>((expectedEndTime - now)/PR_USEC_PER_MSEC);
michael@0 693 }
michael@0 694 HANDLE hThread = waitHandles[2] = scan->GetWaitableThreadHandle();
michael@0 695
michael@0 696 // Wait for the thread (obj 1) or quit event (obj 0)
michael@0 697 waitStatus = WaitForMultipleObjects(2, (waitHandles+1), FALSE, waitTime);
michael@0 698 CloseHandle(hThread);
michael@0 699
michael@0 700 ReleaseDispatcher* releaser = new ReleaseDispatcher(scan);
michael@0 701 if(!releaser)
michael@0 702 continue;
michael@0 703 NS_ADDREF(releaser);
michael@0 704 // Got quit event or error
michael@0 705 if (waitStatus == WAIT_FAILED || waitStatus == WAIT_OBJECT_0) {
michael@0 706 NS_DispatchToMainThread(releaser);
michael@0 707 break;
michael@0 708 // Thread exited normally
michael@0 709 } else if (waitStatus == (WAIT_OBJECT_0+1)) {
michael@0 710 NS_DispatchToMainThread(releaser);
michael@0 711 continue;
michael@0 712 // Timeout case
michael@0 713 } else {
michael@0 714 NS_ASSERTION(waitStatus == WAIT_TIMEOUT, "Unexpected wait status in dlmgr watchdog thread");
michael@0 715 if (!scan->NotifyTimeout()) {
michael@0 716 // If we didn't time out, then release the thread
michael@0 717 NS_DispatchToMainThread(releaser);
michael@0 718 } else {
michael@0 719 // NotifyTimeout did a dispatch which will release the scan, so we
michael@0 720 // don't need to release the scan
michael@0 721 NS_RELEASE(releaser);
michael@0 722 }
michael@0 723 }
michael@0 724 }
michael@0 725 _endthreadex(0);
michael@0 726 return 0;
michael@0 727 }

mercurial