0

I have a windows app that (for company policy reasons) I would like to be limited to a single user at a time, but allowing multiple instances to run simultaneously within a single session.

I am using a mutex to do this much like as already asked and answered in

The first one on the list is promising, but the proposed solution is not clear if it is just for "the same user with the same session id" as asked, or only for "the same user in any session" as is implied in the second answer.

My question is, will the first approach really work to restrict access rights to the mutex to the same user in the same session, or is it just to the same user?

More precisely, does the default security descriptor contain any restriction information for the session id? I suspect that is doesn't, meaning that the same user in a different session would have the same default security descriptor and would still be able to get access rights the mutex.

Am I correct about this?

If so, how would I clone the default security descriptor and add a restriction on being in the same session as the creator?

opeongo
  • 384
  • 1
  • 14
  • begin from win7 (or may be vista) default *DACL* have `GENERIC_READ|GENERIC_EXECUTE` allowed ace for `LogonSessionId_X_Y` to which user belong. on xp - no such in `DACL`. if you not use explicit *SD* - default will be assigned to object. so for win7+ usually use default is ok. for xp need take Logon SID from process token and use explicit *SD*. also you not need mutex exactly. say event object is ok too if you use only object name. and need use `CreateEventExW` for set desired access to object - say `SYNCHRONIZE`. also this need for low integrity app not fail – RbMm Oct 17 '18 at 00:47

1 Answers1

0

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);
    }
}
RbMm
  • 31,280
  • 3
  • 35
  • 56