Named Pipes in der Praxis — Windows' Standard-IPC von Entwurf bis Sicherheit

· · 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 ImpersonateNamedPipeClient ist 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

Piperichtung und die Zugriffsspezifikation des ClientsEin 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 öffnenBidirektionalOutboundInboundRichtung vom Server?Lesen oder SchreibenNur lesend öffnenNur 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.

Grundstruktur einer Named PipeDer 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 InstanzServerInstanz 1Instanz 2Instanz 3Client AClient BClient C

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

Schleife für geteilte Lesung im NachrichtenmodusGelingt 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 TrennungErfolgERROR_MORE_DATAJeder andere FehlerLesen mit ReadFileErgebnis?Nachricht vollständigRest lesen und anhängenAls Trennung behandeln

Abbildung 3: Auch im Nachrichtenmodus brauchen Sie eine „Rest-lesen-Schleife“; ohne sie brechen nur große Nachrichten.

Unterschied zwischen Byte-Modus und NachrichtenmodusIm 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 anByte-Modus: AAA, BB, CCCCEmpfangen als Bytestrom AAABBCCCCFraming selbst entwerfenNachrichtenmodus: dieselben dreiEmpfangen als drei NachrichtenSchreibeinheiten 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.

Struktur eines Overlapped-ServersFertigstellungen 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 istInstanz 1 asynchronArray von EreignissenInstanz 2 asynchronInstanz 3 asynchronWarten mit WaitForMultipleObjectsFertige Instanz voranbringen

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.

Accept-Schleife eines asynchronen .NET-ServersDie 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 werdenServer-Stream anlegenWarten mit WaitForConnectionAsyncVerbindung kommt anClientbehandlung asynchron ablösen

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.

Ablauf der Anfragebehandlung mit IdentitätswechselDer 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 abServerClientServerClientBei Fehlschlag ohne Ausführung ablehnenAnfrage sendenAnfrage lesenImpersonateNamedPipeClientOperation mit ClientrechtenRevertToSelf stellt Kontext herErgebnis 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.

Vier Punkte, die die Pipe eines privilegierten Dienstes schützenDie 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)ClientseiteMinimale Impersonation-StufeServerseitePIPE_REJECT_REMOTE_CLIENTSVerbinder per ACL begrenzenFIRST_PIPE_INSTANCE(nur erste)Die Pipe als Rechtegrenze

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

Ablauf des erneuten Verbindungsversuchs auf der ClientseiteÖ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 KommunikationErfolgPipe fehltERROR_PIPE_BUSYÖffnen mit CreateFileErgebnis?Kommunikation beginnenKurz warten(Server fehlt)WaitNamedPipe (freie Instanz)

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 ImpersonateNamedPipeClient sind die Prüfung des Rückgabewerts und RevertToSelf die 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

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.

Quellen

  1. 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

  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

  3. 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

  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

  5. 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

  6. 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

  7. 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

  8. 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

  9. 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

  10. 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. 

  11. 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. 

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

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.

Zurück zum Blog