http://bugs.winehq.org/show_bug.cgi?id=21777
Summary: CreateMutexExA(): use heap for A to W conversion to work around transbase db engine app bug (affects multiple apps, TecDoc CATALOG ...) Product: Wine Version: 1.1.39 Platform: x86 URL: http://www.tecdoc.de OS/Version: Linux Status: UNCONFIRMED Severity: normal Priority: P2 Component: kernel32 AssignedTo: wine-bugs@winehq.org ReportedBy: focht@gmx.net
Hello,
at the end of TecDoc CATALOG 1/2010 DVD install a crash dialog is displayed while starting a service (the msi part of install is finished anyway). The database engine service has auto-start type set hence on every program start (wine notepad) the infamous crash dialog is displayed.
This turns out to be an app bug but due to differences in Wine vs. Windows API design/usage this bug has no consequences in Windows and goes on unnoticed.
Unfortunately the part of software that crashes comes from another software supplier "Transaction Software" (http://www1.transaction.de/transaction/). Many automotive spare parts catalogue software (BMW, VW, Audi, GM, Fiat ...) internally use the Transbase SQL database engine for years.
In case of Wine/Linux this means many broken apps/versions in the wild due to this bug and even if the original software supplier fixes the bug, it will take years until spare parts catalogue software vendors using the engine to pick up and release new versions of their own products.
Despite being an app bug I suggest to modify Wine to work around this developer stupidity.
Here it goes, first the crash itself:
--- snip --- wine: Unhandled page fault on read access to 0x012f0000 at address 0x681ee546 (thread 001d), starting debugger... Unhandled exception: page fault on read access to 0x012f0000 in 32-bit code (0x681ee546). ... Threads: process tid prio (all id:s are in hex) ... 00000019 (D) C:\TECDOC_CD\1_2010\db\tbmux32.exe 0000001d 0 <== 0000001b 0 0000001a 0 ... Backtrace: =>0 0x681ee546 (0x012eddd4) 1 0x7bc79ac0 NtCreateMutant+0xae(MutantHandle=0x12eded4, access=0x1f0001, attr=0x12edeb4, InitialOwner=0) [/opt/wine/wine-git/dlls/ntdll/sync.c:429] in ntdll (0x012ede94) 2 0x7b869dc8 CreateMutexExW+0xb2(sa=0x12ee16c, name="Global\TBSEM ( TECDOC_C ) = (Key=32769, Ind=0)", flags=0, access=0x1f0001) [/opt/wine/wine-git/dlls/kernel32/sync.c:653] in kernel32 (0x012edee4) 3 0x7b869d0c CreateMutexExA+0xb6(sa=0x12ee16c, name="Global\TBSEM ( TECDOC_C ) = (Key=32769, Ind=0)", flags=0, access=0x1f0001) [/opt/wine/wine-git/dlls/kernel32/sync.c:626] in kernel32 (0x012ee124) 4 0x7b869c08 CreateMutexA+0x3a(sa=0x12ee16c, owner=0, name="Global\TBSEM ( TECDOC_C ) = (Key=32769, Ind=0)") [/opt/wine/wine-git/dlls/kernel32/sync.c:599] in kernel32 (0x012ee144) 5 0x0041a2a3 in tbmux32 (+0x1a2a3) (0x74fd9c13) 6 0x458d28ec (0x83e58955) --- snip ---
Starting the whole thing (db service) with +relay exhibits a different behaviour: the service doesn't crash but it doesn't really work, indicating a stack usage problem.
Have a close look at security descriptor dump, DACL part:
--- snip --- 001e:Call advapi32.InitializeSecurityDescriptor(012ee2bc,00000001) ret=00419b06 001e:Ret advapi32.InitializeSecurityDescriptor() retval=00000001 ret=00419b06 001e:Call advapi32.InitializeAcl(012ede8c,00000400,00000002) ret=00419b28 001e:Ret advapi32.InitializeAcl() retval=00000001 ret=00419b28 001e:Call advapi32.InitializeSid(012eda8c,012eda84,00000001) ret=00419b47 001e:Ret advapi32.InitializeSid() retval=00000001 ret=00419b47 001e:Call advapi32.GetSidSubAuthority(012eda8c,00000000) ret=00419b60 001e:Ret advapi32.GetSidSubAuthority() retval=012eda94 ret=00419b60 001e:Call advapi32.AddAccessAllowedAce(012ede8c,00000002,10000000,012eda8c) ret=00419b7c 001e:Ret advapi32.AddAccessAllowedAce() retval=00000001 ret=00419b7c 001e:Call advapi32.SetSecurityDescriptorDacl(012ee2bc,00000001,012ede8c,00000000) ret=00419b9e 001e:Ret advapi32.SetSecurityDescriptorDacl() retval=00000001 ret=00419b9e 001e:Call KERNEL32.CreateMutexA(012ee2b0,00000000,012ee2d0 "Global\TBSEM ( TECDOC_C ) = (Key=808607544, Ind=0)") ret=0041a2a3 001e: create_mutex( access=001f0001, attributes=00000080, owned=0, objattr={rootdir=0018,sd={control=00000004,owner=<not present>,group=<not present>,sacl={},dacl={{AceType=unknown<156>,AceFlags=e2},{AceType=unknown<46>,AceFlags=1}}},name=L"Global\TBSEM ( TECDOC_C ) = (Key=808607544, Ind=0)"} ) 001e: create_mutex() = INVALID_SECURITY_DESCR { handle=0000 } 001e:Ret KERNEL32.CreateMutexA() retval=00000000 ret=0041a2a3 --- snip ---
Pretty much messed up.
To keep the story short, I translated debugger disassembly snippets and annotated stack dumps into a snippet of C code so you won't get annoyed with too much detail ;-) This is most likely what the app does (might not be 100% accurate, just to illustrate the problem):
--- stupid app code start ---
static int prepare_sd( SECURITY_ATTRIBUTES *sa) { SID_IDENTIFIER_AUTHORITY sia_world = SECURITY_WORLD_SID_AUTHORITY; BYTE user_sid[0x400]; BYTE acl_buffer[0x400];
int res = InitializeSecurityDescriptor( sa->lpSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION); if( !retval) return res;
res = InitializeAcl( (PACL) acl_buffer, sizeof(acl_buffer), ACL_REVISION); if( !retval) return res;
res = InitializeSid( (PSID) user_sid, &sia_world, 1); if( !retval) return res;
*GetSidSubAuthority( (PSID) user_sid, 0) = SECURITY_WORLD_RID; res = AddAccessAllowedAce( (PACL) acl_buffer, ACL_REVISION, 0x10000000, (PSID) user_sid); if( !retval) return res;
return SetSecurityDescriptorDacl( sa->lpSecurityDescriptor, TRUE, (PACL)acl_buffer, FALSE); }
static int open_app_mutex( ...) { SECURITY_DESCRIPTOR sd; SECURITY_ATTRIBUTES sa = {0};
char name[0x80]; wsprintfA( name, "xxx", ...);
sa.nLength= sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = &sd; sa.bInheritHandle = FALSE;
prepare_sd( &sa);
CreateMutexA( &sa, FALSE, name); ... } --- stupid app code end ---
You can probably spot the biggest mistake of all pretty soon: some "genius" decided to put everything on stack in prepare_sd().
Upon return of prepare_sd() helper function, the stack locals used within prepare_sd() are still *intact* When the app calls CreateMutexA(), the call sequence is as follows:
CreateMutexA -> CreateMutexExA -> CreateMutexExW -> NtCreateMutant
--- snip dlls/kernel32/sync.c --- HANDLE WINAPI CreateMutexA( SECURITY_ATTRIBUTES *sa, BOOL owner, LPCSTR name ) { return CreateMutexExA( sa, name, owner ? CREATE_MUTEX_INITIAL_OWNER : 0, MUTEX_ALL_ACCESS ); }
...
HANDLE WINAPI CreateMutexExA( SECURITY_ATTRIBUTES *sa, LPCSTR name, DWORD flags, DWORD access ) { WCHAR buffer[MAX_PATH];
if (!name) return CreateMutexExW( sa, NULL, flags, access );
if (!MultiByteToWideChar( CP_ACP, 0, name, -1, buffer, MAX_PATH )) { SetLastError( ERROR_FILENAME_EXCED_RANGE ); return 0; } return CreateMutexExW( sa, buffer, flags, access ); }
--- snip dlls/kernel32/sync.c ---
The problem is buried within CreateMutexExA(): the A to W conversion uses a stack based buffer of MAX_PATH len. This eats stack, partly overwriting the ACL buffer (DACL) from previous prepare_sd() which used to be a stack local too.
The reason that this doesn't happen on Windows is most likely conversions are heap based or the call path to kernel mode is very short, not consuming that much stack as Wine does.
I verified with a patch to use heap based buffer in CreateMutexExA() for A to W conversion (similar what's being done in CreateMailslotA()) and it lets the app succeed despite that nasty stack bug.
I'll leave it to Alexandre to judge this (change or leave Wine as it is).
If you decide for WONTFIX (that app bug would really deserve such treatment), I will attach a patch to give users a chance work around various broken transbase versions.
Regards