I just spent several days tracking down a bug caused by the fact that an unhandled exception was being thrown because a call to ::CreateMutex() failed. Keep in mind CreateMutex()
doesn't try to lock the mutex, just to open a handle. It shouldn't fail even if the mutex already exists, at least so long as I set bInitialOwner to FALSE, which I did.
But if I call ::CreateMutexA(0, 0, "testtesttest123");
, from a program spawned by a Windows Service (w3wp.exe
is its parent and scvhost.exe
is its grand-parent and it runs under IIS APPPOOL\Worker1
user-account), then it fails and returns NULL if the mutex has been already created by another process running under IIS APPPOOL\*
.
I've created a minimal example that reproduces this problem:
void main()
{
HANDLE handle = CreateMutexA(0, 0, "testtesttest123");
std::string lastError = MyGetLastErrorAsText();
MyAppendToLog("%p %s\n", handle, lastError.c_str());
Sleep(INFINITE);
}
I run the executable 2 times by double-clicking from Explorer (normal user account), then cause it to be spawned by a Windows Service, and again run it from Explorer.
This creates the following log:
C:\Test\ReproduceMutexCrash.exe: 00000034 ERROR_SUCCESS C:\Test\ReproduceMutexCrash.exe: 00000034 ERROR_ALREADY_EXISTS c:\inetpub\wwwroot\Worker1\ReproduceMutexCrash.exe: 00000034 ERROR_SUCCESS c:\inetpub\wwwroot\Worker2\ReproduceMutexCrash.exe: 00000000 ERROR_ACCESS_DENIED C:\Test\ReproduceMutexCrash.exe: 00000034 ERROR_ALREADY_EXISTS
Worker1 here runs under IIS APPPOOL\Worker1
, while Worker2 runs under IIS APPPOOL\Worker2
user account.
From the log it's obvious that if the mutex is created by a normal user process, this doesn't another prevent another user process or even Windows Service from opening a handle. But if a Windows Service has created a mutex, then ::CreateMutex()
fails when called by another Windows Service, although user processes can still open a handle without a hitch.
Does anyone know why this happens?
Edit: I saw that the mutex object is \Sessions\1\BaseNamedObjects\testtesttest123
when the process is spawned by Explorer, while it's \BaseNamedObjects\testtesttest123
when the process is created by IIS
, and the owner is Worker1
(the process that first managed to create the mutex).
Thanks to the help from the comments, I finally figured out why it happens. It was indeed because of permissions, and because the processes were running from different user accounts. I verified that by running 2 separate processes, but from the same user account - IIS APPPOOL\Worker1
, and for both ::CreateMutex()
worked and returned a valid HANDLE.
It fails only when the mutex has been locked by a process under IIS APPPOOL\Worker1
and a process under IIS APPPOOL\Worker2
tries to access it.
Also, I tried creating the mutex without any security, by creating an empty DACL:
SECURITY_ATTRIBUTES sa = { sizeof(sa) };
SECURITY_DESCRIPTOR SD;
InitializeSecurityDescriptor(&SD, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&SD, TRUE, NULL, FALSE);
sa.lpSecurityDescriptor = &SD;
HANDLE mutex = CreateMutexA(&sa, 0, "testtesttest123");
Using an empty DACL, 2 processes from different user accounts could open the same mutex.
Also, the reason why a mutex created by a user-process didn't cause ERROR_ACCESS_DENIED was because it was creating the mutex in a different scope - \Sessions\1\BaseNamedObjects\testtesttest123
, so there was no permissions-conflict because the web-service process was creating a new mutex at \BaseNamedObjects\testtesttest123
, so they weren't accessing the same object anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With