http://bugs.winehq.org/show_bug.cgi?id=7679
------- Additional Comments From focht@gmx.net 2007-26-04 19:16 ------- Hello,
i read some discussion on developer list regarding this one ... so it catched my interest :)
Fired up my debugger and let go (+few cups of coffee).
In short: it's an interesting (subtile) bug in wine trigged by unusual PE section protection flags :)
First, the root cause is a c++ exception generated by precompiled boost::python module which should not hurt at all. When being passed to upper exception handler chains it finally arrives at a point where the application tries to make sense of it. That task is accomplished by a hooking library which exports several APIs to gather process/module information, hook either by patching the import address tables of all modules (1st level hooker) and/or to patch entrypoints of system functions itself (2nd level hooker, trampolines).
The hooker enumerates all modules and their import tables. It modifies the page protection attributes to get write access to the IAT of each module. This is done by a series of VirtualQuery/VirtualProtect.
To answer the question "lots of access violations being seen in debugger": completely harmless and expected. The code checks if page write access has already been enabled to system functions code area by using IsBadWritePtr (2nd level hooking/trampoline). Checks for trampolines code (e.g. insertion of jmp/call opcodes on entry) are made too.
Well, back to topic. One module "Blowfish.pyd" has a rather unusal protection attributes combination to its .idata (imports) section: C0000040 -> readable | writable | initialized data. Usually it should be: readable | initialized data .. i'm pretty sure it was on accident by developers.
This triggers the interesting wine bug. With the module being mapped by wine loader, the page gets PAGE_WRITECOPY attribute. At one place, the hooker basically does following:
if( VirtualQuery( IAT_addr_of_module, &info, sizeof(info)) == sizeof(info)) { DWORD prot = info.AllocationProtect & (PAGE_NOACCESS | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY); prot |= PAGE_READWRITE;
if( VirtualProtect( IAT_addr_of_module, ... , prot, &oldProtect)) { // access the IAT } }
Now the problem: VirtualQuery() -> info.AllocationProtect -> returns 0x8 = PAGE_WRITECOPY for this specific module. That gets masked and or'd PAGE_READWRITE: 0xC -> PAGE_WRITECOPY | PAGE_READWRITE and the passed to VirtualProtect(). Guess. VirtualProtect() returns TRUE and the hooker tries to access IAT area. *boom* Now you have literally a "double fault" - the module which should gather exception information produced exception by itself :)
You may ask where is the wine bug? Well, wine should never have returned TRUE on VirtualProtect with PAGE_READWRITE and PAGE_WRITECOPY flags both set. This is a very little known (undocumented) limitation starting with Windows 2000 and higher. I had this issue some years ago - that's why I remembered this one. The mode attribues PAGE_READWRITE and PAGE_WRITECOPY are mutually exclusive. You can test it by yourself on windows systems (Win2k, WinXp), VirtualProtect will fail if both set.
For verification that my findings are correct you have to options:
- change the section protection of "Blowfish.pyd" .idata section to 0x40000040 (read | initialized data). VirtualProtect will succeed and the app finally starts up with login screen.
- fix wine itself: VirtualProtect( PAGE_READWRITE | PAGE_WRITECOPY) on Win2K+ must return FALSE. PAGE_WRITECOPY is not supported by Win9X. Though I couldnt verify NT4. I'd say: prohibit this combination at all.
Actually these are the bugs i like (along with stack smashers). I had some fun :)
Regards