在 windows c++ 上创建仅具有管理员访问权限的多线程命名管道服务器时出错

Errors creating a multithreaded named pipe server with Administrator only access on windows c++

我正在尝试创建多线程命名管道服务器,如此处的 msdn 示例中所述https://docs.microsoft.com/en-us/windows/win32/ipc/multithreaded-pipe-server,但我正在尝试将命名管道限制为仅由管理员组成员访问。

当未指定 SECURITY_ATTRIBUTES 结构但指定 SA 时,该示例可以正常工作,但只要第一个管道正在侦听或与客户端通信,对 CreateNamedPipe 的后续调用就会失败.创建调用失败,通常为 ACCESS_DENIED,但有时会出现错误 1305 The revision level is unknown。当第一个管道由于客户端断开连接而关闭时,下一个 createnamedpipe 调用的以下调用将成功,但一旦该管道有一个客户端,它将依次失败。

我已尝试为 grfInheritance 字段设置多个值,但均无济于事。这是我第一次尝试明确指定安全性,所以如果我遗漏了一些明显的东西,请原谅我。请注意,在调用 createnamedpipe 的函数中,我在每次创建尝试时都创建了一个新的 SA 结构,但我也尝试创建一个并在创建循环之外共享它。

相关代码如下:

创建管道的函数:

HRESULT DapiSettingsSvr::DapiSettingsListener()
{
    
    
    while(m_run)
    {   
        //find an unused control array member. If they are all used we have max connection so dont create a pipe.
        UINT connectId = 0;
        for (connectId; connectId < MAX_CONNECTIONS; connectId++)
        {
            if (m_controlArray[connectId].inuse == false)
                break;
        }

        SECURITY_ATTRIBUTES sa;
        HRESULT hr =  InitializeSecurity(&sa);
        if (FAILED(hr))
        {
            return hr;
        }

        if (connectId < MAX_CONNECTIONS)
        {
            HANDLE hpipe;
            

                hpipe = CreateNamedPipe(
                lpszPipename,               // pipe name 
                PIPE_ACCESS_DUPLEX,         // read/write access 
                PIPE_TYPE_BYTE |            // byte bipe
                PIPE_READMODE_BYTE |        // read as bytes 
                PIPE_WAIT |                 // do not return until data is recieved
                PIPE_REJECT_REMOTE_CLIENTS, // no remote connections               
                MAX_CONNECTIONS,            // max. instances  
                OUTPUT_BUFFER_SIZE,         // output buffer size 
                INPUT_BUFFER_SIZE,          // input buffer size 
                0,                          // client time-out 
                &sa);                       // default security attribute 

           // CleanUpSecurityResources();

            if (hpipe == INVALID_HANDLE_VALUE)
            {
                swprintf(logbuffer, ARRAYSIZE(logbuffer), L"CreateNamedPipe failed, GLE=%d.\n", GetLastError());
                DapiSettingLogger(logbuffer);
            }
            else
            {
                m_controlArray[connectId].inuse = true;
                m_controlArray[connectId].pThis = this;
                m_controlArray[connectId].connectId = connectId;
                m_controlArray[connectId].pipehandle = hpipe;

                swprintf(logbuffer, ARRAYSIZE(logbuffer), L"\nPipe Server: Main thread awaiting client connection on %s\n", lpszPipename);
                DapiSettingLogger(logbuffer);

                // block until a client tries to connect.success is non zero. However a client can connect between the create call and ConnectNamedPipe call.
                //  In this case ConnectNamedPipe returns zero but GLE = ERROR_PIPE_CONNECTED and a valid connection exists. Check for this case.
                fConnected = ConnectNamedPipe(hpipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);

                if (fConnected)
                {
                    // Create a thread for this client. 
                    m_controlArray[connectId].threadHandle = CreateThread(
                        NULL,              // no security attribute 
                        0,                 // default stack size 
                        WorkerInstance,    // thread proc
                        (LPVOID)&m_controlArray[connectId],    // thread parameter 
                        0,                 // not suspended 
                        &m_controlArray[connectId].threadId);      // returns thread ID 

                    if (m_controlArray[connectId].threadHandle == NULL)
                    {
                        swprintf_s(logbuffer, ARRAYSIZE(logbuffer), L"CreateThread failed, GLE=%d.\n", GetLastError());
                        DapiSettingLogger(logbuffer);
                        CloseHandle(m_controlArray[connectId].pipehandle);
                        ZeroMemory(&m_controlArray[connectId], sizeof(WORKER_INFO));
                    }
                }
                else
                {
                    // The client could not connect, so close the pipe. 
                    CloseHandle(m_controlArray[connectId].pipehandle);
                    ZeroMemory(&m_controlArray[connectId], sizeof(WORKER_INFO));
                }                 
            } //else valid connection            
        }
        else 
        {
            DapiSettingLogger((LPWSTR) L"Max Connections reached\n");
        }       
    }
    return S_OK;
}

创建 SA 的函数

