Tubes nommés en pratique — l'IPC standard de Windows, de la conception à la sécurité

· Mis à jour le: · · Windows, IPC, Développement Windows, C#, C++, Sécurité, Win32 API

Historique des révisions (première version, publiée le 22 Aug 2026)
Première publication
Citer cet article(DOI (archive enregistrée): 10.5281/zenodo.22176767)

Les DOI ci-dessous renvoient à des versions déjà archivées et peuvent différer du texte actuel. Pour citer le texte actuel, utilisez l’URL de cette page.

Go Komura (2026). Tubes nommés en pratique — l'IPC standard de Windows, de la conception à la sécurité. KomuraSoft LLC. https://comcomponent.com/fr/blog/windows-named-pipes-practical-guide/

DOI (archive enregistrée)
10.5281/zenodo.22176767
DOI (dernière version enregistrée)
10.5281/zenodo.22176768

« Je veux envoyer des commandes depuis un écran de paramètres vers un service résident. » « Je veux extraire dans un processus séparé uniquement le travail qui exige des privilèges administrateur. » Lorsque vous construisez ce type de communication inter-processus (IPC) sous Windows, la première chose à considérer est un tube nommé.

La raison pour laquelle c’est le premier candidat pour la communication au sein du même PC n’est pas seulement la facilité de lecture et d’écriture. Le grand avantage est que vous pouvez restreindre qui se connecte avec les ACL Windows et, au besoin, vérifier le compte Windows du pair et travailler avec ses privilèges. Créer un tube ne le rend pas sûr à lui seul ; la connexion et les droits doivent être configurés.123

Cet article organise la conception dans l’ordre forme de la connexion, frontières des messages, structure du serveur, sécurité, et comportement en cas d’échec. Il s’adresse aux développeurs qui écrivent des applications métier et des services sous Windows. Il reprend le « premier candidat pour l’IPC sur la même machine » de l’article sur le tableau de décision de la communication inter-processus et le pousse jusqu’aux décisions d’implémentation.

1. La conclusion d’abord : cinq décisions de conception

Avant de choisir une API, décidez du pair de communication, de l’unité de données, des connexions simultanées, des privilèges et du traitement des échecs.

Décision Règle de base Pour en savoir plus
Avec qui communiquer Pour des processus Windows sur le même PC, un tube nommé est le premier candidat. Si le déploiement distant, d’autres OS ou la réutilisation d’un protocole existant importent, envisagez une option basée sur TCP Chapitre 2
Ce qui compte comme une unité Si une écriture est traitée comme une unité, mode message. Si vous avez déjà un cadrage, mode octet Chapitre 3
Comment accepter plusieurs clients Prévoir plusieurs instances du même nom. Pour une nouvelle implémentation .NET, les E/S asynchrones et async/await sont le choix naturel Chapitre 4
Qui peut se connecter et emprunter des privilèges Concevoir comme un ensemble le refus du distant, l’ACL, la garantie de première instance et le niveau d’emprunt d’identité du client Chapitres 5 et 6
Que faire lorsque la communication échoue Intégrer dans le protocole les attentes de démarrage, la reconnexion, une limite de taille de message et la confirmation de réponse Chapitre 7

Avec un tube nommé, le contrôle des connexions par ACL et l’emprunt d’identité du client sont disponibles comme mécanismes Windows. Avec le TCP localhost, il faut concevoir une authentification séparée pour établir qui est le pair. En revanche, si vous voulez étendre plus tard à la communication distante, parler à d’autres OS, ou utiliser des actifs existants tels que gRPC, une option basée sur TCP est avantageuse.23

Le point le plus important est de ne jamais exécuter une requête dont l’emprunt d’identité a échoué. Ignorez l’échec et le traitement continue avec les privilèges propres du serveur plutôt qu’avec ceux du client. Si vous construisez un service privilégié, lisez les chapitres 5 et 6, pas seulement les exemples de code.4

2. Fonctionnement des connexions : un nom partagé, un canal par client

2.1 Séparer les rôles de création, de connexion et de lecture/écriture

Un tube nommé est un canal unidirectionnel ou bidirectionnel identifié par un nom tel que \\.\pipe\MyCompany.MyApp.Control. Le serveur et le client commencent par des API différentes.1

