8.1 - זהות והרשאות על אובייקטים - פתרון
פתרון - בדיקת הרשאות¶
התרגיל הראשון¶
במדיניות הראשונה מאושר רק הביט הראשון. לכן הקריאה מאושרת, השינוי נדחה וגם הבקשה המשולבת נדחית. אין טעם להסתפק בכך שהמסכה המוחזרת מכילה הרשאה כלשהי; שאלנו על הפעולה שהיישום עומד לבצע.
במדיניות השנייה שני הביטים מאושרים, ולכן שלוש הבקשות מאושרות. במדיניות השלישית הרשימה קיימת אבל אף ACE אינו מאשר את הביטים המבוקשים. שלוש הבקשות נדחות עבור מסכות הניסוי.
במדיניות הרביעית רשומת ה־deny אוסרת שינוי. בקשת קריאה בלבד אינה מבקשת את הביט הזה, ולכן יכולה להמשיך לרשומת ה־allow ולהצליח. שתי הבקשות שמכילות שינוי נדחות. זאת סיבה טובה לבדוק גם בקשות משולבות, ולא רק כל ביט בנפרד.
ההרצה של המקרה הרביעי היא:
הפלט הצפוי של דגלי האישור לפי הסדר הוא אחד, אפס ואפס. אם מתקבלת תוצאה אחרת, בדקו קודם את ה־token ואת המדיניות שהועברה בפועל. אל תשנו את ההסבר רק כדי להתאים אותו לפלט שעדיין לא הבנתם.
התרגיל השני¶
הקטע הבא מחליף את בניית ה־descriptor מתוך מחרוזת. שאר לולאת הבדיקות מההרצאה נשארת ללא שינוי:
SECURITY_DESCRIPTOR absolute{};
if (!InitializeSecurityDescriptor(&absolute,
SECURITY_DESCRIPTOR_REVISION))
fail("InitializeSecurityDescriptor");
SID_IDENTIFIER_AUTHORITY authority = SECURITY_NT_AUTHORITY;
PSID sid = nullptr;
if (!AllocateAndInitializeSid(&authority, 1, SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0, &sid))
fail("AllocateAndInitializeSid");
struct free_sid {
void operator()(void* p) const noexcept { FreeSid(p); }
};
std::unique_ptr<void, free_sid> sid_owner(sid);
if (!SetSecurityDescriptorOwner(&absolute, sid, FALSE))
fail("SetSecurityDescriptorOwner");
if (!SetSecurityDescriptorGroup(&absolute, sid, FALSE))
fail("SetSecurityDescriptorGroup");
if (!SetSecurityDescriptorDacl(&absolute, TRUE, nullptr, FALSE))
fail("SetSecurityDescriptorDacl");
PSECURITY_DESCRIPTOR raw = &absolute;
ה־descriptor המוחלט מחזיק הפניות ל־SID-ים, ולכן האחסון שלהם חייב לשרוד עד סיום כל הבדיקות. כאן האובייקט sid_owner נשאר באותו scope של הלולאה. הפונקציה FreeSid משחררת את ההקצאה המתאימה, ולא CloseHandle או LocalFree.
הדגל השלישי בקריאה ל־SetSecurityDescriptorDacl מעביר מצביע NULL כרשימה עצמה. זהו המקרה של NULL DACL, ובבדיקת הניסוי הוא מאפשר את שלוש מסכות הגישה. לעומת זאת, DACL ריקה קיימת אך אין בה רשומה שתאשר את הביטים.
ההבדל מסביר למה חיבור קוד של בניית הרשאות אינו מקום טוב לניחוש. ערך ריק שנראה כמו ברירת מחדל סגורה עשוי לתאר דווקא מדיניות מתירנית. כאן ראינו את ההבדל על אובייקט בזיכרון, בלי לשנות מדיניות של משאב במחשב.
התרגיל השלישי¶
הערך הלוגי שמוחזר מהפונקציה אינו תשובת האישור. לכן ההחלטה צריכה להיראות כך, אחרי שדאגנו ל־buffer בגודל מתאים:
BOOL evaluated = AccessCheck(sd, token, requested, &mapping,
privileges, &bytes, &granted, &allowed);
if (!evaluated) {
DWORD error = GetLastError();
report_evaluation_error(error);
} else if (!allowed) {
report_denied();
} else {
update_record();
}
הפונקציות שמתחילות ב־report הן נקודות החיבור ללוגיקה של היישום, ולא API של Windows. לצורך הניסוי מחליפים אותן בהדפסה ולא מבצעים כתיבה אמיתית. הפלט של דחייה חייב להיות שונה מתקלה בהערכת המדיניות, כדי שאפשר יהיה לאבחן את שניהם.
ה־descriptor שמאשר רק קריאה נותן דחייה לבקשת שינוי בלי לשבור את מנגנון הבדיקה. מחרוזת שמכילה רק DACL בלי בעלים וקבוצה יכולה להמחיש descriptor שאינו מתאים לדרישות AccessCheck. במקרה כזה אין להשתמש בתוכן של granted כהחלטת הרשאה.
התרגיל הרביעי¶
הבדיקה התייחסה לקובץ הראשון, והפתיחה המאוחרת עלולה להתייחס לקובץ שני. זמן ההמתנה בתור רק מגדיל את ההזדמנות לראות את התקלה; גם שתי שורות סמוכות אינן פעולה אטומית אחת.
תכנון אפשרי הוא לפתוח את המשאב בהקשר הלקוח ובהרשאות המדויקות שנדרשות, לשמור את ה־handle ולבצע את העבודה על האובייקט שכבר נפתח. עדיין צריך להגדיר מדיניות לשינוי תוכן, לחיי הלקוח ולהרשאה שמבקשים לשמר לאורך זמן. הפתרון לזהות המשאב אינו פותר אוטומטית את כל שאלות מדיניות המוצר.
בשירות שמחזיק הרשאות רחבות, אסור לבדוק את זהות הלקוח ואז לבצע פתיחה בשם השירות כאילו הבדיקה הפכה את השם לבטוח. ההרצאות על impersonation ועל IPC יחברו את השלבים האלה למימוש שלם.