创建仅在同一会话中可见的互斥体

时间:2018-10-16 22:51:06

标签: windows winapi mutex single-instance

我有一个Windows应用程序(出于公司政策的原因)我希望一次只限于一个用户,但允许多个实例在单个会话中同时运行。

我正在使用互斥锁来执行此操作,就像已经在其中询问和回答的那样

列表中的第一个是有希望的,但是提出的解决方案尚不清楚,它是否仅适用于询问的“具有相同会话ID的同一用户”,还是仅适用于“任何会话中的相同用户”暗示第二个答案。

我的问题是,第一种方法是否真的可以将互斥量的访问权限限制在同一会话中的同一用户,或者仅是同一用户?

更准确地说,默认安全描述符是否包含会话ID的任何限制信息?我怀疑不是,这意味着在不同会话中的同一用户将具有相同的默认安全描述符,并且仍然能够获得互斥量的访问权限。

我对此是否正确?

如果是这样,我该如何克隆默认的安全描述符并添加与创建者处于同一会话的限制?

1 个答案:

答案 0 :(得分:0)

如果您希望一次只限于一个用户,则最合乎逻辑的解决方案-在通用全局命名空间中创建一些命名对象(对所有用户可见),并且仅允许具体用户 Sid 访问此对象宾语。结果,同一用户运行的所有实例都可以打开该对象,而另一个用户拥有另一个 Sid 时,如果该对象已经存在,则可能无法打开。我们不是必需的互斥体。最好使用命名事件-这是最简单的小对象。还不需要使用Google service account and sheets permissions api,而是使用CreateEvent,因为它可以指定 dwDesiredAccess -如果某些代码将以低/不可信任的完整性运行,这很重要。他无法通过所有访问权限打开现有对象(如果我们不向对象添加不受信任的标签),但是可以使用通用读取/通用执行访问权限打开对象。我们可以说只请求SYNCHRONIZE访问事件。 poc代码:

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;
}

因此,我们从当前进程令牌中查询CreateEventExW,并创建(或打开)命名事件"Global\\<Your Unique Name>"。如果还不存在-我们将其创建并分配 SD ,它仅允许同一用户sid打开它。任何其他用户(甚至本地系统)都无法使用ERROR_ACCESS_DENIED打开此事件(无需先更改此事件)。

演示用法:

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);
    }
}