Étape Côté serveur Côté client
Préparer le canal Créer une instance avec CreateNamedPipe Utiliser le nom de tube préparé par le serveur
Se connecter Attendre un client avec ConnectNamedPipe Ouvrir le même nom avec CreateFile
Échanger des données Lire et écrire avec ReadFile / WriteFile Lire et écrire avec ReadFile / WriteFile

Après la connexion, les deux côtés le traitent sous la même forme que les E/S de fichiers. Au-delà de la spécification du nom, saisir les idées suivantes d’instance et de direction rend plus claires les connexions multiples et les erreurs de connexion.

2.2 Une instance gère un client

Plusieurs tubes du même nom peuvent être créés, et une instance devient le canal avec un client. Partager un nom ne signifie pas que tous les clients partagent un seul canal. Le premier CreateNamedPipe spécifie le nombre maximal d’instances. PIPE_UNLIMITED_INSTANCES est aussi disponible comme limite supérieure.15

Structure de base d'un tube nomméLe serveur crée plusieurs instances de tube du même nom et attend les connexions avec ConnectNamedPipe ; chaque client ouvre le nom avec CreateFile et dispose d'un canal bidirectionnel un-à-un avec une instanceServeurInstance 1Instance 2Instance 3Client AClient BClient C

Figure 1 : un exemple de tube bidirectionnel. En prévoyant plusieurs instances du même nom, le serveur communique un-à-un avec chacun de plusieurs clients.

2.3 La « direction » est nommée du point de vue du serveur

La spécification d’accès du client doit correspondre à la direction du tube créé par le serveur. Si elles divergent, CreateFile échoue.6

Direction créée par le serveur Comportement du serveur Accès spécifié par le client
Bidirectionnel Lit et écrit Peut être ouvert en lecture, en écriture, ou les deux
Sortant Écrit seulement Ouvrir en lecture seule
Entrant Lit seulement Ouvrir en écriture seule

Si toutes les instances sont en usage, le CreateFile du client renvoie ERROR_PIPE_BUSY. Attendez une instance libre avec WaitNamedPipe, puis rappelez CreateFile. C’est un échec différent de celui où le serveur n’a pas encore créé le tube, donc la section 7.1 le traite avec les courses de l’ordre de démarrage.6

2.4 Même pour un usage local, décider du traitement des connexions distantes

Un tube nommé peut aussi servir aux connexions distantes via SMB, sous la forme \\server\pipe\nom. Dans une conception moderne, cependant, il n’y a presque aucune raison d’utiliser activement ce chemin, et pour l’IPC locale l’important est de ne pas laisser ouvert un chemin que vous n’utilisez pas. Refusez-le explicitement avec PIPE_REJECT_REMOTE_CLIENTS à la section 5.1.17

3. Choisir un mode : décider jusqu’où va une unité

3.1 Mode message pour requête/réponse, mode octet si les frontières existent déjà

La différence entre les modes de transfert est de savoir si le tube conserve les frontières des écritures.5

Mode Ce que voit le récepteur Convient à
Mode octet (PIPE_TYPE_BYTE) Un flux d’octets ininterrompu, comme TCP Des données qui portent leur propre cadrage, tel qu’un préfixe de longueur, ou des transferts en flux
Mode message (PIPE_TYPE_MESSAGE avec lecture en mode message) Des unités où une écriture est un message Des requêtes et réponses une par une
Différence entre le mode octet et le mode messageEn mode octet, trois écritures deviennent un flux d'octets ininterrompu que le récepteur doit découper lui-même, alors qu'en mode message l'unité de chaque écriture est conservée et parvient au récepteur telle quelleMode octet : écrire AAA, BB, CCCCRéception comme le flux d'octets AAABBCCCCFrontières (cadrage) conçues par vous-mêmeMode message : les mêmes trois écrituresRéception comme trois unités : AAA, BB, CCCCL'unité de chaque écriture est conservée

Figure 2 : en mode octet, le récepteur gère les frontières. Le mode message peut conserver l’unité de chaque écriture, mais une seule lecture ne reçoit pas nécessairement le message entier.

Si vous n’avez pas encore vos propres frontières et voulez traiter une écriture comme une requête, le mode message est commode. Si vous utilisez déjà quelque chose comme une forme sérialisée préfixée en longueur, le mode octet convient. En .NET, PipeTransmissionMode.Message est la spécification du type message.8

Pour un tube bidirectionnel de type message, il existe aussi TransactNamedPipe, qui envoie une requête et reçoit la réponse en un seul appel.9

