Mutex creation hangs while using impersonation
Asked Answered
A

1

5

While testing an application, I ran a into strange behaviour. Some of the tests use impersonation to run code as a different user, but they would always hang, never complete.

After some investigation, the problem was narrowed down to the use of mutexes. Originally, we used our own impersonation code based on the MSDN documentation, but even when using the SimpleImpersonation library the problem still remains. Here is a minimal example to reproduce the problem:

using (Impersonation.LogonUser(DOMAIN, USER, PASSWORD, LogonType.Interactive))
{
    Console.WriteLine("Impersonated");
    bool mine;
    using (new Mutex(true, "Mutex", out mine))
    {
        if (!mine)
            throw new Exception("Couldn't get mutex");
        Console.WriteLine("Got mutex");
    }
}

Console.WriteLine("Finished");

This never finishes, it's stuck on the line with the mutex creation. The documentation states that it should either throw an exception or return something, but does not mention blocking.

Some other observations, which might or might not be related:

  • if we "impersonate" the current user, it returns immediately
  • if we run the actual application and start another instance as a different user, everything works as intended

Probably there's something going on with underlying system resources, but we couldn't figure it out. How to make this work?

UPDATE: As per Hans' comment, I tried disabling Windows Defender, it didn't help. Here's a stacktrace of the place where it's hanging:

    ntdll.dll!_NtWaitForSingleObject@12()
    KernelBase.dll!_WaitForSingleObjectEx@12()
    mscorlib.ni.dll!719c1867()
    [Frames below may be incorrect and/or missing, native debugger attempting to walk managed call stack]   
    mscorlib.ni.dll!719c1852()
    [Managed to Native Transition]  
    mscorlib.dll!System.Threading.Mutex.CreateMutexHandle(bool initiallyOwned, string name, Microsoft.Win32.Win32Native.SECURITY_ATTRIBUTES securityAttribute, out Microsoft.Win32.SafeHandles.SafeWaitHandle mutexHandle)
        mscorlib.dll!System.Threading.Mutex.MutexTryCodeHelper.MutexTryCode(object userData)
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    mscorlib.dll!System.Threading.Mutex.CreateMutexWithGuaranteedCleanup(bool initiallyOwned, string name, out bool createdNew, Microsoft.Win32.Win32Native.SECURITY_ATTRIBUTES secAttrs)   
    mscorlib.dll!System.Threading.Mutex.Mutex(bool initiallyOwned, string name, out bool createdNew, System.Security.AccessControl.MutexSecurity mutexSecurity) 
    mscorlib.dll!System.Threading.Mutex.Mutex(bool initiallyOwned, string name, out bool createdNew)    
    MutexImpersonationTest.exe!MutexImpersonationTest.Program.Main(string[] args) Line 16   
Alacrity answered 13/4, 2016 at 7:44 Comment(1)
Very strange. FWIW, if you don't get a good answer, a last-resort workaround would be to forget the .net classes and use P/Invoke to call the Win32 API directly.Mortify
J
7

It looks like the code to acquire a Mutex is getting stuck in an infinite loop, and in my tests, it's pegging one core at 100% within the call to new Mutex(...).

The reason for this seems to be that the framework code first tries calling the Win32 CreateMutex, if that fails with an "Access Denied" error, tries calling OpenMutex instead. If the OpenMutex call fails with an error indicating the mutex doesn't exist, it repeats the whole process again, and hence gets stuck in an infinite loop if the mutex doesn't exist.

According to the CreateMutex documentation, this is essentially the correct approach, but doesn't seem to account for the case when the initial CreateMutex fails with an Access Denied that's not due to permissions on an existing mutex.

One thing that did seem to work when I tried it was to prefix the mutex name with "Global\", hopefully this is a suitable workaround for you.

Jurat answered 13/4, 2016 at 13:10 Comment(3)
Global mutexes can be created like this indeed, but that limits instances across all user sessions, which is a dramatic change in behaviour...Alacrity
Nice catch, the while(true) code is here.Livi
@MártonMolnár If you want a session-specific mutex you'd seem to have a couple of options - you could create the mutex before impersonating and set the permissions on it to allow the impersonating user to access it, or use the "Global\" prefix, but incorporate the session ID (Process.GetCurrentProcess().SessionId) into the name of the mutex, thereby creating a "per-session" mutex in the global namespace.Jurat

© 2022 - 2024 — McMap. All rights reserved.