6

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

sashoalm
  • 75,001
  • 122
  • 434
  • 781
  • 3
    You might want to find some code to dump the default security token just before the `CreateMutx()` call. That's what gets used for the ACL when you specify NULL for the first argument to `CreateMutex()`. You might need to specify your own security descriptor when creating the mutex. – Michael Burr Mar 17 '16 at 14:21
  • 1
    @MichaelBurr Thanks for the suggestion. Do you know how I can dump default security token? I couldn't find anything in stackoverflow on the topic. I looked at `SECURITY_ATTRIBUTES` and it has `LPVOID lpSecurityDescriptor`, so I don't know if the security descriptor's structure is even documented. – sashoalm Mar 17 '16 at 15:33
  • 1
    If you want to share a mutex between a service and a user-mode application you should create it in the global namespace by prefixing the mutex name with ``Global\``. If that's not the issue then I would look into the security token as suggested. – Jonathan Potter Mar 17 '16 at 18:45
  • Also http://stackoverflow.com/questions/20580054/how-to-make-a-synchronization-mutex-with-access-by-every-process and http://stackoverflow.com/questions/10139958/createmutex-access-is-denied. – Jonathan Potter Mar 17 '16 at 18:49
  • @JonathanPotter I'm sorry if my edit gave the wrong impression. I do not want to share a mutex between a service and a user process. I want to share it between 2 local services running in different user accounts - `IIS APPPOOL\Worker1` and `IIS APPPOOL\Worker2` respectively. The services already see the same mutex, otherwise I would not have gotten the ERROR_ACCESS_DENIED to begin with. – sashoalm Mar 17 '16 at 18:57
  • Check out those two links then as they're about the security descriptor issue. – Jonathan Potter Mar 17 '16 at 19:38
  • The default permissions definitely won't allow access for other unprivileged users, there's not really any point in checking them. You need to explicitly specify permissions granting access to the accounts that you want to be permitted to access the mutex. (There's probably a group that all the IIS workers belong to, so you could use that.) – Harry Johnston Mar 17 '16 at 21:02
  • @HarryJohnston I tried with `SECURITY_ATTRIBUTES sa = { sizeof(sa) };`, which is the default access token and should grant access to everyone, but the results were still the same. – sashoalm Mar 18 '16 at 09:23
  • 2
    No. Read the documentation again: "If the value of this member is NULL, the object is assigned the default security descriptor associated with the access token of the calling process. This is **not** the same as granting access to everyone by assigning a NULL discretionary access control list (DACL). By default, the default DACL in the access token of a process **allows access only to the user represented by the access token.**" (emphasis mine) – Harry Johnston Mar 18 '16 at 09:51

1 Answers1

6

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.

sashoalm
  • 75,001
  • 122
  • 434
  • 781
  • 3
    I assume you already realize this, but for the benefit of future readers: using an empty DACL is useful to demonstrate that you've correctly identified the problem, but in most cases is unlikely to be a safe solution, as it allows access to **everyone**, including guest accounts and code running anonymously. In general you should choose a DACL that grants access only according to your needs. – Harry Johnston Mar 18 '16 at 22:35