Named Pipes בפועל — ה-IPC הסטנדרטי של Windows, מתכנון ועד אבטחה

· · Windows, IPC, פיתוח Windows, C#, C++, אבטחה, Win32 API

“אני רוצה ששירות תושב וממשק הגדרות יחליפו פקודות.” “אני רוצה לבודד רק את העבודה שדורשת הרשאות מנהל לתהליך נפרד.” “אני רוצה שכלים באותו מחשב יעבירו נתונים זה לזה.” — כשנדרשת תקשורת בין-תהליכית (IPC) כזו ב-Windows, הסטנדרט שכדאי לשקול ראשון הוא Named Pipe.

המאמר על בחירת תקשורת בין-תהליכית ב-Windows מיקם את Named Pipes כ”המועמד הראשון ל-IPC באותו מחשב”. המאמר הזה הוא הטיפול המפורט. למה הם המועמד הראשון, איך בוחרים מצבים וצורות שרת, ומה חייבים להגן כששירות בעל הרשאות משתמש בהם — מיועד למפתחות ולמפתחים שכותבים יישומים עסקיים ושירותים ב-Windows, והוא מסדר את חומר השיפוט התכנוני ממקורות ראשוניים.

1. השורה התחתונה קודם

  • Named Pipe הוא ערוץ דו-כיווני בין תהליכים עם מרחב שמות מהצורה \\.\pipe\name. אפשר ליצור כמה מופעים תחת אותו שם ולקבל כמה לקוחות במקביל.1
  • הסיבה שהם המועמד הראשון ל-IPC באותו מחשב היא מודל האבטחה. אפשר לשלוט במי שמתחבר עם ACL, והשרת יכול לבדוק ולשאול (להתחזות ל)חשבון ה-Windows של הלקוח. ל-TCP ב-localhost אין אף אחד מאלה.2
  • אם רוצים לטפל ב”כתיבה אחת = הודעה אחת”, השתמשו במצב הודעות; אם כבר יש לכם framing משלכם, השתמשו במצב בתים. גם במצב הודעות עדיין צריך לטפל בקריאות מפוצלות על מאגר קצר (ERROR_MORE_DATA).3
  • טפלו בכמה לקוחות עם “כמה מופעים + I/O overlapped” או עם “async/await ב-.NET”. הדוגמה הרשמית מציגה צורה שמעבדת כמה מופעים על תהליכון יחיד.4
  • מינימום האבטחה הוא ארבע נקודות: דחיית מרוחק (PIPE_REJECT_REMOTE_CLIENTS), ACL מפורש, זיהוי חטיפה עם FILE_FLAG_FIRST_PIPE_INSTANCE, וצמצום רמת ההתחזות בצד הלקוח.56
  • ב-ImpersonateNamedPipeClient, בדיקת ערך ההחזרה היא קו החיים. מתעלמים מכישלון — והעיבוד ממשיך בהרשאות השרת.6

2. מהו Named Pipe — מרחב שמות, מופעים ואיך חיבורים עובדים

Named Pipe הוא ערוץ שמזוהה בשם כמו \\.\pipe\MyCompany.MyApp.Control. השרת יוצר אותו עם CreateNamedPipe, והלקוח פותח את אותו שם עם CreateFile. ברגע שהוא פתוח, שני הצדדים קוראים וכותבים עם ReadFile / WriteFile — הנקודה הייחודית היא שאפשר להשתמש בו באותה צורה כמו I/O של קבצים.1

המושג החשוב הוא המופע. אפשר ליצור כמה מופעים של צינור באותו שם, ומופע אחד הוא ערוץ אחד עם לקוח אחד. הקריאה הראשונה ל-CreateNamedPipe קובעת את מספר המופעים המרבי (או ללא הגבלה).3

לחיבור בצד הלקוח יש מתכון סטנדרטי. כשכל המופעים תפוסים, CreateFile נכשל עם ERROR_PIPE_BUSY, אז ממתינים למופע פנוי עם WaitNamedPipe ואז מנסים שוב. בנוסף, הגישה שמציינים בפתיחה צריכה להתאים לכיוון שהשרת יצר — צינור דו-כיווני אפשר לפתוח עם קריאה או כתיבה, אבל צינור יוצא שהשרת רק כותב אליו חייב להיפתח לקריאה בלבד, וצינור נכנס שהשרת רק קורא ממנו חייב להיפתח לכתיבה בלבד, אחרת CreateFile נכשל.7

