if you like to be limited to a single user at a time, most logical solution - create some named object in common global namespace (so visible for all) and allow only concrete user Sid access this object. as result all instance which run by the same user can open this object, when another users, will be have another Sid and can fail open it, if it already exist. we not mandatory need mutex. the best use named event - this is most simply and small object. also need use not CreateEvent
api but CreateEventExW
because it let specify dwDesiredAccess - this is important in case some code will be run as low/untrusted integrity. he fail open existing object with all access (if we not add untrusted label to object), but can open object with generic read/generic execute access. we can say request only SYNCHRONIZE
access for event. the poc code:
ULONG BOOL_TO_ERROR(BOOL f)
{
return f ? NOERROR : GetLastError();
}
volatile UCHAR guz = 0;
ULONG CanRun(BOOL& bCan)
{
bCan = false;
HANDLE hToken;
ULONG dwError = BOOL_TO_ERROR(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken));
if (dwError == NOERROR)
{
ULONG cb = 0, rcb = sizeof(TOKEN_USER) + GetSidLengthRequired(5);
PVOID stack = alloca(guz);
union {
PVOID buf;
PTOKEN_USER User;
};
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
dwError = BOOL_TO_ERROR(GetTokenInformation(hToken, TokenUser, buf, cb, &rcb));
} while (dwError == ERROR_INSUFFICIENT_BUFFER);
CloseHandle(hToken);
if (dwError == NOERROR)
{
PSID UserSid = User->User.Sid;
SECURITY_DESCRIPTOR sd;
SECURITY_ATTRIBUTES sa = { sizeof(sa), &sd, FALSE };
PACL Dacl = (PACL)alloca(cb = sizeof(ACL) + FIELD_OFFSET(ACCESS_ALLOWED_ACE, SidStart) + GetLengthSid(UserSid));
if (
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) &&
InitializeAcl(Dacl, cb, ACL_REVISION) &&
AddAccessAllowedAce(Dacl, ACL_REVISION, GENERIC_ALL, UserSid) &&
SetSecurityDescriptorDacl(&sd, TRUE, Dacl, FALSE) &&
CreateEventExW(&sa, L"Global\\<Your Unique Name>", CREATE_EVENT_MANUAL_RESET, SYNCHRONIZE)
)
{
bCan = true;
}
else
{
switch (dwError = GetLastError())
{
case ERROR_ACCESS_DENIED:
dwError = NOERROR;
break;
}
}
}
}
return dwError;
}
so we query TOKEN_USER
from current process token and create (or open) named event "Global\\<Your Unique Name>"
. if even yet not exist - we create it and assign SD which allow only to the same user sid open it. any other users( even localsystem) fail open this event (without change it dacl first) with ERROR_ACCESS_DENIED
.
demo usage:
BOOL bCan;
ULONG dwError = CanRun(bCan);
if (dwError == NOERROR)
{
MessageBoxW(0, 0, bCan ? L"Yes" : L"No", bCan ? MB_ICONINFORMATION : MB_ICONWARNING);
}
else
{
PWSTR sz;
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, 0, dwError, 0, (PWSTR)&sz, 0, 0))
{
MessageBoxW(0, sz, 0, MB_ICONHAND);
LocalFree(sz);
}
}