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



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

客户端(UWP/C#(:

_namedPipeClientHandle[index] = CreateFileW(@"\.pipestarpipe",
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;
}

服务器(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;

当从UWP进程(appcontainer(创建的命名管道名称必须具有形式时

"\\?\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)

客户端(UWP(应用程序

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

但对于该服务器,需要知道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;
}

客户端只需要

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

根据本页

管道仅在应用程序容器中受支持;即,从一个UWP进程到另一个属于同一应用程序的UWP进程。此外,命名管道必须使用语法".\pipe\LOCAL"作为管道名称。

最新更新