Deprecate ATL based IPC wrapper library (#2248)

* Deprecate ATL based IPC wrapper library

* C# projects now use named pipe server implementations from two_way_pipe_message
through the interop C++/Cli library.

* Added Unit testing to interop library
This commit is contained in:
Tomas Agustin Raies
2020-04-23 17:11:02 -07:00
committed by GitHub
parent 81551104ce
commit 63d989cab4
38 changed files with 820 additions and 1515 deletions

View File

@@ -86,11 +86,14 @@
<ItemGroup>
<ClInclude Include="..\keyboard_layout.h" />
<ClInclude Include="..\keyboard_layout_impl.h" />
<ClInclude Include="..\two_way_pipe_message_ipc.h" />
<ClInclude Include="..\two_way_pipe_message_ipc_impl.h" />
<ClInclude Include="framework.h" />
<ClInclude Include="pch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\keyboard_layout.cpp" />
<ClCompile Include="..\two_way_pipe_message_ipc.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>

View File

@@ -27,6 +27,12 @@
<ClInclude Include="..\keyboard_layout_impl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\two_way_pipe_message_ipc.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\two_way_pipe_message_ipc_impl.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
@@ -35,5 +41,8 @@
<ClCompile Include="..\keyboard_layout.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\two_way_pipe_message_ipc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@@ -126,6 +126,7 @@
<ClInclude Include="notifications.h" />
<ClInclude Include="shared_constants.h" />
<ClInclude Include="timeutil.h" />
<ClInclude Include="two_way_pipe_message_ipc.h" />
<ClInclude Include="VersionHelper.h" />
<ClInclude Include="window_helpers.h" />
<ClInclude Include="icon_helpers.h" />
@@ -140,7 +141,7 @@
<ClInclude Include="common.h" />
<ClInclude Include="Telemetry\ProjectTelemetry.h" />
<ClInclude Include="Telemetry\TraceLoggingDefines.h" />
<ClInclude Include="two_way_pipe_message_ipc.h" />
<ClInclude Include="two_way_pipe_message_ipc_impl.h" />
<ClInclude Include="version.h" />
<ClInclude Include="windows_colors.h" />
<ClInclude Include="winstore.h" />
@@ -167,6 +168,7 @@
<ClCompile Include="tasklist_positions.cpp" />
<ClCompile Include="common.cpp" />
<ClCompile Include="version.cpp" />
<ClCompile Include="two_way_pipe_message_ipc.cpp" />
<ClCompile Include="VersionHelper.cpp" />
<ClCompile Include="windows_colors.cpp" />
<ClCompile Include="window_helpers.cpp" />

View File

@@ -51,9 +51,6 @@
<ClInclude Include="Telemetry\TraceLoggingDefines.h">
<Filter>Header Files\Telemetry</Filter>
</ClInclude>
<ClInclude Include="two_way_pipe_message_ipc.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="async_message_queue.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -108,6 +105,12 @@
<ClInclude Include="keyboard_layout.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="two_way_pipe_message_ipc_impl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="two_way_pipe_message_ipc.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="d2d_svg.cpp">
@@ -174,6 +177,9 @@
<ClCompile Include="version.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="two_way_pipe_message_ipc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -0,0 +1,20 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("interop-tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("interop-tests")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("437ad818-3f1f-4ca5-a79b-25233a157026")]
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,55 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO.Pipes;
using interop;
using System.IO;
using System.Threading;
using System.Text;
namespace interop_tests
{
[TestClass]
public class UnitTest1
{
private string SERVER_SIDE = "\\\\.\\pipe\\serverside";
private string CLIENT_SIDE = "\\\\.\\pipe\\clientside";
private TwoWayPipeMessageIPCManaged clientPipe;
[TestInitialize]
public void Initialize()
{
clientPipe = new TwoWayPipeMessageIPCManaged(CLIENT_SIDE, SERVER_SIDE, null);
}
[TestCleanup]
public void Cleanup()
{
clientPipe.End();
}
[TestMethod]
public void TestSend()
{
var testString = "This string is a test\n";
var reset = new AutoResetEvent(false);
var serverPipe = new TwoWayPipeMessageIPCManaged(
SERVER_SIDE,
CLIENT_SIDE,
(string msg) =>
{
Assert.AreEqual(testString, msg);
reset.Set();
});
serverPipe.Start();
clientPipe.Start();
clientPipe.Send(testString);
reset.WaitOne();
serverPipe.End();
}
}
}

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\packages\MSTest.TestAdapter.2.1.0\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\..\..\packages\MSTest.TestAdapter.2.1.0\build\net45\MSTest.TestAdapter.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{437AD818-3F1F-4CA5-A79B-25233A157026}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>interop_tests</RootNamespace>
<AssemblyName>interop-tests</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\MSTest.TestFramework.2.1.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\MSTest.TestFramework.2.1.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="UnitTest1.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\interop\interop.vcxproj">
<Project>{f055103b-f80b-4d0c-bf48-057c55620033}</Project>
<Name>interop</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\MSTest.TestAdapter.2.1.0\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\MSTest.TestAdapter.2.1.0\build\net45\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\..\..\packages\MSTest.TestAdapter.2.1.0\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\MSTest.TestAdapter.2.1.0\build\net45\MSTest.TestAdapter.targets'))" />
</Target>
<Import Project="..\..\..\packages\MSTest.TestAdapter.2.1.0\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\..\..\packages\MSTest.TestAdapter.2.1.0\build\net45\MSTest.TestAdapter.targets')" />
</Project>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MSTest.TestAdapter" version="2.1.0" targetFramework="net472" />
<package id="MSTest.TestFramework" version="2.1.0" targetFramework="net472" />
</packages>

View File

@@ -1,12 +1,19 @@
#pragma once
#include <common\keyboard_layout.h>
#include <common\two_way_pipe_message_ipc.h>
#include <msclr\marshal.h>
#include <msclr\marshal_cppstd.h>
#include <functional>
using namespace System;
using namespace System::Runtime::InteropServices;
//https://docs.microsoft.com/en-us/cpp/dotnet/how-to-wrap-native-class-for-use-by-csharp?view=vs-2019
namespace interop
{
public ref class LayoutMapManaged
public
ref class LayoutMapManaged
{
public:
LayoutMapManaged() :
@@ -17,12 +24,11 @@ public ref class LayoutMapManaged
delete _map;
}
String ^ GetKeyName(DWORD key)
{
String ^ GetKeyName(DWORD key) {
return gcnew String(_map->GetKeyName(key).c_str());
}
void Updatelayout()
void Updatelayout()
{
_map->UpdateLayout();
}
@@ -36,5 +42,65 @@ public ref class LayoutMapManaged
private:
LayoutMap* _map;
};
}
public
ref class TwoWayPipeMessageIPCManaged
{
public:
delegate void ReadCallback(String ^ message);
TwoWayPipeMessageIPCManaged(String ^ inputPipeName, String ^ outputPipeName, ReadCallback ^ callback)
{
_wrapperCallback = gcnew InternalReadCallback(this, &TwoWayPipeMessageIPCManaged::ReadCallbackHelper);
_callback = callback;
TwoWayPipeMessageIPC::callback_function cb = nullptr;
if (callback != nullptr)
{
cb = (TwoWayPipeMessageIPC::callback_function)(void*)Marshal::GetFunctionPointerForDelegate(_wrapperCallback);
}
_pipe = new TwoWayPipeMessageIPC(
msclr::interop::marshal_as<std::wstring>(inputPipeName),
msclr::interop::marshal_as<std::wstring>(outputPipeName),
cb);
}
~TwoWayPipeMessageIPCManaged()
{
delete _pipe;
}
void Send(String ^ msg)
{
_pipe->send(msclr::interop::marshal_as<std::wstring>(msg));
}
void Start()
{
_pipe->start(nullptr);
}
void End()
{
_pipe->end();
}
protected:
!TwoWayPipeMessageIPCManaged()
{
delete _pipe;
}
private:
delegate void InternalReadCallback(const std::wstring& msg);
TwoWayPipeMessageIPC* _pipe;
ReadCallback ^ _callback;
InternalReadCallback ^ _wrapperCallback;
void ReadCallbackHelper(const std::wstring& msg)
{
_callback(gcnew String(msg.c_str()));
}
};
}

View File

@@ -0,0 +1,498 @@
#include "pch.h"
#include "two_way_pipe_message_ipc_impl.h"
#pragma comment(lib, "advapi32.lib")
TwoWayPipeMessageIPC::TwoWayPipeMessageIPC(
std::wstring _input_pipe_name,
std::wstring _output_pipe_name,
callback_function p_func) :
impl(new TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl(
_input_pipe_name,
_output_pipe_name,
p_func))
{
}
TwoWayPipeMessageIPC::~TwoWayPipeMessageIPC()
{
delete impl;
}
void TwoWayPipeMessageIPC::send(std::wstring msg)
{
impl->send(msg);
}
void TwoWayPipeMessageIPC::start(HANDLE _restricted_pipe_token)
{
impl->start(_restricted_pipe_token);
}
void TwoWayPipeMessageIPC::end()
{
impl->end();
}
TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::TwoWayPipeMessageIPCImpl(
std::wstring _input_pipe_name,
std::wstring _output_pipe_name,
callback_function p_func)
{
input_pipe_name = _input_pipe_name;
output_pipe_name = _output_pipe_name;
dispatch_inc_message_function = p_func;
}
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::send(std::wstring msg)
{
output_queue.queue_message(msg);
}
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::start(HANDLE _restricted_pipe_token)
{
output_queue_thread = std::thread(&TwoWayPipeMessageIPCImpl::consume_output_queue_thread, this);
input_queue_thread = std::thread(&TwoWayPipeMessageIPCImpl::consume_input_queue_thread, this);
input_pipe_thread = std::thread(&TwoWayPipeMessageIPCImpl::start_named_pipe_server, this, _restricted_pipe_token);
}
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::end()
{
closed = true;
input_queue.interrupt();
input_queue_thread.join();
output_queue.interrupt();
output_queue_thread.join();
pipe_connect_handle_mutex.lock();
if (current_connect_pipe_handle != NULL)
{
//Cancels the Pipe currently waiting for a connection.
CancelIoEx(current_connect_pipe_handle, NULL);
}
pipe_connect_handle_mutex.unlock();
input_pipe_thread.join();
}
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::send_pipe_message(std::wstring message)
{
// Adapted from https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-client
HANDLE output_pipe_handle;
const wchar_t* message_send = message.c_str();
BOOL fSuccess = FALSE;
DWORD cbToWrite, cbWritten, dwMode;
const wchar_t* lpszPipename = output_pipe_name.c_str();
// Try to open a named pipe; wait for it, if necessary.
while (1)
{
output_pipe_handle = CreateFile(
lpszPipename, // pipe name
GENERIC_READ | // read and write access
GENERIC_WRITE,
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
0, // default attributes
NULL); // no template file
// Break if the pipe handle is valid.
if (output_pipe_handle != INVALID_HANDLE_VALUE)
break;
// Exit if an error other than ERROR_PIPE_BUSY occurs.
DWORD curr_error = 0;
if ((curr_error = GetLastError()) != ERROR_PIPE_BUSY)
{
return;
}
// All pipe instances are busy, so wait for 20 seconds.
if (!WaitNamedPipe(lpszPipename, 20000))
{
return;
}
}
dwMode = PIPE_READMODE_MESSAGE;
fSuccess = SetNamedPipeHandleState(
output_pipe_handle, // pipe handle
&dwMode, // new pipe mode
NULL, // don't set maximum bytes
NULL); // don't set maximum time
if (!fSuccess)
{
return;
}
// Send a message to the pipe server.
cbToWrite = (lstrlen(message_send)) * sizeof(WCHAR); // no need to send final '\0'. Pipe is in message mode.
fSuccess = WriteFile(
output_pipe_handle, // pipe handle
message_send, // message
cbToWrite, // message length
&cbWritten, // bytes written
NULL); // not overlapped
if (!fSuccess)
{
return;
}
CloseHandle(output_pipe_handle);
return;
}
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::consume_output_queue_thread()
{
while (!closed)
{
std::wstring message = output_queue.pop_message();
if (message.length() == 0)
{
break;
}
send_pipe_message(message);
}
}
BOOL TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::GetLogonSID(HANDLE hToken, PSID* ppsid)
{
// From https://docs.microsoft.com/en-us/previous-versions/aa446670(v=vs.85)
BOOL bSuccess = FALSE;
DWORD dwIndex;
DWORD dwLength = 0;
PTOKEN_GROUPS ptg = NULL;
// Verify the parameter passed in is not NULL.
if (NULL == ppsid)
goto Cleanup;
// Get required buffer size and allocate the TOKEN_GROUPS buffer.
if (!GetTokenInformation(
hToken, // handle to the access token
TokenGroups, // get information about the token's groups
(LPVOID)ptg, // pointer to TOKEN_GROUPS buffer
0, // size of buffer
&dwLength // receives required buffer size
))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
goto Cleanup;
ptg = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
dwLength);
if (ptg == NULL)
goto Cleanup;
}
// Get the token group information from the access token.
if (!GetTokenInformation(
hToken, // handle to the access token
TokenGroups, // get information about the token's groups
(LPVOID)ptg, // pointer to TOKEN_GROUPS buffer
dwLength, // size of buffer
&dwLength // receives required buffer size
))
{
goto Cleanup;
}
// Loop through the groups to find the logon SID.
for (dwIndex = 0; dwIndex < ptg->GroupCount; dwIndex++)
if ((ptg->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID)
{
// Found the logon SID; make a copy of it.
dwLength = GetLengthSid(ptg->Groups[dwIndex].Sid);
*ppsid = (PSID)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
dwLength);
if (*ppsid == NULL)
goto Cleanup;
if (!CopySid(dwLength, *ppsid, ptg->Groups[dwIndex].Sid))
{
HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid);
goto Cleanup;
}
break;
}
bSuccess = TRUE;
Cleanup:
// Free the buffer for the token groups.
if (ptg != NULL)
HeapFree(GetProcessHeap(), 0, (LPVOID)ptg);
return bSuccess;
}
VOID TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::FreeLogonSID(PSID* ppsid)
{
// From https://docs.microsoft.com/en-us/previous-versions/aa446670(v=vs.85)
HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid);
}
int TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::change_pipe_security_allow_restricted_token(HANDLE handle, HANDLE token)
{
PACL old_dacl, new_dacl;
PSECURITY_DESCRIPTOR sd;
EXPLICIT_ACCESS ea;
PSID user_restricted;
int error;
if (!GetLogonSID(token, &user_restricted))
{
error = 5; // No access error.
goto Ldone;
}
if (GetSecurityInfo(handle,
SE_KERNEL_OBJECT,
DACL_SECURITY_INFORMATION,
NULL,
NULL,
&old_dacl,
NULL,
&sd))
{
error = GetLastError();
goto Lclean_sid;
}
memset(&ea, 0, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions |= GENERIC_READ | FILE_WRITE_ATTRIBUTES;
ea.grfAccessPermissions |= GENERIC_WRITE | FILE_READ_ATTRIBUTES;
ea.grfAccessPermissions |= SYNCHRONIZE;
ea.grfAccessMode = SET_ACCESS;
ea.grfInheritance = NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
ea.Trustee.ptstrName = (LPTSTR)user_restricted;
if (SetEntriesInAcl(1, &ea, old_dacl, &new_dacl))
{
error = GetLastError();
goto Lclean_sd;
}
if (SetSecurityInfo(handle,
SE_KERNEL_OBJECT,
DACL_SECURITY_INFORMATION,
NULL,
NULL,
new_dacl,
NULL))
{
error = GetLastError();
goto Lclean_dacl;
}
error = 0;
Lclean_dacl:
LocalFree((HLOCAL)new_dacl);
Lclean_sd:
LocalFree((HLOCAL)sd);
Lclean_sid:
FreeLogonSID(&user_restricted);
Ldone:
return error;
}
HANDLE TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::create_medium_integrity_token()
{
HANDLE restricted_token_handle;
SAFER_LEVEL_HANDLE level_handle = NULL;
DWORD sid_size = SECURITY_MAX_SID_SIZE;
BYTE medium_sid[SECURITY_MAX_SID_SIZE];
if (!SaferCreateLevel(SAFER_SCOPEID_USER, SAFER_LEVELID_NORMALUSER, SAFER_LEVEL_OPEN, &level_handle, NULL))
{
return NULL;
}
if (!SaferComputeTokenFromLevel(level_handle, NULL, &restricted_token_handle, 0, NULL))
{
SaferCloseLevel(level_handle);
return NULL;
}
SaferCloseLevel(level_handle);
if (!CreateWellKnownSid(WinMediumLabelSid, nullptr, medium_sid, &sid_size))
{
CloseHandle(restricted_token_handle);
return NULL;
}
TOKEN_MANDATORY_LABEL integrity_level = { 0 };
integrity_level.Label.Attributes = SE_GROUP_INTEGRITY;
integrity_level.Label.Sid = reinterpret_cast<PSID>(medium_sid);
if (!SetTokenInformation(restricted_token_handle, TokenIntegrityLevel, &integrity_level, sizeof(integrity_level)))
{
CloseHandle(restricted_token_handle);
return NULL;
}
return restricted_token_handle;
}
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::handle_pipe_connection(HANDLE input_pipe_handle)
{
//Adapted from https://docs.microsoft.com/en-us/windows/win32/ipc/multithreaded-pipe-server
HANDLE hHeap = GetProcessHeap();
uint8_t* pchRequest = (uint8_t*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(uint8_t));
DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0;
BOOL fSuccess = FALSE;
// Do some extra error checking since the app will keep running even if this thread fails.
std::list<std::vector<uint8_t>> message_parts;
if (input_pipe_handle == NULL)
{
if (pchRequest != NULL)
HeapFree(hHeap, 0, pchRequest);
return;
}
if (pchRequest == NULL)
{
return;
}
// Loop until done reading
do
{
// Read client requests from the pipe. This simplistic code only allows messages
// up to BUFSIZE characters in length.
ZeroMemory(pchRequest, BUFSIZE * sizeof(uint8_t));
fSuccess = ReadFile(
input_pipe_handle, // handle to pipe
pchRequest, // buffer to receive data
BUFSIZE * sizeof(uint8_t), // size of buffer
&cbBytesRead, // number of bytes read
NULL); // not overlapped I/O
if (!fSuccess && GetLastError() != ERROR_MORE_DATA)
{
break;
}
std::vector<uint8_t> part_vector;
part_vector.reserve(cbBytesRead);
std::copy(pchRequest, pchRequest + cbBytesRead, std::back_inserter(part_vector));
message_parts.push_back(part_vector);
} while (!fSuccess);
if (fSuccess)
{
// Reconstruct the total_message.
std::vector<uint8_t> reconstructed_message;
size_t total_size = 0;
for (auto& part_vector : message_parts)
{
total_size += part_vector.size();
}
reconstructed_message.reserve(total_size);
for (auto& part_vector : message_parts)
{
std::move(part_vector.begin(), part_vector.end(), std::back_inserter(reconstructed_message));
}
std::wstring unicode_msg;
unicode_msg.assign(reinterpret_cast<std::wstring::const_pointer>(reconstructed_message.data()), reconstructed_message.size() / sizeof(std::wstring::value_type));
input_queue.queue_message(unicode_msg);
}
// Flush the pipe to allow the client to read the pipe's contents
// before disconnecting. Then disconnect the pipe, and close the
// handle to this pipe instance.
FlushFileBuffers(input_pipe_handle);
DisconnectNamedPipe(input_pipe_handle);
CloseHandle(input_pipe_handle);
HeapFree(hHeap, 0, pchRequest);
printf("InstanceThread exitting.\n");
}
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::start_named_pipe_server(HANDLE token)
{
// Adapted from https://docs.microsoft.com/en-us/windows/win32/ipc/multithreaded-pipe-server
const wchar_t* pipe_name = input_pipe_name.c_str();
BOOL connected = FALSE;
HANDLE connect_pipe_handle = INVALID_HANDLE_VALUE;
while (!closed)
{
{
std::unique_lock lock(pipe_connect_handle_mutex);
connect_pipe_handle = CreateNamedPipe(
pipe_name,
PIPE_ACCESS_DUPLEX |
WRITE_DAC,
PIPE_TYPE_MESSAGE |
PIPE_READMODE_MESSAGE |
PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFSIZE,
BUFSIZE,
0,
NULL);
if (connect_pipe_handle == INVALID_HANDLE_VALUE)
{
return;
}
if (token != NULL)
{
int err = change_pipe_security_allow_restricted_token(connect_pipe_handle, token);
}
current_connect_pipe_handle = connect_pipe_handle;
}
connected = ConnectNamedPipe(connect_pipe_handle, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
{
std::unique_lock lock(pipe_connect_handle_mutex);
current_connect_pipe_handle = NULL;
}
if (connected)
{
std::thread(&TwoWayPipeMessageIPCImpl::handle_pipe_connection, this, connect_pipe_handle).detach();
}
else
{
// Client could not connect.
CloseHandle(connect_pipe_handle);
}
}
}
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::consume_input_queue_thread()
{
while (!closed)
{
outgoing_message = L"";
std::wstring message = input_queue.pop_message();
if (message.length() == 0)
{
break;
}
// Check if callback method exists first before trying to call it.
// otherwise just store the response message in a variable.
if (dispatch_inc_message_function != nullptr)
{
dispatch_inc_message_function(message);
}
outgoing_message = message;
}
}

View File

@@ -1,485 +1,18 @@
#pragma once
#include <Windows.h>
#include "async_message_queue.h"
#include <WinSafer.h>
#include <Sddl.h>
#include <accctrl.h>
#include <aclapi.h>
#include <list>
class TwoWayPipeMessageIPC
{
public:
typedef void (*callback_function)(const std::wstring&);
void send(std::wstring msg)
{
output_queue.queue_message(msg);
}
TwoWayPipeMessageIPC(std::wstring _input_pipe_name, std::wstring _output_pipe_name, callback_function p_func)
{
input_pipe_name = _input_pipe_name;
output_pipe_name = _output_pipe_name;
dispatch_inc_message_function = p_func;
}
void start(HANDLE _restricted_pipe_token)
{
output_queue_thread = std::thread(&TwoWayPipeMessageIPC::consume_output_queue_thread, this);
input_queue_thread = std::thread(&TwoWayPipeMessageIPC::consume_input_queue_thread, this);
input_pipe_thread = std::thread(&TwoWayPipeMessageIPC::start_named_pipe_server, this, _restricted_pipe_token);
}
void end()
{
closed = true;
input_queue.interrupt();
input_queue_thread.join();
output_queue.interrupt();
output_queue_thread.join();
pipe_connect_handle_mutex.lock();
if (current_connect_pipe_handle != NULL)
{
//Cancels the Pipe currently waiting for a connection.
CancelIoEx(current_connect_pipe_handle, NULL);
}
pipe_connect_handle_mutex.unlock();
input_pipe_thread.join();
}
TwoWayPipeMessageIPC(
std::wstring _input_pipe_name,
std::wstring _output_pipe_name,
callback_function p_func);
~TwoWayPipeMessageIPC();
void send(std::wstring msg);
void start(HANDLE _restricted_pipe_token);
void end();
private:
AsyncMessageQueue input_queue;
AsyncMessageQueue output_queue;
std::wstring output_pipe_name;
std::wstring input_pipe_name;
std::thread input_queue_thread;
std::thread output_queue_thread;
std::thread input_pipe_thread;
std::mutex pipe_connect_handle_mutex; // For manipulating the current_connect_pipe
std::wstring outgoing_message; // Store the updated json settings.
HANDLE current_connect_pipe_handle = NULL;
bool closed = false;
TwoWayPipeMessageIPC::callback_function dispatch_inc_message_function;
const DWORD BUFSIZE = 1024;
void send_pipe_message(std::wstring message)
{
// Adapted from https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-client
HANDLE output_pipe_handle;
const wchar_t* message_send = message.c_str();
BOOL fSuccess = FALSE;
DWORD cbToWrite, cbWritten, dwMode;
const wchar_t* lpszPipename = output_pipe_name.c_str();
// Try to open a named pipe; wait for it, if necessary.
while (1)
{
output_pipe_handle = CreateFile(
lpszPipename, // pipe name
GENERIC_READ | // read and write access
GENERIC_WRITE,
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
0, // default attributes
NULL); // no template file
// Break if the pipe handle is valid.
if (output_pipe_handle != INVALID_HANDLE_VALUE)
break;
// Exit if an error other than ERROR_PIPE_BUSY occurs.
DWORD curr_error = 0;
if ((curr_error = GetLastError()) != ERROR_PIPE_BUSY)
{
return;
}
// All pipe instances are busy, so wait for 20 seconds.
if (!WaitNamedPipe(lpszPipename, 20000))
{
return;
}
}
dwMode = PIPE_READMODE_MESSAGE;
fSuccess = SetNamedPipeHandleState(
output_pipe_handle, // pipe handle
&dwMode, // new pipe mode
NULL, // don't set maximum bytes
NULL); // don't set maximum time
if (!fSuccess)
{
return;
}
// Send a message to the pipe server.
cbToWrite = (lstrlen(message_send)) * sizeof(WCHAR); // no need to send final '\0'. Pipe is in message mode.
fSuccess = WriteFile(
output_pipe_handle, // pipe handle
message_send, // message
cbToWrite, // message length
&cbWritten, // bytes written
NULL); // not overlapped
if (!fSuccess)
{
return;
}
CloseHandle(output_pipe_handle);
return;
}
void consume_output_queue_thread()
{
while (!closed)
{
std::wstring message = output_queue.pop_message();
if (message.length() == 0)
{
break;
}
send_pipe_message(message);
}
}
BOOL GetLogonSID(HANDLE hToken, PSID* ppsid)
{
// From https://docs.microsoft.com/en-us/previous-versions/aa446670(v=vs.85)
BOOL bSuccess = FALSE;
DWORD dwIndex;
DWORD dwLength = 0;
PTOKEN_GROUPS ptg = NULL;
// Verify the parameter passed in is not NULL.
if (NULL == ppsid)
goto Cleanup;
// Get required buffer size and allocate the TOKEN_GROUPS buffer.
if (!GetTokenInformation(
hToken, // handle to the access token
TokenGroups, // get information about the token's groups
(LPVOID)ptg, // pointer to TOKEN_GROUPS buffer
0, // size of buffer
&dwLength // receives required buffer size
))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
goto Cleanup;
ptg = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
dwLength);
if (ptg == NULL)
goto Cleanup;
}
// Get the token group information from the access token.
if (!GetTokenInformation(
hToken, // handle to the access token
TokenGroups, // get information about the token's groups
(LPVOID)ptg, // pointer to TOKEN_GROUPS buffer
dwLength, // size of buffer
&dwLength // receives required buffer size
))
{
goto Cleanup;
}
// Loop through the groups to find the logon SID.
for (dwIndex = 0; dwIndex < ptg->GroupCount; dwIndex++)
if ((ptg->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID)
{
// Found the logon SID; make a copy of it.
dwLength = GetLengthSid(ptg->Groups[dwIndex].Sid);
*ppsid = (PSID)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
dwLength);
if (*ppsid == NULL)
goto Cleanup;
if (!CopySid(dwLength, *ppsid, ptg->Groups[dwIndex].Sid))
{
HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid);
goto Cleanup;
}
break;
}
bSuccess = TRUE;
Cleanup:
// Free the buffer for the token groups.
if (ptg != NULL)
HeapFree(GetProcessHeap(), 0, (LPVOID)ptg);
return bSuccess;
}
VOID FreeLogonSID(PSID* ppsid)
{
// From https://docs.microsoft.com/en-us/previous-versions/aa446670(v=vs.85)
HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid);
}
int change_pipe_security_allow_restricted_token(HANDLE handle, HANDLE token)
{
PACL old_dacl, new_dacl;
PSECURITY_DESCRIPTOR sd;
EXPLICIT_ACCESS ea;
PSID user_restricted;
int error;
if (!GetLogonSID(token, &user_restricted))
{
error = 5; // No access error.
goto Ldone;
}
if (GetSecurityInfo(handle,
SE_KERNEL_OBJECT,
DACL_SECURITY_INFORMATION,
NULL,
NULL,
&old_dacl,
NULL,
&sd))
{
error = GetLastError();
goto Lclean_sid;
}
memset(&ea, 0, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions |= GENERIC_READ | FILE_WRITE_ATTRIBUTES;
ea.grfAccessPermissions |= GENERIC_WRITE | FILE_READ_ATTRIBUTES;
ea.grfAccessPermissions |= SYNCHRONIZE;
ea.grfAccessMode = SET_ACCESS;
ea.grfInheritance = NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
ea.Trustee.ptstrName = (LPTSTR)user_restricted;
if (SetEntriesInAcl(1, &ea, old_dacl, &new_dacl))
{
error = GetLastError();
goto Lclean_sd;
}
if (SetSecurityInfo(handle,
SE_KERNEL_OBJECT,
DACL_SECURITY_INFORMATION,
NULL,
NULL,
new_dacl,
NULL))
{
error = GetLastError();
goto Lclean_dacl;
}
error = 0;
Lclean_dacl:
LocalFree((HLOCAL)new_dacl);
Lclean_sd:
LocalFree((HLOCAL)sd);
Lclean_sid:
FreeLogonSID(&user_restricted);
Ldone:
return error;
}
HANDLE create_medium_integrity_token()
{
HANDLE restricted_token_handle;
SAFER_LEVEL_HANDLE level_handle = NULL;
DWORD sid_size = SECURITY_MAX_SID_SIZE;
BYTE medium_sid[SECURITY_MAX_SID_SIZE];
if (!SaferCreateLevel(SAFER_SCOPEID_USER, SAFER_LEVELID_NORMALUSER, SAFER_LEVEL_OPEN, &level_handle, NULL))
{
return NULL;
}
if (!SaferComputeTokenFromLevel(level_handle, NULL, &restricted_token_handle, 0, NULL))
{
SaferCloseLevel(level_handle);
return NULL;
}
SaferCloseLevel(level_handle);
if (!CreateWellKnownSid(WinMediumLabelSid, nullptr, medium_sid, &sid_size))
{
CloseHandle(restricted_token_handle);
return NULL;
}
TOKEN_MANDATORY_LABEL integrity_level = { 0 };
integrity_level.Label.Attributes = SE_GROUP_INTEGRITY;
integrity_level.Label.Sid = reinterpret_cast<PSID>(medium_sid);
if (!SetTokenInformation(restricted_token_handle, TokenIntegrityLevel, &integrity_level, sizeof(integrity_level)))
{
CloseHandle(restricted_token_handle);
return NULL;
}
return restricted_token_handle;
}
void handle_pipe_connection(HANDLE input_pipe_handle)
{
//Adapted from https://docs.microsoft.com/en-us/windows/win32/ipc/multithreaded-pipe-server
HANDLE hHeap = GetProcessHeap();
uint8_t* pchRequest = (uint8_t*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(uint8_t));
DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0;
BOOL fSuccess = FALSE;
// Do some extra error checking since the app will keep running even if this thread fails.
std::list<std::vector<uint8_t>> message_parts;
if (input_pipe_handle == NULL)
{
if (pchRequest != NULL)
HeapFree(hHeap, 0, pchRequest);
return;
}
if (pchRequest == NULL)
{
return;
}
// Loop until done reading
do
{
// Read client requests from the pipe. This simplistic code only allows messages
// up to BUFSIZE characters in length.
ZeroMemory(pchRequest, BUFSIZE * sizeof(uint8_t));
fSuccess = ReadFile(
input_pipe_handle, // handle to pipe
pchRequest, // buffer to receive data
BUFSIZE * sizeof(uint8_t), // size of buffer
&cbBytesRead, // number of bytes read
NULL); // not overlapped I/O
if (!fSuccess && GetLastError() != ERROR_MORE_DATA)
{
break;
}
std::vector<uint8_t> part_vector;
part_vector.reserve(cbBytesRead);
std::copy(pchRequest, pchRequest + cbBytesRead, std::back_inserter(part_vector));
message_parts.push_back(part_vector);
} while (!fSuccess);
if (fSuccess)
{
// Reconstruct the total_message.
std::vector<uint8_t> reconstructed_message;
size_t total_size = 0;
for (auto& part_vector : message_parts)
{
total_size += part_vector.size();
}
reconstructed_message.reserve(total_size);
for (auto& part_vector : message_parts)
{
std::move(part_vector.begin(), part_vector.end(), std::back_inserter(reconstructed_message));
}
std::wstring unicode_msg;
unicode_msg.assign(reinterpret_cast<std::wstring::const_pointer>(reconstructed_message.data()), reconstructed_message.size() / sizeof(std::wstring::value_type));
input_queue.queue_message(unicode_msg);
}
// Flush the pipe to allow the client to read the pipe's contents
// before disconnecting. Then disconnect the pipe, and close the
// handle to this pipe instance.
FlushFileBuffers(input_pipe_handle);
DisconnectNamedPipe(input_pipe_handle);
CloseHandle(input_pipe_handle);
HeapFree(hHeap, 0, pchRequest);
printf("InstanceThread exitting.\n");
}
void start_named_pipe_server(HANDLE token)
{
// Adapted from https://docs.microsoft.com/en-us/windows/win32/ipc/multithreaded-pipe-server
const wchar_t* pipe_name = input_pipe_name.c_str();
BOOL connected = FALSE;
HANDLE connect_pipe_handle = INVALID_HANDLE_VALUE;
while (!closed)
{
{
std::unique_lock lock(pipe_connect_handle_mutex);
connect_pipe_handle = CreateNamedPipe(
pipe_name,
PIPE_ACCESS_DUPLEX |
WRITE_DAC,
PIPE_TYPE_MESSAGE |
PIPE_READMODE_MESSAGE |
PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFSIZE,
BUFSIZE,
0,
NULL);
if (connect_pipe_handle == INVALID_HANDLE_VALUE)
{
return;
}
if (token != NULL)
{
int err = change_pipe_security_allow_restricted_token(connect_pipe_handle, token);
}
current_connect_pipe_handle = connect_pipe_handle;
}
connected = ConnectNamedPipe(connect_pipe_handle, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
{
std::unique_lock lock(pipe_connect_handle_mutex);
current_connect_pipe_handle = NULL;
}
if (connected)
{
std::thread(&TwoWayPipeMessageIPC::handle_pipe_connection, this, connect_pipe_handle).detach();
}
else
{
// Client could not connect.
CloseHandle(connect_pipe_handle);
}
}
}
void consume_input_queue_thread()
{
while (!closed)
{
outgoing_message = L"";
std::wstring message = input_queue.pop_message();
if (message.length() == 0)
{
break;
}
// Check if callback method exists first before trying to call it.
// otherwise just store the response message in a variable.
if (dispatch_inc_message_function != nullptr)
{
dispatch_inc_message_function(message);
}
outgoing_message = message;
}
}
};
class TwoWayPipeMessageIPCImpl;
TwoWayPipeMessageIPCImpl* impl;
};