3.2 Le type message et le mode de lecture sont des réglages distincts

Le mode de lecture se règle par handle. Spécifier PIPE_READMODE_MESSAGE dans CreateNamedPipe est un réglage côté serveur. Un handle que le client a ouvert avec CreateFile est par défaut en lecture d’octets.6

Implémentation Côté serveur Côté client
Win32 Spécifier PIPE_TYPE_MESSAGE et PIPE_READMODE_MESSAGE Après la connexion, spécifier PIPE_READMODE_MESSAGE avec SetNamedPipeHandleState
.NET Spécifier PipeTransmissionMode.Message Après la connexion, régler NamedPipeClientStream.ReadMode sur Message

Le point est de ne pas supposer que rendre seulement le serveur de type message permet au client de lire dans les mêmes unités.

3.3 Même en mode message, il faut relire le reste

Conserver les frontières de message et faire tenir le message entier dans le tampon de réception sont deux choses distinctes. Si le tampon est petit, ReadFile renvoie ERROR_MORE_DATA et le message est fractionné. Il faut une boucle qui conserve la partie déjà reçue, lit le reste et les concatène.56

Résultat de lecture Traitement
Succès Traiter le message comme complet
ERROR_MORE_DATA Il en reste, donc relire le reste et concaténer
Toute autre erreur Ne pas continuer le traitement de la requête ; traiter comme un échec de communication tel qu’une déconnexion

Si les petites requêtes passent mais que seules les grandes se cassent, vérifiez cette logique de relecture. De plus, une concaténation illimitée n’est pas acceptable. Décidez d’une taille maximale pour un message comme partie du protocole, et adoptez une politique de déconnexion lorsque la limite est dépassée. Cela évite le gaspillage de mémoire causé par d’énormes messages.

4. Structure du serveur : séparer l’acceptation et le traitement des clients

4.1 Comparer la conception à threads synchrones et la conception overlapped

L’opération de base du serveur est le cycle créer une instance, attendre une connexion, lire et écrire, déconnecter et passer à la suivante. Pour accepter plusieurs clients à la fois, faites avancer ce flux sur plusieurs instances.

Conception Mécanisme Avantages et précautions
Conception à threads synchrones Assigner un thread par instance et traiter en E/S synchrones Le code est direct. Elle consomme toutefois un thread par client, et il faut un moyen de sortir des E/S bloquantes à l’arrêt
Conception overlapped (asynchrone) Créer avec FILE_FLAG_OVERLAPPED et émettre connexion, lecture et écriture de façon asynchrone Un petit nombre de threads peut traiter les achèvements de plusieurs instances

L’exemple officiel de Microsoft place l’événement de chaque instance dans un tableau, attend avec WaitForMultipleObjects et traite plusieurs connexions sur un seul thread. Il découple le nombre de clients du nombre de threads. À plus grande échelle, vous pouvez aussi rattacher un IOCP ou les E/S du pool de threads.10

Pour le fonctionnement des E/S asynchrones elles-mêmes, voir l’article sur les E/S synchrones et asynchrones.

4.2 Pour une nouvelle implémentation .NET, async/await est le choix naturel

En .NET, vous pouvez utiliser WaitForConnectionAsync / ReadAsync / WriteAsync de NamedPipeServerStream avec async/await. Sauf raison particulière, je recommande cette conception asynchrone pour les nouvelles implémentations. Lorsque la boucle d’acceptation reçoit une connexion, elle la remet au traitement par client et revient à l’acceptation de l’instance suivante.8

Voici le squelette de cette boucle d’acceptation. Comme exemple d’usage entre processus du même utilisateur, il spécifie PipeOptions.Asynchronous et PipeOptions.CurrentUserOnly. Sous Windows, CurrentUserOnly restreint les connexions au même utilisateur et au même niveau d’élévation. Ce n’est pas un exemple de connexion d’un service et d’une UI sous des comptes différents. Ce cas exige la conception d’ACL de la section 5.2.11

// C# : squelette d'un serveur qui accepte plusieurs 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();        // le libérer soi-même si l'on sort avant la connexion
        throw;
    }
    _ = HandleClientAsync(server, token);   // après la connexion, la propriété passe au gestionnaire
}

Si une exception se produit avant une connexion, le côté acceptation libère le flux ; après une connexion, HandleClientAsync reprend la propriété du flux. Cette propriété inclut non seulement la lecture et l’écriture, mais aussi la libération à la fin.

