Previous Up

הרשאות ורשימות הרשה/סרב

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

קריאת רשימת ההרשאות של עצם. קריאת המערכת GetSecurityInfo קוראת את רשימת ההרשאות של עצם כגון קובץ, מנעול, אירוע, תהליך, וכדומה. בחלונות כמעט כל עצם במערכת ההפעלה הוא בר הגנה על ידי רשימת הרשאות. הקריאה מחזירה מבנה מסוג SECURITY_DESCRIPTOR שיש לו ארבעה שדות: המשתמש/ת בעל/ת העצם (owner), קבוצת המשתמשים (group) שאליה שייך העצם, רשימת הרשאות (discretionary access control list, או בקיצור dacl), ורשימת בקרת גישה (system access control list או sacl). ניתן לבקש מקריאת המערכת כל תת קבוצה של ארבעת השדות הללו; הקריאה לא מחזירה בהכרח את כולם.

#include <aclapi.h>
...
DWORD GetNamedSecurityInfo(
        TCHAR*                name,
        SE_OBJECT_TYPE        object_type,
        SECURITY_INFORMATION  SecurityInfo,
        SID**                 owner,
        SID**                 group,
        ACL**                 dacl,
        ACL**                 sacl,
        SECURITY_DESCRIPTOR** security_descriptor);
הארגומנט הראשון הוא שם העצם, למשל שם קובץ, והארגומנט השני הוא סוג העצם. עבור קבצים, ערך הארגומנט השני צריך להיות SE_FILE_OBJECT. מערכת ההפעלה תומכת בקריאה נוספת, GetSecurityInfo שבה מזהים את העצם על ידי מזהה פתוח, ולא על ידי שמו.

הארגומנט השלישי מציין את השדות שמבקשים לקרוא, והוא צריך להכיל איחוד (or) של תת-קבוצה של ארבעת הקבועים הקריאה מחזירה את המבנה שנקרא בעזרת הארגומנט האחרון. ליתר דיוק, הקריאה קובעת ערך של מצביע למבנה, שכתובתו (של המצביע) מועברת בארגומנט האחרון. לאחר השימוש במידע יש לשחרר את המבנה בעזרת קריאת המערכת LocalFree. בנוסף למצביע הזה, הקריאה קובעת את ערכם של עד ארבעה מצביעים נוספים לחלקים של המבנה. יש להעביר כתובות של מצביעים עבור כל סוג מידע שרוצים לקרוא. אם לא מבקשים, בארגומנט השלישי, סוג מידע מסויים, ניתן להעביר NULL במקום מצביע למצביע מתאים בארגומנטים 4--7.

כדי לקרוא את רשימת בקרת הגישה, את זהות בעליו של העצם, ואת זהות הקבוצה שלה שייך העצם, צריך הרשאה מסוג READ_CONTROL לעצם. כלומר, בהחלט יתכן שאסור יהיה לקרוא את המידע הזה. כדי לקרוא את רשימת רישום הגישה, התהליך הקורא צריך להיות בעל זכות מיוחדת (privilege). בתרגיל זה אין צורך לקרוא את הרשימה הזו.

פענוח זהות המשתמש והקבוצה. הרשאה מתירה או אוסרת למישהו פעולה מסויימת. ישות כזו, שניתן להרשות לה ולאסור עליה פעולות, נקראת מוּרשה. בחלונות לא רק משתמשים בודדים הם מורשים, אלא גם קבוצות שרירותיות של משתמשים, מחשבים הם מורשים, ועוד. מורשה יכול להיות מוגדר במחשב מסויים, או במרחב שמות שנקרא תחום (domain). תחום מכיל בדרך כלל את שמות המורשים באירגון מסויים או בחלק של אירגון. קריאת המערכת הקודמת שתיארנו מחזירה את זהות בעל העצם והקבוצה לא כשמות, אלא כמזהים של מורשים. את המזהים צריך לפענח לשמות על מנת להציגם.

#include <windows.h>
...
BOOL LookupAccountSid(
       TCHAR*         system_name,
       SID*           sid,
       TCHAR*         name,
       DWORD*         name_length,
       TCHAR*         domain,
       DWORD*         domain_length,
       SID_NAME_USE*  what_kind);
קריאת המערכת מקבלת, בארגומנט השני, מצביע למזהה של מורשה, ומתרגמת אותו למחרוזת. התרגום מתבצע במחשב ששמו מועבר בארגומנט הראשון למחרוזת, או במחשב שעליו רצה כעת התוכנית, אם בארגומנט הראשון מועבר NULL.

שם המורשה, שמורכב משם תחום (domain name) ושם המורשה, מוחזר בארגומנטים החמישי והשלישי. הארגומנטים הרביעי והשישי מתארים לקריאת המערכת את גודל החוצצים שהוקצו לשמות הללו. אם אחד החוצצים או שניהם קטנים מידי, הקריאה נכשלת וגודל החוצץ הדרוש מוחזר במשתנים שכתובתם הועברה.

הארגומנט האחרון מחזיר את סוג המורשה, שיכול להיות, בין היתר, אחד מהערכים הבאים פענוח רשימת בקרת הגישה. רשימת בקרת הגישה (dacl) מורכבת מסידרה של איברי בקרת גישה (access control entries או ace). מספר האיברים שמור בשדה AceCount של מבנה מטיפוס ACL. קריאת המערכת GetAce קוראת איבר ברשימה, על פי אינדקס שמתחיל מ--0, ומחזירה אותו במבנה מסוג ACCESS_ACE.

#include <windows.h>
...
BOOL GetAce(ACL*   acl,
            DWORD  index,
            VOID** ace);
הקריאה מחזירה איבר ברשימה על ידי קביעת ערך מצביע אליו. איברים מסוגים שונים מיוצגים על ידי מבנים מטיפוסים שונים. על מנת שניתן יהיה לזהות את טיפוס המבנה המוחזר, המבנה מכיל תמיד תחילית קבועה שמתארת, בין היתר, את סוג האיבר. התחילית היא מטיפוס ACE_HEADER והשדה AceType של התחילית מתאר את סוג האיבר. הסוגים שאנו נטפל בהם הם אבל יש עוד סוגים רבים אחרים. במקרה של איבר הרשאה (הסוג הראשון שציינו) המבנה המוחזר הוא מסוג ACCESS_ALLOWED_OBJECT_ACE. שני השדות החשובים במבנה זה הם Mask שמתאר את הפעולות המותרות (זהו שלם שבו כל סיבית מציינת פעולה מותרת אחת), ו--SidStart, שכתובתו היא כתובת ה--SID של המורשה שהאיבר מתאר הרשאות שלו. כלומר, יש לחשב את כתובת השדה הזה, ולהשתמש בה בתור כתובת של מבנה מטיפוס SID. הסיבה לממשק המוזר היא ככל הנראה שהמבנה הזה הוא בעל גודל משתנה. איבר איסור גישה בנוי בצורה דומה, תוך שימוש במבנה מטיפוס ACCESS_DENIED_OBJECT_ACE. כדאי להשתמש ב--union על מנת להשתמש במצביע ש--GetAce מחזיר בצורות שונות, תחילית ולאחר מכן מבנה מסוג מסויים.

הפעולות המותרות או האסורות מתוארות על ידי סיביות דלוקות בשדה Mask. ניתן לבחון סיביות ספיציפיות (ולהדליק ולכבות אותן) תוך שימוש בקבועים הבאים (שלושת הראשונים ספיציפיים לקבצים, והשאר שימושיים לכל סוג עצם): יצירת רשימת בקרת גישה חדשה. כעת נראה כיצד ליצור רשימת בקרת גישה חדשה. את הרשימה יש ליצור בשטח זיכרון רצוף שאותו מקצים בעזרת LocalAlloc. בהמשך נראה כיצד לחשב את הגודל הנדרש. קודם כל, נלמד כיצד להכין את רשימת בקרת הגישה.

#include <windows.h>
...
BOOL InitializeAcl(ACL*  acl,
                   DWORD acl_length,
                   DWORD acl_revision_level);
BOOL AddAccessAllowedAce(
                   ACL*  acl,
                   DWORD acl_revision,
                   DWORD access_mask,
                   SID*  sid);
BOOL AddAccessDeniedAce(
כנ''ל);
הקריאה הראשונה מאתחלת את הרשימה. צריך להעביר לה מצביע לשטח הזיכרון שהוקצה, את גודל השטח, ואת הגירסה של הרשימה הדרושה (יש שתי גרסאות לרשימות הללו, פשוטה ומתוחכמת יותר). הגרסה יכולה להיות אחד מבין שני הערכים ACL_REVISION ו--ACL_REVISION_DS. הגרסה השנייה מאפשרת שימוש בהרשאות שהן ספיציפיות לעצמים מסוגים שונים, כגון תהליכים, קבצים, וכדומה. בגרסה הראשונה, שמספיקה לצורך התרגיל הזה (ושהיא היחידה שנתמכת במערכות הפעלה ישנות), ניתן לייצג רק פעולות גנריות.

שתי הקריאות הנוספות מוסיפות כל אחת איבר אחד לרשימה, איבר התרה או איבר איסור. בשתיהן הארגומנט הראשון הוא מצביע לרשימה, השני הוא גרסה (כמו בשגרת האיתחול), השלישי הוא הוא איחוד (or) של הפעולות המותרות או האסורות, והרביעי הוא מזהה של מוּרשה.

סדר האיברים ברשימה החדשה הוא סדר הוספתם.

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

sizeof)ACCESS_DENIED_ACE(-sizeof)DWORD(+GetLengthSid)sid(;
קריאת המערכת GetLengthSid מחזירה כמובן את גודל המזהה. הסיבה לחיסור גודל מילה היא שהשדה SidStart מסמן את תחילת המזהה, אבל הוא למעשה חלק מהמזהה.

קישור הרשימה שיצרנו לעצם מתבצע על ידי קריאת המערכת הבאה

#include <aclapi.h>
...
DWORD SetNamedSecurityInfo(
        TCHAR*                name,
        SE_OBJECT_TYPE        object_type,
        SECURITY_INFORMATION  SecurityInfo,
        SID*                  owner,
        SID*                  group,
        ACL*                  dacl,
        ACL*                  sacl);
השימוש בקריאה הזו דומה לשימוש בקריאה GetNamedSecurityInfo, פרט לכך שכאן מעבירים מצביעים למבנים ולא מצביעים למצביעים.

התרגיל. עליך לכתוב תכנית בשם win32-acl שתוכל להדפיס את רשימת בקרת הגישה ושם הבעלים של קובץ, וכן לקבוע רשימה חדשה לקובץ קיים. כאשר מריצים את התוכנית עם ארגומנט אחד, שם קובץ, היא מדפיסה את שם הבעלים של הקובץ (משתמש וקבוצה), ואת רשימת בקרת הגישה של הקובץ. למשל,

c:> win32-acl c\:temp
owner: [U] SWIFT\Administrator 
group: [G] SWIFT\None
Allow: [A] BUILTIN\Administrators
generic:---- file:RWE
...
Allow: [W] \CREATOR OWNER
generic:A--- file:---
...
האות שמופיעה בסוגריים מרובעים לפני כל מורשה מתארת את סוג המורשה, U למשתמשים, G לקבוצות שרירותיות, ו--W לקבוצות "ידועות", כלומר מוגדרות באופן סתום ולא מפורש. התוכנית צריכה לתאר, כמו בדוגמה, את סוג כל איבר ברשימה (הרשה או סרב), את המורשה, ואת ההרשאות הגנריות והרשאות הקבצים, כאשר כל אות מתארת סוג הרשאה.

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

בנוסף למימוש התרגיל, יש לענות על השאלות הבאות.
  1. צרפו לתשובתכם את פלט התוכנית על הקובץ c:\temp ועל קובץ הריצה של התוכנית.
  2. צרו קובץ חדש בעורך טקסטים כלשהו, וצרפו את פלט התוכנית על קובץ הטקסט הזה.
  3. כעת שנו את הרשאות הגישה לקובץ הטקסט באמצעות התוכנית שלכם, ונסו לקרוא ולכתוב ממנו ואליו. האם הצלחתם? צרפו את הפלט שמתאר את ההרשאות עכשיו.
  4. עכשיו יש לנסות לבחון את ההרשאות בעזרת הממשק הגרפי של חלונות. קליק ימני על הקובץ, מאפיינים, security. מה קורה, ולמה לדעתכם זה קורה?
  5. הרשו לחלונות לשנות את ההרשאות על פי המוצע בדיאלוג. צרפו פלט התוכנית שלכם כעת על אותו קובץ, ונסו לקרוא ולכתוב. האם זה מותר?
Copyright Sivan Toledo 2004
Previous Up