IPC: UWP C# pipe client fails on connect C++ serve

2020-05-01 10:02发布

问题:

I am trying to use NamedPipe to communicate between app and service in Win10. APP is developed with C#(UWP),running foreground as Pipe Client. And service is C++ running background as Pipe Server. Now the problem is that the APP couldn't connect the service. I know that MSFT doc said Pipes are only supported within an app-container. But I have tried the following cases: my uwp app VS C#(nonUWP) Server(not in an app-container); C++ Client VS C++ server(same code with service except running in foreground). Both cases works fine. So I think it may have something wrong with Security privilege. But I couldn't find somthing abnormal, Could someone help me out?

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

Server(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;

回答1:

when named pipe created from UWP process (appcontainer) name must have form

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

system convert this name to

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

so desktop application must create pipe with such name format for UWP be able open it. but for this need know appcontainer-sid for uwp app and it session id. ok, appcontainer-sid is permanent for concrete UWP - never changed, but SessionId can be different (usually 1 but can be and another). also need set special security descriptor on pipe, which grant access for SDDL_EVERYONE + SDDL_ALL_APP_PACKAGES + SDDL_ML_LOW. for example

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

example of create such pipe in desktop

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)

in client (UWP) app simply

CreateFileW(L"\\\\?\\pipe\\local\\MyPipe", 
        FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);

but for this server need know UWP session id (or process id) or assume that UWP run in same session with server. in general this is not nice.

i be advice use RPC instead named pipes. here no any problems.

desktop need use RpcServerRegisterIf3 and set SecurityDescriptor for interface for let UWP access it. say "D:P(A;;GA;;;WD)(A;;GA;;;AC)(A;;GA;;;S-1-15-2-2)S:(ML;;;;;LW)". and this fine work with UWP, for example server code

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

and client only need

RpcBindingFromStringBinding((RPC_WSTR)L"ncalrpc:[myname]", &IDL_handle)


回答2:

According to this page

Pipes are only supported within an app-container; ie, from one UWP process to another UWP process that's part of the same app. Also, named pipes must use the syntax ".\pipe\LOCAL" for the pipe name.



标签: c# c++ uwp pipe ipc