HRESULT DapiSettingsSvr::InitializeSecurity(SECURITY_ATTRIBUTES* psa)
{
    HRESULT result = S_OK;
    DWORD res, error;
    EXPLICIT_ACCESS ea[1];
    SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
    
    // Create a SID for the BUILTIN\Administrators group.
    if (!AllocateAndInitializeSid(&SIDAuthNT, 2,
        SECURITY_BUILTIN_DOMAIN_RID,
        DOMAIN_ALIAS_RID_ADMINS,
        0, 0, 0, 0, 0, 0,
        &m_pAdminSID))
    {
        error = GetLastError();
        swprintf(logbuffer, ARRAYSIZE(logbuffer), L"AllocateAndInitializeSid Error %u\n", error);
        DapiSettingLogger(logbuffer);
        result = HRESULT_FROM_WIN32(error);
        goto Cleanup;
    }
    ea[0].grfAccessPermissions = GENERIC_ALL;
    ea[0].grfAccessMode = GRANT_ACCESS;
    ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; //changing
    ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
    ea[0].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
    ea[0].Trustee.ptstrName = (LPTSTR)m_pAdminSID;

    // Create a new ACL that contains the new ACE.
    res = SetEntriesInAcl(1, ea, NULL, &m_pACL);
    if (ERROR_SUCCESS != res)
    {
        swprintf(logbuffer, ARRAYSIZE(logbuffer),L"SetEntriesInAcl Error %u\n", res);
        DapiSettingLogger(logbuffer);
        result = HRESULT_FROM_WIN32(res);
        goto Cleanup;
    }

    // Initialize a descriptor Use localalloc as it allows memory moving without changing handle value 
    m_pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR,
        SECURITY_DESCRIPTOR_MIN_LENGTH);
    if (NULL == m_pSD)
    {
        error = GetLastError();
        swprintf(logbuffer, ARRAYSIZE(logbuffer), L"LocalAlloc Error %u\n", error);
        result = HRESULT_FROM_WIN32(error);
        goto Cleanup;
    }

    if (!InitializeSecurityDescriptor(m_pSD,
        SECURITY_DESCRIPTOR_REVISION))
    {
        error = GetLastError();
        swprintf(logbuffer, ARRAYSIZE(logbuffer), L"InitializeSecurityDescriptor Error %u\n", error);
        result = HRESULT_FROM_WIN32(error);
        goto Cleanup;
    }

    // Add the ACL to the security descriptor. 
    if (!SetSecurityDescriptorDacl(m_pSD,
        TRUE,     // bDaclPresent flag   
        m_pACL,
        FALSE))   // not a default DACL 
    {
        error = GetLastError();
        swprintf(logbuffer, ARRAYSIZE(logbuffer), L"SetSecurityDescriptorDacl Error %u\n", error);
        result = HRESULT_FROM_WIN32(error);
        goto Cleanup;
    }

Cleanup:
    if (FAILED(result))
    {
        CleanUpSecurityResources();
    }
    else
    {
        // Initialize a security attributes structure.
        psa->nLength = sizeof(SECURITY_ATTRIBUTES);
        psa->lpSecurityDescriptor = m_pSD;
        psa->bInheritHandle = TRUE;     /// NOTE I have toyed with this value also
    }
    return result;
}

任何关于我做错了什么的意见都将非常感谢!!

谢谢!

根据Named Pipe Security and Access Rights,

In addition to the requested access rights, the DACL must allow the calling thread FILE_CREATE_PIPE_INSTANCE access to the named pipe.

好的,我明白了。我打算将 YangXiaoPo 的回答标记为正确,因为这为我指明了正确的方向,但为了澄清 GENERIC_ALL 已经包含 FILE_CREATE_PIPE_INSTANCE 的权利,或者至少这是我的测试指示。所以将 EXPICIT_ACCESS 结构字段设置为 ea[0].grfAccessPermissions = GENERIC_ALL | FILE_CREATE_PIPE_INSTANCE;无法解决此问题。

答案在于我是 运行 visual studio ( debug ) 中的 PipeServer 程序,因此是普通用户。因此,第一次通过循环创建管道,然后将具有本地管理员组 ACE 的 SA 应用于管道。 这样我们就得到了一个在监听状态下创建的管道。一旦客户端连接,就会创建工作线程,然后 while(m_run) 循环会进行另一次迭代并尝试创建一个新的管道实例。此尝试失败(实际上是循环失败),因为现在查看了仅具有管理员 ACL 的安全属性,并且该程序不是 运行 作为管理员。一旦第一个客户端断开连接,工作线程就会关闭管道句柄(有效地破坏管道),然后在下一次迭代中再次创建管道。 运行 以管理员身份运行的程序(或以管理员身份启动 Visual studio 然后进行调试)解决了该问题,但我认为完全正确的解决方案是创建第二个 ACE,该 ACE 除了 Admin 之外还指定了 Creator Owner SA DACL。 谢谢!!