כיוון הצינור ומפרט הגישה של הלקוחלקוח יכול לפתוח צינור דו-כיווני עם קריאה או כתיבה, אבל חייב לפתוח צינור יוצא שהשרת רק כותב אליו לקריאה בלבד, וצינור נכנס שהשרת רק קורא ממנו לכתיבה בלבדדו-כיוונייוצאנכנסהכיוון שהשרת יצר?קריאה או כתיבה בסדרפתיחה לקריאה בלבדפתיחה לכתיבה בלבד

איור 1: אי-התאמה בין כיוון למפרט גישה הופכת לכישלון CreateFile. בחקר שגיאת חיבור, בדקו כאן קודם.

המבנה הבסיסי של Named Pipeהשרת יוצר כמה מופעי צינור באותו שם וממתין לחיבור עם ConnectNamedPipe; כל לקוח פותח את השם עם CreateFile ומקבל ערוץ דו-כיווני אחד-לאחד עם מופע אחדשרתמופע 1מופע 2מופע 3לקוח Aלקוח Bלקוח C

איור 2: בהחזקת כמה מופעים באותו שם, שרת אחד יכול לדבר אחד-לאחד עם כמה לקוחות באותו זמן.

אפשר לפתוח Named Pipes גם מרחוק דרך SMB ‏(\\server\pipe\name), אבל בתכנון מודרני כמעט אין סיבה להשתמש בזה באופן פעיל; הבעיה היא דווקא לא להשאיר את זה פתוח כשלא משתמשים בזה (פרק 5).

3. מצב בתים ומצב הודעות

לצינור יש שני מצבי העברה.3

  • מצב בתים (PIPE_TYPE_BYTE): “זרם בתים רציף” כמו TCP. אתם מחליטים בעצמכם איפה הודעה אחת נגמרת (מתכננים framing כמו קידומת אורך).
  • מצב הודעות (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE): כתיבה אחת מטופלת כהודעה אחת, והקורא מקבל אותה ביחידה הזו. זה קל יותר לחילופי בקשה/תשובה.

למצב הודעות יש גם בן-זוג נוח, TransactNamedPipe, ששולח בקשה ומקבל את התשובה בקריאה אחת.8 אבל יש מלכוד. אם מאגר הקבלה קטן מההודעה כולה, הקריאה מחזירה ERROR_MORE_DATA והופכת לקריאה מפוצלת. אל תניחו שמצב הודעות אומר “Read אחד תמיד מביא את הכול”; עדיין צריך לכתוב לולאה שקוראת את היתרה. שימו לב שמצב הקריאה הוא הגדרה לפי-handle, ו-CreateNamedPipe קובע אותו רק בצד השרת. הלקוח מציין אותו עם SetNamedPipeHandleState אחרי CreateFile (ב-.NET, ReadMode אחרי החיבור).7

לולאת קריאה מפוצלת במצב הודעותאם ReadFile מצליח ההודעה שלמה; אם הוא מחזיר ERROR_MORE_DATA קוראים את היתרה שלא נכנסה למאגר ומשרשרים אותה; כל שגיאה אחרת מטופלת כניתוקהצלחהERROR_MORE_DATAכל שגיאה אחרתקריאה עם ReadFileהתוצאה?ההודעה שלמהקריאת היתרה ושרשורטיפול כניתוק

איור 3: גם במצב הודעות צריך “לולאת קריאת-היתר”; בלי זה, רק הודעות גדולות נשברות.

ההבדל בין מצב בתים למצב הודעותבמצב בתים שלוש כתיבות הופכות לזרם בתים רציף והמקבל צריך לפצל אותו; במצב הודעות יחידת כל כתיבה נשמרת ומגיעה למקבל כמות שהיאמצב בתים: כתיבת AAA, BB, CCCCהתקבל כזרם AAABBCCCCאתם מתכננים את ה-framingמצב הודעות: אותן שלוש כתיבותהתקבל כשלוש הודעות: AAA, BB, CCCCיחידות הכתיבה נשמרות

איור 4: מצב הודעות שומר את “יחידת הכתיבה” ומוסר אותה. תכנון framing מיותר; רק אל תשכחו לטפל בקריאות מפוצלות.

