Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / Win32

Using a Win32 Mutex to Figure Out if an Application Instance is the First One

Rate me:
Please Sign up or sign in to vote.
4.88/5 (13 votes)
21 Oct 2022MIT8 min read 9K   194   12   14
This article shows how to use a win32 mutex during application startup to figure out if an application instance is the first one.
In some cases, an application needs to make sure it is the only instance running. This can be done using a mutex. There are a couple of details to consider in terms of applications running in a different logon session, and whether it is permissible to run multiple instances IF those instances are different copies on disk.

Introduction

This question came up in the Microsoft discussion forums and while the answer is relatively simple (use a mutex), the practical implementation requires some understanding of how Windows does things. I made an implementation myself out of curiosity, and decided to share my results.

My implementation uses the win32 API directly for the mutexes. For the rest, I am using the win32 helper library which I am extending with every article just to make life easier because every article uses some functionality of the previous article. The sources are included with this article and as soon as I can do some final cleanup, I'll be opening it on Github.

Background

When implementing mutexes, the idea is simple. We don't actually use the mutex for signalling. Instead, we use it because it is an object that is created in the kernel namespace. And when it is created, the API tells us if it was newly created or already existing. There are a couple of details that matter.

Console Sessions

When a user logs on to Windows, they get assigned a session ID. At any given time, there can be multiple users active on the system. Whether they log in remotely, or directly on the actual physical console is just a technical detail. If we are looking as the requirement of 'is this the first instance', we have to look beyond just our own session, but into other sessions as well. The API for getting the current session ID is WTSGetActiveConsoleSessionId.

The Kernel Namespace

kernel objects live in the kernel namespace. They have a unique name that cannot be reused for another kernel object in the same namespace. The namespace is divided in 2 parts: a global namespace and a local namespace. The global namespace is shared among all user sessions. The local namespace is unique to each session.

Say we're making a mutex named 'bob'. Then 'Global\bob' would be the name of mutex bob in the global namespace. 'Local\bob' would be the name of mutex bob in the local namespace. Note that these names are case sensitive and need to be spelled as shown. If we just use 'bob', then this is automatically assumed to be a local name and placed in the local namespace.

Creating a Mutex

A mutex is created with the API call:

C++
HANDLE CreateMutexA(
  [in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,
  [in]           BOOL                  bInitialOwner,
  [in, optional] LPCSTR                lpName
);

For our purposes, the name is key to identifying our application. We'll discuss the name in detail later. This could be 'Global\bob' for example. bInitialOwner specifies if -when creating the mutex- the thread that creates it is the initial owner of the mutex as well. We don't care about that because we don't use it for signalling.

Suppose we use this call and successfully create a mutex. A subsequent call to GetLastError will tell us if the mutex was newly created (NO_ERROR) or if a handle was opened to an existing mutex (ERROR_ALREADY_EXISTS). That is already half the answer to the question: 'is this the first instance of the application'.

lpMutexAttributes is important mostly for objects in the global namespace. Any mutex that is created without specific security attributes gets the default security descriptor. For objects in the local namespace, that usually doesn't matter because the applications using that mutex are all running with the same user token.

However, for objects in the global namespace, this is not true. If a process running as LocalSystem creates a mutex in the global namespace without specifying security attributes, other processes may not be able to open a handle to that mutex because of security restrictions. For our purposes, this doesn't matter because that failure provides information. If we cannot open a handle to a mutex with that name because of a security restriction, we also know that we were clearly not the first process trying to create it.

Choosing the Mutex Names

With these two namespaces, we can easily determine if the application is the first in the current user session, and if it's the first on the computer as a whole.

Naming a mutex 'bob' isn't a terribly good idea though. It's not inconceivable that another application on the computer would use the same name. Ideally, a mutex has a unique name. Luckily, we have something for that: GUIDs. A GUID is guaranteed to be unique.

However, that's not enough. In some cases, you may want to prohibit the application from running multiple instances concurrently, except if the application is a copy of the application in another location or with another name. For example, if the application is c:\temp\myapp.exe, then in some scenarios, you may want to also allow c:\temp\copy_of_myapp.exe to run next to it. So optionally, we need to consider making the module path part of the mutex name.

The problem is that this way, we get potentially very long mutex names. And '\' is not an allowed character in a mutex name. There is a very easy solution though: we simply push all that information (the GUID and the full module path) through a hash algorithm, and use the resulting hash hex string as a mutex name.

The CAppInstance Class

The implementation is done in a class with the following declaration:

C++
private:
    CHandle m_LocalMutex;
    CHandle m_GlobalMutex;

public:
    CAppInstance(
        LPCWSTR instanceName,
        bool allowCopiesToRun);
    ~CAppInstance();

    bool IsFirstInSession();
    bool IsFirstOnComputer();
};

