Named pipe IPC between elevated and non elevated C# processes - c#

Context: I wrote a non-elevated WinForms app, from which I want to allow the user to query the file system using NTFS' MFT (Master File Table) because it's so damn fast! However, starting with Windows 8 or 10 or 1809 or something or other, querying the MFT requires process elevation, and I don't want to run my app elevated. So I plan to build another executable, running elevated, to query the MFT. If I do that, then I need to use inter-process communication (IPC). Seems like named pipes is simple enough (and safe?).
The challenge: I wrote a test program that uses .NET's NamedPipeServerStream and NamedPipeClientStream classes to do some IPC. When client and server processes run either both elevated or both unelevated, they can communicate using the pipe. However, if one is elevated and the other isn't, then the client throws a System.UnauthorizedAccessException exception when trying to connect to the server. I kind of expected that.
My question: Is it a simple matter of constructing the server and client pipe objects with a carefully-crafted System.IO.Pipes.PipeSecurity object? If so, please help me to craft that object, both for the client and the server.

Below is what I got working, with the first two lines being the relevant ones and the rest just to demonstrate its use.
var ps = new PipeSecurity();
ps.AddAccessRule(new PipeAccessRule(Environment.UserName, PipeAccessRights.ReadWrite, AccessControlType.Allow));
using(var pipe = new NamedPipeServerStream(
pipeName: PipeName,
direction: PipeDirection.InOut,
maxNumberOfServerInstances: 1,
options: PipeOptions.None,
transmissionMode: PipeTransmissionMode.Byte,
inBufferSize: 1024,
outBufferSize: 1024,
pipeSecurity: ps))
{
...
}

Related

IPC with pipes inbetween packaged UWP app and Desktop .Net 6.0 program

A couple questions about the intricacies of doing pipe IPC in UWP (Don't tell me to use an app service, it's too low latency of a solution for my project).
I'm trying to get working IPC between two of my projects, one of them being a UWP packaged app, and the other being a C# .net6 app. I have successfully created a NamedPipeServerStream inside of my .net6 program. Initially, my UWP side pipe client (NamedPipeClientStream) would fail during creation due to a "Access to the path is denied" error.
Doing research, I have deduced that I need to add some access rules related to my UWP package's SID so that my UWP app can successfully communicate with my external app. It seems vanilla .net NamedPipeServerStream does not allow for access rules to be added via constructor, but I was able to work around it with a nuget package, NamedPipeServerStream.NetFrameworkVersion. I was able to get a SID using a function described in the docs, although this turned out to be the wrong SID. Using a hard coded SID from the powershell command "CheckNetIsolation.exe LoopbackExempt -s", I was able to successfully create the NamedPipeClientStream object in my UWP app, getting past the access denied error.
However, the connect method that I call directly after does not succeed. I end up getting an error:
Could not load file or assembly 'System.Security.Principal.Windows,
Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
The located assembly's manifest definition does not match the assembly
reference. (Exception from HRESULT: 0x80131040)
A couple questions:
Is making a pipe between the two projects actually this difficult? Am I close to the light at the end of the tunnel?
Why did my two methods of receiving the SID for my packaged app result in different SIDS? Is there a nice programmatic way in C# to do this?
Here are some of my code samples:
Server:
//IntPtr ptr = IntPtr.Zero;
//var result = DeriveAppContainerSidFromAppContainerName("PackageFamilyName", ref ptr);
//var appSid = new SecurityIdentifier(ptr); //results in incorrect SID
var appSid = new SecurityIdentifier("hardCodedSID");
var access = new PipeSecurity();
SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
access.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.FullControl, System.Security.AccessControl.AccessControlType.Allow));
access.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.AccessSystemSecurity, System.Security.AccessControl.AccessControlType.Allow));
access.AddAccessRule(new PipeAccessRule(appSid, PipeAccessRights.FullControl, System.Security.AccessControl.AccessControlType.Allow));
access.AddAccessRule(new PipeAccessRule(appSid, PipeAccessRights.AccessSystemSecurity, System.Security.AccessControl.AccessControlType.Allow));
pipeServer = NamedPipeServerStreamConstructors.New("testpipe", PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.None, 0, 0, access);
pipeServer.WaitForConnection(); // gets past here when client creates NamedPipeClientStream
Client:
pipeClient = new NamedPipeClientStream(".", "testpipe", PipeDirection.InOut, PipeOptions.Asynchronous);
pipeClient.Connect(5000); // fails here
I was able to get somewhat of a working solution doing independent research. A couple answers:
NamedPipeServerStreamConstructors.New via NamedPipeServerStream.NetFrameworkVersion is a deprecated strategy. .Net 6.0 provides what I was trying to do via NamedPipeServerStreamACL. I opted to use this instead of what is currently in my sample.
I could not figure out the error with "System.Security.Principal.Windows" that was happening in my UWP app when trying to connect using NamedPipeClientStream. I think it just might be a bug or something. If anyone knows what is going on there, would be appreciated.
To work around #2, I implemented a lower level version of what I was trying to do using CreateFileW. This is a lower level c++ function, whilst the previous is the C# equivalent. It appears that the C# version has some intelligence with the ".\pipe\" prefix, so it is not needed when creating the pipe server. However, the C++ function in the client code for UWP will need to include that (no use of LOCAL prefix was needed). With this set up, I was able to send requests from my .Net 6.0 pipe server to my UWP pipe client and get responses back. Towards the end are some code snippets:
Still do not know the answer as to why I got the incorrect SID via DeriveAppContainerSidFromAppContainerName. I looked in my Registry and could not even located the SID it was trying to point me at. Mega confused.
Client code:
var fileName = #"\\.\pipe\\testpipe";
IntPtr pipeHandle = CreateFileW(fileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
IntPtr.Zero);
if (pipeHandle.ToInt32() == INVALID_HANDLE_VALUE)
{
var err = $"Failed to create pipe client, win32 error: {Marshal.GetLastWin32Error()}";
throw new Exception(err);
}
stream = new FileStream(new SafeFileHandle(pipeHandle, true), FileAccess.ReadWrite);
Server construction code:
NamedPipeServerStream pipeServer = NamedPipeServerStreamAcl.Create("testpipe", PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous, 512, 512, access);

