17

I'm getting this exception when trying to open a mutex (it happens only sometimes; the most of calls is successful):

System.UnauthorizedAccessException: Access to the path 'Global\4c7cddf7-e729-43b6-a75c-43f54a0ac6ac' is denied.
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.Threading.Mutex.OpenExisting(String name, MutexRights rights)

The code I'm using to work with mutex:

public class MutexLocker : IDisposable
{
    public MutexLocker(string id)
    {
        var doesNotExist = false;
        var unauthorized = false;

        try
        {
            _mutex = Mutex.OpenExisting(id, MutexRights.Synchronize | MutexRights.Modify);
        }
        catch (WaitHandleCannotBeOpenedException)
        {
            doesNotExist = true;
        }
        catch (UnauthorizedAccessException ex)
        {
            unauthorized = true;
        }

        if (doesNotExist)
        {
            _mutex = new Mutex(false, id);

            var allowEveryoneRule = new MutexAccessRule(
                new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
            var securitySettings = new MutexSecurity();
            securitySettings.AddAccessRule(allowEveryoneRule);
            _mutex.SetAccessControl(securitySettings);
        }
        else if (unauthorized)
        {
            var tempMutex = Mutex.OpenExisting(id, MutexRights.ReadPermissions | MutexRights.ChangePermissions);
            var securitySettings = tempMutex.GetAccessControl();

            var user = Environment.UserDomainName + "\\" + Environment.UserName;

            // the rule that denied the current user the right to enter and release the mutex must be removed
            var rule = new MutexAccessRule(user, MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Deny);
            securitySettings.RemoveAccessRule(rule);

            // Now grant the correct rights
            var allowEveryoneRule = new MutexAccessRule(
                new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
            securitySettings.AddAccessRule(allowEveryoneRule);
            tempMutex.SetAccessControl(securitySettings);

            _mutex = Mutex.OpenExisting(id, MutexRights.Synchronize | MutexRights.Modify);
        }

        var success = _mutex.WaitOne(TimeSpan.FromSeconds(10), false);
        if (success == false)
        {
            _mutex.Dispose();
            _mutex = null;
            throw new ApplicationException(string.Format("Can't lock mutex (timed out): {0}", id));
        }
    }

    public void Dispose()
    {
        if (_mutex != null)
        {
            try
            {
                _mutex.ReleaseMutex();
            }
            catch (Exception exc)
            {
                Trace.WriteLine(exc);
            }

            _mutex.Dispose();
        }
    }

    private readonly Mutex _mutex;
}

The mutex "id" is a guid and name conflicts are impossible.
This is the only code that can create that mutex, and it grants full access to it for all users (my processes can be run under different user credentials).

Any ideas, why this unauthorized access error might happen?

user626528
  • 13,999
  • 30
  • 78
  • 146
  • Did you try removing Global from the name for test purposes? You may also try using a shorter name. Guid may be too long for a name. – Demir Oct 23 '13 at 08:39
  • 1
    Maybe compare your code to the code in this answer - perhaps you might find something: http://stackoverflow.com/a/229567/106159 – Matthew Watson Oct 23 '13 at 08:44
  • @Demir, the name is ok - my code works the most of time. But sometimes I'm getting the exception. – user626528 Oct 23 '13 at 08:47
  • Are you sure it is the DACL? You are explicitly creating it to allow all access so I don't see how it would ever get changed. Try dumping it to a log file when you get the exception. – Luke Oct 26 '13 at 10:21
  • @Luke, that's why I'm asking... dumping what? – user626528 Oct 26 '13 at 13:47
  • 1
    There are two Mutex.OpenExisting which can generate the exception, in the `if (unauthorized)` . Which one is generating it ? – MC ND Oct 26 '13 at 19:58
  • @MC ND, obviously the second one. – user626528 Oct 27 '13 at 10:16
  • 2
    Not so obvious. The mutex is created without a ACL, so it gets an ACL depending of the user rights who created it. Then an ACL is generated and applied to the mutex. In this interval, the ACL could block access to the mutex, so another thread/process/user/session executing `tempMutex = Mutex.OpenExisting` will throw an exception – MC ND Oct 27 '13 at 13:25
  • @MC ND, the `tempMutex = Mutex.OpenExisting` line can be called only if the first OpenExisting method has already failed. – user626528 Oct 27 '13 at 14:27
  • Yes. Under the situation described in previous post, if this line is executed, first call to OpenExisting has failed with a handled UnauthorizedAccessException which assigns true to unauthorized var. So, execution reachs this line , which also can fail with an unhandled exception. – MC ND Oct 27 '13 at 14:35
  • While I assume this has nothing to do with your problem you need to add "_mutex = null; GC.SuppressFinalizer(this);" in your Dispose method after "_mutex.Dispose(). – Dweeberly Oct 30 '13 at 16:28

1 Answers1

24

This class should solve your problem:

    public class MutexLocker: IDisposable
    {
        private Mutex _mutex;

        public MutexLocker ( string id )
        {
            bool createdNew;
            MutexSecurity mutexSecurity = new MutexSecurity();
            mutexSecurity.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), 
                                                            MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));

            try
            {
                // attempt to create the mutex, with the desired DACL..
                _mutex = new Mutex(false, id, out createdNew, mutexSecurity);
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                // the mutex cannot be opened, probably because a Win32 object of a different
                // type with the same name already exists.
                throw;
            }
            catch (UnauthorizedAccessException)
            {
                // the mutex exists, but the current process or thread token does not
                // have permission to open the mutex with SYNCHRONIZE | MUTEX_MODIFY rights.
                throw;
            }
        }

        public void Dispose ()
        {
            if (_mutex != null)
            {
                _mutex.ReleaseMutex();
                _mutex.Dispose();
            }

            _mutex = null;
        }
    }

The only pieces of note is the Mutex constructor, which will attempt to create the mutex (by calling Win32's CreateMutex()) immediately assigning the provided security descriptor to the named object. If the CreateMutex call fails, the framework will attempt to use OpenMutex to open the named mutex requesting SYNCHRONIZE and MUTEX_MODIFY rights.

The problem that you are seeing is a simple race condition between the creation of the mutex and assignment of the security descriptor (at least as far as I noticed). Making the creation and security descriptor assignment atomic will solve that problem.

William
  • 1,867
  • 11
  • 10