כלל הבחירה המעשי פשוט. אם החילופים הם בצורה של “בקשה ותשובה”, מצב הודעות. אם מעבירים צורה שכבר יש בה framing מובנה (נתונים מסורסרים עם קידומת אורך או העברת זרם), השתמשו במצב בתים. ב-.NET, ציון PipeTransmissionMode.Message מתאים לראשון.9

4. תכנון השרת — תהליכון אחד לכל לקוח, או Overlapped?

הפעולה הבסיסית של השרת היא הלולאה “צור מופע → המתן ללקוח עם ConnectNamedPipe → קרא וכתוב → נתק ועבור ללקוח הבא”. יש שתי צורות לדיבור עם כמה לקוחות במקביל.

סינכרוני, תהליכון אחד לכל מופע. מקצים תהליכון אחד לכל מופע, וכל אחד מדבר עם הלקוח שלו ב-I/O סינכרוני. הקוד פשוט, אבל צורכים תהליכון אחד לכל לקוח, וצריך גם דרך לצאת מ-I/O חוסם כשמכבים את הכול.

Overlapped (אסינכרוני). יוצרים מופעים עם FILE_FLAG_OVERLAPPED, מנפיקים ConnectNamedPipe / ReadFile / WriteFile באופן אסינכרוני, ומספר קטן של תהליכונים מטפל בהשלמה של כל המופעים. הדוגמה הרשמית של Microsoft מציגה שרת שממתין על מערך אירועים עם WaitForMultipleObjects ומעבד כמה מופעים על תהליכון יחיד.4 התיאור הכללי של I/O אסינכרוני הוא כפי שהוסבר במאמר סדרת ה-I/O, ובקנה מידה גדול יותר אפשר גם לחבר IOCP או I/O של מאגר תהליכונים.

מבנה של שרת overlappedהשלמות של הפעולות האסינכרוניות של כל מופע מתקבלות על מערך אירועים, ומספר קטן של תהליכונים ממתין עם WaitForMultipleObjects ומקדם את המופע שהושלם, ומנתק את מספר התהליכונים ממספר הלקוחותפעולה אסינכרונית מופע 1מערך אירועיםפעולה אסינכרונית מופע 2פעולה אסינכרונית מופע 3המתנה להשלמה עם WaitForMultipleObjectsקידום המופע שהושלם

איור 5: הצורה ה-overlapped מנתקת את מספר התהליכונים ממספר הלקוחות. הדוגמה הרשמית מריצה את המחזור הזה על תהליכון יחיד.

.NET כמעט מבטל את הבחירה הזו. עם WaitForConnectionAsync / ReadAsync / WriteAsync של NamedPipeServerStream יחד עם async/await, מקבלים יעילות overlapped בקוד פשוט כמו הצורה הסינכרונית.9

// C#: שלד של שרת שמקבל כמה לקוחות
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();        // לשחרר בעצמכם כשיוצאים לפני חיבור
        throw;
    }
    _ = HandleClientAsync(server, token);   // הבעלות אחרי החיבור עוברת למטפל
}

PipeOptions.CurrentUserOnly הוא מפרט ש”מתיר חיבורים רק מתהליכים של אותו משתמש”, ברירת מחדל נוחה ובטוחה שחוסכת כתיבת ACL בעצמכם.10 אי אפשר להשתמש בו בהגדרה שחוצה משתמשים (שירות ↔ יישום בסשן משתמש, וכדומה), ולכן במקרה כזה עוברים לתכנון ה-ACL בפרק הבא.

לולאת הקבלה של שרת אסינכרוני ב-.NETלולאת הקבלה יוצרת NamedPipeServerStream, ממתינה לחיבור עם WaitForConnectionAsync, ובהגעה מנתקת את טיפול הלקוח באופן אסינכרוני וחוזרת מיד לקבלה הבאה, כך שחיבורים מקבילים מטופלים בקוד פשוטיצירת זרם השרתהמתנה עם WaitForConnectionAsyncחיבור מגיעניתוק טיפול הלקוח אסינכרונית

איור 6: לולאת הקבלה נצמדת למחזור “המתן → נתק → הבא”, ועיבוד כל לקוח ממשיך במקביל.

5. אבטחה — ארבעה חובה כששירות בעל הרשאות משתמש בצינורות