There isn't a whole lot to say about it, except that upon construction, we initialize two mutexes which can then be examined later to determine if the process was the first instance of that application. CHandle is a class that wraps an actual HANDLE just for the purpose of making sure it is closed eventually and to check if it is a valid handle or not.

Determining the Names

This is where the heavy lifting is done. First, we figure out the names we want to use:

C++
wstring globalMutexName = L"Global\\MUTEX_";
wstring localMutexName = L"Local\\MUTEX_";

CBCryptProvider hashProvider;
CBCryptHashObject hashObject(hashProvider);

//Guarantee global uniquess by hashing the GUID
AddDataToHash(hashObject, wstring(instanceGuid));

//If copies are allowed to run but not the same image on disk,
//then we add the module path to the hash,
//guaranteeing a uniqueness per each copy of the image.
if (allowCopiesToRun) {
    TCHAR modulePath[4096];
    DWORD numChars = GetModuleFileName(NULL, modulePath,
                                       sizeof(modulePath) / sizeof(TCHAR));
    if (0 == numChars) {
        throw ExWin32Error();
    }

    AddDataToHash(hashObject, wstring(modulePath));
}

wstring name = hashObject.GetHashHexString();

globalMutexName += name;
localMutexName += name;

We need to create two names: one in the Global namespace, the other in the Local namespace. Because the final name will be an unreadable hex string, we use a 'MUTEX_' prefix. Should we be troubleshooting later on and viewing named objects in the namespaces, at least we know we're looking at a mutex.

In order to create a hash, we use the CBCryptProvider and CBCryptHashObject classes which were Implemented in a previous article. The exact hash method doesn't matter so it defaults to BCRYPT_SHA256_ALGORITHM, and we don't have to bother with a hash secret either because we only use it as a glorified statistically unique checksum.

First, we add the GUID to the hash. If we only want to permit a single instance to run, regardless of whether it is copied on diks or not, that is where we stop. No matter where the image is started from, if the mutex is made with just that GUID, there can be only 1.

Now if we want to allow only 1 instance of a specific image on disk, but are fine with running, e.g., c:\temp\myapp.exe and c:\temp\copy_of_myapp.exe, then we simply push the module path into the hash. That will guarantee a unique name for that GUID, per module.

Detecting the Instance

Previously, we discussed that if we create a mutex handle, three things can happen:

  1. It is created without special error information. In that case, it didn't exist before.
  2. It is created and the error status is ERROR_ALREADY_EXISTS. That means it already existed.
  3. There was an error and the mutex handle is not valid. That also means it already existed.
C++
DWORD retVal = NO_ERROR;

m_GlobalMutex = CreateMutex(NULL, TRUE, globalMutexName.c_str());
retVal = GetLastError();
if (m_GlobalMutex.IsValid() && ERROR_ALREADY_EXISTS == GetLastError()) {
    m_GlobalMutex.CloseHandle();
}

m_LocalMutex = CreateMutex(NULL, TRUE, localMutexName.c_str());
retVal = GetLastError();
if (m_LocalMutex != NULL && ERROR_ALREADY_EXISTS == GetLastError()) {
    m_LocalMutex.CloseHandle();
}

At the end of that segment, having a valid mutex for a given scope will be evidence that the instance was the first in that scope.

If the application wants to use that information to make a decision, it can use the following methods:

C++
//Is this the first instance in this particular session?
bool CAppInstance::IsFirstInSession() {
    return m_LocalMutex.IsValid();
}

//Is this the first instance on this particular computer?
bool CAppInstance::IsFirstOnComputer() {
    return m_GlobalMutex.IsValid();
}

Using the CAppInstance Class

Using the class is trivial:

