IPC:UWP C# 管道客户端在连接 C++ 服务器时失败

vin*_*ent 0 c# c++ ipc pipe uwp

我正在尝试使用 NamedPipe 在 Win10 中的应用程序和服务之间进行通信。APP是用C#(UWP)开发的,前台作为Pipe Client运行。服务是 C++ 运行后台作为管道服务器。现在的问题是APP无法连接服务。我知道 MSFT 文档说管道仅在应用程序容器中受支持。但我尝试了以下情况:我的 uwp 应用程序 VS C#(nonUWP) 服务器(不在应用程序容器中);C++ 客户端 VS C++ 服务器(与服务相同的代码,除了在前台运行)。两种情况都可以正常工作。所以我认为它的安全权限可能有问题。但是我找不到任何异常,有人可以帮助我吗?

客户端(UWP/C#):

_namedPipeClientHandle[index] = CreateFileW(@"\\.\pipe\starpipe",
                            DESIREDACCESS.GENERIC_READ | DESIREDACCESS.GENERIC_WRITE,
                            SHAREMODE.FILE_SHARE_READ | SHAREMODE.FILE_SHARE_WRITE,
                            0,
                            CREATIONDISPOSITION.OPEN_EXISTING,
                            FLAGSANDATTRIBUTES.FILE_FLAG_OVERLAPPED,
                            0);
                    if (_namedPipeClientHandle[index] != null && _namedPipeClientHandle[index].IsInvalid == false)
                    {
                        _namedPipeClientStream[index] = new FileStream(_namedPipeClientHandle[index], FileAccess.ReadWrite, 2048, true);
                        isConnected[index] = true;
                        break;
                    }
Run Code Online (Sandbox Code Playgroud)

服务器(C++):

    EXPLICIT_ACCESS ea[2];
    SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
    SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
    SECURITY_ATTRIBUTES sa;

    if (!AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryoneSID)) return false;

    SecureZeroMemory(&ea, 2 * sizeof(EXPLICIT_ACCESS));
    ea[0].grfAccessPermissions = FILE_ALL_ACCESS | GENERIC_WRITE | GENERIC_READ;
    ea[0].grfAccessMode = SET_ACCESS;
    ea[0].grfInheritance = NO_INHERITANCE;
    ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
    ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
    ea[0].Trustee.ptstrName = (LPTSTR)pEveryoneSID;

    if (!AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminSID)) return false;
    ea[1].grfAccessPermissions = FILE_ALL_ACCESS | GENERIC_WRITE | GENERIC_READ;
    ea[1].grfAccessMode = SET_ACCESS;
    ea[1].grfInheritance = NO_INHERITANCE;
    ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
    ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
    ea[1].Trustee.ptstrName = (LPTSTR)pAdminSID;

    DWORD dwRes = SetEntriesInAclW(2, ea, NULL, &pACL);
    if (ERROR_SUCCESS != dwRes) return false;

    auto secDesc = std::vector<unsigned char>(SECURITY_DESCRIPTOR_MIN_LENGTH);
    PSECURITY_DESCRIPTOR pSD = (PSECURITY_DESCRIPTOR)(&secDesc[0]);
    if (nullptr == pSD) return false;

    if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) return false;
    if (!SetSecurityDescriptorDacl(pSD, TRUE, pACL, FALSE)) return false;

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = pSD;
    sa.bInheritHandle = FALSE;

    const char* pStrPipeName = "\\\\.\\pipe\\starpipe";
    m_hPipe = CreateNamedPipeA(
        pStrPipeName,
        PIPE_ACCESS_DUPLEX,
        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_TYPE_BYTE | PIPE_WAIT,
        PIPE_UNLIMITED_INSTANCES,
        2048,
        2048,
        0,
        &sa);

    if (m_hPipe == INVALID_HANDLE_VALUE) return false;

    if (::ConnectNamedPipe(m_hPipe, NULL)) return true;
Run Code Online (Sandbox Code Playgroud)

RbM*_*bMm 5

从 UWP 进程(应用程序容器)创建的命名管道名称必须具有形式

"\\\\?\\pipe\\local\\SomeName"

系统将此名称转换为

"\\\\?\\pipe\\Sessions\\<SessionId>\\AppContainerNamedObjects\\<AppContainerSid>\\SomeName"

所以桌面应用程序必须创建具有这种名称格式的管道,以便 UWP 能够打开它。但为此需要知道 uwp 应用程序的 appcontainer-sid 及其会话 ID。好的,appcontainer-sid 对于具体的 UWP 是永久性的 - 永远不会改变,但 SessionId 可以不同(通常是 1,但可以是另一个)。还需要在管道上设置特殊的安全描述符,授予对SDDL_EVERYONE + SDDL_ALL_APP_PACKAGES + SDDL_ML_LOW. 例如

"D:(A;;GA;;;WD)(A;;GA;;;AC)S:(ML;;;;;LW)"

在桌面上创建此类管道的示例

inline ULONG BOOL_TO_ERROR(BOOL f)
{
    return f ? NOERROR : GetLastError();
}

volatile UCHAR guz = 0;

