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
flowchart TB
accTitle: Structure de base d'un tube nommé
accDescr: 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 instance
s["Serveur"] --> i1["Instance 1"]
s --> i2["Instance 2"]
s --> i3["Instance 3"]
c1["Client A"] <--> i1
c2["Client B"] <--> i2
c3["Client C"] <--> i3
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 |
flowchart TB
accTitle: Différence entre le mode octet et le mode message
accDescr: En 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 quelle
bw["Mode octet : écrire AAA, BB, CCCC"] --> br["Réception comme le flux d'octets AAABBCCCC"]
br --> bf["Frontières (cadrage) conçues par vous-même"]
mw["Mode message : les mêmes trois écritures"] --> mr["Réception comme trois unités : AAA, BB, CCCC"]
mr --> mf["L'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.
sequenceDiagram
accTitle: Flux du traitement d'une requête avec emprunt d'identité
accDescr: 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écuter
participant C as Client
participant S as Serveur
C->>S: Envoyer une requête
S->>S: Lire la requête
S->>S: ImpersonateNamedPipeClient
Note over S: En cas d'échec, refuser sans exécuter
S->>S: Effectuer l'opération avec les privilèges du client
S->>S: Revenir au contexte d'origine avec RevertToSelf
S->>C: Ré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 |
flowchart TB
accTitle: Flux de nouvelle tentative de connexion du client
accDescr: Ouvrir 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 à communiquer
cf["Ouvrir avec CreateFile"] --> ok{"Résultat ?"}
ok -->|"Succès"| go["Commencer à communiquer"]
ok -->|"Le tube n'existe pas"| wait1["Attendre un peu (serveur non démarré)"]
ok -->|"ERROR_PIPE_BUSY"| wnp["Attendre une instance libre avec WaitNamedPipe"]
wait1 --> cf
wnp --> cf
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
- Comment choisir la communication inter-processus sous Windows ── Tableau de décision : tubes nommés / TCP / gRPC / mémoire partagée / COM
- Comment isoler concrètement, dans une application Windows, « uniquement les opérations nécessitant des privilèges administrateur »
- Bien gérer les jetons d’emprunt d’identité Windows — emprunt des privilèges par thread et retour sécurisé au contexte d’origine
- Les profondeurs de l’E/S Windows (partie 2) — E/S synchrones et asynchrones : ce que signifie vraiment OVERLAPPED
- Pièges de la mémoire partagée et bonnes pratiques concrètes
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.
- Développement d’applications Windows
- Conseil technique et revue de conception
- Investigation de bugs et analyse des causes
- Contact
Références
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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. ↩
-
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 associés
Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.
Pourquoi les arguments se cassent — Les règles des arguments de ligne de commande Windows
Windows passe à CreateProcess une seule chaîne que le destinataire découpe. Traite les règles de CommandLineToArgvW, du CRT et de .NET, A...
Ce qui reste après la mort du parent — garder les processus enfants dans un Job Object
Pourquoi les aides du SDK survivent à une IU tuée et gardent la caméra ou le port COM. Concevoir la durée de vie des processus enfants av...
API du pool de threads Win32 — de la concurrence sans créer de threads, avec CreateThreadpoolWork
Vous multipliez les CreateThread dans le code natif ? Cet article explique, à partir des sources primaires, l'API du pool de threads Win3...
DllMain et le verrou du chargeur — la vraie raison pour laquelle on vous dit de « ne rien faire dans l'initialisation d'une DLL »
Pourquoi il ne faut pas appeler LoadLibrary ni synchroniser avec d'autres threads depuis DllMain. À partir des sources primaires, cet art...
Réveils parasites — pourquoi une variable de condition se réveille « sans notification » et comment attendre correctement sous Windows
Le wait d'une variable de condition peut revenir sans notification (réveil parasite). L'article explique, d'après l'implémentation Window...
Sujets associés
Ces pages replacent le sujet dans un contexte plus large de services et de décisions.
Thèmes techniques Windows
Portail des sujets sur le développement Windows, l'analyse des incidents et la valorisation des actifs existants.
Services liés à ce sujet
Cet article est directement lié aux services suivants.
Développement d'applications Windows
Applications métier, intégration d'équipements et outils de communication, des besoins au développement.
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.