Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CreateMutex() fails with ERROR_ACCESS_DENIED when called by process spawned by Windows Service

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).

like image 639
sashoalm Avatar asked Mar 17 '16 13:03

sashoalm


1 Answers

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.

like image 60
sashoalm Avatar answered Oct 22 '22 04:10

sashoalm