Start Node.js server from a C# Application

A requirement has arisen that I need to start a Node.js server from a C# application, this is as simple as running a server.js script within the Node.js console. However, I'm not entirely certain how exactly to achieve that.
Here's what I've looked into so far:
In the Node.js installation, there's a file called C:\Program Files (x86)\nodejs\nodevars.bat, this is the command prompt window for Node.js. To start the server, I could possibly be using the following steps:
Execute the nodevars.bat file.
SendKeys to the new process console window to start the server.
This approach feels a bit fragile. There's no guarantee that the target user will have their Node.js installation in the same place, also sending keys to a process may not be an ideal solution.
Another method could be:
Write a batch file that executes nodevars.bat.
Execute the batch file from the C# application.
This seems like a better approach. However, the only problem here is that the nodevars.bat opens in a new console window.
So to the question(s), is there a way I can start a node.js server script using functionality built into the node.js installation? Perhaps sending arguments to the node.exe?
If it is to serve multiple users, i.e. as a server, then you can use the os-service package, and install a Windows service. You can then start and stop the service using the standard API.
If you are to start the server as a "single purpose" server, i.e. to serve only the current user, then os-service is the wrong approach. (Typically when using this approach you will specify a unique port for the service to use, which will only be used by your application).
To start a batch file or other Console application, from C#, without showing a console window, use the standard method, but be sure to specify:
ProcessStartInfo psi = new ProcessStartInfo();
psi.UseShellExecute = false; // This is important
psi.CreateNoWindow = true; // This is what hides the command window.
psi.FileName = #"c:\Path\to\your\batchfile.cmd";
psi.Arguments = #"-any -arguments -go Here"; // Probably you will pass the port number here
using(var process = Process.Start(psi)){
// Do something with process if you want.
}
There are a few different ones but I recommend the os-service package.

Working with mongodb without a live port (embedded)

I am starting mongodb from C# code.
I am connecting to it without mentioning any port:
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = dir + #"\mongod.exe";
start.WindowStyle = ProcessWindowStyle.Hidden;
start.UseShellExecute = false;
start.Arguments = "--dbpath d:\test\mongodb\data";
Process mongod = Process.Start(start);
MongoClient client = new MongoClient();
MongoServer server = client.GetServer();
MongoDatabase database = server.GetDatabase("db_name");
at the mongodb console (output window) I see that mongodb is listening to a port.
Is it possible to start mongo without a port?
You can't. MongoDB is a standalone server. The only way to communicate with MongoDB is using TCP or unix sockets, so it's never a truly embedded database. Auto-Deploying the database doesn't make it an embedded database, it will have its own process and it will be available to other applications.
When you don't configure a port, MongoDB (and it's drivers) will use port 27017.
If you need an embedded database, use one. Candidates for C# include SQLite, db4o, perst and it's BSD-licensed fork volante, and a ton of smaller projects like siaqodb (some of these not free).
MongoDB will be trouble because it's rather aggressive about memory allocation and might need repair when things go wrong.
You can't start mongod without it listening to a port, however to restrict access you can:
use the bind_ip configuration option to limit connections to localhost only (127.0.0.1)
use the port configuration option to change to a non-standard port specific to your application
configure authentication and appropriate user roles
Given you are planning to spawn the mongod process on the user's machine, you unfortunately can't prevent determined users with Administrator access from bypassing any of the above restrictions.
As noted in the answer by #mnemosyn, there are certainly databases such as SQLite that are designed to be embeddable and compiled into your application code without spawning external processes.