הסיבה הגדולה ביותר ש-Named Pipes הם המועמד הראשון ל-IPC באותו מחשב היא מודל האבטחה, אבל זה רק אם מגדירים אותו נכון. במיוחד בתכנון ברוקר של “שירות בהרשאות מנהל + יישום ממשק בהרשאות נמוכות”, הצינור הוא עצם גבול ההרשאות. יש ארבע נקודות לקבע.

(1) דחו מרוחק. צינור שהתכוונתם אליו כ-IPC מקומי ושניתן לפתוח מהרשת הוא, כשלעצמו, משטח תקיפה. ציינו PIPE_REJECT_REMOTE_CLIENTS ב-CreateNamedPipe וחיבורי לקוח מרוחקים נדחים אוטומטית.5

(2) עשו את ה-ACL מפורש. העבירו מתאר אבטחה ב-SECURITY_ATTRIBUTES וצמצמו את המשתמשים והקבוצות שמותר להם להתחבר. אל תתנו ללקוח GENERIC_WRITE — הזכות FILE_CREATE_PIPE_INSTANCE הכלולה בו תאפשר ללקוח מורשה עצמו ליצור מופע שרת באותו שם ולגנוב חיבורים הבאים. העניקו קריאה וכתיבה כזכויות נפרדות, ואל תעבירו את זכות יצירת המופע.11

(3) מנעו חטיפת שם. שמות צינורות הם כל-הקודם-זוכה. אם תהליך זדוני יוצר קודם צינור באותו שם וממתין, לקוחות מתחברים לשרת המזויף. השרת מציין FILE_FLAG_FIRST_PIPE_INSTANCE ביצירת המופע הראשון, ומבטיח ש”אני ראשון”, ואם זה נכשל הוא חושד בחטיפה ועוצר. הדגל הזה מיועד רק למופע הראשון שתובע את השם; שימו אותו על המופע השני ואילך והיצירה תיכשל.3

(4) הלקוח מצמצם את רמת ההתחזות למה שנדרש. זו הכנה למקרה שהעמית הוא שרת מזויף. אם הלקוח מציין **SECURITY_SQOS_PRESENT SECURITY_IDENTIFICATION** ב-CreateFile, השרת יכול לזהות את הלקוח אבל לא יכול לשאול את ההרשאות האלה ולפעול.2 זהו עם זאת פשרה מול זרימת התחזות — בתכנון ברוקר שבו השרת מבצע גישה אמיתית תחת הרשאות הלקוח, רמת זיהוי לא מספיקה להצלחת ההתחזות, וצריך לאפשר SECURITY_IMPERSONATION. ההיתר הזה מותנה בכך שבטוחים שאתם מחוברים לשרת האמיתי. מניעת חטיפה בצד השרת היא רק מנגנון שמבחין דרך כישלון התחלה; אם השירות האמיתי חסר ותוקף יוצר קודם את הצינור באותו שם, הלקוח עדיין יכול להתחבר לשרת המזויף. אפשרו זאת רק כשאפשר לאשר את העמית דרך התחלת שירות מובטחת או אימות הדדי אחרי החיבור.

בדיקת הזהות ושאילת ההרשאות בצד השרת היא ImpersonateNamedPipeClient. קראו לזה אחרי קריאת בקשה מהצינור ותהליכון הקורא מתחיל לרוץ בהקשר האבטחה של שולח ההודעה האחרונה שנקראה. פתחו קובץ בהרשאות הלקוח ובדיקת הגישה נעשית מול הלקוח — המנגנון שבו שירות בעל הרשאות מבצע “את הפעולה המבוקשת, בהרשאות המבקש”.6 התנאי המוחלט לשימוש הוא בדיקת ערך ההחזרה. המשיכו אחרי כישלון התחזות והפעולות הבאות רצות בהרשאות הגבוהות של השרת עצמו. התיעוד הרשמי קובע במפורש ש”בכישלון אסור לבצע את בקשת הלקוח”. יחד עם RevertToSelf אחרי העבודה, הפרקטיקות במאמר על טוקני התחזות חלות כמות שהן.

זרימת טיפול בבקשה שמשתמשת בהתחזותהשרת קורא בקשה מהצינור, מאשר ש-ImpersonateNamedPipeClient הצליח, ואז מבצע את הפעולה בהרשאות הלקוח וחוזר להקשר שלו עם RevertToSelf. אם ההתחזות נכשלת הוא מסרב לבקשה בלי לבצע אותהשרתלקוחשרתלקוחבכישלון, סירוב בלי לבצע את הבקשהשליחת בקשהקריאת הבקשהImpersonateNamedPipeClientביצוע הפעולה בהרשאות הלקוחRevertToSelf לשחזור ההקשרתשובה עם התוצאה