View File

@@ -0,0 +1,43 @@
#pragma once
#include <Windows.h>
#include "async_message_queue.h"
#include <WinSafer.h>
#include <accctrl.h>
#include <aclapi.h>
#include <list>
#include "two_way_pipe_message_ipc.h"
class TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl
{
public:
void send(std::wstring msg);
TwoWayPipeMessageIPCImpl(std::wstring _input_pipe_name, std::wstring _output_pipe_name, callback_function p_func);
void start(HANDLE _restricted_pipe_token);
void end();
private:
AsyncMessageQueue input_queue;
AsyncMessageQueue output_queue;
std::wstring output_pipe_name;
std::wstring input_pipe_name;
std::thread input_queue_thread;
std::thread output_queue_thread;
std::thread input_pipe_thread;
std::mutex pipe_connect_handle_mutex; // For manipulating the current_connect_pipe
std::wstring outgoing_message; // Store the updated json settings.
HANDLE current_connect_pipe_handle = NULL;
bool closed = false;
TwoWayPipeMessageIPC::callback_function dispatch_inc_message_function;
const DWORD BUFSIZE = 1024;
void send_pipe_message(std::wstring message);
void consume_output_queue_thread();
BOOL GetLogonSID(HANDLE hToken, PSID* ppsid);
VOID FreeLogonSID(PSID* ppsid);
int change_pipe_security_allow_restricted_token(HANDLE handle, HANDLE token);
HANDLE create_medium_integrity_token();
void handle_pipe_connection(HANDLE input_pipe_handle);
void start_named_pipe_server(HANDLE token);
void consume_input_queue_thread();
};