ULONG CreatePipeforUWP(OUT PHANDLE PipeHandle, PCWSTR PipeName, HANDLE hProcess)
{
    SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, FALSE };

    // SDDL_EVERYONE + SDDL_ALL_APP_PACKAGES + SDDL_ML_LOW
    if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
        L"D:(A;;GA;;;WD)(A;;GA;;;AC)S:(ML;;;;;LW)", 
        SDDL_REVISION_1, &sa.lpSecurityDescriptor, 0))
    {
        return GetLastError();
    }

    HANDLE hToken;

    ULONG err = BOOL_TO_ERROR(OpenProcessToken(hProcess, TOKEN_QUERY, &hToken));

    if (err == NOERROR)
    {
        PVOID stack = alloca(guz);

        ULONG cb = 0, rcb = 128;

        union {
            PVOID buf;
            PTOKEN_APPCONTAINER_INFORMATION AppConainer;
            PWSTR sz;
        };

        do 
        {
            if (cb < rcb)
            {
                cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
            }

            err = BOOL_TO_ERROR(GetTokenInformation(hToken, ::TokenAppContainerSid, buf, cb, &rcb));

            if (err == NOERROR)
            {
                ULONG SessionId;

                err = BOOL_TO_ERROR(GetTokenInformation(hToken, ::TokenSessionId, &SessionId, sizeof(SessionId), &rcb));

                if (err == NOERROR)
                {
                    PWSTR szSid;

                    err = BOOL_TO_ERROR(ConvertSidToStringSid(AppConainer->TokenAppContainer, &szSid));

                    if (err == NOERROR)
                    {
                        static const WCHAR fmt[] = L"\\\\?\\pipe\\Sessions\\%d\\AppContainerNamedObjects\\%s\\%s";

                        rcb = (1 + _scwprintf(fmt, SessionId, szSid, PipeName)) * sizeof(WCHAR);

                        if (cb < rcb)
                        {
                            cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
                        }

                        _swprintf(sz, fmt, SessionId, szSid, PipeName);

                        HANDLE hPipe = CreateNamedPipeW(sz,
                            PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, 
                            PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, 
                            PIPE_UNLIMITED_INSTANCES, 0, 0, 0, &sa);

                        if (hPipe == INVALID_HANDLE_VALUE)
                        {
                            err = GetLastError();
                        }
                        else
                        {
                            *PipeHandle = hPipe;
                        }

                        LocalFree(szSid);
                    }
                }
                break;
            }

        } while (err == ERROR_INSUFFICIENT_BUFFER);

        CloseHandle(hToken);
    }

    LocalFree(sa.lpSecurityDescriptor);

    return err;
}

ULONG CreatePipeforUWP(OUT PHANDLE PipeHandle, PCWSTR PipeName, ULONG dwProcessId)
{
    if (HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, dwProcessId))
    {
        ULONG err = CreatePipeforUWP(PipeHandle, PipeName, hProcess);
        CloseHandle(hProcess);
        return err;
    }
    return GetLastError();
}


HANDLE hPipe;
if (CreatePipeforUWP(&hPipe, L"MyPipe", *) == NOERROR)
Run Code Online (Sandbox Code Playgroud)

在客户端 (UWP) 应用程序中

CreateFileW(L"\\\\?\\pipe\\local\\MyPipe", 
        FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
Run Code Online (Sandbox Code Playgroud)

但是对于此服务器,需要知道 UWP 会话 ID(或进程 ID)或假设 UWP 与服务器在同一会话中运行。一般来说,这不好。

我建议使用 RPC 代替命名管道。这里没有任何问题。

桌面需要使用RpcServerRegisterIf3并为界面设置 SecurityDescriptor 以便让 UWP 访问它。说"D:P(A;;GA;;;WD)(A;;GA;;;AC)(A;;GA;;;S-1-15-2-2)S:(ML;;;;;LW)"。和 UWP 的这个很好的工作,例如服务器代码

ULONG InitRpcServer()
{
    PSECURITY_DESCRIPTOR SecurityDescriptor;

    // generic all for SDDL_ALL_APP_PACKAGES + SDDL_EVERYONE

    ULONG dwError = BOOL_TO_ERROR(ConvertStringSecurityDescriptorToSecurityDescriptorW(
        L"D:P(A;;GA;;;WD)(A;;GA;;;AC)(A;;GA;;;S-1-15-2-2)S:(ML;;;;;LW)", SDDL_REVISION, &SecurityDescriptor, 0));

    if (dwError == ERROR_SUCCESS)
    {
        dwError = RpcServerRegisterIf3(hello_v1_0_s_ifspec,
            NULL, NULL, RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH,
            RPC_C_LISTEN_MAX_CALLS_DEFAULT, 0x10000, 0, SecurityDescriptor);

        if (dwError == RPC_S_OK)
        {
            dwError = RpcServerUseProtseqEpW(
                (RPC_WSTR)L"ncalrpc",
                RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
                (RPC_WSTR)L"myname",
                SecurityDescriptor);

            if (dwError == RPC_S_OK)
            {
                dwError = RpcServerListen(1, RPC_C_LISTEN_MAX_CALLS_DEFAULT, TRUE);
            }
        }

        LocalFree(SecurityDescriptor);
    }

    return dwError;
}
Run Code Online (Sandbox Code Playgroud)

和客户只需要

RpcBindingFromStringBinding((RPC_WSTR)L"ncalrpc:[myname]", &IDL_handle)
Run Code Online (Sandbox Code Playgroud)