【问题标题】:Create a mutex that is only visible within the same session创建仅在同一会话中可见的互斥锁
【发布时间】:2018-10-16 22:51:06
【问题描述】:

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

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

列表中的第一个是有希望的,但建议的解决方案尚不清楚它是仅针对所要求的“具有相同会话 id 的同一用户”,还是仅针对“任何会话中的同一用户”暗示在第二个答案中。

我的问题是,第一种方法是否真的可以将互斥锁的访问权限限制为同一会话中的同一用户,还是仅针对同一用户?

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

我说的对吗?

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

【问题讨论】:

  • 从win7开始(或者可能是vista)默认DACLGENERIC_READ|GENERIC_EXECUTE允许用户所属的LogonSessionId_X_Y的ace。在 xp 上 - DACL 中没有。如果您不使用显式 SD - 默认值将分配给对象。所以对于win7+,通常使用默认就可以了。对于 xp 需要从进程令牌中获取登录 SID 并使用显式 SD。你也不需要互斥锁。如果您只使用对象名称,则说事件对象也可以。并且需要使用CreateEventExW 来设置对对象的所需访问权限——比如SYNCHRONIZE。这种对低完整性应用程序的需求也不会失败

标签: windows winapi mutex single-instance


【解决方案1】:

如果您希望一次仅限于单个用户,最合乎逻辑的解决方案 - 在公共全局命名空间中创建一些命名对象(对所有人可见)并只允许具体用户 Sid 访问它目的。结果,由同一用户运行的所有实例都可以打开此对象,当另一个用户将拥有另一个 Sid 并且如果它已经存在,则可能无法打开它。我们不是强制需要互斥锁。最好用的命名事件——这是最简单的小对象。还需要使用不是CreateEvent api 而是CreateEventExW,因为它可以指定 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;
}

所以我们从当前进程令牌中查询TOKEN_USER 并创建(或打开)命名事件"Global\\&lt;Your Unique Name&gt;"。如果甚至不存在 - 我们创建它并分配 SD 只允许同一个用户 sid 打开它。任何其他用户(甚至本地系统)都无法使用ERROR_ACCESS_DENIED 打开此事件(无需先更改 dacl)。

演示用法:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-09
    • 1970-01-01
    • 2012-12-25
    • 1970-01-01
    • 2018-05-23
    • 2015-08-09
    相关资源
    最近更新 更多