«Voglio che un servizio residente e un’interfaccia di impostazioni si scambino comandi.» «Voglio isolare in un processo distinto solo il lavoro che richiede privilegi di amministratore.» «Voglio che gli strumenti sullo stesso PC si passino i dati.» — Quando su Windows diventa necessaria questo tipo di comunicazione tra processi (IPC), lo standard da considerare per primo è una named pipe.
L’articolo sulla scelta della comunicazione tra processi in Windows ha posizionato le named pipe come «la prima candidata per l’IPC sulla stessa macchina». Questo articolo è il trattamento dettagliato. Perché sono la prima candidata, come si scelgono modalità e forme di server, e cosa va protetto quando le usa un servizio privilegiato — rivolto agli sviluppatori che scrivono applicazioni aziendali e servizi su Windows, organizza il materiale per quei giudizi di progetto a partire dalle fonti primarie.
1. Prima di tutto, la conclusione
- Una named pipe è un canale bidirezionale tra processi con uno spazio dei nomi della forma
\\.\pipe\name. Si possono creare più istanze sotto lo stesso nome e accettare più client contemporaneamente.1 - La ragione per cui sono la prima candidata per l’IPC sulla stessa macchina è il modello di sicurezza. Si può controllare chi si connette con un ACL, e il server può ispezionare e prendere in prestito (impersonare) l’account Windows del client. Il TCP localhost non ha nessuna delle due cose.2
- Se si vuole trattare «una scrittura = un messaggio», usare la modalità messaggio; se si ha già un framing proprio, usare la modalità byte. Anche in modalità messaggio occorre gestire le letture spezzate quando il buffer è corto (ERROR_MORE_DATA).3
- Gestire più client con «più istanze + I/O overlapped» o con «async/await di .NET». L’esempio ufficiale mostra una forma che elabora più istanze su un solo thread.4
- Il minimo di sicurezza sono quattro punti: rifiutare il remoto (PIPE_REJECT_REMOTE_CLIENTS), rendere esplicito l’ACL, rilevare il sequestro con FILE_FLAG_FIRST_PIPE_INSTANCE e minimizzare il livello di impersonation lato client.56
- Per
ImpersonateNamedPipeClient, controllare il valore di ritorno è il salvavita. Se si ignora un fallimento, l’elaborazione prosegue con i privilegi del server.6
2. Cos’è una named pipe — Spazio dei nomi, istanze e come funzionano le connessioni
Una named pipe è un canale identificato da un nome come \\.\pipe\MyCompany.MyApp.Control. Il server la crea con CreateNamedPipe e il client apre lo stesso nome con CreateFile. Una volta aperta, entrambi i lati leggono e scrivono con ReadFile / WriteFile: il punto distintivo è che si può usare con la stessa forma dell’I/O su file.1
Il concetto importante è l’istanza. Si possono creare più istanze di una pipe con lo stesso nome, e un’istanza è un canale con un client. La prima chiamata a CreateNamedPipe decide il numero massimo di istanze (o illimitato).3
La connessione lato client ha una ricetta standard. Quando tutte le istanze sono in uso, CreateFile fallisce con ERROR_PIPE_BUSY, quindi si attende una libera con WaitNamedPipe e poi si ritenta. Inoltre, l’accesso specificato all’apertura deve coincidere con la direzione creata dal server: una pipe bidirezionale può essere aperta specificando lettura o scrittura, ma una pipe in uscita che il server scrive soltanto deve essere aperta in sola lettura, e una pipe in ingresso che il server legge soltanto deve essere aperta in sola scrittura, altrimenti CreateFile fallisce.7
flowchart TB
accTitle: Direzione della pipe e accesso specificato dal client
accDescr: Un client può aprire una pipe bidirezionale specificando lettura o scrittura, ma deve aprire in sola lettura una pipe in uscita che il server scrive soltanto, e in sola scrittura una pipe in ingresso che il server legge soltanto
q{"Direzione creata dal server?"} -->|"Bidirezionale"| dc["Lettura o scrittura"]
q -->|"In uscita"| oc["Apri in sola lettura"]
q -->|"In ingresso"| ic["Apri in sola scrittura"]
Figura 1: Un disallineamento tra direzione e specifica di accesso diventa un fallimento di CreateFile. In caso di errore di connessione, controllare prima qui.
flowchart TB
accTitle: Struttura di base di una named pipe
accDescr: Il server crea più istanze di pipe dello stesso nome e attende una connessione con ConnectNamedPipe; ogni client apre il nome con CreateFile e ha un canale bidirezionale uno-a-uno con un'istanza
s["Server"] --> i1["Istanza 1"]
s --> i2["Istanza 2"]
s --> i3["Istanza 3"]
c1["Client A"] <--> i1
c2["Client B"] <--> i2
c3["Client C"] <--> i3
Figura 2: Tenendo più istanze dello stesso nome, un server può parlare uno-a-uno con più client contemporaneamente.
Le named pipe si possono aprire anche da remoto via SMB (\\server\pipe\name), ma in un progetto moderno non c’è quasi motivo di usarlo attivamente; il punto è piuttosto non lasciarlo aperto quando non lo si usa (capitolo 5).
3. Modalità byte e modalità messaggio
Una pipe ha due modalità di trasferimento.3
- Modalità byte (PIPE_TYPE_BYTE): un «flusso di byte ininterrotto» come TCP. Si decide da soli dove finisce un messaggio (si progetta un framing come un prefisso di lunghezza).
- Modalità messaggio (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE): una scrittura viene trattata come un messaggio, e il lettore la riceve in quella unità. È più semplice per gli scambi richiesta/risposta.
La modalità messaggio ha anche un compagno comodo, TransactNamedPipe, che invia una richiesta e riceve la risposta in una sola chiamata.8 C’è però una trappola. Se il buffer di ricezione è più piccolo dell’intero messaggio, la lettura restituisce ERROR_MORE_DATA e diventa una lettura spezzata. Non si presupponga che la modalità messaggio significhi «un Read porta sempre il tutto»; occorre comunque scrivere un ciclo che legge il resto. Si noti che la modalità di lettura è un’impostazione per handle, e CreateNamedPipe la decide solo lato server. Il client la specifica con SetNamedPipeHandleState dopo CreateFile (in .NET, ReadMode dopo la connessione).7
flowchart TB
accTitle: Ciclo di lettura spezzata in modalità messaggio
accDescr: Se ReadFile riesce il messaggio è completo; se restituisce ERROR_MORE_DATA si legge il resto che non è entrato nel buffer e lo si concatena; qualsiasi altro errore si tratta come una disconnessione
read["Leggi con ReadFile"] --> r{"Esito?"}
r -->|"Successo"| done["Messaggio completo"]
r -->|"ERROR_MORE_DATA"| more["Leggi il resto e unisci"]
more --> read
r -->|"Altro errore"| dis["Tratta come disconnessione"]
Figura 3: Anche in modalità messaggio serve un «ciclo che legge il resto»; senza di esso si rompono solo i messaggi grandi.
flowchart TB
accTitle: Differenza tra modalità byte e modalità messaggio
accDescr: In modalità byte tre scritture diventano un flusso di byte ininterrotto e il ricevente deve spezzarlo; in modalità messaggio l'unità di ogni scrittura è preservata e arriva al ricevente così com'è
bw["Modo byte: AAA, BB, CCCC"] --> br["Ricevuto come flusso AAABBCCCC"]
br --> bf["Il framing lo si progetta"]
mw["Modo messaggio: le 3 stesse"] --> mr["Ricevuto in 3 messaggi: AAA, BB, CCCC"]
mr --> mf["Le unità di scrittura restano"]
Figura 4: La modalità messaggio preserva «l’unità di una scrittura» e la consegna. Il progetto del framing diventa superfluo; basta non dimenticare le letture spezzate.
La regola pratica per scegliere è semplice. Se lo scambio ha la forma «richiesta e risposta», modalità messaggio. Se si trasporta una forma che ha già un framing incorporato (dati serializzati con prefisso di lunghezza o un trasferimento a flusso), usare la modalità byte. In .NET, specificare PipeTransmissionMode.Message corrisponde al primo caso.9
4. Progetto del server — Un thread per client, oppure overlapped?
L’operazione di base del server è il ciclo «crea un’istanza → attendi un client con ConnectNamedPipe → leggi e scrivi → disconnetti e passa al client successivo». Ci sono due forme per parlare con più client contemporaneamente.
Sincrono, un thread per istanza. Si assegna un thread a ogni istanza, e ciascuna parla con il proprio client in I/O sincrono. Il codice è lineare, ma si consuma un thread per client, e serve anche un modo per uscire dall’I/O bloccante quando si spegne il tutto.
Overlapped (asincrono). Si creano le istanze con FILE_FLAG_OVERLAPPED, si emettono ConnectNamedPipe / ReadFile / WriteFile in modo asincrono, e un numero ridotto di thread gestisce il completamento di ogni istanza. L’esempio ufficiale di Microsoft mostra un server che attende su un array di eventi con WaitForMultipleObjects e elabora più istanze su un solo thread.4 Il racconto generale dell’I/O asincrono è quello dell’articolo della serie I/O, e su scala più grande si possono anche attaccare IOCP o l’I/O del pool di thread.
flowchart TB
accTitle: Struttura di un server overlapped
accDescr: I completamenti delle operazioni asincrone di ogni istanza arrivano su un array di eventi, e un numero ridotto di thread attende con WaitForMultipleObjects e fa avanzare l'istanza completata, sganciando il numero di thread dal numero di client
i1["Operazione async istanza 1"] --> ev["Array di eventi"]
i2["Operazione async istanza 2"] --> ev
i3["Operazione async istanza 3"] --> ev
ev --> wait["Attendi con WaitForMultipleObjects"]
wait --> proc["Avanza l'istanza completata"]
proc --> wait
Figura 5: La forma overlapped sgancia il numero di thread dal numero di client. L’esempio ufficiale fa girare questo ciclo su un solo thread.
.NET elimina quasi questa scelta. Usando WaitForConnectionAsync / ReadAsync / WriteAsync di NamedPipeServerStream insieme ad async/await, si ottiene l’efficienza overlapped in un codice altrettanto lineare della forma sincrona.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 è una specifica che «consente connessioni solo da processi dello stesso utente», un default comodo e sicuro che evita di scrivere un ACL in proprio.10 Non si può usare in una configurazione che attraversa gli utenti (un servizio ↔ un’app in una sessione utente, e simili), quindi in quel caso si passa al progetto ACL del capitolo successivo.
flowchart TB
accTitle: Ciclo di accept di un server asincrono .NET
accDescr: Il ciclo di accept crea un NamedPipeServerStream, attende una connessione con WaitForConnectionAsync e, all'arrivo, stacca in modo asincrono la gestione del client e torna subito al prossimo accept, così le connessioni concorrenti si gestiscono con codice lineare
mk["Crea lo stream server"] --> wc["Attendi con WaitForConnectionAsync"]
wc --> got["Arriva la connessione"]
got --> hd["Stacca la gestione in async"]
hd --> mk
Figura 6: Il ciclo di accept resta sul ciclo «attendi → stacca → prossimo», e l’elaborazione di ogni client procede in parallelo.
5. Sicurezza — Quattro cose da fare quando un servizio privilegiato usa le pipe
La ragione più grande per cui le named pipe sono la prima candidata per l’IPC sulla stessa macchina è il modello di sicurezza, ma solo se lo si configura correttamente. Soprattutto in un progetto broker di «servizio con privilegi di amministratore + app UI a bassi privilegi», la pipe è il confine di privilegi stesso. Ci sono quattro punti da fissare.
(1) Rifiutare il remoto. Una pipe pensata come IPC locale che sia apribile dalla rete è, di per sé, superficie di attacco. Specificare PIPE_REJECT_REMOTE_CLIENTS su CreateNamedPipe e le connessioni di client remoti vengono rifiutate automaticamente.5
(2) Rendere esplicito l’ACL. Passare un descrittore di sicurezza in SECURITY_ATTRIBUTES e restringere utenti e gruppi autorizzati a connettersi. Non dare al client GENERIC_WRITE: il diritto FILE_CREATE_PIPE_INSTANCE incluso in esso permetterebbe a un client autorizzato di creare esso stesso un’istanza server dello stesso nome e di rubare le connessioni successive. Concedere lettura e scrittura come diritti singoli, e non passare il diritto di creazione dell’istanza.11
(3) Impedire il sequestro del nome. I nomi delle pipe sono first-come, first-served. Se un processo malevolo crea per primo una pipe dello stesso nome e attende, i client si connettono al server falso. Il server specifica FILE_FLAG_FIRST_PIPE_INSTANCE alla creazione della prima istanza, garantendo «sono il primo», e se fallisce sospetta un sequestro e si ferma. Questo flag è solo per la prima istanza che rivendica il nome; metterlo sulla seconda e sulle successive fa fallire la creazione.3
| (4) Il client minimizza il livello di impersonation a quanto serve. È una preparazione al caso in cui l’interlocutore sia un server falso. Se il client specifica **SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION** su CreateFile, il server può identificare il client ma non può prendere in prestito quei privilegi e agire.2 È però un compromesso rispetto a un flusso di impersonation: in un progetto broker in cui il server esegue un accesso reale con i privilegi del client, il livello identification non basta perché l’impersonation abbia successo, e occorre consentire SECURITY_IMPERSONATION. Quel consenso è condizionato all’essere sicuri di essere connessi al server genuino. L’anti-sequestro lato server è solo un meccanismo che se ne accorge tramite un avvio fallito; se il servizio genuino è assente e un attaccante crea per primo la pipe dello stesso nome, il client può comunque connettersi al server falso. Consentirlo solo quando si può confermare l’interlocutore tramite un avvio garantito del servizio o un’autenticazione reciproca dopo la connessione. |
Il controllo di identità e il prestito di privilegi lato server è ImpersonateNamedPipeClient. Chiamarlo dopo aver letto una richiesta dalla pipe e il thread chiamante inizia a girare nel contesto di sicurezza del mittente dell’ultimo messaggio letto. Aprire un file con i privilegi del client e il controllo di accesso avviene contro il client: è il meccanismo con cui un servizio privilegiato esegue «l’operazione richiesta, con i privilegi del richiedente».6 La condizione assoluta per usarlo è controllare il valore di ritorno. Continuare dopo un fallimento dell’impersonation e le operazioni successive girano con i privilegi elevati del server. La documentazione ufficiale indica esplicitamente che «in caso di fallimento non si deve eseguire la richiesta del client». Insieme a RevertToSelf a lavoro finito, le pratiche dell’articolo sui token di impersonation valgono così come sono.
sequenceDiagram
accTitle: Flusso di gestione di una richiesta che usa l'impersonation
accDescr: Il server legge una richiesta dalla pipe, conferma che ImpersonateNamedPipeClient è riuscito, poi esegue l'operazione con i privilegi del client e torna al proprio contesto con RevertToSelf. Se l'impersonation fallisce rifiuta la richiesta senza eseguirla
participant C as Client
participant S as Server
C->>S: Invia una richiesta
S->>S: Legge la richiesta
S->>S: ImpersonateNamedPipeClient
Note over S: Se fallisce, rifiuta senza eseguire
S->>S: Esegue l'operazione coi privilegi del client
S->>S: RevertToSelf per ripristinare il contesto
S->>C: Risponde con l'esito
Figura 7: Confermare che l’impersonation è riuscita e un RevertToSelf affidabile vanno in coppia. Continuare in caso di fallimento e gira con i privilegi del server.
flowchart TB
accTitle: Quattro punti che proteggono la pipe di un servizio privilegiato
accDescr: Il lato server indurisce l'ingresso con il rifiuto del remoto, un ACL esplicito e la garanzia di prima istanza; il lato client specifica il livello minimo di impersonation necessario perché un server falso non possa prendere in prestito i privilegi (restringerlo al livello identification se il progetto non lascia al server il prestito dei privilegi)
subgraph sv["Lato server"]
r1["PIPE_REJECT_REMOTE_CLIENTS"]
r2["Restringi i connettori con ACL"]
r3["FIRST_PIPE_INSTANCE(solo 1ª)"]
end
subgraph cl["Lato client"]
r4["Livello minimo di impersonation"]
end
sv --> safe["La pipe come confine di privilegi"]
cl --> safe
Figura 8: In un progetto in cui la pipe è il confine di privilegi, implementare i tre punti lato server più quello lato client come insieme.
6. Trappole pratiche
Una corsa sull’ordine di avvio. Se un client arriva a connettersi prima che il server abbia creato la pipe, si ottiene un errore «la pipe non esiste». Il lato client incorpora «non esiste → aspetta un poco e ritenta». Viceversa, il principio lato server è iniziare ad ascoltare con ConnectNamedPipe prima che il client parta.8
flowchart TB
accTitle: Flusso di retry della connessione del client
accDescr: Aprire la pipe con CreateFile; se la pipe non esiste attendere brevemente e ritentare; se tutte le istanze sono in uso (ERROR_PIPE_BUSY) attendere una libera con WaitNamedPipe e poi ritentare; in caso di successo entrare in comunicazione
cf["Apri con CreateFile"] --> ok{"Esito?"}
ok -->|"Successo"| go["Inizia a comunicare"]
ok -->|"Pipe inesistente"| wait1["Attendi un poco(server non avviato)"]
ok -->|"ERROR_PIPE_BUSY"| wnp["Attendi un'istanza libera con WaitNamedPipe"]
wait1 --> cf
wnp --> cf
Figura 9: La gestione della connessione del client distingue i due tipi di fallimento, «non esiste» e «piena», e li riconduce entrambi a un retry.
Rilevare una disconnessione. Quando l’interlocutore esce, Read/Write falliscono con ERROR_BROKEN_PIPE e simili. Non è un’anomalia; è il quotidiano della comunicazione. Il server rileva la disconnessione, fa DisconnectNamedPipe sull’istanza e si prepara alla connessione successiva; il client si riconnette: l’idea di «riconnessione idempotente» descritta nell’articolo su sleep/resume vale anche qui.
Assunzioni sulla dimensione del messaggio. Oltre alle letture spezzate della modalità messaggio (capitolo 3), se non si decide come parte del protocollo «qual è il massimo di byte di un messaggio», un interlocutore malevolo (o buggato) può sprecare memoria con un messaggio enorme. Decidere un limite superiore, e disconnettere se viene superato: è l’approccio sicuro.
Il completamento della scrittura e la ricezione da parte dell’interlocutore sono cose diverse. Il successo di WriteFile non significa che l’applicazione dell’interlocutore abbia elaborato i dati. Le operazioni che richiedono certezza si fondano su progetti come confermare con un messaggio di risposta, e includere nel protocollo la corrispondenza tra richiesta e risposta.
7. Riepilogo
- Le named pipe sono la prima candidata per l’IPC sulla stessa macchina. Le ragioni sono la stessa facilità d’uso dell’I/O su file, e l’integrazione con il modello di sicurezza Windows di ACL e impersonation.
- La scelta di modalità è «modalità messaggio per richiesta e risposta, modalità byte se si ha già un framing proprio». Anche in modalità messaggio occorre gestire le letture spezzate (ERROR_MORE_DATA).
- Più client sono più istanze + overlapped, oppure async/await di .NET. Per il lavoro nuovo la forma asincrona di .NET è quella lineare.
- Su una pipe che è confine di privilegi, prendere come insieme il rifiuto del remoto, un ACL esplicito, FIRST_PIPE_INSTANCE (solo la prima istanza) e la minimizzazione del livello di impersonation lato client.
- Per
ImpersonateNamedPipeClient, il controllo del valore di ritorno eRevertToSelfsono il salvavita. - Intessere nel progetto del protocollo il «quotidiano della comunicazione»: ordine di avvio, disconnessione, tetto del messaggio, conferma di risposta.
Le named pipe sono un’API antica, ma per l’uso di «far parlare i processi tra loro sulla stessa macchina rispettando i confini degli account Windows» restano lo strumento più naturale per il lavoro. I punti di giudizio di progetto si esauriscono quasi tutti nell’ambito di questo articolo. Dopo, scrivere il proprio protocollo su un foglio prima di iniziare a implementare.
Articoli correlati
- Come scegliere la comunicazione tra processi in Windows — tabella decisionale named pipe / TCP / gRPC / memoria condivisa / COM
- Come isolare concretamente “solo le operazioni che necessitano dei privilegi di amministratore” in un’app Windows
- Gestire correttamente i token di impersonation di Windows — prestito di privilegi per thread e ripristino in sicurezza
- Le profondità dell’I/O di Windows (parte 2) — I/O sincrono e asincrono: cosa significa davvero OVERLAPPED
- Trappole della memoria condivisa e buone pratiche operative
Aree di consulenza correlate
KomuraSoft LLC si occupa di progettazione e implementazione che coinvolgono la comunicazione tra processi — separare un servizio da un’app UI, isolare i privilegi di amministratore e simili —, della sostituzione di IPC esistente (memoria condivisa, un socket fatto in casa, COM, e così via) con named pipe, e di revisioni di sicurezza della comunicazione via pipe di un servizio privilegiato. La consulenza che parte dal confronto di un progetto di protocollo è benvenuta.
- Sviluppo di app Windows
- Consulenza tecnica e revisione della progettazione
- Indagine sui difetti e analisi delle cause
- Contattaci
Riferimenti
-
Microsoft Learn, Named Pipes. Sul fatto che una named pipe sia un canale unidirezionale o bidirezionale tra un pipe server e uno o più pipe client; sul fatto che ogni istanza condivida lo stesso nome pur avendo buffer e handle indipendenti; e sul fatto che sia utilizzabile da processi locali e remoti. ↩ ↩2
-
Microsoft Learn, Impersonating a Named Pipe Client. Sul fatto che l’impersonation lasci al thread del server operare entro i privilegi del client; sul fatto che il livello di impersonation predefinito sia SecurityImpersonation; e sul fatto che il client possa controllare il livello di impersonation con il flag SECURITY_SQOS_PRESENT al momento di CreateFile (SECURITY_IDENTIFICATION consente solo l’identificazione). ↩ ↩2
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). Sulla direzione della pipe (ingresso, uscita, bidirezionale), sul tipo byte e sul tipo messaggio (PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE) e sulla modalità di lettura (PIPE_READMODE_MESSAGE), sul numero massimo di istanze (PIPE_UNLIMITED_INSTANCES), sulla modalità asincrona tramite FILE_FLAG_OVERLAPPED, sulla garanzia di prima istanza tramite FILE_FLAG_FIRST_PIPE_INSTANCE, e sul timeout predefinito di WaitNamedPipe. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Named Pipe Server Using Overlapped I/O. Sull’esempio ufficiale di un server a un solo thread che elabora connessioni simultanee con più client tramite operazioni overlapped. Sulla forma che attende sulla struttura OVERLAPPED e sull’evento di ogni istanza con WaitForMultipleObjects e fa avanzare la macchina a stati dell’istanza completata, e sulla conferma del completamento dell’I/O in sospeso con GetOverlappedResult. ↩ ↩2
-
Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). Sulle due modalità di client remoto, PIPE_ACCEPT_REMOTE_CLIENTS (accettare le connessioni remote e ispezionarle rispetto al descrittore di sicurezza) e PIPE_REJECT_REMOTE_CLIENTS (rifiutare automaticamente le connessioni di client remoti). ↩ ↩2
-
Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). Sul fatto che un thread lato server inizi l’impersonation nel contesto di sicurezza del client dell’ultimo messaggio letto dalla pipe; sul ritorno con RevertToSelf dopo il completamento; e sul fatto che continuare dopo un fallimento dell’impersonation provochi l’esecuzione nel contesto proprio (privilegiato) del processo server, quindi il valore di ritorno va sempre controllato e, in caso di fallimento, la richiesta del client non va eseguita. ↩ ↩2 ↩3
-
Microsoft Learn, Named Pipe Client. Sul fatto che il client apra la pipe con CreateFile; su ERROR_PIPE_BUSY quando tutte le istanze sono in uso, attendendo una libera con WaitNamedPipe; e sul fatto che l’handle aperto parta in lettura byte, bloccante e non overlapped, e SetNamedPipeHandleState possa cambiarlo in modalità di lettura messaggio. ↩ ↩2
-
Microsoft Learn, Named Pipe Operations. Sulle operazioni overlapped tramite ReadFileEx / WriteFileEx, su una lettura non consumante tramite PeekNamedPipe, su TransactNamedPipe che esegue invio della richiesta e ricezione della risposta in una sola chiamata su una pipe bidirezionale di tipo messaggio, e sul fatto che una lettura bloccante prima che il client parta possa provocare una corsa. ↩ ↩2
-
Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). Sulla connessione e sulla lettura/scrittura con NamedPipeServerStream / NamedPipeClientStream, sul trasferimento per unità di messaggio tramite PipeTransmissionMode.Message, e sulla gestione di più client con metodi asincroni. ↩ ↩2
-
Microsoft Learn, PipeOptions Enum (System.IO.Pipes). Sull’abilitazione dell’I/O asincrono con Asynchronous, e sul fatto che CurrentUserOnly possa consentire connessioni solo con processi dello stesso utente (e dello stesso livello di elevazione). ↩
-
Microsoft Learn, Named Pipe Security and Access Rights. Sulla composizione dei diritti di accesso di una named pipe; sul fatto che GENERIC_WRITE include FILE_CREATE_PIPE_INSTANCE, così che dare a un client la scrittura generica consente anche di creare un’istanza server; e sul fatto che lettura e scrittura dei dati si concedono come diritti di accesso singoli. ↩
Articoli correlati
Articoli recenti con gli stessi tag per approfondire argomenti vicini.
L'API thread pool Win32 — Concorrenza senza creare thread, tramite CreateThreadpoolWork
State spargendo chiamate CreateThread per tutto il codice nativo? Questo articolo spiega l'API thread pool Win32 ridisegnata in Vista — i...
Checklist per gestire in sicurezza processi figli nelle app Windows - Job Object, propagazione exit, stdio e watchdog
Come far morire i processi figli con il padre su Windows: Job Object, propagazione exit, drenaggio stdout/stderr e posizionamento del wat...
Chiamando un C# Native AOT DLL da C / C++
Esporta i punti di ingresso C da C# con Native AOT e UnmanagedCallersOnly e chiamali da C / C++: impostazione del progetto, gestione dell...
Le profondità della virtualizzazione Windows (parte 2) — Memoria che nemmeno il kernel può vedere: come funzionano VBS, HVCI e Credential Guard
Su un'installazione pulita su hardware compatibile, VBS è abilitata per default e usa l'hypervisor e SLAT per creare un isolamento più fo...
Icone nella system tray e notifiche toast nelle app Windows — le insidie di NotifyIcon e come scegliere l'AppNotification giusta
Una guida pratica per mantenere un'applicazione Windows aziendale residente nella system tray (area di notifica) e avvisare l'utente tram...
Argomenti correlati
Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.
Argomenti tecnici Windows
Portale su sviluppo Windows, analisi dei problemi e valorizzazione delle risorse esistenti.
Servizi collegati all’argomento
L’articolo è direttamente collegato ai servizi seguenti.
Sviluppo di applicazioni Windows
Applicazioni aziendali, integrazione di dispositivi e strumenti di comunicazione, dai requisiti allo sviluppo.
Domande frequenti
Domande che ricorrono nelle consulenze sull’argomento dell’articolo.
- Come scegliere tra named pipe e TCP (un socket localhost)?
- Per la comunicazione tra processi sulla stessa macchina, la named pipe è la prima candidata. Il motivo è il modello di sicurezza. Una pipe può controllare «chi può connettersi» a livello di sistema operativo con un descrittore di sicurezza Windows (ACL), e il server può ispezionare e prendere in prestito l'account Windows dell'interlocutore con ImpersonateNamedPipeClient. Questo contrasta con una porta TCP localhost, a cui chiunque può connettersi, quindi occorre stabilire chi sia l'interlocutore con un'autenticazione propria. D'altra parte, le opzioni basate su TCP sono vantaggiose quando è probabile che in seguito si sviluppi una comunicazione remota, quando si parla anche con processi su altri sistemi operativi, o quando si vuole riusare un protocollo già esistente come gRPC. Questo criterio è sviluppato anche nell'articolo sulla scelta della comunicazione tra processi in Windows.
- Meglio la modalità byte o la modalità messaggio?
- Se si vuole trattare «una scrittura = un'unità di significato», la modalità messaggio (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE) è comoda. Il ricevente può leggere nelle unità scritte dal mittente, quindi non deve gestire i confini da solo. La modalità byte è un «flusso di byte ininterrotto» come TCP, e il framing va progettato in proprio — ad esempio un prefisso di lunghezza. Se si trasporta un protocollo che ha già un framing (ad esempio una forma serializzata con prefisso di lunghezza), la modalità byte va bene. Un avvertimento: anche in modalità messaggio, se il buffer di ricezione è più piccolo del messaggio si ottiene una lettura spezzata (ERROR_MORE_DATA), quindi va comunque gestita. Inoltre, la modalità di lettura è un'impostazione per handle, e CreateNamedPipe la fissa solo lato server. Il client deve specificare PIPE_READMODE_MESSAGE con SetNamedPipeHandleState dopo CreateFile. In .NET il server specifica PipeTransmissionMode.Message, e il client imposta NamedPipeClientStream.ReadMode su Message dopo la connessione.
- Come costruire un server che parla con più client contemporaneamente?
- Una named pipe può creare più istanze sotto lo stesso nome, e un'istanza gestisce un client. Ci sono due forme. Una è un progetto sincrono che assegna un thread per client; l'implementazione è lineare, ma consuma un thread per client. L'altra è usare I/O asincrono con FILE_FLAG_OVERLAPPED e far gestire ConnectNamedPipe, ReadFile e WriteFile di ogni istanza da un numero ridotto di thread; l'esempio ufficiale di Microsoft mostra anche un'implementazione che elabora più istanze su un solo thread. In .NET si può scrivere la forma asincrona con quasi la stessa linearità di quella sincrona, usando NamedPipeServerStream.WaitForConnectionAsync e async/await. Salvo un motivo particolare, la forma asincrona di .NET è quella che raccomando per le implementazioni nuove.
- Qual è il minimo da fare per la sicurezza delle named pipe?
- Quattro punti. Primo: se le connessioni remote non servono, specificare PIPE_REJECT_REMOTE_CLIENTS e rifiutare esplicitamente le connessioni via rete. Secondo: impostare un ACL adeguato con SECURITY_ATTRIBUTES e restringere utenti e gruppi che possono connettersi (l'ACL predefinito è troppo lasco per alcuni usi). Terzo: specificare FILE_FLAG_FIRST_PIPE_INSTANCE alla creazione della prima istanza, così si rileva il «sequestro del nome» in cui una pipe dello stesso nome viene creata per prima (non mettere questo flag sulla seconda istanza e sulle successive). Quarto: un client che vuole solo che il server lo identifichi deve specificare SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION su CreateFile, così un server falso non può prendere in prestito (impersonare) i suoi privilegi. In un progetto broker in cui il server esegue un accesso reale con i privilegi del client, l'impersonation deve essere consentita, quindi si usa o meno questa restrizione a seconda che il progetto lasci al server il prestito dei privilegi.
- Ci sono accortezze nell'uso di ImpersonateNamedPipeClient?
- La più importante è controllare il valore di ritorno. Se si continua dopo un fallimento dell'impersonation, le operazioni successive girano con i privilegi propri (spesso elevati) del processo server, e passano operazioni che non avrebbero dovuto essere concesse al client. La documentazione ufficiale indica esplicitamente che, in caso di fallimento, non si deve eseguire la richiesta del client. Occorre inoltre chiamarla solo dopo aver letto qualcosa — l'impersonation avviene nel contesto dell'«ultimo messaggio letto dalla pipe» — e tornare in modo affidabile al contesto originale con RevertToSelf a lavoro finito. I meccanismi intorno all'impersonation (token, livelli di impersonation, SeImpersonatePrivilege) sono trattati in dettaglio nell'articolo sui token di impersonation.
Profilo dell’autore
Pagina di presentazione dell’autore dell’articolo.
Go Komura
Rappresentante di KomuraSoft LLC
Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.