Ce code est un squelette qui omet le protocole de communication et le corps du gestionnaire. En production, gérez les exceptions et l’achèvement des tâches détachées, et combinez-le avec la relecture et la limite de taille du chapitre 3, la sécurité des chapitres 5 et 6, et le traitement des déconnexions et la confirmation de réponse du chapitre 7. CurrentUserOnly est une option commode pour limiter les connectés sans écrire une ACL soi-même, mais à elle seule, elle ne complète pas une conception pour un service privilégié.

5. Sécurité : trois points côté serveur et un côté client

Les avantages de sécurité des tubes nommés ne s’obtiennent que lorsqu’ils sont configurés correctement. En particulier, dans la conception de courtier « un service à privilèges administrateur plus une application UI à faibles privilèges », le tube est la frontière des privilèges.

5.1 Côté serveur : refuser les connexions distantes inutiles

Si vous utilisez le tube comme IPC locale, spécifiez PIPE_REJECT_REMOTE_CLIENTS dans CreateNamedPipe. Les clients distants sont refusés automatiquement, ce qui ferme le chemin où un tube destiné aux applications du même PC peut aussi s’ouvrir depuis le réseau.7

5.2 Côté serveur : restreindre les connectés et les droits avec une ACL

Passez un descripteur de sécurité dans SECURITY_ATTRIBUTES et rendez explicites les utilisateurs et groupes qui peuvent se connecter. L’ACL par défaut n’est pas nécessairement assez stricte pour votre usage.2

Ce qui est facile à manquer ici, c’est le droit d’écriture que vous accordez aux clients. Si l’ACL accorde l’écriture générique (GENERIC_WRITE / FILE_GENERIC_WRITE), elle inclut le droit équivalent à FILE_CREATE_PIPE_INSTANCE. Un client autorisé peut alors créer lui-même une instance serveur du même nom et intercepter les connexions suivantes.2

Accordez la lecture et l’écriture comme les droits individuels nécessaires, et ne remettez pas aux clients le droit de créer des instances. La conception d’ACL couvre non seulement « qui est autorisé » mais aussi « ce qui est autorisé ».

5.3 Côté serveur : vérifier que la première instance n’a pas été prise avant vous

Si un processus malveillant crée d’abord un tube du même nom, les clients se connectent à ce faux serveur. Le serveur spécifie FILE_FLAG_FIRST_PIPE_INSTANCE uniquement lors de la création de la première instance, garantissant qu’il est le premier. Si cela échoue, soupçonnez une prise anticipée et arrêtez.5

Ce drapeau ne concerne que la première instance, celle qui réserve le nom. Le mettre aussi sur la deuxième instance et les suivantes fait échouer la création. Dans une boucle d’acceptation pour plusieurs clients, ne répétez pas la même spécification à chaque création.5

Notez toutefois que c’est un mécanisme pour que le serveur légitime détecte une anomalie au démarrage, pas une authentification de la cible de connexion par le client. Si le service légitime est absent, un attaquant a encore de la place pour créer d’abord un tube du même nom et attendre.

5.4 Côté client : décider jusqu’où le serveur peut emprunter vos privilèges

Comme défense contre un faux serveur, le client maintient le niveau d’emprunt d’identité au minimum nécessaire. Si la conception n’autorise que la vérification d’identité, spécifiez SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION dans CreateFile. Le serveur peut alors vérifier le compte mais ne peut plus emprunter ses privilèges pour effectuer un accès réel.3

Ce que vous voulez que le serveur fasse Politique côté client
Seulement vérifier l’identité du connecté Restreindre au niveau identification (SECURITY_IDENTIFICATION)
Effectuer un accès réel, tel qu’ouvrir des fichiers avec les privilèges du client Le niveau d’emprunt (SECURITY_IMPERSONATION) doit être autorisé. Associez-le à la vérification que vous êtes connecté au serveur légitime

Au niveau identification, le traitement de courtier qui effectue un accès réel avec les privilèges du client n’est pas possible. À l’inverse, autoriser l’emprunt d’identité sans vérifier la cible de connexion risque de voir un faux serveur emprunter vos privilèges.

Le principe est de n’autoriser l’emprunt d’identité nécessaire que lorsque vous pouvez confirmer la cible de connexion légitime, par un démarrage de service garanti ou une authentification mutuelle après la connexion. Ne supposez pas que la vérification côté client est inutile parce que la détection de prise anticipée de la section 5.3 existe.