C++
try {
    CAppInstance g_AppInstance(L"406B6F5D-4A7B-43A7-8CF8-1E44B3C938BE", false);

    wcout << L"Started process as user " << w32_GetCurrentUserName()
        << L" in session " << WTSGetActiveConsoleSessionId() << endl;
    cout << "App is first in session: " << g_AppInstance.IsFirstInSession() << endl;
    cout << "App is first on computer: " <<
             g_AppInstance.IsFirstOnComputer() << endl;
    cout << "Press any key to end the application." << endl;
    string input;
    getline(cin, input);
}
catch (exception& ex) {
    cout << ex.what() << endl;
}

For our application, we use GUIDGen to create a GUID and paste that as instance name. We also indicate that we do not want copies of the application on disk to be identified as different applications.

Note that the format of the GUID doesn't matter. It all goes into the hash algorithm. As long as the GUID is in there, any whitespace or special characters do not detract from the 'unique-ness'.

When I tested the program, I first ran it as admin:

Image 1

And then two times as myself:

Image 2

Points of Interest

Aside from having the option to restrict instances this way, there is the possibility for implementing additional restrictions no matter how farfetched they seem. It all depends on what we push into the hash. If we add the year and the day of the year, then we can implement a policy where one instance can start per day of the year and multiple instances can run concurrently as long as they start on different days.

It wouldn't be very useful of course, but I just wanted to indicate that you can define any sort of uniqueness that you want and make runtime decisions based on that definition of uniqueness.

It should be noted that this sort of construction could potentially lead to a vulnerability. The global and local namespace can be inspected and if an attacker knows the name of the mutex that is being used, it could potentially lead to a situation where they create the mutex in order to prevent your application from running (denial of service). Some mitigation is possible but after the first time a mutex with that name has been created, it is no longer guaranteed to be a secret.

History

  • 10th November, 2022: Added note about possible denial of service.
  • 21st October, 2022: First version

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
Belgium Belgium
I am a former professional software developer (now a system admin) with an interest in everything that is about making hardware work. In the course of my work, I have programmed device drivers and services on Windows and linux.

I have written firmware for embedded devices in C and assembly language, and have designed and implemented real-time applications for testing of satellite payload equipment.

Generally, finding out how to interface hardware with software is my hobby and job.

Comments and Discussions

 
BugIncorrect behaviour in CHandle::CloseHandle Pin
Member 130170272-Oct-23 20:43
Member 130170272-Oct-23 20:43 
QuestionGreat tip Pin
Member 1452198428-Oct-22 19:18
Member 1452198428-Oct-22 19:18 
AnswerRe: Great tip Pin
Bruno van Dooren1-Nov-22 11:02
mvaBruno van Dooren1-Nov-22 11:02 
AnswerRe: Great tip Pin
charlieg9-Dec-22 0:48
charlieg9-Dec-22 0:48 
QuestionGreat Article Pin
Ruth_Haephrati27-Oct-22 9:36
professionalRuth_Haephrati27-Oct-22 9:36 
AnswerRe: Great Article Pin
Bruno van Dooren27-Oct-22 21:46
mvaBruno van Dooren27-Oct-22 21:46 
GeneralRe: Great Article Pin
Ruth_Haephrati28-Oct-22 7:08
professionalRuth_Haephrati28-Oct-22 7:08 
QuestionAn in-depth article on the subject Pin
Damir Valiulin26-Oct-22 6:06
Damir Valiulin26-Oct-22 6:06 
AnswerRe: An in-depth article on the subject Pin
Bruno van Dooren26-Oct-22 20:42
mvaBruno van Dooren26-Oct-22 20:42 
GeneralMy vote of 5 Pin
Martin ISDN24-Oct-22 23:13
Martin ISDN24-Oct-22 23:13 
Suggestioncreate the mutex in nonsignaled state? Pin
Martin ISDN24-Oct-22 13:01
Martin ISDN24-Oct-22 13:01 
GeneralRe: create the mutex in nonsignaled state? Pin
Bruno van Dooren24-Oct-22 19:32
mvaBruno van Dooren24-Oct-22 19:32 
QuestionSecurity Pin
Eric Kenslow24-Oct-22 8:38
Eric Kenslow24-Oct-22 8:38 
AnswerRe: Security Pin
Bruno van Dooren24-Oct-22 20:24
mvaBruno van Dooren24-Oct-22 20:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.