“I want a resident service and a settings UI to exchange commands.” “I want to isolate only the work that needs administrator privileges into a separate process.” “I want tools on the same PC to pass data to each other.” — When this kind of inter-process communication (IPC) becomes necessary on Windows, the standard you should consider first is a named pipe.
The article on choosing Windows inter-process communication positioned named pipes as “the first candidate for same-machine IPC”. This article is the detailed treatment. Why they are the first candidate, how you choose modes and server shapes, and what you must protect when a privileged service uses them — aimed at developers writing business applications and services on Windows, it organises the material for those design judgements from primary sources.
1. The Bottom Line First
- A named pipe is a bidirectional inter-process channel with a namespace of the form
\\.\pipe\name. You can create multiple instances under the same name and accept several clients at once.1 - The reason they are the first candidate for same-machine IPC is the security model. You can control who connects with an ACL, and the server can inspect and borrow (impersonate) the client’s Windows account. Localhost TCP has neither.2
- If you want to treat “one write = one message”, use message mode; if you already have your own framing, use byte mode. Even in message mode you still need to handle split reads on a short buffer (ERROR_MORE_DATA).3
- Handle multiple clients with “several instances + overlapped I/O” or with “.NET async/await”. The official sample shows a shape that processes multiple instances on a single thread.4
- The security minimum is four points: reject remote (PIPE_REJECT_REMOTE_CLIENTS), make the ACL explicit, detect hijacking with FILE_FLAG_FIRST_PIPE_INSTANCE, and minimise the impersonation level on the client side.56
- For
ImpersonateNamedPipeClient, checking the return value is the lifeline. Ignore a failure and processing runs on with the server’s privileges.6
2. What a Named Pipe Is — Namespace, Instances, and How Connections Work
A named pipe is a channel identified by a name such as \\.\pipe\MyCompany.MyApp.Control. The server creates it with CreateNamedPipe, and the client opens the same name with CreateFile. Once it is open, both sides read and write with ReadFile / WriteFile — the distinctive point is that you can use it in the same shape as file I/O.1
The important concept is the instance. You can create multiple instances of a pipe with the same name, and one instance is one channel with one client. The first CreateNamedPipe call decides the maximum instance count (or unlimited).3
Client-side connection has a standard recipe. When every instance is in use, CreateFile fails with ERROR_PIPE_BUSY, so you wait for a free one with WaitNamedPipe and then retry. Also, the access you specify when opening has to match the direction the server created — a bidirectional pipe can be opened with either read or write specified, but an outbound pipe that the server only writes must be opened read-only, and an inbound pipe that the server only reads must be opened write-only, or CreateFile fails.7
flowchart TB
accTitle: Pipe direction and the client's access specification
accDescr: A client can open a bidirectional pipe with either read or write specified, but must open an outbound pipe that the server only writes as read-only, and an inbound pipe that the server only reads as write-only
q{"Direction the server created?"} -->|"Bidirectional"| dc["Either read or write is fine"]
q -->|"Outbound"| oc["Open read-only"]
q -->|"Inbound"| ic["Open write-only"]
Figure 1: A mismatch between direction and access specification becomes a CreateFile failure. When investigating a connection error, check here first.
flowchart TB
accTitle: Basic structure of a named pipe
accDescr: The server creates multiple pipe instances of the same name and waits for a connection with ConnectNamedPipe; each client opens the name with CreateFile and has a one-to-one bidirectional channel with one instance
s["Server"] --> i1["Instance 1"]
s --> i2["Instance 2"]
s --> i3["Instance 3"]
c1["Client A"] <--> i1
c2["Client B"] <--> i2
c3["Client C"] <--> i3
Figure 2: By holding several instances of the same name, one server can talk one-to-one with several clients at the same time.
Named pipes can also be opened remotely over SMB (\\server\pipe\name), but in a modern design there is almost no reason to use that actively; the issue is rather not leaving it open when you are not using it (Chapter 5).
3. Byte Mode and Message Mode
A pipe has two transfer modes.3
- Byte mode (PIPE_TYPE_BYTE): an “unbroken byte stream” like TCP. You decide for yourself where one message ends (you design framing such as a length prefix).
- Message mode (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE): one write is treated as one message, and the reader receives it in that unit. This is easier for request/response exchanges.
Message mode also has a convenient companion, TransactNamedPipe, which sends a request and receives the response in a single call.8 There is a pitfall, though. If the receive buffer is smaller than the whole message, the read returns ERROR_MORE_DATA and becomes a split read. Do not assume that message mode means “one Read always brings the whole thing”; you still need to write a loop that reads the remainder. Note that the read mode is a per-handle setting, and CreateNamedPipe decides it only on the server side. The client specifies it with SetNamedPipeHandleState after CreateFile (in .NET, ReadMode after connecting).7
flowchart TB
accTitle: Split-read loop in message mode
accDescr: If ReadFile succeeds the message is complete; if it returns ERROR_MORE_DATA you read the remainder that did not fit in the buffer and concatenate it; any other error is treated as a disconnect
read["Read with ReadFile"] --> r{"Result?"}
r -->|"Success"| done["Message complete"]
r -->|"ERROR_MORE_DATA"| more["Read the remainder and concatenate"]
more --> read
r -->|"Any other error"| dis["Treat as a disconnect"]
Figure 3: Even in message mode you need a “read-the-rest loop”; without it, only large messages break.
flowchart TB
accTitle: Difference between byte mode and message mode
accDescr: In byte mode three writes become an unbroken byte stream and the receiver has to split it; in message mode the unit of each write is preserved and arrives at the receiver as-is
bw["Byte mode: write AAA, BB, CCCC"] --> br["Received as the byte stream AAABBCCCC"]
br --> bf["You design the framing yourself"]
mw["Message mode: the same three writes"] --> mr["Received as three messages: AAA, BB, CCCC"]
mr --> mf["The write units are preserved"]
Figure 4: Message mode preserves “the unit of a write” and delivers it. Framing design becomes unnecessary; just do not forget to handle split reads.
The practical rule for which to choose is simple. If the exchange is in the shape of “request and response”, message mode. If you are carrying a form that already has framing built in (length-prefixed serialised data or a stream transfer), use byte mode. In .NET, specifying PipeTransmissionMode.Message corresponds to the former.9
4. Server Design — One Thread per Client, or Overlapped?
The server’s basic operation is the loop “create an instance → wait for a client with ConnectNamedPipe → read and write → disconnect and go to the next client”. There are two shapes for talking to several clients at once.
Synchronous, one thread per instance. You assign one thread to each instance, and each talks to its own client with synchronous I/O. The code is straightforward, but you consume one thread per client, and you also need a way to break out of blocking I/O when shutting the whole thing down.
Overlapped (asynchronous). You create instances with FILE_FLAG_OVERLAPPED, issue ConnectNamedPipe / ReadFile / WriteFile asynchronously, and have a small number of threads handle completion for every instance. Microsoft’s official sample shows a server that waits on an array of events with WaitForMultipleObjects and processes multiple instances on a single thread.4 The general account of asynchronous I/O is as explained in the I/O series article, and at larger scale you can also attach IOCP or thread-pool I/O.
flowchart TB
accTitle: Structure of an overlapped server
accDescr: Completions of each instance's asynchronous operations are received on an array of events, and a small number of threads wait with WaitForMultipleObjects and advance the completed instance, decoupling the thread count from the client count
i1["Instance 1 async operation"] --> ev["Array of events"]
i2["Instance 2 async operation"] --> ev
i3["Instance 3 async operation"] --> ev
ev --> wait["Wait for completion with WaitForMultipleObjects"]
wait --> proc["Advance the completed instance"]
proc --> wait
Figure 5: The overlapped shape decouples the thread count from the client count. The official sample turns this cycle on a single thread.
.NET almost removes this choice. Using NamedPipeServerStream’s WaitForConnectionAsync / ReadAsync / WriteAsync together with async/await, you get overlapped efficiency in code as straightforward as the synchronous shape.9
// C#: skeleton of a server that accepts multiple clients
while (!token.IsCancellationRequested)
{
var server = new NamedPipeServerStream(
"MyCompany.MyApp.Control",
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Message,
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
try
{
await server.WaitForConnectionAsync(token);
}
catch
{
await server.DisposeAsync(); // dispose yourself when leaving before a connection
throw;
}
_ = HandleClientAsync(server, token); // ownership after connect goes to the handler
}
PipeOptions.CurrentUserOnly is a specification that “allows connections only from processes of the same user”, a convenient and safe default that saves you writing an ACL yourself.10 It cannot be used in a setup that crosses users (a service ↔ an app in a user session, and the like), so in that case you move on to the ACL design in the next chapter.
flowchart TB
accTitle: Accept loop of a .NET asynchronous server
accDescr: The accept loop creates a NamedPipeServerStream, waits for a connection with WaitForConnectionAsync, and on arrival detaches client handling asynchronously and immediately returns to the next accept, so concurrent connections are handled in straightforward code
mk["Create the server stream"] --> wc["Wait with WaitForConnectionAsync"]
wc --> got["Connection arrives"]
got --> hd["Detach client handling asynchronously"]
hd --> mk
Figure 6: The accept loop sticks to the cycle “wait → detach → next”, and each client’s processing proceeds in parallel.
5. Security — Four Must-Dos When a Privileged Service Uses Pipes
The biggest reason named pipes are the first candidate for same-machine IPC is the security model, but that is only if you configure it correctly. Especially in a broker design of “an administrator-privilege service + a low-privilege UI app”, the pipe is the privilege boundary itself. There are four points to pin down.
(1) Reject remote. A pipe you intended as local IPC being openable from the network is, by itself, attack surface. Specify PIPE_REJECT_REMOTE_CLIENTS on CreateNamedPipe and remote-client connections are refused automatically.5
(2) Make the ACL explicit. Pass a security descriptor in SECURITY_ATTRIBUTES and narrow the users and groups allowed to connect. Do not give the client GENERIC_WRITE — the FILE_CREATE_PIPE_INSTANCE right included in it would let an authorised client itself create a server instance of the same name and steal subsequent connections. Grant read and write as individual rights, and do not pass the instance-creation right.11
(3) Prevent name hijacking. Pipe names are first-come, first-served. If a malicious process creates a pipe of the same name first and waits, clients connect to the fake server. The server specifies FILE_FLAG_FIRST_PIPE_INSTANCE when creating the first instance, guaranteeing that “I am first”, and if that fails it suspects hijacking and stops. This flag is only for the first instance that claims the name; putting it on the second and later instances makes creation fail.3
| (4) The client minimises the impersonation level to what is needed. This is preparation for the case where the peer is a fake server. If the client specifies **SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION** on CreateFile, the server can identify the client but cannot borrow those privileges and act.2 This is a trade-off against an impersonation workflow, though — in a broker design where the server performs real access under the client’s privileges, identification level is not enough for impersonation to succeed, and you need to permit SECURITY_IMPERSONATION. That permission is conditional on being sure you are connected to the genuine server. Anti-hijacking on the server side is only a mechanism that notices via a failed start; if the genuine service is absent and an attacker creates the same-name pipe first, the client can still connect to the fake server. Permit it only when you can confirm the peer through a guaranteed service start or mutual authentication after connect. |
The server-side identity check and privilege borrow is ImpersonateNamedPipeClient. Call this after reading a request from the pipe and the calling thread starts running in the security context of the sender of the last message read. Open a file with the client’s privileges and the access check is done against the client — the mechanism by which a privileged service executes “the requested operation, with the requester’s privileges”.6 The absolute condition for using it is checking the return value. Continue after impersonation fails and subsequent operations run with the server’s own high privileges. The official documentation states explicitly that “on failure you must not execute the client’s request”. Together with RevertToSelf after the work, the practices in the article on impersonation tokens apply as-is.
sequenceDiagram
accTitle: Flow of request handling that uses impersonation
accDescr: The server reads a request from the pipe, confirms that ImpersonateNamedPipeClient succeeded, then performs the operation with the client's privileges and returns to its own context with RevertToSelf. If impersonation fails it refuses the request without executing it
participant C as Client
participant S as Server
C->>S: Send a request
S->>S: Read the request
S->>S: ImpersonateNamedPipeClient
Note over S: On failure, refuse without executing the request
S->>S: Perform the operation with the client's privileges
S->>S: RevertToSelf to restore the original context
S->>C: Reply with the result
Figure 7: Confirming that impersonation succeeded and a reliable RevertToSelf come as a package. Continue on failure and it runs with the server’s privileges.
flowchart TB
accTitle: Four points that protect a privileged service's pipe
accDescr: The server side hardens the entrance with remote rejection, an explicit ACL, and a first-instance guarantee; the client side specifies the minimum impersonation level needed so a fake server cannot borrow privileges (narrow it to identification level if the design does not let the server borrow privileges)
subgraph sv["Server side"]
r1["PIPE_REJECT_REMOTE_CLIENTS"]
r2["Restrict connectors with an ACL"]
r3["FIRST_PIPE_INSTANCE (first instance only)"]
end
subgraph cl["Client side"]
r4["Specify the minimum impersonation level"]
end
sv --> safe["The pipe as a privilege boundary"]
cl --> safe
Figure 8: In a design where the pipe is the privilege boundary, implement the three server-side points plus the one client-side point as a set.
6. Practical Pitfalls
A race on startup order. If a client comes to connect before the server has created the pipe, you get a “the pipe does not exist” error. The client side builds in “does not exist → wait a little and retry”. Conversely, the principle on the server side is to start listening with ConnectNamedPipe before the client starts.8
flowchart TB
accTitle: Client connection retry flow
accDescr: Open the pipe with CreateFile; if the pipe does not exist wait briefly and retry; if every instance is in use (ERROR_PIPE_BUSY) wait for a free one with WaitNamedPipe and then retry; on success enter communication
cf["Open with CreateFile"] --> ok{"Result?"}
ok -->|"Success"| go["Start communicating"]
ok -->|"Pipe does not exist"| wait1["Wait briefly (server not started)"]
ok -->|"ERROR_PIPE_BUSY"| wnp["Wait for a free instance with WaitNamedPipe"]
wait1 --> cf
wnp --> cf
Figure 9: Client connection handling distinguishes the two kinds of failure, “does not exist” and “full”, and joins both back into a retry.
Detecting a disconnect. When the peer exits, Read/Write fail with ERROR_BROKEN_PIPE and the like. That is not an anomaly; it is everyday communication. The server detects the disconnect, DisconnectNamedPipes the instance, and prepares for the next connection; the client reconnects — the idea of “idempotent reconnection” described in the article on sleep/resume applies here too.
Assumptions about message size. On top of the split reads of message mode (Chapter 3), if you do not decide as part of the protocol “what is the maximum bytes of one message”, a malicious (or buggy) peer can waste your memory with a huge message. Decide an upper bound, and disconnect if it is exceeded; that is the safe approach.
Write completion and the peer receiving are different things. Success of WriteFile does not mean the peer’s app has processed the data. Operations that need certainty are underwritten by designs such as confirming with a response message, and including the correspondence of request and response in the protocol.
7. Summary
- Named pipes are the first candidate for same-machine IPC. The reasons are the same ease of use as file I/O, and integration with the Windows security model of ACLs and impersonation.
- Mode choice is “message mode for request and response, byte mode if you already have your own framing”. Even in message mode you still need to handle split reads (ERROR_MORE_DATA).
- Multiple clients are several instances + overlapped, or .NET async/await. For new work the .NET asynchronous shape is the straightforward one.
- On a pipe that is a privilege boundary, take remote rejection, an explicit ACL, FIRST_PIPE_INSTANCE (first instance only), and minimising the client-side impersonation level as a set.
- For
ImpersonateNamedPipeClient, checking the return value andRevertToSelfare the lifeline. - Weave the “everyday of communication” — startup order, disconnect, message upper bound, response confirmation — into the protocol design.
Named pipes are an old API, but for the use of “having processes talk to each other on the same machine while respecting Windows account boundaries”, they are still the most natural tool for the job. The design-judgement points are almost exhausted within the scope of this article. After that, write your own protocol out on a single sheet of paper before you start implementing.
Related Articles
- Choosing Windows Inter-Process Communication ── A Decision Table for Named Pipes / TCP / gRPC / Shared Memory / COM
- How to Concretely Isolate “Only the Operations That Need Administrator Privileges” in a Windows App
- Handling Windows Impersonation Tokens Correctly — Borrowing Privileges per Thread and Reverting Safely
- The Depths of Windows I/O (Part 2) — Synchronous and Asynchronous I/O: What OVERLAPPED Really Means
- Shared Memory Pitfalls and Practical Best Practices
Related Consulting Areas
KomuraSoft LLC handles design and implementation that involve inter-process communication — separating a service from a UI app, isolating administrator privileges, and the like — replacing existing IPC (shared memory, a homegrown socket, COM, and so on) with named pipes, and security reviews of a privileged service’s pipe communication. Consultation from bouncing a protocol design off us is welcome.
- Windows Application Development
- Technical Consulting & Design Review
- Bug Investigation & Root-Cause Analysis
- Contact Us
References
-
Microsoft Learn, Named Pipes. On a named pipe being a one-way or bidirectional channel between a pipe server and one or more pipe clients; on every instance sharing the same name while having independent buffers and handles; and on being usable from local and remote processes. ↩ ↩2
-
Microsoft Learn, Impersonating a Named Pipe Client. On impersonation letting the server thread operate within the client’s privileges; on the default impersonation level being SecurityImpersonation; and on the client being able to control the impersonation level with the SECURITY_SQOS_PRESENT flag at CreateFile time (SECURITY_IDENTIFICATION permits identification only). ↩ ↩2
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). On pipe direction (inbound, outbound, bidirectional), byte type and message type (PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE) and read mode (PIPE_READMODE_MESSAGE), the maximum instance count (PIPE_UNLIMITED_INSTANCES), asynchronous mode via FILE_FLAG_OVERLAPPED, a first-instance guarantee via FILE_FLAG_FIRST_PIPE_INSTANCE, and the default timeout for WaitNamedPipe. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Named Pipe Server Using Overlapped I/O. On the official sample of a single-thread server processing simultaneous connections with multiple clients via overlapped operations. On the shape that waits on each instance’s OVERLAPPED structure and event with WaitForMultipleObjects and advances the completed instance’s state machine, and on confirming completion of pending I/O with GetOverlappedResult. ↩ ↩2
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). On the two remote-client modes, PIPE_ACCEPT_REMOTE_CLIENTS (accept remote connections and inspect them against the security descriptor) and PIPE_REJECT_REMOTE_CLIENTS (automatically refuse remote-client connections). ↩ ↩2
-
Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). On a server-side thread beginning impersonation in the security context of the client of the last message read from the pipe; on returning with RevertToSelf after completion; and on continuing after impersonation fails causing execution in the server process’s own (privileged) context, so the return value must always be checked and on failure the client’s request must not be executed. ↩ ↩2 ↩3
-
Microsoft Learn, Named Pipe Client. On the client opening the pipe with CreateFile; on ERROR_PIPE_BUSY when every instance is in use, waiting for a free one with WaitNamedPipe; and on the opened handle defaulting to byte-read, blocking, and non-overlapped, with SetNamedPipeHandleState able to change it to message-read mode. ↩ ↩2
-
Microsoft Learn, Named Pipe Operations. On overlapped operations via ReadFileEx / WriteFileEx, a non-consuming read via PeekNamedPipe, TransactNamedPipe performing request send and response receive in one call on a message-type bidirectional pipe, and a blocking read before the client starts being able to cause a race. ↩ ↩2
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). On connecting and reading/writing with NamedPipeServerStream / NamedPipeClientStream, message-unit transfer via PipeTransmissionMode.Message, and handling multiple clients with asynchronous methods. ↩ ↩2
-
Microsoft Learn, PipeOptions Enum (System.IO.Pipes). On enabling asynchronous I/O with Asynchronous, and on CurrentUserOnly being able to permit connections only with processes of the same user (and the same elevation level). ↩
-
Microsoft Learn, Named Pipe Security and Access Rights. On the composition of named-pipe access rights; on GENERIC_WRITE including FILE_CREATE_PIPE_INSTANCE, so that giving a client generic write also permits creating a server instance; and on read and write of data being granted as individual access rights. ↩
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
The Win32 Thread Pool API — Concurrency Without Creating Threads, via CreateThreadpoolWork
Are you spawning CreateThread calls all over your native code? This article explains the Win32 thread pool API redesigned in Vista — the ...
DllMain and the Loader Lock — The Real Reason You're Told to "Do Nothing in DLL Initialization"
Why you must not call LoadLibrary or synchronize with other threads from DllMain. Drawing on primary sources, this article explains how t...
Spurious Wakeups — Why Condition Variables Wake "Without Being Notified" and How to Wait Correctly on Windows
A condition variable's wait can return even when no notification has arrived (a spurious wakeup). This article explains, from the Windows...
A Checklist for Safely Handling Child Processes in Windows Apps
How to make child processes die with the parent on Windows: Job Objects, exit propagation, stdout/stderr draining, and watchdog placement...
Shared Memory Pitfalls and Practical Best Practices
Shared memory is fast IPC, but it does not synchronize itself. How to avoid the classic traps: atomics, ABI and layout, lifetime, permiss...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Frequently Asked Questions
Common questions about the topic of this article.
- How should I choose between named pipes and TCP (a localhost socket)?
- For inter-process communication on the same machine, a named pipe is the first candidate. The reason is the security model. A pipe can control "who may connect" at the OS level with a Windows security descriptor (ACL), and the server can inspect and borrow the connecting peer's Windows account with ImpersonateNamedPipeClient. That contrasts with a localhost TCP port, which anyone can connect to, so you have to establish who the peer is with your own authentication. On the other hand, TCP-based options are advantageous when remote communication is likely to develop later, when you also talk to processes on other OSes, or when you want to reuse an existing protocol asset such as gRPC. This judgement is also laid out in the article on choosing Windows inter-process communication.
- Should I use byte mode or message mode?
- If you want to treat "one write = one unit of meaning", message mode (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE) is convenient. The receiver can read in the units the sender wrote, so you do not have to manage the boundaries yourself. Byte mode is an "unbroken byte stream" like TCP, and you have to design the framing yourself — a length prefix, for example. If you are carrying a protocol that already has framing (for example a length-prefixed serialised form), byte mode is fine. A caveat: even in message mode, if the receive buffer is smaller than the message you get a split read (ERROR_MORE_DATA), so you still need to handle that. Also, the read mode is a per-handle setting, and CreateNamedPipe sets it only on the server side. The client must specify PIPE_READMODE_MESSAGE with SetNamedPipeHandleState after CreateFile. In .NET the server specifies PipeTransmissionMode.Message, and the client sets NamedPipeClientStream.ReadMode to Message after connecting.
- How do I build a server that talks to several clients at once?
- A named pipe can create multiple instances under the same name, and one instance handles one client. There are two shapes. One is a synchronous design that assigns a thread per client; the implementation is straightforward, but it consumes one thread per client. The other is to use asynchronous I/O with FILE_FLAG_OVERLAPPED and have a small number of threads handle ConnectNamedPipe, ReadFile, and WriteFile for every instance; Microsoft's official sample also shows an implementation that processes multiple instances on a single thread. In .NET you can write the asynchronous shape with almost the same straightforwardness as the synchronous one, using NamedPipeServerStream.WaitForConnectionAsync and async/await. Unless you have a particular reason, the .NET asynchronous shape is what I recommend for new implementations.
- What is the minimum I should do for named-pipe security?
- Four points. First, if remote connections are not needed, specify PIPE_REJECT_REMOTE_CLIENTS and explicitly refuse connections over the network. Second, set an appropriate ACL with SECURITY_ATTRIBUTES and narrow the users and groups that may connect (the default ACL is too loose for some uses). Third, specify FILE_FLAG_FIRST_PIPE_INSTANCE when creating the first instance, so you detect "name hijacking" in which a pipe of the same name is created first (do not put this flag on the second and later instances). Fourth, a client that only wants the server to identify it should specify SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION on CreateFile, so a fake server cannot borrow (impersonate) its privileges. In a broker design where the server performs real access under the client's privileges, impersonation has to be permitted, so you use this restriction or not depending on whether the design lets the server borrow privileges.
- Are there caveats when using ImpersonateNamedPipeClient?
- The most important is checking the return value. If you continue after impersonation fails, subsequent operations run with the server process's own (often high) privileges, and operations that should not have been allowed to the client go through. The official documentation also states explicitly that on failure you must not execute the client's request. You also need to call it only after reading something — impersonation is performed in the context of "the last message read from the pipe" — and to return reliably to the original context with RevertToSelf when the work is done. The machinery around impersonation (tokens, impersonation levels, SeImpersonatePrivilege) is covered in detail in the article on impersonation tokens.