6. Utiliser l’emprunt d’identité : ne pas exécuter une requête dont l’emprunt a échoué

6.1 Emprunter l’identité après avoir lu la requête, pas seulement après la connexion

L’API pour que le serveur vérifie l’identité du client et traite avec ses privilèges est ImpersonateNamedPipeClient. Son objet est le contexte de sécurité de l’émetteur du dernier message lu depuis ce tube. Lisez d’abord la requête, puis appelez-la.4

L’emprunt d’identité s’applique au thread appelant. Si vous ouvrez un fichier dans cet état, l’autorisation d’accès se juge d’après les privilèges du client. Même pour un service privilégié, c’est le mécanisme pour « exécuter l’opération demandée avec les privilèges du demandeur ».3

6.2 Faire de la lecture, de la confirmation de succès et du retour un seul ensemble

L’ordre requis est lire la requête, tenter l’emprunt d’identité, confirmer le succès, opérer avec les privilèges du client, et revenir au contexte d’origine.

Flux du traitement d'une requête avec emprunt d'identitéLe serveur lit une requête depuis le tube, confirme le succès de ImpersonateNamedPipeClient, puis effectue l'opération avec les privilèges du client et revient à son propre contexte avec RevertToSelf. Si l'emprunt d'identité échoue, il refuse la requête sans l'exécuterServeurClientServeurClientEn cas d'échec, refuser sans exécuterEnvoyer une requêteLire la requêteImpersonateNamedPipeClientEffectuer l'opération avec les privilèges du clientRevenir au contexte d'origine avec RevertToSelfRépondre avec le résultat

Figure 3 : si l’emprunt d’identité échoue, n’exécutez pas la requête. Même en cas de succès, la séquence n’est terminée que lorsque RevertToSelf rétablit le contexte d’origine après le travail.

N’avancez pas au traitement de la requête sans vérifier la valeur de retour de ImpersonateNamedPipeClient. En cas d’échec, le thread n’est pas passé aux privilèges du client, et les privilèges propres du processus serveur sont utilisés. Si le serveur s’exécute sous un compte privilégié, il laisse passer des opérations que le client n’a pas le droit d’effectuer. La documentation de Microsoft indique aussi explicitement qu’en cas d’échec, la requête du client ne doit pas être exécutée.4

Lorsque le travail est terminé, revenez de façon fiable au contexte d’origine avec RevertToSelf. Ayez à la fois la vérification de succès et le nettoyage. Les détails tels que les jetons, les niveaux d’emprunt d’identité et SeImpersonatePrivilege sont couverts dans l’article sur les jetons d’emprunt d’identité Windows.

7. Pièges pratiques : concevoir en supposant que la communication se coupe

7.1 Traiter l’attente du démarrage et l’attente d’une instance libre comme des échecs distincts

Lorsque vous ne pouvez pas vous connecter, distinguez le cas où le serveur n’a pas encore créé le tube de celui où toutes les instances créées sont en usage.69

État Action du client
Le tube n’existe pas Prévoir que le serveur est encore en train de démarrer ; attendre un peu et réessayer
ERROR_PIPE_BUSY Attendre une instance libre avec WaitNamedPipe et réessayer CreateFile
Connecté Passer à la communication. Au besoin, régler aussi le mode de lecture de la section 3.2
Flux de nouvelle tentative de connexion du clientOuvrir le tube avec CreateFile ; s'il n'existe pas, attendre un peu et réessayer ; si toutes les instances sont en usage et qu'ERROR_PIPE_BUSY est renvoyé, attendre une instance libre avec WaitNamedPipe puis réessayer ; en cas de succès, commencer à communiquerSuccèsLe tube n'existe pasERROR_PIPE_BUSYOuvrir avec CreateFileRésultat ?Commencer à communiquerAttendre un peu (serveur non démarré)Attendre une instance libre avec WaitNamedPipe

Figure 4 : « n’existe pas » et « complet » sont des états différents. Dans les deux cas, attendez selon l’état, puis réessayez la connexion.

Côté serveur, le principe est de commencer à attendre avec ConnectNamedPipe avant que le client ne démarre. Des courses d’ordre de démarrage peuvent encore se produire, donc intégrez aussi des nouvelles tentatives côté client.9

7.2 Faire de la déconnexion un chemin de traitement normal, pas une urgence