C# program parameters from the command line?

I'm trying to start a C# program running, and then give it command from the cmd.exe after it's started running. For instance, suppose I started my .exe from the command line (C://FILEPATH/my_program.exe). I'd then like to have that program continue running, and then have me be able to pass commands to it that it is capable of handling. In my ideal world this would be something like "C://FILEPATH/my_program.exe run_my_command()" which would execute the run_my_command function, or "C://FILEPATH/my_program.exe k", which would do something in response to the char k that I'd pre-programmed in. I know that, as I've typed, would start a new copy of my_program.exe. I'd only like to have one running while I pass something like that in.
Does anyone know how to do this? Sample code would be wonderfully appreciated. Thanks!!
The simplest solution would be for your second instance of "my_program.exe" to look for an existing instance that's already running, "pass" the message over to it and then exit immediately.
The usual way this is implemented is via named pipes (System.IO.Pipes in .NET 3.5+). When your program starts up, listen on a named pipe with a given name. If there's something else already listening on that pipe, send the message to it and exit.
You are describing a typical service and command tool. The service (demon) runs in the background and executes commands. The command tool takes user commands and passes them to the service. See Windows Service Applications. Having a service instead of starting several processes takes care of some issues your approach has, like security isolation between the processes (eg. one user starts the a command, another user starts another command and gets executed in the context of the first user) and process lifetime issues (user launches a command and then closes his session).
The command tool would communicate with the process via classic IPC (local RPC, pipes, shared memory, etc).

Custom API requirement

We are currently working on an API for an existing system.
It basically wraps some web-requests as an easy-to-use library that 3rd party companies should be able to use with our product.
As part of the API, there is an event mechanism where the server can call back to the client via a constantly-running socket connection.
To minimize load on the server, we want to only have one connection per computer. Currently there is a socket open per process, and that could eventually cause load problems if you had multiple applications using the API.
So my question is: if we want to deploy our API as a single standalone assembly, what is the best way to fix our problem?
A couple options we thought of:
Write an out of process COM object (don't know if that works in .Net)
Include a second exe file that would be required for events, it would have to single-instance itself, and open a named pipe or something to communicate through multiple processes
Extract this exe file from an embedded resource and execute it
None of those really seem ideal.
Any better ideas?
Do you mean something like Net.TCP port sharing?
You could fix the client-side port while opening your socket, say 45534. Since one port can be opened by only one process, only one process at a time would be able to open socket connection to the server.
Well, there are many ways to solve this as expressed in all the answers and comments, but may be the simpler way you can use is just have global status store in a place accesible for all the users of the current machine (may be you might have various users logged-in on the machine) where you store WHO has the right to have this open. Something like a "lock" as is used to be called. That store can be a field in a local or intranet database, a simple file, or whatever. That way you don't need to build or distribute extra binaries.
When a client connects to your server you create a new thread to handle him (not a process). You can store his IP address in a static dictionary (shared between all threads).
Something like:
static Dictionary<string, TcpClient> clients = new Dictionary<string, TcpClient>();
//This method is executed in a thread
void ProcessRequest(TcpClient client)
{
string ip = null;
//TODO: get client IP address
lock (clients)
{
...
if (clients.ContainsKey(ip))
{
//TODO: Deny connection
return;
}
else
{
clients.Add(ip, client);
}
}
//TODO: Answer the client
}
//TODO: Delete client from list on disconnection
The best solution we've come up with is to create a windows service that opens up a named pipe to manage multiple client processes through one socket connection to the server.
Then our API will be able to detect if the service is running/installed and fall back to creating it's own connection for the client otherwise.
3rd parties can decide if they want to bundle the service with their product or not, but core applications from our system will have it installed.
I will mark this as the answer in a few days if no one has a better option. I was hoping there was a way to execute our assembly as a new process, but all roads to do this do not seem very reliable.

Categories

Resources