1 /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
10 #pragma warning( push )
11 #pragma warning( disable : 4275 4530 ) // See msvc-stl-wrapper.template.h
13 #pragma warning( pop )
15 #define MOZ_NO_MOZALLOC
16 #include "nsAutoPtr.h"
18 #include "nsWindowsDllInterceptor.h"
19 #include "mozilla/WindowsVersion.h"
20 #include "nsWindowsHelpers.h"
22 using namespace mozilla
;
24 #define ALL_VERSIONS ((unsigned long long)-1LL)
26 // DLLs sometimes ship without a version number, particularly early
27 // releases. Blocking "version <= 0" has the effect of blocking unversioned
28 // DLLs (since the call to get version info fails), but not blocking
29 // any versioned instance.
30 #define UNVERSIONED ((unsigned long long)0LL)
32 // Convert the 4 (decimal) components of a DLL version number into a
33 // single unsigned long long, as needed by the blocklist
34 #define MAKE_VERSION(a,b,c,d)\
35 ((a##ULL << 48) + (b##ULL << 32) + (c##ULL << 16) + d##ULL)
38 // The name of the DLL -- in LOWERCASE! It will be compared to
39 // a lowercase version of the DLL name only.
42 // If maxVersion is ALL_VERSIONS, we'll block all versions of this
43 // dll. Otherwise, we'll block all versions less than or equal to
44 // the given version, as queried by GetFileVersionInfo and
45 // VS_FIXEDFILEINFO's dwFileVersionMS and dwFileVersionLS fields.
47 // Note that the version is usually 4 components, which is A.B.C.D
48 // encoded as 0x AAAA BBBB CCCC DDDD ULL (spaces added for clarity),
49 // but it's not required to be of that format.
51 // If the USE_TIMESTAMP flag is set, then we use the timestamp from
52 // the IMAGE_FILE_HEADER in lieu of a version number.
53 unsigned long long maxVersion
;
57 BLOCK_WIN8PLUS_ONLY
= 1,
63 static DllBlockInfo sWindowsDllBlocklist
[] = {
65 // { "uxtheme.dll", ALL_VERSIONS },
66 // { "uxtheme.dll", 0x0000123400000000ULL },
67 // The DLL name must be in lowercase!
69 // NPFFAddon - Known malware
70 { "npffaddon.dll", ALL_VERSIONS
},
72 // AVG 8 - Antivirus vendor AVG, old version, plugin already blocklisted
73 {"avgrsstx.dll", MAKE_VERSION(8,5,0,401)},
75 // calc.dll - Suspected malware
76 {"calc.dll", MAKE_VERSION(1,0,0,1)},
78 // hook.dll - Suspected malware
79 {"hook.dll", ALL_VERSIONS
},
81 // GoogleDesktopNetwork3.dll - Extremely old, unversioned instances
82 // of this DLL cause crashes
83 {"googledesktopnetwork3.dll", UNVERSIONED
},
85 // rdolib.dll - Suspected malware
86 {"rdolib.dll", MAKE_VERSION(6,0,88,4)},
88 // fgjk4wvb.dll - Suspected malware
89 {"fgjk4wvb.dll", MAKE_VERSION(8,8,8,8)},
91 // radhslib.dll - Naomi internet filter - unmaintained since 2006
92 {"radhslib.dll", UNVERSIONED
},
94 // Music download filter for vkontakte.ru - old instances
95 // of this DLL cause crashes
96 {"vksaver.dll", MAKE_VERSION(2,2,2,0)},
98 // Topcrash in Firefox 4.0b1
99 {"rlxf.dll", MAKE_VERSION(1,2,323,1)},
101 // psicon.dll - Topcrashes in Thunderbird, and some crashes in Firefox
102 // Adobe photoshop library, now redundant in later installations
103 {"psicon.dll", ALL_VERSIONS
},
105 // Topcrash in Firefox 4 betas (bug 618899)
106 {"accelerator.dll", MAKE_VERSION(3,2,1,6)},
108 // Topcrash with Roboform in Firefox 8 (bug 699134)
109 {"rf-firefox.dll", MAKE_VERSION(7,6,1,0)},
110 {"roboform.dll", MAKE_VERSION(7,6,1,0)},
112 // Topcrash with Babylon Toolbar on FF16+ (bug 721264)
113 {"babyfox.dll", ALL_VERSIONS
},
115 // sprotector.dll crashes, bug 957258
116 {"sprotector.dll", ALL_VERSIONS
},
118 // Topcrash with Websense Endpoint, bug 828184
119 {"qipcap.dll", MAKE_VERSION(7, 6, 815, 1)},
121 // leave these two in always for tests
122 { "mozdllblockingtest.dll", ALL_VERSIONS
},
123 { "mozdllblockingtest_versioned.dll", 0x0000000400000000ULL
},
125 // Windows Media Foundation FLAC decoder and type sniffer (bug 839031).
126 { "mfflac.dll", ALL_VERSIONS
},
128 // Older Relevant Knowledge DLLs cause us to crash (bug 904001).
129 { "rlnx.dll", MAKE_VERSION(1, 3, 334, 9) },
130 { "pmnx.dll", MAKE_VERSION(1, 3, 334, 9) },
131 { "opnx.dll", MAKE_VERSION(1, 3, 334, 9) },
132 { "prnx.dll", MAKE_VERSION(1, 3, 334, 9) },
134 // Older belgian ID card software causes Firefox to crash or hang on
135 // shutdown, bug 831285 and 918399.
136 { "beid35cardlayer.dll", MAKE_VERSION(3, 5, 6, 6968) },
138 // bug 925459, bitguard crashes
139 { "bitguard.dll", ALL_VERSIONS
},
141 // bug 812683 - crashes in Windows library when Asus Gamer OSD is installed
142 // Software is discontinued/unsupported
143 { "atkdx11disp.dll", ALL_VERSIONS
},
145 // Topcrash with Conduit SearchProtect, bug 944542
146 { "spvc32.dll", ALL_VERSIONS
},
148 // XP topcrash with F-Secure, bug 970362
149 { "fs_ccf_ni_umh32.dll", MAKE_VERSION(1, 42, 101, 0), DllBlockInfo::BLOCK_XP_ONLY
},
151 // Topcrash with V-bates, bug 1002748 and bug 1023239
152 { "libinject.dll", UNVERSIONED
},
153 { "libinject2.dll", 0x537DDC93, DllBlockInfo::USE_TIMESTAMP
},
154 { "libredir2.dll", 0x5385B7ED, DllBlockInfo::USE_TIMESTAMP
},
156 // Crashes with RoboForm2Go written against old SDK, bug 988311
157 { "rf-firefox-22.dll", ALL_VERSIONS
},
159 // Crashes with DesktopTemperature, bug 1046382
160 { "dtwxsvc.dll", 0x53153234, DllBlockInfo::USE_TIMESTAMP
},
165 #ifndef STATUS_DLL_NOT_FOUND
166 #define STATUS_DLL_NOT_FOUND ((DWORD)0xC0000135L)
169 // define this for very verbose dll load debug spew
170 #undef DEBUG_very_verbose
172 static const char kBlockedDllsParameter
[] = "BlockedDllList=";
173 static const int kBlockedDllsParameterLen
=
174 sizeof(kBlockedDllsParameter
) - 1;
176 static const char kBlocklistInitFailedParameter
[] = "BlocklistInitFailed=1\n";
177 static const int kBlocklistInitFailedParameterLen
=
178 sizeof(kBlocklistInitFailedParameter
) - 1;
180 static const char kUser32BeforeBlocklistParameter
[] = "User32BeforeBlocklist=1\n";
181 static const int kUser32BeforeBlocklistParameterLen
=
182 sizeof(kUser32BeforeBlocklistParameter
) - 1;
184 static DWORD sThreadLoadingXPCOMModule
;
185 static bool sBlocklistInitFailed
;
186 static bool sUser32BeforeBlocklist
;
188 // Duplicated from xpcom glue. Ideally this should be shared.
190 printf_stderr(const char *fmt
, ...)
192 if (IsDebuggerPresent()) {
196 vsnprintf(buf
, sizeof(buf
), fmt
, args
);
197 buf
[sizeof(buf
) - 1] = '\0';
199 OutputDebugStringA(buf
);
202 FILE *fp
= _fdopen(_dup(2), "a");
208 vfprintf(fp
, fmt
, args
);
216 typedef NTSTATUS (NTAPI
*LdrLoadDll_func
) (PWCHAR filePath
, PULONG flags
, PUNICODE_STRING moduleFileName
, PHANDLE handle
);
218 static LdrLoadDll_func stub_LdrLoadDll
= 0;
222 RVAMap(HANDLE map
, DWORD offset
) {
224 GetSystemInfo(&info
);
226 DWORD alignedOffset
= (offset
/ info
.dwAllocationGranularity
) *
227 info
.dwAllocationGranularity
;
229 MOZ_ASSERT(offset
- alignedOffset
< info
.dwAllocationGranularity
, "Wtf");
231 mRealView
= ::MapViewOfFile(map
, FILE_MAP_READ
, 0, alignedOffset
,
232 sizeof(T
) + (offset
- alignedOffset
));
234 mMappedView
= mRealView
? reinterpret_cast<T
*>((char*)mRealView
+ (offset
- alignedOffset
)) :
239 ::UnmapViewOfFile(mRealView
);
242 operator const T
*() const { return mMappedView
; }
243 const T
* operator->() const { return mMappedView
; }
245 const T
* mMappedView
;
250 CheckASLR(const wchar_t* path
)
254 HANDLE file
= ::CreateFileW(path
, GENERIC_READ
, FILE_SHARE_READ
,
255 nullptr, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
,
257 if (file
!= INVALID_HANDLE_VALUE
) {
258 HANDLE map
= ::CreateFileMappingW(file
, nullptr, PAGE_READONLY
, 0, 0,
261 RVAMap
<IMAGE_DOS_HEADER
> peHeader(map
, 0);
263 RVAMap
<IMAGE_NT_HEADERS
> ntHeader(map
, peHeader
->e_lfanew
);
265 // If the DLL has no code, permit it regardless of ASLR status.
266 if (ntHeader
->OptionalHeader
.SizeOfCode
== 0) {
269 // Check to see if the DLL supports ASLR
270 else if ((ntHeader
->OptionalHeader
.DllCharacteristics
&
271 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
) != 0) {
285 GetTimestamp(const wchar_t* path
)
289 HANDLE file
= ::CreateFileW(path
, GENERIC_READ
, FILE_SHARE_READ
,
290 nullptr, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
,
292 if (file
!= INVALID_HANDLE_VALUE
) {
293 HANDLE map
= ::CreateFileMappingW(file
, nullptr, PAGE_READONLY
, 0, 0,
296 RVAMap
<IMAGE_DOS_HEADER
> peHeader(map
, 0);
298 RVAMap
<IMAGE_NT_HEADERS
> ntHeader(map
, peHeader
->e_lfanew
);
300 timestamp
= ntHeader
->FileHeader
.TimeDateStamp
;
311 // This lock protects both the reentrancy sentinel and the crash reporter
313 static CRITICAL_SECTION sLock
;
316 * Some versions of Windows call LoadLibraryEx to get the version information
317 * for a DLL, which causes our patched LdrLoadDll implementation to re-enter
318 * itself and cause infinite recursion and a stack-exhaustion crash. We protect
319 * against reentrancy by allowing recursive loads of the same DLL.
321 * Note that we don't use __declspec(thread) because that doesn't work in DLLs
322 * loaded via LoadLibrary and there can be a limited number of TLS slots, so
325 class ReentrancySentinel
328 explicit ReentrancySentinel(const char* dllName
)
330 DWORD currentThreadId
= GetCurrentThreadId();
331 AutoCriticalSection
lock(&sLock
);
332 mPreviousDllName
= (*sThreadMap
)[currentThreadId
];
334 // If there is a DLL currently being loaded and it has the same name
335 // as the current attempt, we're re-entering.
336 mReentered
= mPreviousDllName
&& !stricmp(mPreviousDllName
, dllName
);
337 (*sThreadMap
)[currentThreadId
] = dllName
;
340 ~ReentrancySentinel()
342 DWORD currentThreadId
= GetCurrentThreadId();
343 AutoCriticalSection
lock(&sLock
);
344 (*sThreadMap
)[currentThreadId
] = mPreviousDllName
;
352 static void InitializeStatics()
354 InitializeCriticalSection(&sLock
);
355 sThreadMap
= new std::map
<DWORD
, const char*>;
359 static std::map
<DWORD
, const char*>* sThreadMap
;
361 const char* mPreviousDllName
;
365 std::map
<DWORD
, const char*>* ReentrancySentinel::sThreadMap
;
368 * This is a linked list of DLLs that have been blocked. It doesn't use
369 * mozilla::LinkedList because this is an append-only list and doesn't need
370 * to be doubly linked.
375 static void Add(const char* name
, unsigned long long version
);
377 // Write the list of blocked DLLs to a file HANDLE. This method is run after
378 // a crash occurs and must therefore not use the heap, etc.
379 static void Write(HANDLE file
);
382 DllBlockSet(const char* name
, unsigned long long version
)
389 const char* mName
; // points into the sWindowsDllBlocklist string
390 unsigned long long mVersion
;
393 static DllBlockSet
* gFirst
;
396 DllBlockSet
* DllBlockSet::gFirst
;
399 DllBlockSet::Add(const char* name
, unsigned long long version
)
401 AutoCriticalSection
lock(&sLock
);
402 for (DllBlockSet
* b
= gFirst
; b
; b
= b
->mNext
) {
403 if (0 == strcmp(b
->mName
, name
) && b
->mVersion
== version
) {
407 // Not already present
408 DllBlockSet
* n
= new DllBlockSet(name
, version
);
414 DllBlockSet::Write(HANDLE file
)
416 AutoCriticalSection
lock(&sLock
);
419 // Because this method is called after a crash occurs, and uses heap memory,
420 // protect this entire block with a structured exception handler.
422 for (DllBlockSet
* b
= gFirst
; b
; b
= b
->mNext
) {
423 // write name[,v.v.v.v];
424 WriteFile(file
, b
->mName
, strlen(b
->mName
), &nBytes
, nullptr);
425 if (b
->mVersion
!= -1) {
426 WriteFile(file
, ",", 1, &nBytes
, nullptr);
428 parts
[0] = b
->mVersion
>> 48;
429 parts
[1] = (b
->mVersion
>> 32) & 0xFFFF;
430 parts
[2] = (b
->mVersion
>> 16) & 0xFFFF;
431 parts
[3] = b
->mVersion
& 0xFFFF;
432 for (int p
= 0; p
< 4; ++p
) {
434 ltoa(parts
[p
], buf
, 10);
435 WriteFile(file
, buf
, strlen(buf
), &nBytes
, nullptr);
437 WriteFile(file
, ".", 1, &nBytes
, nullptr);
441 WriteFile(file
, ";", 1, &nBytes
, nullptr);
444 MOZ_SEH_EXCEPT (EXCEPTION_EXECUTE_HANDLER
) { }
448 wchar_t* getFullPath (PWCHAR filePath
, wchar_t* fname
)
450 // In Windows 8, the first parameter seems to be used for more than just the
451 // path name. For example, its numerical value can be 1. Passing a non-valid
452 // pointer to SearchPathW will cause a crash, so we need to check to see if we
453 // are handed a valid pointer, and otherwise just pass nullptr to SearchPathW.
454 PWCHAR sanitizedFilePath
= (intptr_t(filePath
) < 1024) ? nullptr : filePath
;
456 // figure out the length of the string that we need
457 DWORD pathlen
= SearchPathW(sanitizedFilePath
, fname
, L
".dll", 0, nullptr,
463 wchar_t* full_fname
= new wchar_t[pathlen
+1];
465 // couldn't allocate memory?
469 // now actually grab it
470 SearchPathW(sanitizedFilePath
, fname
, L
".dll", pathlen
+ 1, full_fname
,
475 // No builtin function to find the last character matching a set
476 static wchar_t* lastslash(wchar_t* s
, int len
)
478 for (wchar_t* c
= s
+ len
- 1; c
>= s
; --c
) {
479 if (*c
== L
'\\' || *c
== L
'/') {
486 static NTSTATUS NTAPI
487 patched_LdrLoadDll (PWCHAR filePath
, PULONG flags
, PUNICODE_STRING moduleFileName
, PHANDLE handle
)
489 // We have UCS2 (UTF16?), we want ASCII, but we also just want the filename portion
490 #define DLLNAME_MAX 128
491 char dllName
[DLLNAME_MAX
+1];
496 int len
= moduleFileName
->Length
/ 2;
497 wchar_t *fname
= moduleFileName
->Buffer
;
498 nsAutoArrayPtr
<wchar_t> full_fname
;
500 // The filename isn't guaranteed to be null terminated, but in practice
501 // it always will be; ensure that this is so, and bail if not.
502 // This is done instead of the more robust approach because of bug 527122,
503 // where lots of weird things were happening when we tried to make a copy.
504 if (moduleFileName
->MaximumLength
< moduleFileName
->Length
+2 ||
508 printf_stderr("LdrLoadDll: non-null terminated string found!\n");
510 goto continue_loading
;
513 dll_part
= lastslash(fname
, len
);
515 dll_part
= dll_part
+ 1;
516 len
-= dll_part
- fname
;
521 #ifdef DEBUG_very_verbose
522 printf_stderr("LdrLoadDll: dll_part '%S' %d\n", dll_part
, len
);
525 // if it's too long, then, we assume we won't want to block it,
526 // since DLLNAME_MAX should be at least long enough to hold the longest
527 // entry in our blocklist.
528 if (len
> DLLNAME_MAX
) {
530 printf_stderr("LdrLoadDll: len too long! %d\n", len
);
532 goto continue_loading
;
535 // copy over to our char byte buffer, lowercasing ASCII as we go
536 for (int i
= 0; i
< len
; i
++) {
537 wchar_t c
= dll_part
[i
];
540 // welp, it's not ascii; if we need to add non-ascii things to
541 // our blocklist, we'll have to remove this limitation.
542 goto continue_loading
;
545 // ensure that dll name is all lowercase
546 if (c
>= 'A' && c
<= 'Z')
549 dllName
[i
] = (char) c
;
554 #ifdef DEBUG_very_verbose
555 printf_stderr("LdrLoadDll: dll name '%s'\n", dllName
);
558 // Block a suspicious binary that uses various 12-digit hex strings
559 // e.g. MovieMode.48CA2AEFA22D.dll (bug 973138)
560 dot
= strchr(dllName
, '.');
561 if (dot
&& (strchr(dot
+1, '.') == dot
+13)) {
562 char * end
= nullptr;
563 _strtoui64(dot
+1, &end
, 16);
565 return STATUS_DLL_NOT_FOUND
;
569 // then compare to everything on the blocklist
570 info
= &sWindowsDllBlocklist
[0];
572 if (strcmp(info
->name
, dllName
) == 0)
579 bool load_ok
= false;
581 #ifdef DEBUG_very_verbose
582 printf_stderr("LdrLoadDll: info->name: '%s'\n", info
->name
);
585 if ((info
->flags
== DllBlockInfo::BLOCK_WIN8PLUS_ONLY
) &&
587 goto continue_loading
;
590 if ((info
->flags
== DllBlockInfo::BLOCK_XP_ONLY
) &&
591 IsWin2003OrLater()) {
592 goto continue_loading
;
595 unsigned long long fVersion
= ALL_VERSIONS
;
597 if (info
->maxVersion
!= ALL_VERSIONS
) {
598 ReentrancySentinel
sentinel(dllName
);
599 if (sentinel
.BailOut()) {
600 goto continue_loading
;
603 full_fname
= getFullPath(filePath
, fname
);
605 // uh, we couldn't find the DLL at all, so...
606 printf_stderr("LdrLoadDll: Blocking load of '%s' (SearchPathW didn't find it?)\n", dllName
);
607 return STATUS_DLL_NOT_FOUND
;
610 if (info
->flags
& DllBlockInfo::USE_TIMESTAMP
) {
611 fVersion
= GetTimestamp(full_fname
);
612 if (fVersion
> info
->maxVersion
) {
617 DWORD infoSize
= GetFileVersionInfoSizeW(full_fname
, &zero
);
619 // If we failed to get the version information, we block.
622 nsAutoArrayPtr
<unsigned char> infoData(new unsigned char[infoSize
]);
623 VS_FIXEDFILEINFO
*vInfo
;
626 if (GetFileVersionInfoW(full_fname
, 0, infoSize
, infoData
) &&
627 VerQueryValueW(infoData
, L
"\\", (LPVOID
*) &vInfo
, &vInfoLen
))
630 ((unsigned long long)vInfo
->dwFileVersionMS
) << 32 |
631 ((unsigned long long)vInfo
->dwFileVersionLS
);
633 // finally do the version check, and if it's greater than our block
634 // version, keep loading
635 if (fVersion
> info
->maxVersion
)
643 printf_stderr("LdrLoadDll: Blocking load of '%s' -- see http://www.mozilla.com/en-US/blocklist/\n", dllName
);
644 DllBlockSet::Add(info
->name
, fVersion
);
645 return STATUS_DLL_NOT_FOUND
;
650 #ifdef DEBUG_very_verbose
651 printf_stderr("LdrLoadDll: continuing load... ('%S')\n", moduleFileName
->Buffer
);
654 if (GetCurrentThreadId() == sThreadLoadingXPCOMModule
) {
655 // Check to ensure that the DLL has ASLR.
656 full_fname
= getFullPath(filePath
, fname
);
658 // uh, we couldn't find the DLL at all, so...
659 printf_stderr("LdrLoadDll: Blocking load of '%s' (SearchPathW didn't find it?)\n", dllName
);
660 return STATUS_DLL_NOT_FOUND
;
663 if (IsVistaOrLater() && !CheckASLR(full_fname
)) {
664 printf_stderr("LdrLoadDll: Blocking load of '%s'. XPCOM components must support ASLR.\n", dllName
);
665 return STATUS_DLL_NOT_FOUND
;
669 return stub_LdrLoadDll(filePath
, flags
, moduleFileName
, handle
);
672 WindowsDllInterceptor NtDllIntercept
;
674 } // anonymous namespace
677 DllBlocklist_Initialize()
679 if (GetModuleHandleA("user32.dll")) {
680 sUser32BeforeBlocklist
= true;
683 NtDllIntercept
.Init("ntdll.dll");
685 ReentrancySentinel::InitializeStatics();
687 // We specifically use a detour, because there are cases where external
688 // code also tries to hook LdrLoadDll, and doesn't know how to relocate our
689 // nop space patches. (Bug 951827)
690 bool ok
= NtDllIntercept
.AddDetour("LdrLoadDll", reinterpret_cast<intptr_t>(patched_LdrLoadDll
), (void**) &stub_LdrLoadDll
);
693 sBlocklistInitFailed
= true;
695 printf_stderr ("LdrLoadDll hook failed, no dll blocklisting active\n");
701 DllBlocklist_SetInXPCOMLoadOnMainThread(bool inXPCOMLoadOnMainThread
)
703 if (inXPCOMLoadOnMainThread
) {
704 MOZ_ASSERT(sThreadLoadingXPCOMModule
== 0, "Only one thread should be doing this");
705 sThreadLoadingXPCOMModule
= GetCurrentThreadId();
707 sThreadLoadingXPCOMModule
= 0;
712 DllBlocklist_WriteNotes(HANDLE file
)
716 WriteFile(file
, kBlockedDllsParameter
, kBlockedDllsParameterLen
, &nBytes
, nullptr);
717 DllBlockSet::Write(file
);
718 WriteFile(file
, "\n", 1, &nBytes
, nullptr);
720 if (sBlocklistInitFailed
) {
721 WriteFile(file
, kBlocklistInitFailedParameter
,
722 kBlocklistInitFailedParameterLen
, &nBytes
, nullptr);
725 if (sUser32BeforeBlocklist
) {
726 WriteFile(file
, kUser32BeforeBlocklistParameter
,
727 kUser32BeforeBlocklistParameterLen
, &nBytes
, nullptr);