איור 7: אישור שההתחזות הצליחה ו-RevertToSelf אמין באים כחבילה. ממשיכים בכישלון — וזה רץ בהרשאות השרת.

ארבע נקודות שמגנות על צינור של שירות בעל הרשאותצד השרת מחזק את הכניסה בדחיית מרוחק, ACL מפורש והבטחת מופע ראשון; צד הלקוח מציין את רמת ההתחזות המינימלית הנדרשת כדי ששרת מזויף לא יוכל לשאול הרשאות (מצמצמים לרמת זיהוי אם התכנון לא נותן לשרת לשאול הרשאות)צד הלקוחציון רמת ההתחזות המינימליתצד השרתPIPE_REJECT_REMOTE_CLIENTSהגבלת מתחברים עם ACLFIRST_PIPE_INSTANCE (מופע ראשון בלבד)הצינור כגבול הרשאות

איור 8: בתכנון שבו הצינור הוא גבול ההרשאות, מממשים את שלוש הנקודות בצד השרת ועוד אחת בצד הלקוח כמערכת.

6. מלכודות מעשיות

מירוץ בסדר ההפעלה. אם לקוח מגיע להתחבר לפני שהשרת יצר את הצינור, מתקבלת שגיאת “הצינור לא קיים”. צד הלקוח בונה “לא קיים → המתן קצת ונסה שוב”. ולהפך, העיקרון בצד השרת הוא להתחיל להאזין עם ConnectNamedPipe לפני שהלקוח מתחיל.8

זרימת ניסיון חוזר לחיבור לקוחפותחים את הצינור עם CreateFile; אם הצינור לא קיים ממתינים קצרות ומנסים שוב; אם כל המופעים תפוסים (ERROR_PIPE_BUSY) ממתינים לפנוי עם WaitNamedPipe ואז מנסים שוב; בהצלחה נכנסים לתקשורתהצלחההצינור לא קייםERROR_PIPE_BUSYפתיחה עם CreateFileהתוצאה?התחלת תקשורתהמתנה קצרה (השרת לא התחיל)המתנה למופע פנוי עם WaitNamedPipe

איור 9: טיפול החיבור של הלקוח מבדיל בין שני סוגי הכישלון, “לא קיים” ו”מלא”, ומחזיר את שניהם לניסיון חוזר.

זיהוי ניתוק. כשהעמית יוצא, Read/Write נכשלים עם ERROR_BROKEN_PIPE וכדומה. זו לא חריגה; זו תקשורת יומיומית. השרת מזהה את הניתוק, עושה DisconnectNamedPipe למופע, ומתכונן לחיבור הבא; הלקוח מתחבר מחדש — הרעיון של “חיבור מחדש אידמפוטנטי” שתואר במאמר על שינה/התעוררות חל גם כאן.

הנחות על גודל הודעה. מעל הקריאות המפוצלות של מצב הודעות (פרק 3), אם לא מחליטים כחלק מהפרוטוקול “מה מספר הבתים המרבי של הודעה אחת”, עמית זדוני (או באג) יכול לבזבז לכם זיכרון בהודעה ענקית. קבעו תקרה, ונתקו אם היא חורגת; זו הגישה הבטוחה.

השלמת כתיבה וקבלת העמית הם דברים שונים. הצלחה של WriteFile לא אומרת שיישום העמית עיבד את הנתונים. פעולות שדורשות ודאות נשענות על תכנונים כמו אישור בהודעת תשובה, וכללת ההתאמה בין בקשה לתשובה בפרוטוקול.