Lorsque le processus pair se termine, les lectures et écritures échouent avec ERROR_BROKEN_PIPE et assimilés. Traitez cela comme le quotidien de la communication.

Lorsque le serveur détecte une déconnexion, il détache l’instance avec DisconnectNamedPipe et se prépare à la connexion suivante. Le client se reconnecte. L’idée de « reconnexion idempotente », qui ne corrompt pas l’état quelle que soit la fréquence de répétition, expliquée dans l’article sur la reprise de veille, est efficace ici aussi.

7.3 Les grands messages exigent à la fois la relecture et une limite

Le traitement de ERROR_MORE_DATA à la section 3.3 est la logique pour lire un message en morceaux. La taille maximale de message, elle, est une règle pour limiter la quantité de données acceptée. Ne confondez pas les deux.

Si un pair malveillant ou bogué envoie un énorme message, une implémentation qui relit le reste sans limite gaspille de la mémoire. Adoptez une politique de définir une limite dans le protocole et de déconnecter lorsqu’elle est dépassée.

7.4 Distinguer le succès de l’écriture de la fin du traitement chez le pair

Le succès de WriteFile ne signifie pas que l’application du pair a traité les données. Pour les opérations où vous devez savoir qu’elles ont réellement été exécutées, confirmez avec un message de réponse.

De plus, pour que chaque réponse puisse être appariée à la requête à laquelle elle répond, incluez la relation entre requêtes et réponses dans le protocole. Séparer « cela a été envoyé » de « le traitement est terminé » est ce qui mène à la confirmation d’achèvement au sens métier.

8. Résumé : décider séparément le canal, les données et les privilèges

Un tube nommé combine la facilité d’emploi des E/S de fichiers avec le modèle de sécurité Windows des ACL et de l’emprunt d’identité, et c’est le premier candidat pour l’IPC sur la même machine. Créer un canal, toutefois, est une affaire différente de créer un protocole sûr et difficile à casser.

Dans la conception de la connexion et des données, partez du fait qu’une instance gère un client. Choisissez le mode message pour requête/réponse et le mode octet si vous avez déjà un cadrage, et réglez aussi le mode de lecture message côté client. Le traitement des lectures fractionnées et une taille maximale sont également nécessaires. Pour une nouvelle implémentation .NET qui accepte plusieurs connexions, les E/S asynchrones et async/await sont le choix naturel.

Dans la conception des privilèges, traitez comme un ensemble le refus du distant, une ACL explicite, la garantie de première instance et la minimisation du niveau d’emprunt d’identité côté client. Si vous utilisez l’emprunt d’identité, appelez-le après avoir lu la requête, n’exécutez pas en cas d’échec, et revenez avec RevertToSelf après le travail.

Enfin, écrivez l’ordre de démarrage, la déconnexion, la limite de taille de message et la confirmation de réponse comme votre propre protocole. Le point pratique est de décider, avant de commencer à implémenter, d’une forme qui peut se rétablir lorsque la communication se coupe sans dépasser les privilèges du pair, plutôt que de supposer « c’est sur le même PC, donc cela n’échouera pas ».

Articles connexes

Domaines de conseil associés

KomuraSoft LLC prend en charge la conception et l’implémentation de systèmes qui impliquent une communication inter-processus, telles que la séparation d’un service et de son application UI et l’isolation des privilèges administrateur ; le remplacement d’une IPC existante (mémoire partagée, sockets maison, COM, etc.) par des tubes nommés ; et les revues de sécurité de la communication par tube d’un service privilégié. N’hésitez pas à nous contacter même si vous voulez seulement discuter d’une conception de protocole.

