https://bugs.winehq.org/show_bug.cgi?id=44460
Bug ID: 44460 Summary: Wine's loader should prevent multiple executable mappings of images (dlls) backed by the same physical file (long path vs. short path) Product: Wine Version: 3.0 Hardware: x86-64 OS: Linux Status: NEW Severity: normal Priority: P2 Component: ntdll Assignee: wine-bugs@winehq.org Reporter: focht@gmx.net Distribution: ---
Hello folks,
split out from bug 35106 because there are multiple issues and this one affects a different area: Wine loader.
Currently, loading a dll with default load flags one time with long path name and the other time with short path name (= same physical file) results in two different mappings of the dll which creates problems with apps expecting unique instance data. In short: Wine's loader currently lacks a more elaborate identity check to prevent this case.
CMS DVR (video surveillance app) v1.0.0.8 uses multiple MFC-based ActiveX/COM servers.
Taking the first one 'WndManager.ocx' as example:
--- snip --- -=[ ProtectionID v0.6.9.0 DECEMBER]=- (c) 2003-2017 CDKiLLER & TippeX Build 24/12/17-21:05:42 Ready... Scanning -> C:\Program Files\CMS\WndManager.ocx File Type : 32-Bit Dll (Subsystem : Win GUI / 2), Size : 266240 (041000h) Byte(s) | Machine: 0x14C (I386) Compilation TimeStamp : 0x4B583655 -> Thu 21st Jan 2010 11:11:17 (GMT) [TimeStamp] 0x4B583655 -> Thu 21st Jan 2010 11:11:17 (GMT) | PE Header | - | Offset: 0x000000F8 | VA: 0x100000F8 | - [TimeStamp] 0x4B583655 -> Thu 21st Jan 2010 11:11:17 (GMT) | Export | - | Offset: 0x00020A74 | VA: 0x10020A74 | - [LoadConfig] CodeIntegrity -> Flags 0xA3F0 | Catalog 0x46 (70) | Catalog Offset 0x2000001 | Reserved 0x46A4A0 [LoadConfig] GuardAddressTakenIatEntryTable 0x8000011 | Count 0x46A558 (4629848) [LoadConfig] GuardLongJumpTargetTable 0x8000001 | Count 0x46A5F8 (4630008) [LoadConfig] HybridMetadataPointer 0x8000011 | DynamicValueRelocTable 0x46A66C [LoadConfig] FailFastIndirectProc 0x8000011 | FailFastPointer 0x46C360 [LoadConfig] UnknownZero1 0x8000011 [File Heuristics] -> Flag #1 : 00000000000000000000010100000000 (0x00000500) [Entrypoint Section Entropy] : 5.56 (section #0) ".text " | Size : 0x1B276 (111222) byte(s) [DllCharacteristics] -> Flag : (0x0000) -> NONE [SectionCount] 5 (0x5) | ImageSize 0x44000 (278528) byte(s) [Export] 100% of function(s) (4 of 4) are in file | 0 are forwarded | 4 code | 0 data | 0 uninit data | 0 unknown | [VersionInfo] Product Name : VideoWindow ActiveX Control Module [VersionInfo] Product Version : 1. 0. 0. 4 [VersionInfo] File Description : VideoWindow ActiveX Control Module [VersionInfo] File Version : 1. 0. 0. 4 [VersionInfo] Original FileName : VideoWindow.OCX [VersionInfo] Internal Name : VideoWindow [VersionInfo] Legal Copyrights : Copyright (C) 2006 [ModuleReport] [IAT] Modules -> MFC42.DLL | MSVCRT.dll | KERNEL32.dll | USER32.dll | GDI32.dll | COMCTL32.dll | ole32.dll | OLEPRO32.DLL | OLEAUT32.dll [!] File appears to have no protection or is using an unknown protection - Scan Took : 0.224 Second(s) [0000000E0h (224) tick(s)] [246 of 580 scan(s) done] --- snip ---
The app explicitly loads its ActiveX/OCX to register these inproc servers (registry):
--- snip --- $ pwd /home/focht/.wine/drive_c/Program Files/CMS ... 002d:Call KERNEL32.LoadLibraryA(01421b78 "C:\Program Files\CMS\WndManager.ocx") ret=00404efc ... 002d:Call PE DLL (proc=0x154b467,module=0x1530000 L"WndManager.ocx",reason=PROCESS_ATTACH,res=(nil)) ... 002d:Ret PE DLL (proc=0x154b467,module=0x1530000 L"WndManager.ocx",reason=PROCESS_ATTACH,res=(nil)) retval=1 002d:Ret KERNEL32.LoadLibraryA() retval=01530000 ret=00404efc 002d:Call KERNEL32.GetProcAddress(01530000,0050d090 "DllRegisterServer") ret=00404fa9 002d:Ret KERNEL32.GetProcAddress() retval=01580022 ret=00404fa9 002d:CALL VIDEOWINDOW.OCX.DllRegisterServer(<unknown, check return>) ret=00404fb3 ... 002d:CALL MFC42.1212(<unknown, check return>) ret=01545e8f 002d:Call KERNEL32.LoadLibraryA(5f49ed88 "ole32.dll") ret=5f405b81 002d:Ret KERNEL32.LoadLibraryA() retval=7e580000 ret=5f405b81 ... 002d:Call KERNEL32.GetProcAddress(7e580000,5f4c5ace "StringFromGUID2") ret=5f405b3f 002d:Ret KERNEL32.GetProcAddress() retval=7e58ba80 ret=5f405b3f 002d:Call ole32.StringFromGUID2(01553df0,0032bf28,00000027) ret=5f4392e7 002d:Ret ole32.StringFromGUID2() retval=00000027 ret=5f4392e7 002d:Call KERNEL32.lstrlenW(0032bf28 L"{2FBB7C49-53D0-4FFF-B3A0-B3880750976A}") ret=5f43931c 002d:Ret KERNEL32.lstrlenW() retval=00000026 ret=5f43931c 002d:Call KERNEL32.WideCharToMultiByte(00000000,00000000,0032bf28 L"{2FBB7C49-53D0-4FFF-B3A0-B3880750976A}",ffffffff,0032bdc8,0000004e,00000000,00000000) ret=5f439345 002d:Ret KERNEL32.WideCharToMultiByte() retval=00000027 ret=5f439345 002d:Call KERNEL32.GetModuleFileNameA(01530000,0032bcb4,00000104) ret=5f411dc5 002d:Ret KERNEL32.GetModuleFileNameA() retval=00000023 ret=5f411dc5 002d:Call msvcrt.memcpy(00fd04a0,5f4d1c58,00000001) ret=5f402437 002d:Ret msvcrt.memcpy() retval=00fd04a0 ret=5f402437 002d:Call KERNEL32.GetShortPathNameA(0032bcb4 "C:\Program Files\CMS\WndManager.ocx",00fd04a0,00000104) ret=5f411ddd 002d:Ret KERNEL32.GetShortPathNameA() retval=0000001c ret=5f411ddd 002d:Call KERNEL32.lstrlenA(00fd04a0 "C:\PROG~FBU\CMS\WNDM~VNJ.OCX") ret=5f40248f 002d:Ret KERNEL32.lstrlenA() retval=0000001c ret=5f40248f 002d:Call KERNEL32.FindResourceA(01530000,00000001,00000006) ret=5f403a4e 002d:Ret KERNEL32.FindResourceA() retval=01556f98 ret=5f403a4e 002d:Call user32.LoadStringA(01530000,00000002,0032bcb8,00000100) ret=5f403a5e 002d:Ret user32.LoadStringA() retval=00000019 ret=5f403a5e 002d:Call KERNEL32.lstrlenA(0032bcb8 "VideoWindow Property Page") ret=5f4038d7 002d:Ret KERNEL32.lstrlenA() retval=00000019 ret=5f4038d7 002d:Call msvcrt.memcpy(00fce610,0032bcb8,00000019) ret=5f403905 002d:Ret msvcrt.memcpy() retval=00fce610 ret=5f403905 ... 002d:Call KERNEL32.GetProcAddress(7e9d0000,5f4c549a "RegCreateKeyA") ret=5f405b3f 002d:Ret KERNEL32.GetProcAddress() retval=7e9dcd60 ret=5f405b3f 002d:Call advapi32.RegCreateKeyA(80000000,0032be24 "CLSID\{2FBB7C49-53D0-4FFF-B3A0-B3880750976A}",0032bfa8) ret=5f4393b1 002d:Ret advapi32.RegCreateKeyA() retval=00000000 ret=5f4393b1 ... --- snip ---
The interesting part is that all inproc server paths are created with short path name in registry (see 'GetShortPathNameA' calls). I found little information on the "why". This seems to be MFC built-in/enforced behaviour, for example mentioned here:
https://jeffpar.github.io/kbarchive/kb/179/Q179690/
--- quote --- MFC uses short file names for registration. --- quote ---
Later the same ActiveX/inproc server was loaded using 'ole32.CoGetClassObject' which used the short path name from registry data but still referred to the same physical file. To my surprise it was mapped a second time at a different base address 0x2ab0000 (relocated), resulting in another set of instance data.
--- snip --- ... 002d:Call ole32.CoGetClassObject(0032a304,00000003,00000000,0050bddc,00329fa0) ret=004b34b4 002d:Call KERNEL32.FindActCtxSectionGuid(00000001,00000000,00000004,0032a304,00329e40) ret=7e59e1f6 002d:Ret KERNEL32.FindActCtxSectionGuid() retval=00000000 ret=7e59e1f6 002d:Call ntdll.RtlInitUnicodeString(00329cd4,7e665600 L"\Registry\Machine\Software\Classes") ret=7e598c09 002d:Ret ntdll.RtlInitUnicodeString() retval=00329cd4 ret=7e598c09 002d:Call ntdll.NtCreateKey(00329cf4,02000000,00329cdc,00000000,00000000,00000000,00000000) ret=7e598a08 002d:Ret ntdll.NtCreateKey() retval=00000000 ret=7e598a08 002d:Call ntdll.RtlInitUnicodeString(00329d70,00329dc2 L"CLSID\{6AD651C3-AB21-4FCB-90D8-FB1396B04A07}") ret=7e598e9a 002d:Ret ntdll.RtlInitUnicodeString() retval=00329d70 ret=7e598e9a 002d:Call ntdll.NtOpenKey(00329dbc,00020019,00329d78) ret=7e598eaf 002d:Ret ntdll.NtOpenKey() retval=00000000 ret=7e598eaf 002d:Call ntdll.RtlNtStatusToDosError(00000000) ret=7e598ebb 002d:Ret ntdll.RtlNtStatusToDosError() retval=00000000 ret=7e598ebb 002d:Call ntdll.RtlInitUnicodeString(00329d70,7e666c48 L"InprocServer32") ret=7e598e9a 002d:Ret ntdll.RtlInitUnicodeString() retval=00329d70 ret=7e598e9a 002d:Call ntdll.NtOpenKey(00329e84,00020019,00329d78) ret=7e598eaf 002d:Ret ntdll.NtOpenKey() retval=00000000 ret=7e598eaf 002d:Call ntdll.RtlNtStatusToDosError(00000000) ret=7e598ebb 002d:Ret ntdll.RtlNtStatusToDosError() retval=00000000 ret=7e598ebb 002d:Call advapi32.RegCloseKey(00000170) ret=7e59cf21 002d:Ret advapi32.RegCloseKey() retval=00000000 ret=7e59cf21 002d:Call advapi32.RegQueryValueExW(00000174,7e666bd0 L"ThreadingModel",00000000,00329ba0,00329ba8,00329ba4) ret=7e59dd41 002d:Ret advapi32.RegQueryValueExW() retval=00000000 ret=7e59dd41 002d:Call advapi32.RegQueryValueExW(00000174,00000000,00000000,00329bac,0032999c,00329ba8) ret=7e59b18a 002d:Ret advapi32.RegQueryValueExW() retval=00000000 ret=7e59b18a ... 002d:Call KERNEL32.LoadLibraryExW(00329bee L"C:\PROG~FBU\CMS\WNDM~VNJ.OCX",00000000,00000008) ret=7e598ff2 ... 002d:Call PE DLL (proc=0x2acb467,module=0x2ab0000 L"WNDM~VNJ.OCX",reason=PROCESS_ATTACH,res=(nil)) 002d:Call KERNEL32.LocalAlloc(00000000,00002000) ret=02acb1be ... --- snip ---
Winedbg:
--- snip --- Wine-dbg>bt
Backtrace: =>0 0x7b463443 MODULE_get_dll_load_path(module="C:\PROG~FBU\CMS\WNDM~VNJ.OCX", safe_mode=0xffffffff) [/home/focht/projects/wine/wine.repo/src/dlls/kernel32/module.c:974] in kernel32 (0x00329ba8) 1 0x7b463df7 LoadLibraryExW+0x73(libnameW=<couldn't compute location>, hfile=<couldn't compute location>, flags=<couldn't compute location>) [/home/focht/projects/wine/wine.repo/src/dlls/kernel32/module.c:1272] in kernel32 (0x00329bd8) 2 0x7e5a6ff2 COMPOBJ_DllList_Add+0x8d(library_name="C:\PROG~FBU\CMS\WNDM~VNJ.OCX", ret=0x1c7dcf8) [/home/focht/projects/wine/wine.repo/src/dlls/ole32/compobj.c:521] in ole32 (0x00329c48) 3 0x7e5a8fe1 apartment_getclassobject+0x1d3(apt=0x1f7ed08, dllpath="C:\PROG~FBU\CMS\WNDM~VNJ.OCX", apartment_threaded=0x1, rclsid=0x32a3c4, riid=0x50bddc, ppv=0x32a060) [/home/focht/projects/wine/wine.repo/src/dlls/ole32/compobj.c:1366] in ole32 (0x00329cb8) 4 0x7e5abfbb get_inproc_class_object+0x1e1(apt=0x1f7ed08, regdata=0x329f8c, rclsid=0x32a3c4, riid=0x50bddc, hostifnecessary=0x1, ppv=0x32a060) [/home/focht/projects/wine/wine.repo/src/dlls/ole32/compobj.c:2954] in ole32 (0x00329f18) 5 0x7e5ac42d CoGetClassObject+0x457(rclsid=<couldn't compute location>, dwClsContext=<couldn't compute location>, pServerInfo=<couldn't compute location>, iid=<couldn't compute location>, ppv=<couldn't compute location>) [/home/focht/projects/wine/wine.repo/src/dlls/ole32/compobj.c:3094] in ole32 (0x0032a018) 6 0x004b34b4 in cms (+0xb34b3) (0x0032a044) 7 0x004b4d92 in cms (+0xb4d91) (0x0032a0e8) 8 0x004b48d4 in cms (+0xb48d3) (0x0032a158) 9 0x004b344b in cms (+0xb344a) (0x0032a1c8) 10 0x004aeace in cms (+0xaeacd) (0x0032a224) 11 0x004aebb1 in cms (+0xaebb0) (0x0032a298) 12 0x004aef64 in cms (+0xaef63) (0x0032a2f0) 13 0x004abc5d in cms (+0xabc5c) (0x0032a38c) 14 0x0032a3b0 (0x0032a38c) ... --- snip ---
Another session, showing the same dll present two times:
--- snip --- Wine-dbg>info share Module Address Debug info Name (100 modules) PE 340000- 36f000 Export h264play PE 370000- 380000 Export dlldeinterlace PE 380000- 391000 Export streamreader PE 3a0000- 3ab000 Export password PE 400000- 5c7000 Export cms PE 5d0000- 659000 Export playback PE 660000- 736000 Export configmodule PE 740000- 7ae000 Export localrecord PE 1480000- 14c4000 Export wndmanager PE 14d0000- 1571000 Export mapctrl PE 2380000- 23c4000 Deferred wndm~vnj PE 10000000-10083000 Export netsdk PE 5f400000-5f4f2000 Export mfc42 ELF 7a800000-7a949000 Dwarf opengl32<elf> -PE 7a840000-7a949000 \ opengl32 ELF 7b400000-7b7f0000 Dwarf kernel32<elf> -PE 7b420000-7b7f0000 \ kernel32 ELF 7bc00000-7bd0a000 Dwarf ntdll<elf> -PE 7bc30000-7bd0a000 \ ntdll ELF 7c000000-7c004000 Dwarf <wine-loader> ... --- snip ---
wndmanager -> 0x1480000 (by long path name "C:\Program Files\CMS\WndManager.ocx")) wndm~vnj -> 0x2380000 (by short path name "C:\PROG~FBU\CMS\WNDM~VNJ.OCX")
To avoid this, Wine loader 'load_native_dll' function needs to implement additional checks after creating the file-backed section view, before doing anything further (reloc fixups, allocating module entry, ...).
Essentially walk the current module list and for each entry:
* basic (quick) check: time/date stamp and image size against NT file header data from the newly created section view (NOTE: 'alloc_module' currently doesn't copy 'TimeDateStamp' from 'FileHeader.TimeDateStamp' which is needed for comparison) * memory compare IMAGE_NT_HEADERS * use NtAreMappedFilesTheSame() to check if the views from the image sections are backed by the same physical file
If these conditions are met, the existing module entry shall be used and the new mapping must be destroyed (NtUnmapViewOfSection).
I've already tested this using an own prototype. It seems to work fine without breaking other apps.
NOTE: It doesn't prevent the crash reported in bug 35106 (another issue, unrelated to loader) but makes Wine more compliant with Windows OS loader behaviour.
$ sha1sum General_CMS_Eng_V1.0.0.8.T.20101202.exe 902e9408ebfc295ce2477fe3d9ca05e8a7588589 General_CMS_Eng_V1.0.0.8.T.20101202.exe
$ du -sh General_CMS_Eng_V1.0.0.8.T.20101202.exe 4.7M General_CMS_Eng_V1.0.0.8.T.20101202.exe
$ wine --version wine-3.0-260-gdf715e5a9a
Regards
https://bugs.winehq.org/show_bug.cgi?id=44460
Anastasius Focht focht@gmx.net changed:
What |Removed |Added ---------------------------------------------------------------------------- Keywords| |download URL| |http://www.tayama.com.pl/~m | |arcin/PDR/Software/General_ | |CMS_Eng_V1.0.0.8.T.20101202 | |.exe
https://bugs.winehq.org/show_bug.cgi?id=44460
Anastasius Focht focht@gmx.net changed:
What |Removed |Added ---------------------------------------------------------------------------- Fixed by SHA1| |1cee60d06823a6224646b425243 | |ed0617c4d1c0f Resolution|--- |FIXED Status|NEW |RESOLVED
--- Comment #1 from Anastasius Focht focht@gmx.net --- Hello folks,
this is fixed by commit https://source.winehq.org/git/wine.git/commitdiff/1cee60d06823a6224646b42524...
Thanks Alexandre
--- snip --- $ pwd /home/focht/.wine/drive_c/Program Files/CMS
$ WINEDEBUG=+seh,+relay,+module wine ./CMS.exe >>log.txt 2>&1 ... 003f:Call KERNEL32.LoadLibraryA(01375fb0 "C:\Program Files\CMS\WndManager.ocx") ret=00404efc ... 003f:Ret PE DLL (proc=0x15ab467,module=0x1590000 L"WndManager.ocx",reason=PROCESS_ATTACH,res=(nil)) retval=1 003f:trace:module:process_attach (L"WndManager.ocx",(nil)) - END 003f:Ret KERNEL32.LoadLibraryA() retval=01590000 ret=00404efc 003f:Call KERNEL32.GetProcAddress(01590000,0050d090 "DllRegisterServer") ret=00404fa9 003f:Ret KERNEL32.GetProcAddress() retval=015a0010 ret=00404fa9 ... 003f:Call KERNEL32.LoadLibraryExW(0032bc8e L"C:\Program Files\CMS\WndManager.ocx",00000000,0000000b) ret=7e3b701c 003f:trace:module:LdrGetDllHandle L"C:\Program Files\CMS\WndManager.ocx" -> 0x1590000 (load path L"C:\Program Files\CMS;C:\windows\system32;C:\windows\system;C:\windows;.;C:\windows\system32;C:\windows;C:\windows\system32\wbem") 003f:trace:module:LdrAddRefDll (L"WndManager.ocx") ldr.LoadCount: 2 003f:Ret KERNEL32.LoadLibraryExW() retval=01590000 ret=7e3b701c ... 003f:Call KERNEL32.FreeLibrary(01590000) ret=7e3b6f87 003f:trace:module:LdrUnloadDll (0x1590000) 003f:trace:module:LdrUnloadDll (L"WndManager.ocx") - START 003f:trace:module:MODULE_DecRefCount (L"WndManager.ocx") ldr.LoadCount: 1 003f:trace:module:LdrUnloadDll END 003f:Ret KERNEL32.FreeLibrary() retval=00000001 ret=7e3b6f87 ... 003f:Call KERNEL32.GetShortPathNameA(0032bcb4 "C:\Program Files\CMS\WndManager.ocx",00f304a0,00000104) ret=5f411ddd 003f:Ret KERNEL32.GetShortPathNameA() retval=0000001c ret=5f411ddd ... 003f:Call KERNEL32.LoadLibraryExW(00329bee L"C:\PROG~FBU\CMS\WNDM~VNJ.OCX",00000000,00000008) ret=7e598fff 003f:trace:module:load_dll looking for L"C:\PROG~FBU\CMS\WNDM~VNJ.OCX" in L"C:\PROG~FBU\CMS;C:\windows\system32;C:\windows\system;C:\windows;.;C:\windows\system32;C:\windows;C:\windows\system32\wbem" 003f:trace:module:open_dll_file L"\??\C:\PROG~FBU\CMS\WNDM~VNJ.OCX" is the same file as existing module 0x1590000 L"C:\Program Files\CMS\WndManager.ocx" 003f:trace:module:load_dll Found L"C:\Program Files\CMS\WndManager.ocx" for L"C:\PROG~FBU\CMS\WNDM~VNJ.OCX" at 0x1590000, count=2 003f:Ret KERNEL32.LoadLibraryExW() retval=01590000 ret=7e598fff ... --- snip ---
"same file as existing module" = identity check works.
$ wine --version wine-3.2-293-g0a72708126
Regards
https://bugs.winehq.org/show_bug.cgi?id=44460
Alexandre Julliard julliard@winehq.org changed:
What |Removed |Added ---------------------------------------------------------------------------- Status|RESOLVED |CLOSED
--- Comment #2 from Alexandre Julliard julliard@winehq.org --- Closing bugs fixed in 3.3.
https://bugs.winehq.org/show_bug.cgi?id=44460
Anastasius Focht focht@gmx.net changed:
What |Removed |Added ---------------------------------------------------------------------------- URL|http://www.tayama.com.pl/~m |https://web.archive.org/web |arcin/PDR/Software/General_ |/20200404210448/https://fil |CMS_Eng_V1.0.0.8.T.20101202 |es.zmodo.com/Software%20Fil |.exe |es/PC%20Viewing%20Software/ | |DVRs/General_CMS_V3.0.9.18. | |T.20130629.exe