惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Secure Thoughts
雷峰网
雷峰网
罗磊的独立博客
T
The Blog of Author Tim Ferriss
阮一峰的网络日志
阮一峰的网络日志
量子位
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
人人都是产品经理
人人都是产品经理
GbyAI
GbyAI
Cisco Talos Blog
Cisco Talos Blog
Engineering at Meta
Engineering at Meta
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
A
About on SuperTechFans
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Cloudflare Blog
Know Your Adversary
Know Your Adversary
T
Threat Research - Cisco Blogs
Spread Privacy
Spread Privacy
D
DataBreaches.Net
T
The Exploit Database - CXSecurity.com
K
Kaspersky official blog
Cyberwarzone
Cyberwarzone
爱范儿
爱范儿
U
Unit 42
Security Latest
Security Latest
M
MIT News - Artificial intelligence
月光博客
月光博客
Scott Helme
Scott Helme
G
Google Developers Blog
有赞技术团队
有赞技术团队
T
Tor Project blog
宝玉的分享
宝玉的分享
Y
Y Combinator Blog
博客园 - Franky
H
Hackread – Cybersecurity News, Data Breaches, AI and More
aimingoo的专栏
aimingoo的专栏
The GitHub Blog
The GitHub Blog
V
V2EX
B
Blog
Apple Machine Learning Research
Apple Machine Learning Research
S
Securelist
博客园 - 三生石上(FineUI控件)
Blog — PlanetScale
Blog — PlanetScale
TaoSecurity Blog
TaoSecurity Blog
Stack Overflow Blog
Stack Overflow Blog
P
Proofpoint News Feed
腾讯CDC
D
Docker
Google Online Security Blog
Google Online Security Blog

The Old New Thing

Why has the display control panel pointer truncation bug gone unfixed for so long? - The Old New Thing Speculating on how the buggy control panel extension truncated a value that it had right in front of it - The Old New Thing The case of the invalid function pointer when shutting down the display control panel - The Old New Thing Microspeak: Double-click and drill down - The Old New Thing Why don't we just make the entire stack out of guard pages? - The Old New Thing The case of the mysterious changes to integers when there shouldn't have been any code generation effect - The Old New Thing I've decoded a #pragma detect_mismatch error and fixed the mismatch, but I still get the error - The Old New Thing The other kind of control flow guard check: The combined validate and call - The Old New Thing How did Windows 95 decide that a setup program ran? - The Old New Thing I opened a file with FILE_FLAG_DELETE_ON_CLOSE, but now I changed my mind - The Old New Thing How did we conclude that CcNamespace.dll was the ringleader of a group of DLLs that unloaded prematurely? - The Old New Thing It rather involved being on the other side of this airtight hatchway: Changing administrative settings - The Old New Thing 2026 mid-year link clearance - The Old New Thing A compatibility note on the abuse of Windows window class extra bytes - The Old New Thing The evolution of window and class extra bytes in Windows - The Old New Thing The case of the DLL that was not present in memory despite not being formally unloaded, part 2 - The Old New Thing Raymond's hot take on Hainanese chicken - The Old New Thing The case of the DLL that was not present in memory despite not being formally unloaded, part 1 - The Old New Thing Cancellation of Windows Runtime activities is asynchronous - The Old New Thing Microspeak elaborated: Isn't escrow just a release candidate by another name? - The Old New Thing In memory of the man who put red and green squiggles under words - The Old New Thing What does it mean when the bottom bit of my HMODULE is set? - The Old New Thing Why doesn't Get­Last­Input­Info() return info for the user I'm impersonating? - The Old New Thing Windows stack limit checking retrospective, follow-up - The Old New Thing Retrofitting the WM_COPY­DATA message onto Windows 3.1 - The Old New Thing The time the x86 emulator team found code so bad that they fixed it during emulation - The Old New Thing How can I schedule work on a thread pool with low latency? Understanding the rationale behind a rule when trying to circumvent it What’s the opposite of Clip­Cursor that lets me exclude the cursor from a region? The Microsoft Company Party where everybody played name tag swap Rotation revisited: Shuffling more than three blocks, and other small notes The back cover of C++: The Programming Language also raises questions not answered by the front cover Rotation revisited: Avoiding having to calculate the gcd when doing cycle decomposition Rotation revisited: Cycle decomposition in clang’s libcxx Rotation revisited: A shocking discovery about gcc’s unidirectional rotation algorithm Rotation revisited: Another unidirectional algorithm The placeholder name for the Windows 8 experience was “modern” Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 3 Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 2 Sharing the result of a single Windows Runtime IAsyncOperation among multiple coroutines, part 1 If C# and JavaScript lets me await a Windows Runtime asynchronous operation more than once, why not C++/WinRT? A hypothetical redesign of System.Diagnostics.Process to avoid confusion over properties that are valid only when you are the one who called Start Why do you say that a COM STA thread must pump messages if I see sample code creating STA threads and not pumping messages? How do I use Win32 structures from the Windows Runtime? What is the history of the ERROR_ARENA_TRASHED error code? The classic TreeView control lets me sort by name or by lParam, but why not both? Just shows that nobody cares about debugging the parity flag any more The case of the Create­File­Mapping that always reported ERROR_ALREADY_EXISTS
The case of the thread executing from an unloaded third-party DLL - The Old New Thing
Raymond Chen · 2026-07-02 · via The Old New Thing

The Explorer team was investigating a crash that was occuring at a relatively high rate and found that it took the form of a thread executing from an unloaded third-party DLL.

0:173> k
RetAddr               Call Site
00000000`557c5820     <Unloaded_LibUtils_CloudNs_3.dll>+0x265fe
00000000`00000008     <Unloaded_LibUtils_CloudNs_3.dll>+0x2b5820
00000000`0000000e     0x8
00000000`00000008     0xe
00000000`557c8c18     0x8
ffffffff`fffffffe     <Unloaded_LibUtils_CloudNs_3.dll>+0x2b8c18
00000000`00000000     0xffffffff`fffffffe

There isn’t much on the stack at all.

0:173> dps @rsp
00000000`1248f920  00000000`557c5820 <Unloaded_LibUtils_CloudNs_3.dll>+0x2b5820
00000000`1248f928  00000000`00000008
00000000`1248f930  00000000`0000000e
00000000`1248f938  00000000`00000008
00000000`1248f940  00000000`557c8c18 <Unloaded_LibUtils_CloudNs_3.dll>+0x2b8c18
00000000`1248f948  ffffffff`fffffffe
00000000`1248f950  00000000`00000000
00000000`1248f958  00000000`00000000
00000000`1248f960  00000000`00000000
00000000`1248f968  00000000`00000000
00000000`1248f970  00000000`00000000
00000000`1248f978  00000000`00000000
00000000`1248f980  00000000`00000000
00000000`1248f988  00007ff9`a2117344 kernel32!BaseThreadInitThunk+0x14
00000000`1248f990  00000000`00000000
00000000`1248f998  00000000`00000000

This is just a worker thread the operates entirely inside LibDB.CloudNs.3.dll. It doesn’t have a very deep stack, so I suspect that it’s idle and is waiting for work to do.

For these types of investigations, there usually isn’t much to see directly in the crashing thread. That thread is the victim. You have to do additional research to figure out who unloaded the DLL prematurely.

Some snooping around found another stack that involves this unloaded DLL:

0:159> k
RetAddr               Call Site
00007ff9`9fdbbea0     ntdll!ZwWaitForMultipleObjects+0x14
00007ff9`9fdbbd9e     KERNELBASE!WaitForMultipleObjectsEx+0xf0
00000000`554d65fe     KERNELBASE!WaitForMultipleObjects+0xe
00000000`55765820     <Unloaded_LibDB_CloudNs_3.dll>+0x965fe
00000000`00000003     <Unloaded_LibUtils_JsonNs_3.dll>+0x255820
00000000`00000004     0x3
00000000`00000008     0x4
00000000`55768c18     0x8
ffffffff`fffffffe     <Unloaded_LibUtils_CloudNs_3.dll>+0x258c18
00000000`00000000     0xffffffff`fffffffe

The most recently unloaded DLLs are

00007ff9`6d7c0000 00007ff9`6d80a000   FabrikamContextMenu.dll
00007ff9`115e0000 00007ff9`1172f000   LitWareSync.dll
00007ff9`643d0000 00007ff9`64681000   CcNamespace.dll
00000000`55440000 00000000`5550b000   LibDB_CloudNs_3.dll
00000000`55860000 00000000`55998000   LibNet_CloudNs_3.dll
00000000`557f0000 00000000`5585b000   LibJson_CloudNs_3.dll
00000000`55510000 00000000`557e7000   LibUtils	_CloudNs_3.dll
00000000`561a0000 00000000`56238000   MSVCP100.dll
00000000`56240000 00000000`56312000   MSVCR100.dll
00007ff9`85130000 00007ff9`85167000   EhStorShell.dll
00007ff9`3cac0000 00007ff9`3cb61000   wpdshext.dll
00007ff9`78a00000 00007ff9`78a26000   EhStorAPI.dll
00007ff9`686f0000 00007ff9`68754000   PlayToDevice.dll
00007ff9`67110000 00007ff9`6718d000   provsvc.dll

So the LibDB.CloudNs.3.dll that got unloaded is just part of an entire ecosystem of Lib*.CloudNs.3.dll dynamic libraries that all got unloaded together.

The ringleader of this operation appears to be CcNamespace.dll, which looks like the Contoso namespace extension that adds a “Contoso” node under My Computer This PC that gives you a view into all your Contoso things stored in the Contoso cloud service. All the other DLLs are helpers that the main CcNamespace.dll uses to accomplish its tasks.

The main CcNamespace.dll was loaded by Explorer as a shell extension, and its DllCanUnloadNow function was returning S_OK when there were no active references to objects in CcNamespace.dll. Unfortunately, when it said “Sure, it’s safe to unload me”, that linchpin DLL unloaded all its minions, unaware that one of the minions (the utility library) had spun up some worker threads.

You might think that the fix is to update the utility library’s DllCanUnloadNow to return S_FALSE if there are still busy background threads.¹ But that doesn’t work because the utility library is probably not a COM DLL in the first place. It’s just a traditional DLL that CcNamespace.dll uses, and it is CcNamespace.dll that is the COM DLL.

The DllCanUnloadNow in CcNamespace.dll could warn LibUtils.CloudNs.3.dll that it should start winding down, but you’re basically in a tricky spot because the DLL_PROCESS_ATTACH cannot wait for the worker thread to exit.

I think the way to go is for the worker thread to increment the DLL reference count when it starts its worker thread, and to use FreeLibraryAndExitThread to exit the worker thread. Alternatively, it could make its worker thread a threadpool thread and use FreeLibraryWhenCallbackReturns to request that the system decrement the DLL reference count when it finishes.

This is probably something the utility library should have done anyway. I suspect that the worker thread is not something that clients of the utility library are even aware of. It is just an implementation detail of the utility library, created without the knowledge of the main DLL.

Fortunately, the application compatibility team has a copy of Contoso Cloud in their library, so even though we couldn’t reproduce the crash, we were still able to confirm that CcNamespace.dll is indeed the shell extension DLL whose unloading triggers the unloading of all the dependent DLLs.

We were about to contact Contoso with our conclusions and suggestions for improvement, but we discovered that it would be pointless because Contoso discontinued that namespace extension years ago. They replaced it with a different way of integrating their cloud content into Windows; the only people using the namespace extension are those who still using an old version, either because they don’t want to pay for the upgrade, or because they are actively avoiding the upgrade because they like the old way.

Those customers are using a product that has gone out of support. Contoso doesn’t care about those old customers any more. Windows will have to fix it without Contoso’s help.

The Explorer team added an application compatibility flag for the Contoso Cloud namespace extension to say “When you load this shell extension, do a GetModuleHandleEx with the GET_MODULE_HANDLE_EX_FLAG_PIN flag so the DLL never unloads.” That way, even if the DLL says “Sure, go ahead and unload me, it’s totally safe, trust me,” and COM does a FreeLibrary, the DLL doesn’t actually unload.

¹ Even if you manage to get return DllCanUnloadNow to return S_FALSE, it doesn’t help if COM is being uninitialized. In that case, CoUninitalize will ask a DLL if it is okay to unload now, but the answer is a foregone conclusion: If COM is shutting down, COM is going to unload all the DLLs that it loaded. It asks you if you are okay with it, not because it cares what your answer is, but to give you a chance to do cleanup outside of DllMain.

Category

Topics

Author

Raymond Chen

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.