Références

  1. Microsoft Learn, Named Pipes. Sur le fait qu’un tube nommé est un canal unidirectionnel ou bidirectionnel entre un serveur de tube et un ou plusieurs clients de tube ; sur le fait que toutes les instances partagent le même nom tout en ayant des tampons et des handles indépendants ; et sur l’utilisation depuis des processus locaux et distants. ↩ ↩2 ↩3 ↩4

  2. Microsoft Learn, Named Pipe Security and Access Rights. Sur la composition des droits d’accès des tubes nommés ; sur le fait que GENERIC_WRITE inclut FILE_CREATE_PIPE_INSTANCE, de sorte qu’accorder l’écriture générique à un client permet aussi de créer une instance serveur ; et sur le fait que la lecture et l’écriture de données doivent être accordées comme droits d’accès individuels. ↩ ↩2 ↩3 ↩4

  3. Microsoft Learn, Impersonating a Named Pipe Client. Sur le fait que l’emprunt d’identité permet au thread serveur d’opérer dans les privilèges du client ; sur le fait que le niveau d’emprunt par défaut est SecurityImpersonation ; et sur le fait que le client peut contrôler le niveau d’emprunt avec le drapeau SECURITY_SQOS_PRESENT au moment de CreateFile (SECURITY_IDENTIFICATION n’autorise que l’identification). ↩ ↩2 ↩3 ↩4

  4. Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). Sur le fait qu’un thread côté serveur commence l’emprunt d’identité dans le contexte de sécurité du client du dernier message lu depuis le tube ; sur le retour avec RevertToSelf après achèvement ; et sur le fait que continuer après un échec de l’emprunt d’identité provoque une exécution dans le contexte propre (privilégié) du processus serveur, de sorte que la valeur de retour doit toujours être vérifiée et qu’en cas d’échec la requête du client ne doit pas être exécutée. ↩ ↩2 ↩3

  5. Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). Sur la direction du tube (entrant, sortant, bidirectionnel), le type octet et le type message (PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE) et le mode de lecture (PIPE_READMODE_MESSAGE), le nombre maximal d’instances (PIPE_UNLIMITED_INSTANCES), le mode asynchrone via FILE_FLAG_OVERLAPPED, la garantie de première instance via FILE_FLAG_FIRST_PIPE_INSTANCE, et le délai d’attente par défaut utilisé par WaitNamedPipe. ↩ ↩2 ↩3 ↩4 ↩5

  6. Microsoft Learn, Named Pipe Client. Sur le fait que le client ouvre le tube avec CreateFile ; sur ERROR_PIPE_BUSY lorsque toutes les instances sont en usage et l’attente d’une libre avec WaitNamedPipe ; et sur le fait que le handle ouvert est par défaut en lecture d’octets, bloquant et non overlapped, SetNamedPipeHandleState pouvant le passer en mode de lecture message. ↩ ↩2 ↩3 ↩4 ↩5

  7. Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). Sur les deux modes de clients distants, PIPE_ACCEPT_REMOTE_CLIENTS (accepter les connexions distantes et les vérifier contre le descripteur de sécurité) et PIPE_REJECT_REMOTE_CLIENTS (refuser automatiquement les connexions des clients distants). ↩ ↩2

  8. Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). Sur la connexion et la lecture/écriture avec NamedPipeServerStream / NamedPipeClientStream, le transfert par unités de message via PipeTransmissionMode.Message, et le traitement de plusieurs clients avec des méthodes asynchrones. ↩ ↩2

  9. Microsoft Learn, Named Pipe Operations. Sur les opérations overlapped via ReadFileEx / WriteFileEx, une lecture non consommatrice via PeekNamedPipe, TransactNamedPipe envoyant une requête et recevant la réponse en un appel sur un tube bidirectionnel de type message, et une lecture bloquante avant le démarrage du client pouvant causer une course. ↩ ↩2 ↩3

  10. Microsoft Learn, Named Pipe Server Using Overlapped I/O. Sur l’exemple officiel d’un serveur à un seul thread gérant des connexions simultanées avec plusieurs clients par des opérations overlapped. Sur la structure qui attend la structure OVERLAPPED et l’événement de chaque instance avec WaitForMultipleObjects et fait avancer la machine d’états de l’instance achevée, et sur la confirmation de l’achèvement des E/S en attente avec GetOverlappedResult. ↩

  11. Microsoft Learn, PipeOptions Enum (System.IO.Pipes). Sur l’activation des E/S asynchrones avec Asynchronous, et sur le fait que CurrentUserOnly peut n’autoriser les connexions qu’avec des processus du même utilisateur (et du même niveau d’élévation). ↩

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Comment choisir entre un tube nommé et TCP (un socket localhost) ?
Pour la communication inter-processus sur la même machine, un tube nommé est le premier candidat. La raison est le modèle de sécurité. Un tube contrôle qui a le droit de se connecter au niveau de l'OS avec un descripteur de sécurité Windows (ACL), et le serveur peut inspecter et emprunter le compte Windows du pair connecté avec ImpersonateNamedPipeClient. Cela contraste avec un port TCP localhost, auquel n'importe qui peut se connecter, de sorte qu'il faut établir qui est le pair avec sa propre authentification. En revanche, une option basée sur TCP est avantageuse lorsque la communication distante est susceptible de se développer plus tard, lorsque l'on parle aussi à des processus sur d'autres OS, ou lorsque l'on veut utiliser un protocole existant tel que gRPC. Ce jugement est également exposé dans l'article sur le tableau de décision de la communication inter-processus.
Faut-il utiliser le mode octet ou le mode message ?
Si vous voulez traiter une écriture comme une unité de sens, le mode message (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE) est commode. Le récepteur peut lire dans les unités que l'émetteur a écrites, donc vous n'avez pas à gérer les frontières vous-même. Le mode octet est un flux d'octets ininterrompu comme TCP, et vous devez concevoir le cadrage vous-même — un préfixe de longueur, par exemple. Si vous transportez un protocole qui a déjà un cadrage (par exemple une forme sérialisée préfixée en longueur), le mode octet convient. Un point d'attention : même en mode message, une lecture fractionnée (ERROR_MORE_DATA) se produit si le tampon de réception est plus petit que le message, donc il faut tout de même la traiter. De plus, le mode de lecture est un réglage par handle, et CreateNamedPipe ne le fixe que côté serveur. Le client doit spécifier PIPE_READMODE_MESSAGE avec SetNamedPipeHandleState après CreateFile. En .NET, le serveur spécifie PipeTransmissionMode.Message, et le client règle NamedPipeClientStream.ReadMode sur Message après la connexion.
Comment construire un serveur qui parle à plusieurs clients à la fois ?
Un tube nommé peut créer plusieurs instances sous le même nom, et une instance gère un client. Il y a deux formes. L'une est une conception synchrone qui assigne un thread par client ; l'implémentation est directe, mais elle consomme un thread par client. L'autre consiste à utiliser des E/S asynchrones avec FILE_FLAG_OVERLAPPED et à faire gérer ConnectNamedPipe, ReadFile et WriteFile de chaque instance par un petit nombre de threads ; l'exemple officiel de Microsoft montre aussi une implémentation qui traite plusieurs instances sur un seul thread. En .NET, NamedPipeServerStream.WaitForConnectionAsync et async/await permettent d'écrire la forme asynchrone avec presque la même simplicité que la forme synchrone. Sauf raison particulière, la forme asynchrone .NET est celle que je recommande pour les nouvelles implémentations.
Quel est le minimum à faire pour la sécurité des tubes nommés ?
Quatre points. Premier : si les connexions distantes ne sont pas nécessaires, spécifier PIPE_REJECT_REMOTE_CLIENTS et refuser explicitement les connexions via le réseau. Deuxième : poser une ACL appropriée avec SECURITY_ATTRIBUTES et restreindre les utilisateurs et groupes qui peuvent se connecter (l'ACL par défaut est trop lâche pour certains usages). Troisième : spécifier FILE_FLAG_FIRST_PIPE_INSTANCE lors de la création de la première instance, afin de détecter le détournement de nom dans lequel un tube du même nom est créé en premier (ne pas mettre ce drapeau sur la deuxième instance et les suivantes). Quatrième : un client qui veut seulement que le serveur l'identifie doit spécifier SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION sur CreateFile, afin qu'un faux serveur ne puisse pas emprunter (usurper) ses privilèges. Dans une conception de courtier où le serveur effectue un accès réel sous les privilèges du client, l'emprunt d'identité doit être autorisé, donc on applique ou non cette restriction selon que la conception laisse le serveur emprunter les privilèges.
Y a-t-il des précautions à prendre avec ImpersonateNamedPipeClient ?
La plus importante est de vérifier la valeur de retour. Si vous continuez après un échec de l'emprunt d'identité, les opérations suivantes s'exécutent avec les privilèges propres (souvent élevés) du processus serveur, et des opérations qui n'auraient pas dû être permises au client passent. La documentation officielle indique aussi explicitement qu'en cas d'échec, il ne faut pas exécuter la requête du client. Il faut aussi l'appeler seulement après avoir lu quelque chose — l'emprunt d'identité s'effectue dans le contexte du dernier message lu depuis le tube — et revenir de façon fiable au contexte d'origine avec RevertToSelf une fois le travail terminé. Les mécanismes autour de l'emprunt d'identité (jetons, niveaux d'emprunt, SeImpersonatePrivilege) sont couverts en détail dans l'article sur les jetons d'emprunt d'identité.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog