1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/downloads/nsDownloadScanner.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,727 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: se cin sw=2 ts=2 et : */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "nsDownloadScanner.h" 1.11 +#include <comcat.h> 1.12 +#include <process.h> 1.13 +#include "nsDownloadManager.h" 1.14 +#include "nsIXULAppInfo.h" 1.15 +#include "nsXULAppAPI.h" 1.16 +#include "nsIPrefService.h" 1.17 +#include "nsNetUtil.h" 1.18 +#include "nsDeque.h" 1.19 +#include "nsIFileURL.h" 1.20 +#include "nsIPrefBranch.h" 1.21 +#include "nsXPCOMCIDInternal.h" 1.22 + 1.23 +/** 1.24 + * Code overview 1.25 + * 1.26 + * Download scanner attempts to make use of one of two different virus 1.27 + * scanning interfaces available on Windows - IOfficeAntiVirus (Windows 1.28 + * 95/NT 4 and IE 5) and IAttachmentExecute (XPSP2 and up). The latter 1.29 + * interface supports calling IOfficeAntiVirus internally, while also 1.30 + * adding support for XPSP2+ ADS forks which define security related 1.31 + * prompting on downloaded content. 1.32 + * 1.33 + * Both interfaces are synchronous and can take a while, so it is not a 1.34 + * good idea to call either from the main thread. Some antivirus scanners can 1.35 + * take a long time to scan or the call might block while the scanner shows 1.36 + * its UI so if the user were to download many files that finished around the 1.37 + * same time, they would have to wait a while if the scanning were done on 1.38 + * exactly one other thread. Since the overhead of creating a thread is 1.39 + * relatively small compared to the time it takes to download a file and scan 1.40 + * it, a new thread is spawned for each download that is to be scanned. Since 1.41 + * most of the mozilla codebase is not threadsafe, all the information needed 1.42 + * for the scanner is gathered in the main thread in nsDownloadScanner::Scan::Start. 1.43 + * The only function of nsDownloadScanner::Scan which is invoked on another 1.44 + * thread is DoScan. 1.45 + * 1.46 + * Watchdog overview 1.47 + * 1.48 + * The watchdog is used internally by the scanner. It maintains a queue of 1.49 + * current download scans. In a separate thread, it dequeues the oldest scan 1.50 + * and waits on that scan's thread with a timeout given by WATCHDOG_TIMEOUT 1.51 + * (default is 30 seconds). If the wait times out, then the watchdog notifies 1.52 + * the Scan that it has timed out. If the scan really has timed out, then the 1.53 + * Scan object will dispatch its run method to the main thread; this will 1.54 + * release the watchdog thread's addref on the Scan. If it has not timed out 1.55 + * (i.e. the Scan just finished in time), then the watchdog dispatches a 1.56 + * ReleaseDispatcher to release its ref of the Scan on the main thread. 1.57 + * 1.58 + * In order to minimize execution time, there are two events used to notify the 1.59 + * watchdog thread of a non-empty queue and a quit event. Every blocking wait 1.60 + * that the watchdog thread does waits on the quit event; this lets the thread 1.61 + * quickly exit when shutting down. Also, the download scan queue will be empty 1.62 + * most of the time; rather than use a spin loop, a simple event is triggered 1.63 + * by the main thread when a new scan is added to an empty queue. When the 1.64 + * watchdog thread knows that it has run out of elements in the queue, it will 1.65 + * wait on the new item event. 1.66 + * 1.67 + * Memory/resource leaks due to timeout: 1.68 + * In the event of a timeout, the thread must remain alive; terminating it may 1.69 + * very well cause the antivirus scanner to crash or be put into an 1.70 + * inconsistent state; COM resources may also not be cleaned up. The downside 1.71 + * is that we need to leave the thread running; suspending it may lead to a 1.72 + * deadlock. Because the scan call may be ongoing, it may be dependent on the 1.73 + * memory referenced by the MSOAVINFO structure, so we cannot free mName, mPath 1.74 + * or mOrigin; this means that we cannot free the Scan object since doing so 1.75 + * will deallocate that memory. Note that mDownload is set to null upon timeout 1.76 + * or completion, so the download itself is never leaked. If the scan does 1.77 + * eventually complete, then the all the memory and resources will be freed. 1.78 + * It is possible, however extremely rare, that in the event of a timeout, the 1.79 + * mStateSync critical section will leak its event; this will happen only if 1.80 + * the scanning thread, watchdog thread or main thread try to enter the 1.81 + * critical section when one of the others is already in it. 1.82 + * 1.83 + * Reasoning for CheckAndSetState - there exists a race condition between the time when 1.84 + * either the timeout or normal scan sets the state and when Scan::Run is 1.85 + * executed on the main thread. Ex: mStatus could be set by Scan::DoScan* which 1.86 + * then queues a dispatch on the main thread. Before that dispatch is executed, 1.87 + * the timeout code fires and sets mStatus to AVSCAN_TIMEDOUT which then queues 1.88 + * its dispatch to the main thread (the same function as DoScan*). Both 1.89 + * dispatches run and both try to set the download state to AVSCAN_TIMEDOUT 1.90 + * which is incorrect. 1.91 + * 1.92 + * There are 5 possible outcomes of the virus scan: 1.93 + * AVSCAN_GOOD => the file is clean 1.94 + * AVSCAN_BAD => the file has a virus 1.95 + * AVSCAN_UGLY => the file had a virus, but it was cleaned 1.96 + * AVSCAN_FAILED => something else went wrong with the virus scanner. 1.97 + * AVSCAN_TIMEDOUT => the scan (thread setup + execution) took too long 1.98 + * 1.99 + * Both the good and ugly states leave the user with a benign file, so they 1.100 + * transition to the finished state. Bad files are sent to the blocked state. 1.101 + * The failed and timedout states transition to finished downloads. 1.102 + * 1.103 + * Possible Future enhancements: 1.104 + * * Create an interface for scanning files in general 1.105 + * * Make this a service 1.106 + * * Get antivirus scanner status via WMI/registry 1.107 + */ 1.108 + 1.109 +// IAttachementExecute supports user definable settings for certain 1.110 +// security related prompts. This defines a general GUID for use in 1.111 +// all projects. Individual projects can define an individual guid 1.112 +// if they want to. 1.113 +#ifndef MOZ_VIRUS_SCANNER_PROMPT_GUID 1.114 +#define MOZ_VIRUS_SCANNER_PROMPT_GUID \ 1.115 + { 0xb50563d1, 0x16b6, 0x43c2, { 0xa6, 0x6a, 0xfa, 0xe6, 0xd2, 0x11, 0xf2, \ 1.116 + 0xea } } 1.117 +#endif 1.118 +static const GUID GUID_MozillaVirusScannerPromptGeneric = 1.119 + MOZ_VIRUS_SCANNER_PROMPT_GUID; 1.120 + 1.121 +// Initial timeout is 30 seconds 1.122 +#define WATCHDOG_TIMEOUT (30*PR_USEC_PER_SEC) 1.123 + 1.124 +// Maximum length for URI's passed into IAE 1.125 +#define MAX_IAEURILENGTH 1683 1.126 + 1.127 +class nsDownloadScannerWatchdog 1.128 +{ 1.129 + typedef nsDownloadScanner::Scan Scan; 1.130 +public: 1.131 + nsDownloadScannerWatchdog(); 1.132 + ~nsDownloadScannerWatchdog(); 1.133 + 1.134 + nsresult Init(); 1.135 + nsresult Shutdown(); 1.136 + 1.137 + void Watch(Scan *scan); 1.138 +private: 1.139 + static unsigned int __stdcall WatchdogThread(void *p); 1.140 + CRITICAL_SECTION mQueueSync; 1.141 + nsDeque mScanQueue; 1.142 + HANDLE mThread; 1.143 + HANDLE mNewItemEvent; 1.144 + HANDLE mQuitEvent; 1.145 +}; 1.146 + 1.147 +nsDownloadScanner::nsDownloadScanner() : 1.148 + mAESExists(false) 1.149 +{ 1.150 +} 1.151 + 1.152 +// This destructor appeases the compiler; it would otherwise complain about an 1.153 +// incomplete type for nsDownloadWatchdog in the instantiation of 1.154 +// nsAutoPtr::~nsAutoPtr 1.155 +// Plus, it's a handy location to call nsDownloadScannerWatchdog::Shutdown from 1.156 +nsDownloadScanner::~nsDownloadScanner() { 1.157 + if (mWatchdog) 1.158 + (void)mWatchdog->Shutdown(); 1.159 +} 1.160 + 1.161 +nsresult 1.162 +nsDownloadScanner::Init() 1.163 +{ 1.164 + // This CoInitialize/CoUninitialize pattern seems to be common in the Mozilla 1.165 + // codebase. All other COM calls/objects are made on different threads. 1.166 + nsresult rv = NS_OK; 1.167 + CoInitialize(nullptr); 1.168 + 1.169 + if (!IsAESAvailable()) { 1.170 + CoUninitialize(); 1.171 + return NS_ERROR_NOT_AVAILABLE; 1.172 + } 1.173 + 1.174 + mAESExists = true; 1.175 + 1.176 + // Initialize scanning 1.177 + mWatchdog = new nsDownloadScannerWatchdog(); 1.178 + if (mWatchdog) { 1.179 + rv = mWatchdog->Init(); 1.180 + if (FAILED(rv)) 1.181 + mWatchdog = nullptr; 1.182 + } else { 1.183 + rv = NS_ERROR_OUT_OF_MEMORY; 1.184 + } 1.185 + 1.186 + if (NS_FAILED(rv)) 1.187 + return rv; 1.188 + 1.189 + return rv; 1.190 +} 1.191 + 1.192 +bool 1.193 +nsDownloadScanner::IsAESAvailable() 1.194 +{ 1.195 + // Try to instantiate IAE to see if it's available. 1.196 + nsRefPtr<IAttachmentExecute> ae; 1.197 + HRESULT hr; 1.198 + hr = CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_INPROC, 1.199 + IID_IAttachmentExecute, getter_AddRefs(ae)); 1.200 + if (FAILED(hr)) { 1.201 + NS_WARNING("Could not instantiate attachment execution service\n"); 1.202 + return false; 1.203 + } 1.204 + return true; 1.205 +} 1.206 + 1.207 +// If IAttachementExecute is available, use the CheckPolicy call to find out 1.208 +// if this download should be prevented due to Security Zone Policy settings. 1.209 +AVCheckPolicyState 1.210 +nsDownloadScanner::CheckPolicy(nsIURI *aSource, nsIURI *aTarget) 1.211 +{ 1.212 + nsresult rv; 1.213 + 1.214 + if (!mAESExists || !aSource || !aTarget) 1.215 + return AVPOLICY_DOWNLOAD; 1.216 + 1.217 + nsAutoCString source; 1.218 + rv = aSource->GetSpec(source); 1.219 + if (NS_FAILED(rv)) 1.220 + return AVPOLICY_DOWNLOAD; 1.221 + 1.222 + nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(aTarget)); 1.223 + if (!fileUrl) 1.224 + return AVPOLICY_DOWNLOAD; 1.225 + 1.226 + nsCOMPtr<nsIFile> theFile; 1.227 + nsAutoString aFileName; 1.228 + if (NS_FAILED(fileUrl->GetFile(getter_AddRefs(theFile))) || 1.229 + NS_FAILED(theFile->GetLeafName(aFileName))) 1.230 + return AVPOLICY_DOWNLOAD; 1.231 + 1.232 + // IAttachementExecute prohibits src data: schemes by default but we 1.233 + // support them. If this is a data src, skip off doing a policy check. 1.234 + // (The file will still be scanned once it lands on the local system.) 1.235 + bool isDataScheme(false); 1.236 + nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aSource); 1.237 + if (innerURI) 1.238 + (void)innerURI->SchemeIs("data", &isDataScheme); 1.239 + if (isDataScheme) 1.240 + return AVPOLICY_DOWNLOAD; 1.241 + 1.242 + nsRefPtr<IAttachmentExecute> ae; 1.243 + HRESULT hr; 1.244 + hr = CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_INPROC, 1.245 + IID_IAttachmentExecute, getter_AddRefs(ae)); 1.246 + if (FAILED(hr)) 1.247 + return AVPOLICY_DOWNLOAD; 1.248 + 1.249 + (void)ae->SetClientGuid(GUID_MozillaVirusScannerPromptGeneric); 1.250 + (void)ae->SetSource(NS_ConvertUTF8toUTF16(source).get()); 1.251 + (void)ae->SetFileName(aFileName.get()); 1.252 + 1.253 + // Any failure means the file download/exec will be blocked by the system. 1.254 + // S_OK or S_FALSE imply it's ok. 1.255 + hr = ae->CheckPolicy(); 1.256 + 1.257 + if (hr == S_OK) 1.258 + return AVPOLICY_DOWNLOAD; 1.259 + 1.260 + if (hr == S_FALSE) 1.261 + return AVPOLICY_PROMPT; 1.262 + 1.263 + if (hr == E_INVALIDARG) 1.264 + return AVPOLICY_PROMPT; 1.265 + 1.266 + return AVPOLICY_BLOCKED; 1.267 +} 1.268 + 1.269 +#ifndef THREAD_MODE_BACKGROUND_BEGIN 1.270 +#define THREAD_MODE_BACKGROUND_BEGIN 0x00010000 1.271 +#endif 1.272 + 1.273 +#ifndef THREAD_MODE_BACKGROUND_END 1.274 +#define THREAD_MODE_BACKGROUND_END 0x00020000 1.275 +#endif 1.276 + 1.277 +unsigned int __stdcall 1.278 +nsDownloadScanner::ScannerThreadFunction(void *p) 1.279 +{ 1.280 + HANDLE currentThread = GetCurrentThread(); 1.281 + NS_ASSERTION(!NS_IsMainThread(), "Antivirus scan should not be run on the main thread"); 1.282 + nsDownloadScanner::Scan *scan = static_cast<nsDownloadScanner::Scan*>(p); 1.283 + if (!SetThreadPriority(currentThread, THREAD_MODE_BACKGROUND_BEGIN)) 1.284 + (void)SetThreadPriority(currentThread, THREAD_PRIORITY_IDLE); 1.285 + scan->DoScan(); 1.286 + (void)SetThreadPriority(currentThread, THREAD_MODE_BACKGROUND_END); 1.287 + _endthreadex(0); 1.288 + return 0; 1.289 +} 1.290 + 1.291 +// The sole purpose of this class is to release an object on the main thread 1.292 +// It assumes that its creator will addref it and it will release itself on 1.293 +// the main thread too 1.294 +class ReleaseDispatcher : public nsRunnable { 1.295 +public: 1.296 + ReleaseDispatcher(nsISupports *ptr) 1.297 + : mPtr(ptr) {} 1.298 + NS_IMETHOD Run(); 1.299 +private: 1.300 + nsISupports *mPtr; 1.301 +}; 1.302 + 1.303 +nsresult ReleaseDispatcher::Run() { 1.304 + NS_ASSERTION(NS_IsMainThread(), "Antivirus scan release dispatch should be run on the main thread"); 1.305 + NS_RELEASE(mPtr); 1.306 + NS_RELEASE_THIS(); 1.307 + return NS_OK; 1.308 +} 1.309 + 1.310 +nsDownloadScanner::Scan::Scan(nsDownloadScanner *scanner, nsDownload *download) 1.311 + : mDLScanner(scanner), mThread(nullptr), 1.312 + mDownload(download), mStatus(AVSCAN_NOTSTARTED), 1.313 + mSkipSource(false) 1.314 +{ 1.315 + InitializeCriticalSection(&mStateSync); 1.316 +} 1.317 + 1.318 +nsDownloadScanner::Scan::~Scan() { 1.319 + DeleteCriticalSection(&mStateSync); 1.320 +} 1.321 + 1.322 +nsresult 1.323 +nsDownloadScanner::Scan::Start() 1.324 +{ 1.325 + mStartTime = PR_Now(); 1.326 + 1.327 + mThread = (HANDLE)_beginthreadex(nullptr, 0, ScannerThreadFunction, 1.328 + this, CREATE_SUSPENDED, nullptr); 1.329 + if (!mThread) 1.330 + return NS_ERROR_OUT_OF_MEMORY; 1.331 + 1.332 + nsresult rv = NS_OK; 1.333 + 1.334 + // Get the path to the file on disk 1.335 + nsCOMPtr<nsIFile> file; 1.336 + rv = mDownload->GetTargetFile(getter_AddRefs(file)); 1.337 + NS_ENSURE_SUCCESS(rv, rv); 1.338 + rv = file->GetPath(mPath); 1.339 + NS_ENSURE_SUCCESS(rv, rv); 1.340 + 1.341 + // Grab the app name 1.342 + nsCOMPtr<nsIXULAppInfo> appinfo = 1.343 + do_GetService(XULAPPINFO_SERVICE_CONTRACTID, &rv); 1.344 + NS_ENSURE_SUCCESS(rv, rv); 1.345 + 1.346 + nsAutoCString name; 1.347 + rv = appinfo->GetName(name); 1.348 + NS_ENSURE_SUCCESS(rv, rv); 1.349 + CopyUTF8toUTF16(name, mName); 1.350 + 1.351 + // Get the origin 1.352 + nsCOMPtr<nsIURI> uri; 1.353 + rv = mDownload->GetSource(getter_AddRefs(uri)); 1.354 + NS_ENSURE_SUCCESS(rv, rv); 1.355 + 1.356 + nsAutoCString origin; 1.357 + rv = uri->GetSpec(origin); 1.358 + NS_ENSURE_SUCCESS(rv, rv); 1.359 + 1.360 + // Certain virus interfaces do not like extremely long uris. 1.361 + // Chop off the path and cgi data and just pass the base domain. 1.362 + if (origin.Length() > MAX_IAEURILENGTH) { 1.363 + rv = uri->GetPrePath(origin); 1.364 + NS_ENSURE_SUCCESS(rv, rv); 1.365 + } 1.366 + 1.367 + CopyUTF8toUTF16(origin, mOrigin); 1.368 + 1.369 + // We count https/ftp/http as an http download 1.370 + bool isHttp(false), isFtp(false), isHttps(false); 1.371 + nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri); 1.372 + if (!innerURI) innerURI = uri; 1.373 + (void)innerURI->SchemeIs("http", &isHttp); 1.374 + (void)innerURI->SchemeIs("ftp", &isFtp); 1.375 + (void)innerURI->SchemeIs("https", &isHttps); 1.376 + mIsHttpDownload = isHttp || isFtp || isHttps; 1.377 + 1.378 + // IAttachementExecute prohibits src data: schemes by default but we 1.379 + // support them. Mark the download if it's a data scheme, so we 1.380 + // can skip off supplying the src to IAttachementExecute when we scan 1.381 + // the resulting file. 1.382 + (void)innerURI->SchemeIs("data", &mSkipSource); 1.383 + 1.384 + // ResumeThread returns the previous suspend count 1.385 + if (1 != ::ResumeThread(mThread)) { 1.386 + CloseHandle(mThread); 1.387 + return NS_ERROR_UNEXPECTED; 1.388 + } 1.389 + return NS_OK; 1.390 +} 1.391 + 1.392 +nsresult 1.393 +nsDownloadScanner::Scan::Run() 1.394 +{ 1.395 + NS_ASSERTION(NS_IsMainThread(), "Antivirus scan dispatch should be run on the main thread"); 1.396 + 1.397 + // Cleanup our thread 1.398 + if (mStatus != AVSCAN_TIMEDOUT) 1.399 + WaitForSingleObject(mThread, INFINITE); 1.400 + CloseHandle(mThread); 1.401 + 1.402 + DownloadState downloadState = 0; 1.403 + EnterCriticalSection(&mStateSync); 1.404 + switch (mStatus) { 1.405 + case AVSCAN_BAD: 1.406 + downloadState = nsIDownloadManager::DOWNLOAD_DIRTY; 1.407 + break; 1.408 + default: 1.409 + case AVSCAN_FAILED: 1.410 + case AVSCAN_GOOD: 1.411 + case AVSCAN_UGLY: 1.412 + case AVSCAN_TIMEDOUT: 1.413 + downloadState = nsIDownloadManager::DOWNLOAD_FINISHED; 1.414 + break; 1.415 + } 1.416 + LeaveCriticalSection(&mStateSync); 1.417 + // Download will be null if we already timed out 1.418 + if (mDownload) 1.419 + (void)mDownload->SetState(downloadState); 1.420 + 1.421 + // Clean up some other variables 1.422 + // In the event of a timeout, our destructor won't be called 1.423 + mDownload = nullptr; 1.424 + 1.425 + NS_RELEASE_THIS(); 1.426 + return NS_OK; 1.427 +} 1.428 + 1.429 +static DWORD 1.430 +ExceptionFilterFunction(DWORD exceptionCode) { 1.431 + switch(exceptionCode) { 1.432 + case EXCEPTION_ACCESS_VIOLATION: 1.433 + case EXCEPTION_ILLEGAL_INSTRUCTION: 1.434 + case EXCEPTION_IN_PAGE_ERROR: 1.435 + case EXCEPTION_PRIV_INSTRUCTION: 1.436 + case EXCEPTION_STACK_OVERFLOW: 1.437 + return EXCEPTION_EXECUTE_HANDLER; 1.438 + default: 1.439 + return EXCEPTION_CONTINUE_SEARCH; 1.440 + } 1.441 +} 1.442 + 1.443 +bool 1.444 +nsDownloadScanner::Scan::DoScanAES() 1.445 +{ 1.446 + // This warning is for the destructor of ae which will not be invoked in the 1.447 + // event of a win32 exception 1.448 +#pragma warning(disable: 4509) 1.449 + HRESULT hr; 1.450 + nsRefPtr<IAttachmentExecute> ae; 1.451 + MOZ_SEH_TRY { 1.452 + hr = CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_ALL, 1.453 + IID_IAttachmentExecute, getter_AddRefs(ae)); 1.454 + } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) { 1.455 + return CheckAndSetState(AVSCAN_NOTSTARTED,AVSCAN_FAILED); 1.456 + } 1.457 + 1.458 + // If we (somehow) already timed out, then don't bother scanning 1.459 + if (CheckAndSetState(AVSCAN_SCANNING, AVSCAN_NOTSTARTED)) { 1.460 + AVScanState newState; 1.461 + if (SUCCEEDED(hr)) { 1.462 + bool gotException = false; 1.463 + MOZ_SEH_TRY { 1.464 + (void)ae->SetClientGuid(GUID_MozillaVirusScannerPromptGeneric); 1.465 + (void)ae->SetLocalPath(mPath.get()); 1.466 + // Provide the src for everything but data: schemes. 1.467 + if (!mSkipSource) 1.468 + (void)ae->SetSource(mOrigin.get()); 1.469 + 1.470 + // Save() will invoke the scanner 1.471 + hr = ae->Save(); 1.472 + } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) { 1.473 + gotException = true; 1.474 + } 1.475 + 1.476 + MOZ_SEH_TRY { 1.477 + ae = nullptr; 1.478 + } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) { 1.479 + gotException = true; 1.480 + } 1.481 + 1.482 + if(gotException) { 1.483 + newState = AVSCAN_FAILED; 1.484 + } 1.485 + else if (SUCCEEDED(hr)) { // Passed the scan 1.486 + newState = AVSCAN_GOOD; 1.487 + } 1.488 + else if (HRESULT_CODE(hr) == ERROR_FILE_NOT_FOUND) { 1.489 + NS_WARNING("Downloaded file disappeared before it could be scanned"); 1.490 + newState = AVSCAN_FAILED; 1.491 + } 1.492 + else if (hr == E_INVALIDARG) { 1.493 + NS_WARNING("IAttachementExecute returned invalid argument error"); 1.494 + newState = AVSCAN_FAILED; 1.495 + } 1.496 + else { 1.497 + newState = AVSCAN_UGLY; 1.498 + } 1.499 + } 1.500 + else { 1.501 + newState = AVSCAN_FAILED; 1.502 + } 1.503 + return CheckAndSetState(newState, AVSCAN_SCANNING); 1.504 + } 1.505 + return false; 1.506 +} 1.507 +#pragma warning(default: 4509) 1.508 + 1.509 +void 1.510 +nsDownloadScanner::Scan::DoScan() 1.511 +{ 1.512 + CoInitialize(nullptr); 1.513 + 1.514 + if (DoScanAES()) { 1.515 + // We need to do a few more things on the main thread 1.516 + NS_DispatchToMainThread(this); 1.517 + } else { 1.518 + // We timed out, so just release 1.519 + ReleaseDispatcher* releaser = new ReleaseDispatcher(this); 1.520 + if(releaser) { 1.521 + NS_ADDREF(releaser); 1.522 + NS_DispatchToMainThread(releaser); 1.523 + } 1.524 + } 1.525 + 1.526 + MOZ_SEH_TRY { 1.527 + CoUninitialize(); 1.528 + } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) { 1.529 + // Not much we can do at this point... 1.530 + } 1.531 +} 1.532 + 1.533 +HANDLE 1.534 +nsDownloadScanner::Scan::GetWaitableThreadHandle() const 1.535 +{ 1.536 + HANDLE targetHandle = INVALID_HANDLE_VALUE; 1.537 + (void)DuplicateHandle(GetCurrentProcess(), mThread, 1.538 + GetCurrentProcess(), &targetHandle, 1.539 + SYNCHRONIZE, // Only allow clients to wait on this handle 1.540 + FALSE, // cannot be inherited by child processes 1.541 + 0); 1.542 + return targetHandle; 1.543 +} 1.544 + 1.545 +bool 1.546 +nsDownloadScanner::Scan::NotifyTimeout() 1.547 +{ 1.548 + bool didTimeout = CheckAndSetState(AVSCAN_TIMEDOUT, AVSCAN_SCANNING) || 1.549 + CheckAndSetState(AVSCAN_TIMEDOUT, AVSCAN_NOTSTARTED); 1.550 + if (didTimeout) { 1.551 + // We need to do a few more things on the main thread 1.552 + NS_DispatchToMainThread(this); 1.553 + } 1.554 + return didTimeout; 1.555 +} 1.556 + 1.557 +bool 1.558 +nsDownloadScanner::Scan::CheckAndSetState(AVScanState newState, AVScanState expectedState) { 1.559 + bool gotExpectedState = false; 1.560 + EnterCriticalSection(&mStateSync); 1.561 + if(gotExpectedState = (mStatus == expectedState)) 1.562 + mStatus = newState; 1.563 + LeaveCriticalSection(&mStateSync); 1.564 + return gotExpectedState; 1.565 +} 1.566 + 1.567 +nsresult 1.568 +nsDownloadScanner::ScanDownload(nsDownload *download) 1.569 +{ 1.570 + if (!mAESExists) 1.571 + return NS_ERROR_NOT_AVAILABLE; 1.572 + 1.573 + // No ref ptr, see comment below 1.574 + Scan *scan = new Scan(this, download); 1.575 + if (!scan) 1.576 + return NS_ERROR_OUT_OF_MEMORY; 1.577 + 1.578 + NS_ADDREF(scan); 1.579 + 1.580 + nsresult rv = scan->Start(); 1.581 + 1.582 + // Note that we only release upon error. On success, the scan is passed off 1.583 + // to a new thread. It is eventually released in Scan::Run on the main thread. 1.584 + if (NS_FAILED(rv)) 1.585 + NS_RELEASE(scan); 1.586 + else 1.587 + // Notify the watchdog 1.588 + mWatchdog->Watch(scan); 1.589 + 1.590 + return rv; 1.591 +} 1.592 + 1.593 +nsDownloadScannerWatchdog::nsDownloadScannerWatchdog() 1.594 + : mNewItemEvent(nullptr), mQuitEvent(nullptr) { 1.595 + InitializeCriticalSection(&mQueueSync); 1.596 +} 1.597 +nsDownloadScannerWatchdog::~nsDownloadScannerWatchdog() { 1.598 + DeleteCriticalSection(&mQueueSync); 1.599 +} 1.600 + 1.601 +nsresult 1.602 +nsDownloadScannerWatchdog::Init() { 1.603 + // Both events are auto-reset 1.604 + mNewItemEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); 1.605 + if (INVALID_HANDLE_VALUE == mNewItemEvent) 1.606 + return NS_ERROR_OUT_OF_MEMORY; 1.607 + mQuitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); 1.608 + if (INVALID_HANDLE_VALUE == mQuitEvent) { 1.609 + (void)CloseHandle(mNewItemEvent); 1.610 + return NS_ERROR_OUT_OF_MEMORY; 1.611 + } 1.612 + 1.613 + // This thread is always running, however it will be asleep 1.614 + // for most of the dlmgr's lifetime 1.615 + mThread = (HANDLE)_beginthreadex(nullptr, 0, WatchdogThread, 1.616 + this, 0, nullptr); 1.617 + if (!mThread) { 1.618 + (void)CloseHandle(mNewItemEvent); 1.619 + (void)CloseHandle(mQuitEvent); 1.620 + return NS_ERROR_OUT_OF_MEMORY; 1.621 + } 1.622 + 1.623 + return NS_OK; 1.624 +} 1.625 + 1.626 +nsresult 1.627 +nsDownloadScannerWatchdog::Shutdown() { 1.628 + // Tell the watchdog thread to quite 1.629 + (void)SetEvent(mQuitEvent); 1.630 + (void)WaitForSingleObject(mThread, INFINITE); 1.631 + (void)CloseHandle(mThread); 1.632 + // Manually clear and release the queued scans 1.633 + while (mScanQueue.GetSize() != 0) { 1.634 + Scan *scan = reinterpret_cast<Scan*>(mScanQueue.Pop()); 1.635 + NS_RELEASE(scan); 1.636 + } 1.637 + (void)CloseHandle(mNewItemEvent); 1.638 + (void)CloseHandle(mQuitEvent); 1.639 + return NS_OK; 1.640 +} 1.641 + 1.642 +void 1.643 +nsDownloadScannerWatchdog::Watch(Scan *scan) { 1.644 + bool wasEmpty; 1.645 + // Note that there is no release in this method 1.646 + // The scan will be released by the watchdog ALWAYS on the main thread 1.647 + // when either the watchdog thread processes the scan or the watchdog 1.648 + // is shut down 1.649 + NS_ADDREF(scan); 1.650 + EnterCriticalSection(&mQueueSync); 1.651 + wasEmpty = mScanQueue.GetSize()==0; 1.652 + mScanQueue.Push(scan); 1.653 + LeaveCriticalSection(&mQueueSync); 1.654 + // If the queue was empty, then the watchdog thread is/will be asleep 1.655 + if (wasEmpty) 1.656 + (void)SetEvent(mNewItemEvent); 1.657 +} 1.658 + 1.659 +unsigned int 1.660 +__stdcall 1.661 +nsDownloadScannerWatchdog::WatchdogThread(void *p) { 1.662 + NS_ASSERTION(!NS_IsMainThread(), "Antivirus scan watchdog should not be run on the main thread"); 1.663 + nsDownloadScannerWatchdog *watchdog = (nsDownloadScannerWatchdog*)p; 1.664 + HANDLE waitHandles[3] = {watchdog->mNewItemEvent, watchdog->mQuitEvent, INVALID_HANDLE_VALUE}; 1.665 + DWORD waitStatus; 1.666 + DWORD queueItemsLeft = 0; 1.667 + // Loop until quit event or error 1.668 + while (0 != queueItemsLeft || 1.669 + (WAIT_OBJECT_0 + 1) != 1.670 + (waitStatus = 1.671 + WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE)) && 1.672 + waitStatus != WAIT_FAILED) { 1.673 + Scan *scan = nullptr; 1.674 + PRTime startTime, expectedEndTime, now; 1.675 + DWORD waitTime; 1.676 + 1.677 + // Pop scan from queue 1.678 + EnterCriticalSection(&watchdog->mQueueSync); 1.679 + scan = reinterpret_cast<Scan*>(watchdog->mScanQueue.Pop()); 1.680 + queueItemsLeft = watchdog->mScanQueue.GetSize(); 1.681 + LeaveCriticalSection(&watchdog->mQueueSync); 1.682 + 1.683 + // Calculate expected end time 1.684 + startTime = scan->GetStartTime(); 1.685 + expectedEndTime = WATCHDOG_TIMEOUT + startTime; 1.686 + now = PR_Now(); 1.687 + // PRTime is not guaranteed to be a signed integral type (afaik), but 1.688 + // currently it is 1.689 + if (now > expectedEndTime) { 1.690 + waitTime = 0; 1.691 + } else { 1.692 + // This is a positive value, and we know that it will not overflow 1.693 + // (bounded by WATCHDOG_TIMEOUT) 1.694 + // waitTime is in milliseconds, nspr uses microseconds 1.695 + waitTime = static_cast<DWORD>((expectedEndTime - now)/PR_USEC_PER_MSEC); 1.696 + } 1.697 + HANDLE hThread = waitHandles[2] = scan->GetWaitableThreadHandle(); 1.698 + 1.699 + // Wait for the thread (obj 1) or quit event (obj 0) 1.700 + waitStatus = WaitForMultipleObjects(2, (waitHandles+1), FALSE, waitTime); 1.701 + CloseHandle(hThread); 1.702 + 1.703 + ReleaseDispatcher* releaser = new ReleaseDispatcher(scan); 1.704 + if(!releaser) 1.705 + continue; 1.706 + NS_ADDREF(releaser); 1.707 + // Got quit event or error 1.708 + if (waitStatus == WAIT_FAILED || waitStatus == WAIT_OBJECT_0) { 1.709 + NS_DispatchToMainThread(releaser); 1.710 + break; 1.711 + // Thread exited normally 1.712 + } else if (waitStatus == (WAIT_OBJECT_0+1)) { 1.713 + NS_DispatchToMainThread(releaser); 1.714 + continue; 1.715 + // Timeout case 1.716 + } else { 1.717 + NS_ASSERTION(waitStatus == WAIT_TIMEOUT, "Unexpected wait status in dlmgr watchdog thread"); 1.718 + if (!scan->NotifyTimeout()) { 1.719 + // If we didn't time out, then release the thread 1.720 + NS_DispatchToMainThread(releaser); 1.721 + } else { 1.722 + // NotifyTimeout did a dispatch which will release the scan, so we 1.723 + // don't need to release the scan 1.724 + NS_RELEASE(releaser); 1.725 + } 1.726 + } 1.727 + } 1.728 + _endthreadex(0); 1.729 + return 0; 1.730 +}