nsDownloadScanner.cpp 23 KB

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