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