Named Pipes in der Praxis — Windows' Standard-IPC von Entwurf bis Sicherheit
· Go Komura · Windows, IPC, Windows-Entwicklung, C#, C++, Sicherheit, Win32-API
„Ein residenter Dienst und eine Einstellungsoberfläche sollen Befehle austauschen.“ „Nur die Arbeit, die Administratorrechte braucht, soll in einen eigenen Prozess isoliert werden.“ „Werkzeuge auf demselben PC sollen einander Daten übergeben.“ — Wenn unter Windows diese Art von Prozesskommunikation (IPC) nötig wird, ist der Standard, den Sie zuerst prüfen sollten, eine Named Pipe.
Der Artikel zur Wahl der Windows-Prozesskommunikation hat Named Pipes als „ersten Kandidaten für IPC auf demselben Rechner“ eingeordnet. Dieser Artikel ist die ausführliche Behandlung. Warum sie der erste Kandidat sind, wie Sie Modi und Serverformen wählen und was Sie schützen müssen, wenn ein privilegierter Dienst sie nutzt — gerichtet an Entwickler, die Geschäftsanwendungen und Dienste unter Windows schreiben, ordnet er das Material für diese Entwurfsentscheidungen anhand von Primärquellen.
1. Zuerst das Fazit
- Eine Named Pipe ist ein bidirektionaler Prozesskanal mit einem Namensraum der Form
\\.\pipe\name. Unter demselben Namen können Sie mehrere Instanzen anlegen und mehrere Clients gleichzeitig entgegennehmen.1 - Der Grund, warum sie der erste Kandidat für IPC auf demselben Rechner sind, ist das Sicherheitsmodell. Wer sich verbindet, steuern Sie mit einer ACL, und der Server kann das Windows-Konto des Clients prüfen und ausleihen (impersonieren). Localhost-TCP hat beides nicht.2
- Wenn Sie „ein Schreibvorgang = eine Nachricht“ behandeln wollen, nutzen Sie den Nachrichtenmodus; wenn Sie bereits eigenes Framing haben, den Byte-Modus. Auch im Nachrichtenmodus müssen Sie geteilte Lesungen bei zu kurzem Puffer (ERROR_MORE_DATA) behandeln.3
- Mehrere Clients bedienen Sie mit „mehreren Instanzen + Overlapped I/O“ oder mit „.NET async/await“. Das offizielle Beispiel zeigt eine Form, die mehrere Instanzen auf einem einzigen Thread verarbeitet.4
- Das Sicherheitsminimum sind vier Punkte: Remote ablehnen (PIPE_REJECT_REMOTE_CLIENTS), die ACL explizit machen, Entführung mit FILE_FLAG_FIRST_PIPE_INSTANCE erkennen und die Impersonation-Stufe auf der Clientseite minimieren.56
- Bei
ImpersonateNamedPipeClientist die Prüfung des Rückgabewerts die Lebensader. Ignorieren Sie einen Fehlschlag, läuft die Verarbeitung mit den Rechten des Servers weiter.6
2. Was eine Named Pipe ist — Namensraum, Instanzen und wie Verbindungen funktionieren
Eine Named Pipe ist ein Kanal, der durch einen Namen wie \\.\pipe\MyCompany.MyApp.Control identifiziert wird. Der Server legt sie mit CreateNamedPipe an, und der Client öffnet denselben Namen mit CreateFile. Sobald sie offen ist, lesen und schreiben beide Seiten mit ReadFile / WriteFile — der besondere Punkt ist, dass Sie sie in derselben Form wie Datei-I/O nutzen können.1
Der wichtige Begriff ist die Instanz. Sie können mehrere Instanzen einer Pipe mit demselben Namen anlegen, und eine Instanz ist ein Kanal mit einem Client. Der erste Aufruf von CreateNamedPipe legt die maximale Instanzzahl fest (oder unbegrenzt).3
Die Verbindung auf der Clientseite hat ein Standardrezept. Wenn jede Instanz belegt ist, schlägt CreateFile mit ERROR_PIPE_BUSY fehl, also warten Sie mit WaitNamedPipe auf eine freie und versuchen es erneut. Außerdem muss der Zugriff, den Sie beim Öffnen angeben, zur Richtung passen, die der Server angelegt hat — eine bidirektionale Pipe kann mit Lese- oder Schreibangabe geöffnet werden, aber eine Outbound-Pipe, die der Server nur schreibt, muss nur lesend geöffnet werden, und eine Inbound-Pipe, die der Server nur liest, muss nur schreibend geöffnet werden, sonst schlägt CreateFile fehl.7
flowchart TB
accTitle: Piperichtung und die Zugriffsspezifikation des Clients
accDescr: Ein Client kann eine bidirektionale Pipe mit Lese- oder Schreibangabe öffnen, muss aber eine Outbound-Pipe, die der Server nur schreibt, nur lesend und eine Inbound-Pipe, die der Server nur liest, nur schreibend öffnen
q{"Richtung vom Server?"} -->|"Bidirektional"| dc["Lesen oder Schreiben"]
q -->|"Outbound"| oc["Nur lesend öffnen"]
q -->|"Inbound"| ic["Nur schreibend öffnen"]
Abbildung 1: Eine Diskrepanz zwischen Richtung und Zugriffsspezifikation wird zu einem CreateFile-Fehlschlag. Bei der Untersuchung eines Verbindungsfehlers prüfen Sie zuerst hier.
flowchart TB
accTitle: Grundstruktur einer Named Pipe
accDescr: Der Server legt mehrere Pipe-Instanzen desselben Namens an und wartet mit ConnectNamedPipe auf eine Verbindung; jeder Client öffnet den Namen mit CreateFile und hat einen bidirektionalen Eins-zu-eins-Kanal mit einer Instanz
s["Server"] --> i1["Instanz 1"]
s --> i2["Instanz 2"]
s --> i3["Instanz 3"]
c1["Client A"] <--> i1
c2["Client B"] <--> i2
c3["Client C"] <--> i3
Abbildung 2: Indem ein Server mehrere Instanzen desselben Namens hält, kann er gleichzeitig eins zu eins mit mehreren Clients sprechen.
Named Pipes können auch remote über SMB (\\server\pipe\name) geöffnet werden, aber in einem modernen Entwurf gibt es fast keinen Grund, das aktiv zu nutzen; das Thema ist vielmehr, sie nicht offen zu lassen, wenn Sie sie nicht nutzen (Kapitel 5).
3. Byte-Modus und Nachrichtenmodus
Eine Pipe hat zwei Übertragungsmodi.3
- Byte-Modus (PIPE_TYPE_BYTE): ein „ununterbrochener Bytestrom“ wie TCP. Sie entscheiden selbst, wo eine Nachricht endet (Sie entwerfen Framing wie ein Längenpräfix).
- Nachrichtenmodus (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE): ein Schreibvorgang wird als eine Nachricht behandelt, und der Leser empfängt sie in dieser Einheit. Das ist für Request/Response-Austausch einfacher.
Der Nachrichtenmodus hat außerdem einen bequemen Begleiter, TransactNamedPipe, der eine Anfrage sendet und die Antwort in einem einzigen Aufruf empfängt.8 Es gibt allerdings eine Falle. Ist der Empfangspuffer kleiner als die ganze Nachricht, gibt das Lesen ERROR_MORE_DATA zurück und wird zu einer geteilten Lesung. Gehen Sie nicht davon aus, dass Nachrichtenmodus bedeutet „ein Read bringt immer das Ganze“; Sie müssen trotzdem eine Schleife schreiben, die den Rest liest. Beachten Sie, dass der Lesemodus eine pro-Handle-Einstellung ist und CreateNamedPipe ihn nur auf der Serverseite festlegt. Der Client gibt ihn nach CreateFile mit SetNamedPipeHandleState an (in .NET ReadMode nach dem Verbinden).7
flowchart TB
accTitle: Schleife für geteilte Lesung im Nachrichtenmodus
accDescr: Gelingt ReadFile, ist die Nachricht vollständig; gibt es ERROR_MORE_DATA zurück, lesen Sie den Rest, der nicht in den Puffer passte, und hängen ihn an; jeder andere Fehler gilt als Trennung
read["Lesen mit ReadFile"] --> r{"Ergebnis?"}
r -->|"Erfolg"| done["Nachricht vollständig"]
r -->|"ERROR_MORE_DATA"| more["Rest lesen und anhängen"]
more --> read
r -->|"Jeder andere Fehler"| dis["Als Trennung behandeln"]
Abbildung 3: Auch im Nachrichtenmodus brauchen Sie eine „Rest-lesen-Schleife“; ohne sie brechen nur große Nachrichten.
flowchart TB
accTitle: Unterschied zwischen Byte-Modus und Nachrichtenmodus
accDescr: Im Byte-Modus werden drei Schreibvorgänge zu einem ununterbrochenen Bytestrom und der Empfänger muss ihn teilen; im Nachrichtenmodus bleibt die Einheit jedes Schreibvorgangs erhalten und kommt so beim Empfänger an
bw["Byte-Modus: AAA, BB, CCCC"] --> br["Empfangen als Bytestrom AAABBCCCC"]
br --> bf["Framing selbst entwerfen"]
mw["Nachrichtenmodus: dieselben drei"] --> mr["Empfangen als drei Nachrichten"]
mr --> mf["Schreibeinheiten bleiben"]
Abbildung 4: Der Nachrichtenmodus bewahrt „die Einheit eines Schreibvorgangs“ und liefert sie. Framing-Entwurf wird unnötig; vergessen Sie nur nicht, geteilte Lesungen zu behandeln.
Die praktische Regel, welchen Sie wählen, ist einfach. Ist der Austausch in der Form „Anfrage und Antwort“, Nachrichtenmodus. Tragen Sie eine Form, die bereits Framing eingebaut hat (längenvorangestellte serialisierte Daten oder eine Stromübertragung), nutzen Sie den Byte-Modus. In .NET entspricht die Angabe von PipeTransmissionMode.Message dem Ersteren.9
4. Serverentwurf — Ein Thread pro Client oder Overlapped?
Die Grundoperation des Servers ist die Schleife „eine Instanz anlegen → mit ConnectNamedPipe auf einen Client warten → lesen und schreiben → trennen und zum nächsten Client“. Es gibt zwei Formen, gleichzeitig mit mehreren Clients zu sprechen.
Synchron, ein Thread pro Instanz. Sie weisen jeder Instanz einen Thread zu, und jede spricht mit ihrem eigenen Client über synchrones I/O. Der Code ist geradlinig, aber Sie verbrauchen einen Thread pro Client, und Sie brauchen außerdem einen Weg, blockierendes I/O beim Herunterfahren des Ganzen zu unterbrechen.
Overlapped (asynchron). Sie legen Instanzen mit FILE_FLAG_OVERLAPPED an, geben ConnectNamedPipe / ReadFile / WriteFile asynchron aus und lassen eine kleine Zahl von Threads die Fertigstellung für jede Instanz behandeln. Microsofts offizielles Beispiel zeigt einen Server, der mit WaitForMultipleObjects auf ein Array von Ereignissen wartet und mehrere Instanzen auf einem einzigen Thread verarbeitet.4 Die allgemeine Darstellung von asynchronem I/O ist wie im Artikel der I/O-Reihe erklärt, und in größerem Maßstab können Sie auch IOCP oder Thread-Pool-I/O anbinden.
flowchart TB
accTitle: Struktur eines Overlapped-Servers
accDescr: Fertigstellungen der asynchronen Operationen jeder Instanz kommen auf einem Array von Ereignissen an, und eine kleine Zahl von Threads wartet mit WaitForMultipleObjects und treibt die fertige Instanz voran, sodass die Threadzahl von der Clientzahl entkoppelt ist
i1["Instanz 1 asynchron"] --> ev["Array von Ereignissen"]
i2["Instanz 2 asynchron"] --> ev
i3["Instanz 3 asynchron"] --> ev
ev --> wait["Warten mit WaitForMultipleObjects"]
wait --> proc["Fertige Instanz voranbringen"]
proc --> wait
Abbildung 5: Die Overlapped-Form entkoppelt die Threadzahl von der Clientzahl. Das offizielle Beispiel dreht diesen Zyklus auf einem einzigen Thread.
.NET nimmt Ihnen diese Wahl fast ab. Mit WaitForConnectionAsync / ReadAsync / WriteAsync von NamedPipeServerStream zusammen mit async/await erhalten Sie Overlapped-Effizienz in Code, der so geradlinig ist wie die synchrone Form.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 ist eine Angabe, die „Verbindungen nur von Prozessen desselben Benutzers zulässt“, eine bequeme und sichere Voreinstellung, die Ihnen das eigene Schreiben einer ACL erspart.10 In einem Aufbau, der Benutzer überschreitet (ein Dienst ↔ eine App in einer Benutzersitzung und Ähnliches), lässt sie sich nicht nutzen, also gehen Sie in dem Fall zum ACL-Entwurf im nächsten Kapitel über.
flowchart TB
accTitle: Accept-Schleife eines asynchronen .NET-Servers
accDescr: Die Accept-Schleife legt einen NamedPipeServerStream an, wartet mit WaitForConnectionAsync auf eine Verbindung und löst bei Ankunft die Clientbehandlung asynchron ab und kehrt sofort zum nächsten Accept zurück, sodass gleichzeitige Verbindungen in geradlinigem Code behandelt werden
mk["Server-Stream anlegen"] --> wc["Warten mit WaitForConnectionAsync"]
wc --> got["Verbindung kommt an"]
got --> hd["Clientbehandlung asynchron ablösen"]
hd --> mk
Abbildung 6: Die Accept-Schleife bleibt beim Zyklus „warten → ablösen → nächste“, und die Verarbeitung jedes Clients läuft parallel.
5. Sicherheit — Vier Muss-Punkte, wenn ein privilegierter Dienst Pipes nutzt
Der größte Grund, warum Named Pipes der erste Kandidat für IPC auf demselben Rechner sind, ist das Sicherheitsmodell — aber nur, wenn Sie es richtig konfigurieren. Besonders in einem Broker-Entwurf aus „einem Dienst mit Administratorrechten + einer UI-App mit geringen Rechten“ ist die Pipe selbst die Rechtegrenze. Es gibt vier Punkte festzuzurren.
(1) Remote ablehnen. Eine Pipe, die Sie als lokale IPC meinten und die vom Netz aus zu öffnen ist, ist für sich schon Angriffsfläche. Geben Sie PIPE_REJECT_REMOTE_CLIENTS bei CreateNamedPipe an, und Verbindungen von Remote-Clients werden automatisch abgelehnt.5
(2) Die ACL explizit machen. Übergeben Sie in SECURITY_ATTRIBUTES einen Sicherheitsdeskriptor und engen Sie die Benutzer und Gruppen ein, die sich verbinden dürfen. Geben Sie dem Client nicht GENERIC_WRITE — das darin enthaltene Recht FILE_CREATE_PIPE_INSTANCE würde einem berechtigten Client selbst erlauben, eine Serverinstanz desselben Namens anzulegen und nachfolgende Verbindungen zu stehlen. Gewähren Sie Lesen und Schreiben als Einzelrechte, und geben Sie das Recht zum Anlegen von Instanzen nicht weiter.11
(3) Namensentführung verhindern. Pipenamen gelten nach dem Prinzip „wer zuerst kommt“. Legt ein bösartiger Prozess zuerst eine Pipe desselben Namens an und wartet, verbinden sich Clients mit dem gefälschten Server. Der Server gibt beim Anlegen der ersten Instanz FILE_FLAG_FIRST_PIPE_INSTANCE an, garantiert damit „ich bin der Erste“, und wenn das fehlschlägt, verdächtigt er Entführung und hält an. Dieses Flag gilt nur für die erste Instanz, die den Namen beansprucht; auf der zweiten und späteren Instanzen lässt es das Anlegen fehlschlagen.3
(4) Der Client minimiert die Impersonation-Stufe auf das Nötige. Das ist die Vorbereitung für den Fall, dass das Gegenüber ein gefälschter Server ist. Gibt der Client bei CreateFile **SECURITY_SQOS_PRESENT |
SECURITY_IDENTIFICATION** an, kann der Server den Client identifizieren, aber nicht diese Rechte ausleihen und handeln.2 Das ist allerdings ein Kompromiss gegenüber einem Identitätswechsel-Ablauf — in einem Broker-Entwurf, in dem der Server unter den Rechten des Clients echten Zugriff ausführt, reicht die Identification-Stufe nicht für erfolgreichen Identitätswechsel, und Sie müssen SECURITY_IMPERSONATION zulassen. Diese Erlaubnis steht unter der Bedingung, dass Sie sicher mit dem echten Server verbunden sind. Die Anti-Entführung auf der Serverseite ist nur ein Mechanismus, der über einen fehlgeschlagenen Start bemerkt; fehlt der echte Dienst und legt ein Angreifer zuerst die gleichnamige Pipe an, kann der Client sich trotzdem mit dem gefälschten Server verbinden. Erlauben Sie es nur, wenn Sie das Gegenüber durch einen garantierten Dienststart oder gegenseitige Authentifizierung nach dem Verbinden bestätigen können. |
Die Identitätsprüfung und das Ausleihen von Rechten auf der Serverseite ist ImpersonateNamedPipeClient. Rufen Sie das nach dem Lesen einer Anfrage von der Pipe auf, und der aufrufende Thread beginnt, im Sicherheitskontext des Absenders der zuletzt gelesenen Nachricht zu laufen. Öffnen Sie eine Datei mit den Rechten des Clients, und die Zugriffsprüfung gilt gegenüber dem Client — der Mechanismus, mit dem ein privilegierter Dienst „die angeforderte Operation mit den Rechten des Anfragenden“ ausführt.6 Die absolute Bedingung für die Nutzung ist die Prüfung des Rückgabewerts. Setzen Sie nach fehlgeschlagenem Identitätswechsel fort, laufen nachfolgende Operationen mit den eigenen hohen Rechten des Servers. Die offizielle Dokumentation sagt ausdrücklich, dass „Sie bei Fehlschlag die Anfrage des Clients nicht ausführen dürfen“. Zusammen mit RevertToSelf nach der Arbeit gelten die Praktiken im Artikel zu Identitätswechseltoken unverändert.
sequenceDiagram
accTitle: Ablauf der Anfragebehandlung mit Identitätswechsel
accDescr: Der Server liest eine Anfrage von der Pipe, bestätigt, dass ImpersonateNamedPipeClient gelungen ist, führt dann die Operation mit den Rechten des Clients aus und kehrt mit RevertToSelf in den eigenen Kontext zurück. Schlägt der Identitätswechsel fehl, lehnt er die Anfrage ohne Ausführung ab
participant C as Client
participant S as Server
C->>S: Anfrage senden
S->>S: Anfrage lesen
S->>S: ImpersonateNamedPipeClient
Note over S: Bei Fehlschlag ohne Ausführung ablehnen
S->>S: Operation mit Clientrechten
S->>S: RevertToSelf stellt Kontext her
S->>C: Ergebnis antworten
Abbildung 7: Die Bestätigung, dass Identitätswechsel gelungen ist, und ein zuverlässiges RevertToSelf gehören zusammen. Setzen Sie bei Fehlschlag fort, läuft es mit den Rechten des Servers.
flowchart TB
accTitle: Vier Punkte, die die Pipe eines privilegierten Dienstes schützen
accDescr: Die Serverseite härtet den Eingang mit Remote-Ablehnung, expliziter ACL und Garantie der ersten Instanz; die Clientseite gibt die minimale nötige Impersonation-Stufe an, sodass ein gefälschter Server keine Rechte ausleihen kann (auf Identification-Stufe einengen, wenn der Entwurf dem Server das Ausleihen von Rechten nicht gestattet)
subgraph sv["Serverseite"]
r1["PIPE_REJECT_REMOTE_CLIENTS"]
r2["Verbinder per ACL begrenzen"]
r3["FIRST_PIPE_INSTANCE(nur erste)"]
end
subgraph cl["Clientseite"]
r4["Minimale Impersonation-Stufe"]
end
sv --> safe["Die Pipe als Rechtegrenze"]
cl --> safe
Abbildung 8: In einem Entwurf, in dem die Pipe die Rechtegrenze ist, setzen Sie die drei Punkte auf der Serverseite plus den einen auf der Clientseite als Satz um.
6. Praktische Fallstricke
Ein Wettlauf um die Startreihenfolge. Kommt ein Client zum Verbinden, bevor der Server die Pipe angelegt hat, erhalten Sie einen Fehler „die Pipe existiert nicht“. Die Clientseite baut „existiert nicht → kurz warten und erneut versuchen“ ein. Umgekehrt gilt auf der Serverseite das Prinzip, mit ConnectNamedPipe zu lauschen, bevor der Client startet.8
flowchart TB
accTitle: Ablauf des erneuten Verbindungsversuchs auf der Clientseite
accDescr: Öffnen Sie die Pipe mit CreateFile; existiert die Pipe nicht, warten Sie kurz und versuchen Sie es erneut; sind alle Instanzen belegt(ERROR_PIPE_BUSY), warten Sie mit WaitNamedPipe auf eine freie und versuchen Sie es dann erneut; bei Erfolg gehen Sie in die Kommunikation
cf["Öffnen mit CreateFile"] --> ok{"Ergebnis?"}
ok -->|"Erfolg"| go["Kommunikation beginnen"]
ok -->|"Pipe fehlt"| wait1["Kurz warten(Server fehlt)"]
ok -->|"ERROR_PIPE_BUSY"| wnp["WaitNamedPipe (freie Instanz)"]
wait1 --> cf
wnp --> cf
Abbildung 9: Die Verbindungsbehandlung auf der Clientseite unterscheidet die zwei Arten von Fehlschlag, „existiert nicht“ und „voll“, und führt beide wieder in einen erneuten Versuch.
Eine Trennung erkennen. Wenn das Gegenüber beendet, schlagen Read/Write mit ERROR_BROKEN_PIPE und Ähnlichem fehl. Das ist keine Anomalie; es ist Alltag der Kommunikation. Der Server erkennt die Trennung, ruft DisconnectNamedPipe für die Instanz auf und bereitet die nächste Verbindung vor; der Client verbindet sich erneut — die Idee der „idempotenten Wiederverbindung“, die im Artikel zu Ruhezustand/Fortsetzen beschrieben ist, gilt hier ebenfalls.
Annahmen über die Nachrichtengröße. Zusätzlich zu den geteilten Lesungen des Nachrichtenmodus (Kapitel 3) kann ein bösartiges (oder fehlerhaftes) Gegenüber Ihren Speicher mit einer riesigen Nachricht verschwenden, wenn Sie als Teil des Protokolls nicht festlegen, „wie viele Bytes eine Nachricht höchstens hat“. Legen Sie eine Obergrenze fest und trennen Sie bei Überschreitung — das ist der sichere Ansatz.
Schreibfertigstellung und Empfang beim Gegenüber sind verschiedene Dinge. Erfolg von WriteFile bedeutet nicht, dass die App des Gegenübers die Daten verarbeitet hat. Operationen, die Gewissheit brauchen, werden durch Entwürfe unterlegt wie Bestätigung mit einer Antwortnachricht und die Zuordnung von Anfrage und Antwort im Protokoll.
7. Zusammenfassung
- Named Pipes sind der erste Kandidat für IPC auf demselben Rechner. Die Gründe sind dieselbe einfache Nutzung wie bei Datei-I/O und die Integration ins Windows-Sicherheitsmodell aus ACL und Identitätswechsel.
- Die Moduswahl ist „Nachrichtenmodus für Anfrage und Antwort, Byte-Modus, wenn Sie bereits eigenes Framing haben“. Auch im Nachrichtenmodus müssen Sie geteilte Lesungen (ERROR_MORE_DATA) behandeln.
- Mehrere Clients sind mehrere Instanzen + Overlapped oder .NET async/await. Für neue Arbeit ist die asynchrone .NET-Form die geradlinige.
- Auf einer Pipe, die eine Rechtegrenze ist, nehmen Sie Remote-Ablehnung, eine explizite ACL, FIRST_PIPE_INSTANCE (nur erste Instanz) und die Minimierung der Impersonation-Stufe auf der Clientseite als Satz.
- Bei
ImpersonateNamedPipeClientsind die Prüfung des Rückgabewerts undRevertToSelfdie Lebensader. - Weben Sie den „Alltag der Kommunikation“ — Startreihenfolge, Trennung, Nachrichtenobergrenze, Antwortbestätigung — in den Protokollentwurf ein.
Named Pipes sind eine alte API, aber für den Einsatz „Prozesse auf demselben Rechner miteinander sprechen zu lassen und dabei Windows-Kontogrenzen zu achten“ sind sie immer noch das natürlichste Werkzeug für die Aufgabe. Die Punkte der Entwurfsentscheidung sind im Rahmen dieses Artikels fast erschöpft. Danach schreiben Sie Ihr eigenes Protokoll auf ein einziges Blatt, bevor Sie mit der Umsetzung beginnen.
Weiterführende Artikel
- Windows-Prozesskommunikation richtig wählen ── Eine Entscheidungstabelle für Named Pipes / TCP / gRPC / Shared Memory / COM
- Wie man in einer Windows-App konkret nur die „Vorgänge, die Administratorrechte brauchen“ isoliert
- Windows-Identitätswechseltoken richtig handhaben ── Rechte pro Thread ausleihen und sicher zurückgeben
- Die Tiefen von Windows I/O (Teil 2) — Synchrones und asynchrones I/O: Was OVERLAPPED wirklich bedeutet
- Fallstricke bei Shared Memory und Best Practices für die Praxis
Zugehörige Beratungsfelder
KomuraSoft LLC übernimmt Entwurf und Umsetzung, die Prozesskommunikation einbeziehen — Trennung eines Dienstes von einer UI-App, Isolierung von Administratorrechten und Ähnliches —, den Ersatz bestehender IPC (Shared Memory, ein selbstgebauter Socket, COM und so weiter) durch Named Pipes sowie Sicherheitsreviews der Pipe-Kommunikation eines privilegierten Dienstes. Eine Beratung, die beim Abklopfen eines Protokollentwurfs beginnt, ist willkommen.
- Windows-Anwendungsentwicklung
- Technische Beratung und Design-Review
- Fehleruntersuchung und Ursachenanalyse
- Kontakt
Quellen
-
Microsoft Learn, Named Pipes. Dazu, dass eine Named Pipe ein einseitiger oder bidirektionaler Kanal zwischen einem Pipe-Server und einem oder mehreren Pipe-Clients ist; dazu, dass jede Instanz denselben Namen teilt, aber unabhängige Puffer und Handles hat; sowie dazu, dass sie von lokalen und Remote-Prozessen nutzbar ist. ↩ ↩2
-
Microsoft Learn, Impersonating a Named Pipe Client. Dazu, dass Identitätswechsel dem Serverthread erlaubt, innerhalb der Rechte des Clients zu arbeiten; dazu, dass die Standard-Impersonation-Stufe SecurityImpersonation ist; sowie dazu, dass der Client die Impersonation-Stufe mit dem Flag SECURITY_SQOS_PRESENT zum Zeitpunkt von CreateFile steuern kann (SECURITY_IDENTIFICATION erlaubt nur Identifikation). ↩ ↩2
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). Zur Piperichtung (Inbound, Outbound, bidirektional), zum Byte-Typ und Nachrichten-Typ (PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE) und Lesemodus (PIPE_READMODE_MESSAGE), zur maximalen Instanzzahl (PIPE_UNLIMITED_INSTANCES), zum asynchronen Modus über FILE_FLAG_OVERLAPPED, zur Garantie der ersten Instanz über FILE_FLAG_FIRST_PIPE_INSTANCE und zum Standardtimeout für WaitNamedPipe. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Named Pipe Server Using Overlapped I/O. Zum offiziellen Beispiel eines Einzelthread-Servers, der gleichzeitige Verbindungen mit mehreren Clients über Overlapped-Operationen verarbeitet. Zur Form, die auf die OVERLAPPED-Struktur und das Ereignis jeder Instanz mit WaitForMultipleObjects wartet und die Zustandsmaschine der fertigen Instanz voranbringt, sowie zur Bestätigung der Fertigstellung ausstehenden I/O mit GetOverlappedResult. ↩ ↩2
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). Zu den zwei Remote-Client-Modi PIPE_ACCEPT_REMOTE_CLIENTS (Remote-Verbindungen annehmen und sie gegen den Sicherheitsdeskriptor prüfen) und PIPE_REJECT_REMOTE_CLIENTS (Verbindungen von Remote-Clients automatisch ablehnen). ↩ ↩2
-
Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). Dazu, dass ein Thread auf der Serverseite den Identitätswechsel im Sicherheitskontext des Clients der zuletzt von der Pipe gelesenen Nachricht beginnt; zum Zurückkehren mit RevertToSelf nach Abschluss; sowie dazu, dass Fortsetzen nach fehlgeschlagenem Identitätswechsel zur Ausführung im eigenen (privilegierten) Kontext des Serverprozesses führt, sodass der Rückgabewert stets geprüft werden muss und bei Fehlschlag die Anfrage des Clients nicht ausgeführt werden darf. ↩ ↩2 ↩3
-
Microsoft Learn, Named Pipe Client. Dazu, dass der Client die Pipe mit CreateFile öffnet; zu ERROR_PIPE_BUSY, wenn jede Instanz belegt ist, und zum Warten auf eine freie mit WaitNamedPipe; sowie dazu, dass das geöffnete Handle standardmäßig byte-lesend, blockierend und nicht-overlapped ist und SetNamedPipeHandleState es in den Nachrichten-Lesemodus ändern kann. ↩ ↩2
-
Microsoft Learn, Named Pipe Operations. Zu Overlapped-Operationen über ReadFileEx / WriteFileEx, zu einem nicht verbrauchenden Lesen über PeekNamedPipe, dazu, dass TransactNamedPipe auf einer bidirektionalen Pipe vom Nachrichtentyp Senden der Anfrage und Empfang der Antwort in einem Aufruf ausführt, sowie dazu, dass ein blockierendes Lesen vor dem Start des Clients einen Wettlauf verursachen kann. ↩ ↩2
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). Zu Verbinden und Lesen/Schreiben mit NamedPipeServerStream / NamedPipeClientStream, zur Übertragung in Nachrichteneinheiten über PipeTransmissionMode.Message und zur Behandlung mehrerer Clients mit asynchronen Methoden. ↩ ↩2
-
Microsoft Learn, PipeOptions Enum (System.IO.Pipes). Zum Aktivieren von asynchronem I/O mit Asynchronous und dazu, dass CurrentUserOnly Verbindungen nur mit Prozessen desselben Benutzers (und derselben Erhöhungsstufe) zulassen kann. ↩
-
Microsoft Learn, Named Pipe Security and Access Rights. Zur Zusammensetzung der Zugriffsrechte von Named Pipes; dazu, dass GENERIC_WRITE FILE_CREATE_PIPE_INSTANCE enthält, sodass das Gewähren von generischem Schreiben an einen Client auch das Anlegen einer Serverinstanz erlaubt; sowie dazu, dass Lesen und Schreiben von Daten als einzelne Zugriffsrechte gewährt werden. ↩
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Die Win32-Thread-Pool-API — Nebenläufigkeit ohne eigene Threads, über CreateThreadpoolWork
Verstreuen Sie CreateThread-Aufrufe über Ihren nativen Code? Dieser Artikel erklärt die in Vista neu entworfene Win32-Thread-Pool-API — d...
Eine Checkliste für den sicheren Umgang mit Kindprozessen in Windows-Apps
Beim sicheren Umgang mit Kindprozessen in Windows-Apps zählt weniger die Start-API als der Besitz des Prozessbaums und das Design der Bee...
Fallstricke bei Shared Memory und Best Practices für die Praxis
Wir ordnen die Fallstricke beim praktischen Einsatz von Shared Memory und ein Design ein, das die Unfallrate senkt – von Synchronisation ...
WMI/CIM aus C# und PowerShell verwenden — Praxisleitfaden für Hardwareinformationen, Prozessüberwachung und Remoteabfragen
WMI/CIM ist die Standardlösung, um die Seriennummer eines PCs auszulesen, freien Festplattenspeicher zu überwachen und den Start von Proz...
Was ist das TPM in Windows? — Eine bebilderte Einführung in den „Tresor, der Schlüssel nie herausgibt“ und das gemessene Starten
Eine bebilderte Erklärung des TPM: wie Schlüssel den Chip nie verlassen, PCRs und Measured Boot, wie BitLocker und Windows Hello es nutze...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Wie wähle ich zwischen Named Pipes und TCP (einem Localhost-Socket)?
- Für die Prozesskommunikation auf demselben Rechner ist eine Named Pipe der erste Kandidat. Der Grund ist das Sicherheitsmodell. Eine Pipe kann mit einem Windows-Sicherheitsdeskriptor (ACL) auf Betriebssystemebene steuern, wer sich verbinden darf, und der Server kann mit ImpersonateNamedPipeClient das Windows-Konto des verbindenden Gegenübers prüfen und ausleihen. Das steht im Gegensatz zu einem Localhost-TCP-Port, an den sich jeder verbinden kann, sodass Sie die Identität des Gegenübers mit einer eigenen Authentifizierung feststellen müssen. TCP-basierte Optionen sind dagegen vorteilhaft, wenn später Remote-Kommunikation wahrscheinlich ist, wenn Sie auch mit Prozessen auf anderen Betriebssystemen sprechen oder wenn Sie ein bestehendes Protokollvermögen wie gRPC wiederverwenden wollen. Diese Einschätzung ist auch im Artikel zur Wahl der Windows-Prozesskommunikation ausgearbeitet.
- Soll ich Byte-Modus oder Nachrichtenmodus verwenden?
- Wenn Sie „ein Schreibvorgang = eine Bedeutungseinheit“ behandeln wollen, ist der Nachrichtenmodus (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE) bequem. Der Empfänger kann in den Einheiten lesen, die der Sender geschrieben hat, sodass Sie die Grenzen nicht selbst verwalten müssen. Der Byte-Modus ist ein „ununterbrochener Bytestrom“ wie TCP, und Sie müssen das Framing selbst entwerfen — etwa ein Längenpräfix. Wenn Sie ein Protokoll tragen, das bereits Framing hat (zum Beispiel eine längenvorangestellte serialisierte Form), ist der Byte-Modus in Ordnung. Eine Einschränkung: Auch im Nachrichtenmodus erhalten Sie bei einem Empfangspuffer, der kleiner als die Nachricht ist, eine geteilte Lesung (ERROR_MORE_DATA), die Sie trotzdem behandeln müssen. Außerdem ist der Lesemodus eine Einstellung pro Handle, und CreateNamedPipe setzt ihn nur auf der Serverseite. Der Client muss nach CreateFile mit SetNamedPipeHandleState PIPE_READMODE_MESSAGE angeben. In .NET gibt der Server PipeTransmissionMode.Message an, und der Client setzt NamedPipeClientStream.ReadMode nach dem Verbinden auf Message.
- Wie baue ich einen Server, der gleichzeitig mit mehreren Clients spricht?
- Eine Named Pipe kann mehrere Instanzen unter demselben Namen anlegen, und eine Instanz bedient einen Client. Es gibt zwei Formen. Die eine ist ein synchroner Entwurf, der jedem Client einen Thread zuweist; die Umsetzung ist geradlinig, verbraucht aber einen Thread pro Client. Die andere ist asynchrones I/O mit FILE_FLAG_OVERLAPPED, bei dem eine kleine Zahl von Threads ConnectNamedPipe, ReadFile und WriteFile für jede Instanz bedient; Microsofts offizielles Beispiel zeigt auch eine Umsetzung, die mehrere Instanzen auf einem einzigen Thread verarbeitet. In .NET können Sie die asynchrone Form mit NamedPipeServerStream.WaitForConnectionAsync und async/await fast so geradlinig schreiben wie die synchrone. Ohne besonderen Grund empfehle ich für neue Umsetzungen die asynchrone .NET-Form.
- Was ist das Minimum, das ich für die Sicherheit von Named Pipes tun sollte?
- Vier Punkte. Erstens: Wenn Remote-Verbindungen nicht nötig sind, geben Sie PIPE_REJECT_REMOTE_CLIENTS an und lehnen Sie Verbindungen über das Netz ausdrücklich ab. Zweitens: Setzen Sie mit SECURITY_ATTRIBUTES eine passende ACL und engen Sie die Benutzer und Gruppen ein, die sich verbinden dürfen (die Standard-ACL ist für manchen Einsatz zu locker). Drittens: Geben Sie beim Anlegen der ersten Instanz FILE_FLAG_FIRST_PIPE_INSTANCE an, damit Sie „Namensentführung“ erkennen, bei der zuerst eine Pipe desselben Namens angelegt wird (dieses Flag nicht auf die zweite und spätere Instanzen setzen). Viertens: Ein Client, der nur will, dass der Server ihn identifiziert, sollte bei CreateFile SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION angeben, sodass ein gefälschter Server seine Rechte nicht ausleihen (impersonieren) kann. In einem Broker-Entwurf, in dem der Server unter den Rechten des Clients echten Zugriff ausführt, muss Identitätswechsel erlaubt sein; diese Einschränkung nutzen Sie also je nachdem, ob der Entwurf dem Server das Ausleihen von Rechten gestattet.
- Gibt es Fallstricke bei ImpersonateNamedPipeClient?
- Am wichtigsten ist die Prüfung des Rückgabewerts. Setzen Sie nach fehlgeschlagenem Identitätswechsel fort, laufen nachfolgende Operationen mit den eigenen (oft hohen) Rechten des Serverprozesses, und Operationen, die dem Client nicht erlaubt gewesen wären, gehen durch. Die offizielle Dokumentation sagt ausdrücklich, dass Sie bei Fehlschlag die Anfrage des Clients nicht ausführen dürfen. Sie müssen es außerdem erst nach dem Lesen von etwas aufrufen — Identitätswechsel geschieht im Kontext der „letzten von der Pipe gelesenen Nachricht“ — und nach getaner Arbeit zuverlässig mit RevertToSelf in den ursprünglichen Kontext zurückkehren. Die Mechanik rund um Identitätswechsel (Token, Impersonation-Stufen, SeImpersonatePrivilege) ist ausführlich im Artikel zu Identitätswechseltoken behandelt.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.