7. סיכום

  • Named Pipes הם המועמד הראשון ל-IPC באותו מחשב. הסיבות הן אותה קלות שימוש כמו I/O של קבצים, והשילוב עם מודל האבטחה של Windows — ACL והתחזות.
  • בחירת המצב היא “מצב הודעות לבקשה ותשובה, מצב בתים אם כבר יש framing משלכם”. גם במצב הודעות עדיין צריך לטפל בקריאות מפוצלות (ERROR_MORE_DATA).
  • כמה לקוחות הם כמה מופעים + overlapped, או async/await ב-.NET. לעבודה חדשה הצורה האסינכרונית של .NET היא הפשוטה.
  • בצינור שהוא גבול הרשאות, קחו כמערכת דחיית מרוחק, ACL מפורש, FIRST_PIPE_INSTANCE (מופע ראשון בלבד), וצמצום רמת ההתחזות בצד הלקוח.
  • ב-ImpersonateNamedPipeClient, בדיקת ערך ההחזרה ו-RevertToSelf הם קו החיים.
  • ארגו לתוך תכנון הפרוטוקול את “היומיום של התקשורת” — סדר הפעלה, ניתוק, תקרת הודעה, אישור תשובה.

Named Pipes הם API ישן, אבל לשימוש של “לתת לתהליכים לדבר זה עם זה באותו מחשב תוך כיבוד גבולות חשבון Windows”, הם עדיין הכלי הטבעי ביותר לעבודה. נקודות השיפוט התכנוני כמעט ממוצות בהיקף המאמר הזה. אחר כך, כתבו את הפרוטוקול שלכם על דף אחד לפני שמתחילים לממש.

מאמרים קשורים

תחומי ייעוץ קשורים

KomuraSoft LLC מטפלת בתכנון ובמימוש שכוללים תקשורת בין-תהליכית — הפרדת שירות מיישום ממשק, בידוד הרשאות מנהל וכדומה — בהחלפת IPC קיים (זיכרון משותף, שקע ביתי, COM ועוד) ב-Named Pipes, ובסקירות אבטחה של תקשורת צינור של שירות בעל הרשאות. התייעצות שמתחילה מהקפצת תכנון פרוטוקול מוזמנת.

קישורים

  1. Microsoft Learn, Named Pipes. על כך ש-Named Pipe הוא ערוץ חד-כיווני או דו-כיווני בין שרת צינור ללקוח צינור אחד או יותר; על כך שכל המופעים חולקים את אותו שם אך יש להם מאגרים ו-handles עצמאיים; ועל כך שאפשר להשתמש בו מתהליכים מקומיים ומרוחקים.  2

  2. Microsoft Learn, Impersonating a Named Pipe Client. על כך שהתחזות מאפשרת לתהליכון השרת לפעול בתוך הרשאות הלקוח; על כך שרמת ההתחזות כברירת מחדל היא SecurityImpersonation; ועל כך שהלקוח יכול לשלוט ברמת ההתחזות עם הדגל SECURITY_SQOS_PRESENT בזמן CreateFile (SECURITY_IDENTIFICATION מתיר זיהוי בלבד).  2

  3. Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). על כיוון הצינור (נכנס, יוצא, דו-כיווני), סוג בתים וסוג הודעות (PIPE_TYPE_BYTE / PIPE_TYPE_MESSAGE) ומצב קריאה (PIPE_READMODE_MESSAGE), מספר המופעים המרבי (PIPE_UNLIMITED_INSTANCES), מצב אסינכרוני דרך FILE_FLAG_OVERLAPPED, הבטחת מופע ראשון דרך FILE_FLAG_FIRST_PIPE_INSTANCE, וזמן הקצוב כברירת מחדל ל-WaitNamedPipe.  2 3 4

  4. Microsoft Learn, Named Pipe Server Using Overlapped I/O. על הדוגמה הרשמית של שרת בתהליכון יחיד שמעבד חיבורים סימולטניים עם כמה לקוחות דרך פעולות overlapped. על הצורה שממתינה על מבנה OVERLAPPED ועל אירוע של כל מופע עם WaitForMultipleObjects ומקדמת את מכונת המצבים של המופע שהושלם, ועל אישור השלמת I/O ממתין עם GetOverlappedResult.  2

  5. Microsoft Learn, CreateNamedPipeW function (namedpipeapi.h). על שני מצבי הלקוח המרוחק, PIPE_ACCEPT_REMOTE_CLIENTS (קבלת חיבורים מרוחקים ובדיקתם מול מתאר האבטחה) ו-PIPE_REJECT_REMOTE_CLIENTS (סירוב אוטומטי לחיבורי לקוח מרוחקים).  2

  6. Microsoft Learn, ImpersonateNamedPipeClient function (namedpipeapi.h). על כך שתהליכון בצד השרת מתחיל התחזות בהקשר האבטחה של לקוח ההודעה האחרונה שנקראה מהצינור; על חזרה עם RevertToSelf אחרי ההשלמה; ועל כך שהמשך אחרי כישלון התחזות גורם לביצוע בהקשר (בעל ההרשאות) של תהליך השרת עצמו, ולכן חייבים תמיד לבדוק את ערך ההחזרה ובכישלון אסור לבצע את בקשת הלקוח.  2 3

  7. Microsoft Learn, Named Pipe Client. על כך שהלקוח פותח את הצינור עם CreateFile; על ERROR_PIPE_BUSY כשכל המופעים תפוסים, והמתנה לפנוי עם WaitNamedPipe; ועל כך שה-handle שנפתח כברירת מחדל הוא קריאת בתים, חוסם ולא-overlapped, ו-SetNamedPipeHandleState יכול לשנות אותו למצב קריאת הודעות.  2

  8. Microsoft Learn, Named Pipe Operations. על פעולות overlapped דרך ReadFileEx / WriteFileEx, קריאה לא-צורכת דרך PeekNamedPipe, TransactNamedPipe שמבצע שליחת בקשה וקבלת תשובה בקריאה אחת על צינור דו-כיווני מסוג הודעות, ועל כך שקריאה חוסמת לפני שהלקוח מתחיל עלולה לגרום למירוץ.  2

  9. Microsoft Learn, How to: Use Named Pipes for Network Interprocess Communication (.NET). על חיבור וקריאה/כתיבה עם NamedPipeServerStream / NamedPipeClientStream, העברה ביחידות הודעה דרך PipeTransmissionMode.Message, וטיפול בכמה לקוחות עם מתודות אסינכרוניות.  2

  10. Microsoft Learn, PipeOptions Enum (System.IO.Pipes). על הפעלת I/O אסינכרוני עם Asynchronous, ועל כך ש-CurrentUserOnly יכול להתיר חיבורים רק עם תהליכים של אותו משתמש (ואותה רמת העלאת הרשאות). 

  11. Microsoft Learn, Named Pipe Security and Access Rights. על הרכב זכויות הגישה של Named Pipe; על כך ש-GENERIC_WRITE כולל FILE_CREATE_PIPE_INSTANCE, כך שמתן כתיבה כללית ללקוח מתיר גם יצירת מופע שרת; ועל כך שקריאה וכתיבה של נתונים מוענקות כזכויות גישה נפרדות. 

מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.

העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.

המאמר קשור ישירות לשירותים הבאים.

שאלות נפוצות

שאלות נפוצות בפניות בנושא המאמר.

איך בוחרים בין Named Pipes לבין TCP (שקע localhost)?
לתקשורת בין-תהליכית באותו מחשב, Named Pipe הוא המועמד הראשון. הסיבה היא מודל האבטחה. צינור יכול לשלוט ברמת מערכת ההפעלה ב"מי רשאי להתחבר" באמצעות מתאר אבטחה של Windows ‏(ACL), והשרת יכול לבדוק ולשאול את חשבון ה-Windows של העמית המתחבר עם ImpersonateNamedPipeClient. בניגוד לכך, לפורט TCP ב-localhost יכול להתחבר כל אחד, ולכן צריך לקבוע מי העמית באימות עצמאי. מצד שני, אפשרויות מבוססות TCP עדיפות כשצפויה בהמשך תקשורת מרוחקת, כשמדברים גם עם תהליכים במערכות הפעלה אחרות, או כשרוצים לעשות שימוש חוזר בנכס פרוטוקול קיים כמו gRPC. השיקול הזה מפורט גם במאמר על בחירת תקשורת בין-תהליכית ב-Windows.
להשתמש במצב בתים או במצב הודעות?
אם רוצים לטפל ב"כתיבה אחת = יחידת משמעות אחת", מצב הודעות (PIPE_TYPE_MESSAGE + PIPE_READMODE_MESSAGE) נוח. המקבל יכול לקרוא ביחידות שבהן השולח כתב, כך שאין צורך לנהל את הגבולות בעצמכם. מצב בתים הוא "זרם בתים רציף" כמו TCP, וצריך לתכנן את ה-framing בעצמכם — למשל קידומת אורך. אם מעבירים פרוטוקול שכבר כולל framing (למשל צורה מסורסרת עם קידומת אורך), מצב בתים מתאים. הערה: גם במצב הודעות, אם מאגר הקבלה קטן מההודעה מתקבלת קריאה מפוצלת (ERROR_MORE_DATA), ולכן עדיין צריך לטפל בזה. בנוסף, מצב הקריאה הוא הגדרה לפי-handle, ו-CreateNamedPipe קובע אותו רק בצד השרת. הלקוח חייב לציין PIPE_READMODE_MESSAGE עם SetNamedPipeHandleState אחרי CreateFile. ב-.NET השרת מציין PipeTransmissionMode.Message, והלקוח מגדיר את NamedPipeClientStream.ReadMode ל-Message אחרי החיבור.
איך בונים שרת שמדבר עם כמה לקוחות במקביל?
Named Pipe יכול ליצור כמה מופעים תחת אותו שם, ומופע אחד מטפל בלקוח אחד. יש שתי צורות. האחת היא תכנון סינכרוני שמקצה תהליכון לכל לקוח; המימוש פשוט, אבל הוא צורך תהליכון אחד לכל לקוח. האחרת היא I/O אסינכרוני עם FILE_FLAG_OVERLAPPED, שבו מספר קטן של תהליכונים מטפלים ב-ConnectNamedPipe,‏ ReadFile ו-WriteFile לכל המופעים; גם הדוגמה הרשמית של Microsoft מציגה מימוש שמעבד כמה מופעים על תהליכון יחיד. ב-.NET אפשר לכתוב את הצורה האסינכרונית כמעט באותה פשטות כמו הסינכרונית, עם NamedPipeServerStream.WaitForConnectionAsync ו-async/await. אם אין סיבה מיוחדת, הצורה האסינכרונית של .NET היא המומלצת למימושים חדשים.
מה המינימום שצריך לעשות לאבטחת Named Pipe?
ארבע נקודות. ראשית, אם אין צורך בחיבורים מרוחקים, ציינו PIPE_REJECT_REMOTE_CLIENTS וסרבו במפורש לחיבורים דרך הרשת. שנית, הגדירו ACL מתאים עם SECURITY_ATTRIBUTES וצמצמו את המשתמשים והקבוצות שרשאים להתחבר (ה-ACL כברירת מחדל רחב מדי לחלק מהשימושים). שלישית, ציינו FILE_FLAG_FIRST_PIPE_INSTANCE ביצירת המופע הראשון, כדי לזהות "חטיפת שם" שבה צינור באותו שם נוצר קודם (אל תשימו את הדגל הזה על המופע השני ואילך). רביעית, לקוח שרק רוצה שהשרת יזהה אותו צריך לציין SECURITY_SQOS_PRESENT|SECURITY_IDENTIFICATION ב-CreateFile, כדי ששרת מזויף לא יוכל לשאול (להתחזות ל)הרשאותיו. בתכנון ברוקר שבו השרת מבצע גישה אמיתית תחת הרשאות הלקוח, צריך לאפשר התחזות, ולכן משתמשים בהגבלה הזו או לא לפי השאלה אם התכנון נותן לשרת לשאול הרשאות.
יש נקודות לתשומת לב בשימוש ב-ImpersonateNamedPipeClient?
החשובה ביותר היא בדיקת ערך ההחזרה. אם ממשיכים אחרי כישלון התחזות, הפעולות הבאות רצות עם ההרשאות של תהליך השרת עצמו (לרוב גבוהות), ופעולות שלא היו אמורות להיות מותרות ללקוח עוברות. גם התיעוד הרשמי קובע במפורש שבכישלון אסור לבצע את בקשת הלקוח. צריך גם לקרוא לזה רק אחרי שקראתם משהו — ההתחזות מתבצעת בהקשר של "ההודעה האחרונה שנקראה מהצינור" — ולחזור באופן אמין להקשר המקורי עם RevertToSelf כשהעבודה מסתיימת. המנגנון סביב התחזות (טוקנים, רמות התחזות, SeImpersonatePrivilege) מכוסה בפירוט במאמר על טוקני התחזות.

פרופיל הכותב

עמוד היכרות עם כותב המאמר.

Go Komura

מנהל KomuraSoft LLC

מתמחה בפיתוח תוכנה עבור Windows, ייעוץ טכני וחקירת תקלות, בעיקר בפרויקטים עם מערכות קיימות ובאגים שקשה לשחזר.

קישורים ציבוריים

חזרה לבלוג