In a previous posting, I discussed some of the pitfalls of unhandled exception filters (and how they can become a security problem for your application). I mentioned some guidelines you can use to help work around these problems and minimize the risk, but, as I alluded to earlier, the problem is actually worse than it might appear on the surface.
The real gotcha about unhandled exception filters is that you have probably used them before in programs or DLLs and not even known that you were using them, which makes it very hard to not use them in dangerous situations. How can this be, you might ask? Well, it turns out that the Microsoft C runtime library uses an unhandled exception filter to catch unhandled C++ exceptions and call the terminate handler registered by set_terminate.
This unhandled exception filter is setup by the internal CRT functions _cinit (via _initterm_e). If you have the CRT source handy, this lives in crt0dat.c. The call looks like:
/* * do initializations */ initret = _initterm_e( __xi_a, __xi_z );
Here, “__xi_a” and “__xi_z” define the bounds of an array of function pointers to initializers called during the CRT’s initialization. There is a pointer to a function (_CxxSetUnhandledExceptionFilter) that sets up the unhandled exception filter for C++ exceptions in this array. Unfortunately, source code for the function used to setup _CxxUnhandledExceptionFilter is not present, but you can find it by looking at the CRT in a disassembler.
push offset CxxUnhandledExceptionFilter call SetUnhandledExceptionFilter mov lpTopLevelExceptionFilter, eax xor eax, eax retn
This is pretty standard; it is just saving away the old exception filter and registering its new exception filter. The unhandled exception filter itself checks for a C++ exception – if found, it calls terminate, otherwise it tries to verify that the previous exception filter points to executable code, and if so, it will call it.
push esi mov esi, [esp+arg_0] mov eax, [esi] cmp dword ptr [eax], 0E06D7363h jnz short not_cpp_except cmp dword ptr [eax+10h], 3 jnz short not_cpp_except mov eax, [eax+14h] cmp eax, 19930520h jz short is_cpp_except cmp eax, 19930521h jnz short not_cpp_except is_cpp_except: call terminate not_cpp_except: mov eax, lpTopLevelExceptionFilter test eax, eax jz short old_filter_unloaded push eax ; lpfn call _ValidateExecute test eax, eax pop ecx jz short old_filter_unloaded push esi call lpTopLevelExceptionFilter jmp short done old_filter_unloaded: xor eax, eax done: pop esi retn 4
The problem with the latter validation is there is no way to tell if the code is part of a legitimate DLL, or part of the heap or some other allocation that has moved over where a DLL had previously been unloaded, which is where the security risk is introduced.
So, we have established that the CRT potentially does bad things by installing an unhandled exception filter – so what? Well, if you link to the DLL version of the CRT, you are probably fine. The CRT DLL is unlikely to be unloaded during the process lifetime and will only be initialized once.
The kicker is if you linked to the static (non-DLL) version of the CRT. This is where things start to get dicey. The dangerous combination here is that each image linked to the static version of the CRT will have its own copy of _cinit, and its own copy of _CxxSetUnhandledExceptionFilter, its own copy of _CxxUnhandledExceptionFilter, and soforth. What this boils down to is that every image linked to the static version of the Microsoft C runtime installs an unhandled exception filter. So, if you have a DLL (say one that hosts an ActiveX object) which links to the static CRT (which is pretty attractive, as for plugin type DLLs you don’t want to have to write a separate installer to ensure that end users have that cumbersome msvcr80.dll), then you’re in trouble. Since this is an especially common scenario (plugin DLL linking to the static CRT), you have probably ended up using an unhandled exception filter without knowing it (and probably without realizing the implications of doing so) – simply by making an ActiveX control usable by Internet Explorer, for example. This really turns into a worst case scenario when it comes to DLLs that host ActiveX objects. These are DLLs that are going to be frequently loaded and unloaded, are controllable by untrusted script, and are very likely to link to the static CRT to get out of the headache of having to manage installation of the DLL CRT version. If you put all of these things together and throw in any kind of crash bug, you’ve got a recipie for remote code execution. What is even worse is that this isn’t just quick-fixable with a patch to the CRT, as the vulnerable CRT version is compiled into your binaries and not in its own hotfixable standalone DLL.
So, in order to be truly safe from the dangers of unhandled exception filters, you also need to rid your programs of the static CRT. Yes, it does make setup more of a pain, but the DLL CRT is superior in many ways (not to mention that it doesn’t suffer from this security problem!).
Hi Skywing,
I’ve just been reading ‘Beware of custom unhandled exception filters in DLLs’ and this article. Thanks a lot for this very detailed explanation of this pitfall (I’m also using this api’s in an application with the big advantange of detecting the source of almost any unhandled Exception but as pointed out also the risk of introducing new crashes (and even security holes).
I think it is almost the same with _set_se_translator apis.
Are you aware of any >clean
The way I typically do this is by hooking SetUnhandledExceptionFilter and disallowing further registrations after I register my SEH. Yes, it’s a hack, but it’s been unfortunately better than the documented API as far as working reliably and not breaking when used with unloading DLLs in my experience.
[…] [Skywing] You might be using UEFs without even knowing it. […]
[…] [Skywing] You might be using UEFs without even knowing it. […]