תאימות למדיניות

במאמר הזה נסביר איך Android מטפלת בבעיות תאימות המדיניות עם עדכוני OTA בפלטפורמה, שבהם הגדרות SELinux החדשות של הפלטפורמה עשויות להיות שונות מהגדרות SELinux הישנות של הספק.

בתכנון מדיניות SELinux שמבוססת על טרבל, יש הבחנה בינארית בין מדיניות הפלטפורמה לבין מדיניות הספק. התוכנית הופכת מורכבת יותר אם מחיצות של ספקים יוצרות יחסי תלות, כמו platform < vendor < oem.

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

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

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

בעלות על אובייקטים וסימון שלהם

כשמתאימים אישית את המדיניות ב-Android מגרסה 8.0 ואילך, צריך להגדיר בבירור את הבעלות על כל אובייקט כדי להפריד בין המדיניות של הפלטפורמה לבין המדיניות של הספק. לדוגמה, אם הספק מתייג את /dev/foo ואז הפלטפורמה מתייגת את /dev/foo בהודעת OTA הבאה, תהיה התנהגות לא מוגדרת. ב-SELinux, המצב הזה מתבטא כהתנגשות בתוויות. לצומת המכשיר יכולה להיות רק תווית אחת, שמפנה לתווית האחרונה שהוחלו עליו. כתוצאה מכך:

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

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

בנוסף להתנגשויות בין תוויות, יכולות להיות גם התנגשויות בין שמות של סוגים או מאפיינים של SELinux. התנגשות בשמות של סוגים או מאפיינים תגרום תמיד לשגיאה במהלך הידור המדיניות.

שמות של סוגי מאפיינים

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

type foo, domain;  type vendor_foo, domain;

בעלות על תוויות של מאפייני מערכת ותהליכים

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

סוג הנכס קידומות קבילות
מאפייני בקרה ctl.vendor.
ctl.start$vendor.
ctl.stop$vendor.
init.svc.vendor.
read-writable vendor.
קריאה בלבד ro.vendor.
ro.boot.
ro.hardware.
קבוע persist.vendor.

ספקים יכולים להמשיך להשתמש ב-ro.boot.* (שנלקח מ-cmdline של הליבה) וב-ro.hardware.* (מאפיין ברור שקשור לחומרה).

כל שירותי הספק בקובצי init rc צריכים לכלול את הערך vendor. לשירותים בקובצי init rc של מחיצות שאינן מחיצות מערכת. כללים דומים חלים על תוויות SELinux של נכסי הספק (vendor_ לנכסי הספק).

הבעלות על הקובץ

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

מערכת (‎/system)

רק קובץ האימג' של המערכת צריך לספק תוויות לרכיבי /system דרך file_contexts,‏ service_contexts וכו'. אם תוויות לרכיבי /system מתווספות למדיניות /vendor, יכול להיות שלא תהיה אפשרות לבצע עדכון OTA של המסגרת בלבד.

ספק (‎/vendor)

מדיניות SELinux של AOSP כבר מסמנת חלקים מהמחיצה vendor שבהם הפלטפורמה מקיימת אינטראקציה, וכך מאפשרת לכתוב כללי SELinux לתהליכי הפלטפורמה כדי שיוכלו לתקשר עם חלקים מהמחיצה vendor ו/או לגשת אליהם. לדוגמה:

נתיב /vendor תווית שסופקה על ידי הפלטפורמה תהליכי הפלטפורמה בהתאם לתווית
/vendor(/.*)? vendor_file כל לקוחות ה-HAL ב-framework,‏ ueventd וכו'
/vendor/framework(/.*)? vendor_framework_file dex2oat,‏ appdomain וכו'
/vendor/app(/.*)? vendor_app_file dex2oat,‏ installd,‏ idmap וכו'
/vendor/overlay(/.*) vendor_overlay_file system_server,‏ zygote,‏ idmap וכו'

לכן, צריך לפעול לפי כללים ספציפיים (המאוכפים באמצעות neverallows) כשמתייגים קבצים נוספים במחיצה vendor:

  • vendor_file חייבת להיות תווית ברירת המחדל לכל הקבצים במחיצה vendor. המדיניות של הפלטפורמה דורשת זאת כדי לגשת להטמעות HAL של העברה דרך.
  • כל exec_types חדש שנוסף למחיצה vendor דרך SEPolicy של הספק חייב לכלול את המאפיין vendor_file_type. האכיפה מתבצעת באמצעות neverallows.
  • כדי למנוע התנגשויות עם עדכונים עתידיים של פלטפורמה או מסגרת, מומלץ לא לתייג קבצים אחרים מלבד exec_types במחיצה vendor.
  • כל יחסי התלות בספריות של ממשקי HAL לאותו תהליך שזוהו ב-AOSP חייבים להיות מסומנים בתווית same_process_hal_file.

Procfs‏ (‎/proc)

אפשר לתייג קבצים ב-/proc רק באמצעות התווית genfscon. ב-Android 7.0, גם המדיניות של הפלטפורמה וגם המדיניות של הספק השתמשו ב-genfscon כדי לתייג קבצים ב-procfs.

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

Debugfs‏ (‎/sys/kernel/debug)

אפשר לתייג את Debugfs גם ב-file_contexts וגם ב-genfscon. ב-Android מגרסה 7.0 עד Android מגרסה 10, צריך לציין גם את התווית של הפלטפורמה וגם את התווית של הספק בשדה debugfs.

ב-Android 11, אי אפשר לגשת ל-debugfs או לטעון אותו במכשירי ייצור. יצרני המכשירים צריכים להסיר את debugfs.

Tracefs‏ (‎/sys/kernel/debug/tracing)

אפשר לתייג את Tracefs גם ב-file_contexts וגם ב-genfscon. ב-Android 7.0, רק תוויות הפלטפורמה tracefs.

המלצה: רק הפלטפורמה יכולה לתייג את השדה tracefs.

Sysfs‏ (‎/sys)

אפשר לתייג קבצים ב-/sys גם באמצעות file_contexts וגם באמצעות genfscon. ב-Android 7.0, גם הפלטפורמה וגם הספק משתמשים ב-genfscon כדי לתייג קבצים ב-sysfs.

המלצה: הפלטפורמה עשויה לתייג צמתים של sysfs שלא ספציפיים למכשיר. אחרת, רק הספק יכול לתייג קבצים.

tmpfs‏ (‎/dev)

קבצים ב-/dev יכולים להיות מתויגים ב-file_contexts. ב-Android 7.0, גם קובצי התוויות של הפלטפורמה וגם קובצי התוויות של הספקים נמצאים כאן.

המלצה: הספק יכול לתייג רק קבצים ב-/dev/vendor (לדוגמה, /dev/vendor/foo,‏ /dev/vendor/socket/bar).

Rootfs‏ (‎/)

קבצים ב-/ יכולים להיות מתויגים ב-file_contexts. ב-Android 7.0, קובצי התוויות של הפלטפורמה והספק נמצאים כאן.

המלצה: רק המערכת יכולה לתייג קבצים ב-/.

נתונים (‎/data)

הנתונים מתויגים באמצעות שילוב של file_contexts ו-seapp_contexts.

המלצה: אסור לאפשר לתייג ספקים מחוץ למאפיין /data/vendor. רק הפלטפורמה יכולה לתייג חלקים אחרים של /data.

גרסת התוויות של Genfs

החל מ-רמת API של הספק 202504, תוויות SELinux חדשות יותר שהוקצו באמצעות genfscon ב-system/sepolicy/compat/plat_sepolicy_genfs_{ver}.cil הן אופציונליות למחיצות ישנות יותר של ספקים. כך אפשר לשמור את ההטמעה הקיימת של SEPolicy במחיצות ישנות של ספקים. ההגדרה הזו נשלטת על ידי המשתנה BOARD_GENFS_LABELS_VERSION ב-Makefile, שמאוחסן ב-/vendor/etc/selinux/genfs_labels_version.txt.

דוגמה:

  • ברמת ה-API של הספק 202404, הצומת /sys/class/udc מסומן כברירת מחדל בתווית sysfs.
  • החל מרמת ה-API של הספק 202504, השדה /sys/class/udc מסומן בתווית sysfs_udc.

עם זאת, יכול להיות ש-/sys/class/udc נמצא בשימוש במחיצות של ספקים שמשתמשים ברמת API 202404, עם תווית sysfs שמוגדרת כברירת מחדל או עם תווית ספציפית לספק. תיוג בלתי מותנה של /sys/class/udc כ-sysfs_udc עלול לפגוע בתאימות למחיצות של הספקים האלה. כשמסמנים את BOARD_GENFS_LABELS_VERSION, הפלטפורמה ממשיכה להשתמש בתוויות ובהרשאות הקודמות של מחיצות הספק הישנות.

הערך של BOARD_GENFS_LABELS_VERSION יכול להיות גדול מ-API של הספק או שווה לו. לדוגמה, מחיצות של ספקים שמשתמשות ברמת API 202404 יכולות להגדיר את BOARD_GENFS_LABELS_VERSION לערך 202504 כדי לאמץ תוויות חדשות שהוצגו ב-202504. רשימת תוויות genfs ספציפיות ל-202504

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

מאפייני תאימות

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

המדיניות נכתבת בעיקר במונחים של סוגים קיימים:

allow source_type target_type:target_class permission(s);

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

File_contexts:
/sys/A   u:object_r:sysfs:s0
Platform: allow p_domain sysfs:class perm;
Vendor: allow v_domain sysfs:class perm;

אפשר לשנות ל:

File_contexts:
/sys/A   u:object_r:sysfs_A:s0

מדיניות הספק תישאר ללא שינוי, אבל ה-v_domain יפסיד את הגישה בגלל שאין מדיניות לסוג החדש של sysfs_A.

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

הגדרת מדיניות ציבורית כמאפיינים עם גרסאות עומדת בשני יעדי תאימות למדיניות:

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

יכולת כתיבה של מדיניות

כדי לעמוד ביעד של פיתוח מדיניות בלי צורך בידע לגבי שינויים ספציפיים בגרסאות, Android 8.0 כולל מיפוי בין סוגי המדיניות שגלויים לכולם בפלטפורמה לבין המאפיינים שלהם. הסוג foo ממופה למאפיין foo_vN, כאשר N הוא הגרסה שאליה מכוון הטירגוט. הערך של vN תואם למשתנה ה-build‏ PLATFORM_SEPOLICY_VERSION, והוא בפורמט MM.NN, כאשר MM תואם למספר ה-SDK של הפלטפורמה ו-NN הוא גרסת sepolicy ספציפית לפלטפורמה.

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

מדיניות הפלטפורמה הציבורית שיוצאת כ-allow source_foo target_bar:class perm; נכללת כחלק ממדיניות הספק. במהלך הקמפלקציה (שכוללת את הגרסה המתאימה), היא עוברת טרנספורמציה למדיניות שתעבור לחלק של הספק במכשיר (מוצגת בשפה המתורגמת Common Intermediate Language‏ (CIL)):

 (allow source_foo_vN target_bar_vN (class (perm)))

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

הבדלים בין גרסאות של מדיניות

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

(typeattributeset foo_vN (foo))

שדרוגי פלטפורמה

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

סוגים זהים

התרחיש הזה מתרחש כשלא משנים את התוויות של אובייקט בגרסאות של המדיניות. זהו אותו ערך עבור סוגי המקור והיעד, וניתן לראות אותו באמצעות /dev/binder, שמסומן בתווית binder_device בכל הגרסאות. הוא מיוצג במדיניות שעברה טרנספורמציה בתור:

binder_device_v1 … binder_device_vN

כשמשדרגים מ-v1 ל-v2, מדיניות הפלטפורמה חייבת לכלול:

type binder_device; -> (type binder_device) (in CIL)

בקובץ המיפוי של v1‏ (CIL):

(typeattributeset binder_device_v1 (binder_device))

בקובץ המיפוי של v2‏ (CIL):

(typeattributeset binder_device_v2 (binder_device))

במדיניות הספק בגרסה 1 (CIL):

(typeattribute binder_device_v1)
(allow binder_device_v1 )

במדיניות הספק בגרסה 2 (CIL):

(typeattribute binder_device_v2)
(allow binder_device_v2 )
סוגים חדשים

התרחיש הזה מתרחש כשנוסף לסוג חדש בפלטפורמה, דבר שיכול לקרות כשמוסיפים תכונות חדשות או במהלך הקשחת המדיניות.

  • תכונה חדשה כשהסוג מתייג אובייקט שלא היה קיים בעבר (למשל תהליך שירות חדש), קוד הספק לא היה באינטראקציה ישירה איתו בעבר, ולכן אין מדיניות תואמת. למאפיין החדש התואם לסוג אין מאפיין בגרסה הקודמת, ולכן לא תהיה צורך ברשומה בקובץ המיפוי שמטרגט את הגרסה הזו.
  • הקשחת מדיניות כשהסוג מייצג הקשחת מדיניות, מאפיין הסוג החדש חייב לקשר בחזרה לרשת של מאפיינים שתואמים למאפיין הקודם (בדומה לדוגמה הקודמת שבה השינויים ב-/sys/A מ-sysfs ל-sysfs_A). קוד הספק מסתמך על כלל שמאפשר גישה ל-sysfs, וצריך לכלול את הכלל הזה כמאפיין של הסוג החדש.

כשמשדרגים מ-v1 ל-v2, מדיניות הפלטפורמה חייבת לכלול:

type sysfs_A; -> (type sysfs_A) (in CIL)
type sysfs; (type sysfs) (in CIL)

בקובץ המיפוי של v1‏ (CIL):

(typeattributeset sysfs_v1 (sysfs sysfs_A))

בקובץ המיפוי של v2‏ (CIL):

(typeattributeset sysfs_v2 (sysfs))
(typeattributeset sysfs_A_v2 (sysfs_A))

במדיניות הספק בגרסה 1 (CIL):

(typeattribute sysfs_v1)
(allow  sysfs_v1 )

במדיניות הספק בגרסה 2 (CIL):

(typeattribute sysfs_A_v2)
(allow  sysfs_A_v2 )
(typeattribute sysfs_v2)
(allow  sysfs_v2 )
סוגי מודעות שהוסרו

התרחיש הזה (הנדיר) מתרחש כאשר מסירים סוג. זה יכול לקרות אם:

  • נשארת, אבל מקבלת תווית אחרת.
  • המערכת מסירה אותו.

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

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

(typeattribute sysfs_v1)
(allow  sysfs_v1 )

גרסה לדוגמה 1: קיבוץ סוגי נתונים (הסרת sysfs_A)

כשמשדרגים מ-v1 ל-v2, מדיניות הפלטפורמה חייבת לכלול:

type sysfs; (type sysfs) (in CIL)

בקובץ המיפוי של v1‏ (CIL):

(typeattributeset sysfs_v1 (sysfs))
(type sysfs_A) # in case vendors used the sysfs_A label on objects
(typeattributeset sysfs_A_v1 (sysfs sysfs_A))

בקובץ המיפוי של v2‏ (CIL):

(typeattributeset sysfs_v2 (sysfs))

במדיניות הספק בגרסה 1 (CIL):

(typeattribute sysfs_A_v1)
(allow  sysfs_A_v1 )
(typeattribute sysfs_v1)
(allow  sysfs_v1 )

במדיניות הספק בגרסה 2 (CIL):

(typeattribute sysfs_v2)
(allow  sysfs_v2 )

דוגמה לגרסה 2: הסרה מלאה (סוג foo)

כשמשדרגים מ-v1 ל-v2, מדיניות הפלטפורמה חייבת לכלול:

# nothing - we got rid of the type

בקובץ המיפוי של v1‏ (CIL):

(type foo) #needed in case vendors used the foo label on objects
(typeattributeset foo_v1 (foo))

בקובץ המיפוי של v2‏ (CIL):

# nothing - get rid of it

במדיניות הספק בגרסה 1 (CIL):

(typeattribute foo_v1)
(allow foo )
(typeattribute sysfs_v1)
(allow sysfs_v1 )

במדיניות הספק בגרסה 2 (CIL):

(typeattribute sysfs_v2)
(allow sysfs_v2 )
כיתה/הרשאות חדשות

התרחיש הזה מתרחש כששדרוג פלטפורמה כולל רכיבי מדיניות חדשים שלא קיימים בגרסאות קודמות. לדוגמה, כש-Android הוסיפה את מנהל האובייקטים servicemanager שיצר את ההרשאות להוספה, לחיפוש וליצירת רשימה, דמונים של ספקים שרצו להירשם עם ההרשאות הנדרשות של servicemanager לא היו זמינות. ב-Android 8.0, רק מדיניות הפלטפורמה יכולה להוסיף כיתות והרשאות חדשות.

כדי לאפשר לכל הדומיינים שיכולים להיות שנוצרו או הורחבו לפי מדיניות הספק להשתמש בכיתה החדשה ללא הפרעה, מדיניות הפלטפורמה צריכה לכלול כלל דומה לזה:

allow {domain -coredomain} *:new_class perm;

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

הכיתה או ההרשאות הוסרו

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

התאמה אישית של הספק לסוגים חדשים או לסוגים שסומנו מחדש

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

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

שינויים במאפיינים ל-Android מגרסה 9

במכשירים שמשודרגים ל-Android 9 אפשר להשתמש במאפיינים הבאים, אבל במכשירים שמופקים עם Android 9 אסור להשתמש בהם.

מאפייני המפר

מערכת Android 9 כוללת את המאפיינים הבאים שקשורים לדומיין:

  • data_between_core_and_vendor_violators. מאפיין לכל הדומיינים שמפירים את הדרישה לא לשתף קבצים לפי נתיב בין vendor ל-coredomains. תהליכים של פלטפורמות ושל ספקים לא צריכים להשתמש בקבצים בדיסק כדי לתקשר (ABI לא יציב). המלצה:
    • בקוד הספק צריך להשתמש ב-/data/vendor.
    • המערכת לא צריכה להשתמש ב-/data/vendor.
  • system_executes_vendor_violators. מאפיין לכל הדומיינים של המערכת (למעט init ו-shell domains) שמפירים את הדרישה לא להריץ קובצי בינאריים של ספקים. ביצוע של קובצי בינאריים של ספקים עם ממשק API לא יציב. הפלטפורמה לא צריכה להריץ קובצי בינארי של ספקים ישירות. המלצה:
    • יחסי התלות האלה של הפלטפורמה בקבצים הבינאריים של הספקים חייבים להיות מאחורי ממשקי HAL של HIDL.

      או

    • coredomains שצריכים גישה לקובצי הבינארי של הספק צריכים לעבור למחיצה של הספק, וכך להפסיק להיות coredomain.

מאפיינים לא מהימנים

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

  1. שרתי HwBinder לא מבצעים אימות לקוח כי HIDL לא חושף כרגע את פרטי ה-UID של מבצע הקריאה החוזרת. גם אם HIDL חושף נתונים כאלה, הרבה שירותי HwBinder פועלים ברמה נמוכה יותר מזו של האפליקציות (למשל, HAL) או שאסור להם להסתמך על זהות האפליקציה לצורך הרשאה. לכן, כדי להימנע מבעיות, ההנחה שמוגדרת כברירת מחדל היא שכל שירות HwBinder מתייחס לכל הלקוחות שלו ככאלה שיש להם הרשאה זהה לבצע את הפעולות שהשירות מציע.
  2. שרתי HAL (קבוצת משנה של שירותי HwBinder) מכילים קוד עם שיעור גבוה יותר של בעיות אבטחה בהשוואה לרכיבי system/core, ויש להם גישה לשכבות התחתונות של הסטאק (עד לחומרה), כך שיש יותר הזדמנויות לעקוף את מודל האבטחה של Android.

שירותים בטוחים

שירותים בטוחים כוללים:

  • same_process_hwservice. השירותים האלה (בהגדרה) פועלים בתהליך של הלקוח, ולכן יש להם את אותה גישה שיש לדומיין הלקוח שבו פועל התהליך.
  • coredomain_hwservice. השירותים האלה לא גורמים לסיכונים שקשורים לסיבה מס' 2.
  • hal_configstore_ISurfaceFlingerConfigs. השירות הזה תוכנן במיוחד לשימוש בכל דומיין.
  • hal_graphics_allocator_hwservice. הפעולות האלה מוצעות גם על ידי שירות ה-Binder‏ surfaceflinger, לאפליקציות שיש להן גישה אליו.
  • hal_omx_hwservice. זוהי גרסה של HwBinder לשירות mediacodec Binder, שאפליקציות מורשות לגשת אליו.
  • hal_codec2_hwservice. זוהי גרסה חדשה יותר של hal_omx_hwservice.

מאפיינים שאפשר להשתמש בהם

לכל hwservices שלא נחשבים בטוחים יש את המאפיין untrusted_app_visible_hwservice. למאפיין untrusted_app_visible_halserver יש את הערך 'HAL' בשרתים התואמים של HAL. אסור להשתמש באף אחד מהמאפיינים untrusted במכשירים שמריצים Android מגרסה 9 ואילך.

המלצה:

  • במקום זאת, אפליקציות לא מהימנות צריכות לדבר עם שירות מערכת שמדבר עם HIDL HAL של הספק. לדוגמה, אפליקציות יכולות לדבר עם binderservicedomain, ואז mediaserver (שנחשב ל-binderservicedomain) מדבר עם hal_graphics_allocator.

    או

  • לאפליקציות שזקוקות לגישה ישירה ל-HALs של vendor צריך להיות דומיין משלהם של מדיניות אבטחה (sepolicy) שהוגדר על ידי הספק.

בדיקות של מאפייני קבצים

Android 9 כולל בדיקות בזמן ה-build שמבטיחות שלכל הקבצים במיקומים ספציפיים יש את המאפיינים המתאימים (לדוגמה, כל הקבצים ב-sysfs כוללים את המאפיין הנדרש sysfs_type).

מדיניות ציבורית של פלטפורמה

המדיניות הציבורית של הפלטפורמה היא הליבה של התאימות למודל הארכיטקטורה של Android 8.0, בלי פשוט לשמור על האיחוד של כללי המדיניות של הפלטפורמה מגרסה 1 וגרסה 2. הספקים נחשפים לקבוצת משנה של מדיניות הפלטפורמה שמכילה סוגים ומאפיינים שאפשר להשתמש בהם וכללים לגבי הסוגים והמאפיינים האלה, שהופכים לאחר מכן לחלק ממדיניות הספק (כלומר, vendor_sepolicy.cil).

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

מיפוי לרשתות של מאפיינים

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

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

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

גרסאות משופרות

כדי לפשט את התהליך, פלטפורמת Android משחררת גרסה של sepolicy כשמחלקים את ההסתעפות החדשה של הגרסה. כפי שמתואר למעלה, מספר הגרסה נכלל ב-PLATFORM_SEPOLICY_VERSION והוא בפורמט MM.nn, כאשר MM תואם לערך ה-SDK ו-nn הוא ערך פרטי שנשמר ב- /platform/system/sepolicy.. לדוגמה, 19.0 עבור Kitkat, ‏ 21.0 עבור Lollipop,‏ 22.0 עבור Lollipop-MR1,‏ 23.0 עבור Marshmallow, ‏ 24.0 עבור Nougat,‏ 25.0 עבור Nougat-MR1, ‏ 26.0 עבור Oreo, ‏ 27.0 עבור Oreo-MR1 ו-28.0 עבור Android 9. העלאות לא תמיד הן מספרים שלמים. לדוגמה, אם שדרוג של MR לגרסה מחייב שינוי לא תואם ב-system/sepolicy/public אבל לא שדרוג של API, גרסת ה-sepolicy יכולה להיות: vN.1. הגרסה שנמצאת בהסתעפות הפיתוח היא 10000.0, שאסור להשתמש בה במכשירים שיוצאים לשוק.

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

ההשפעה של מספר מאפיינים על הביצועים

כפי שמתואר במאמר https://github.com/SELinuxProject/cil/issues/9, מספר גדול של מאפיינים שמוקצים לסוג מסוים גורם לבעיות בביצועים במקרה של החמצה של מדיניות במטמון.

הבעיה אומתה כבעיה ב-Android, ולכן בוצעו שינויים ב-Android 8.0 כדי להסיר מאפיינים שנוספו למדיניות על ידי המהדר של המדיניות, וגם כדי להסיר מאפיינים שלא בשימוש. השינויים האלה פתרו נסיגות בביצועים.

המדיניות הציבורית של system_ext והמדיניות הציבורית של המוצר

החל מ-Android 11, אפשר לייצא את הסוגים הציבוריים הייעודיים של המחיצות system_ext ו-product למחיצה של הספק. בדומה למדיניות הציבורית של הפלטפורמה, הספק משתמש בסוגי כללים וכללים שמתורגמים באופן אוטומטי למאפיינים עם גרסאות, לדוגמה, מ-type ל-type_N, כאשר N היא הגרסה של הפלטפורמה שבה הוקם המחיצה של הספק.

כשמחיצות system_ext ו-product מבוססות על אותה גרסת פלטפורמה N, מערכת ה-build יוצרת קובצי מיפוי בסיס ל-system_ext/etc/selinux/mapping/N.cil ול-product/etc/selinux/mapping/N.cil, שמכילים מיפויים של זהויות מ-type ל-type_N. הספק יכול לגשת ל-type באמצעות המאפיין המנוהל בגרסאות type_N.

אם רק המחיצות system_ext ו-product מתעדכנות, למשל מ-N ל-N+1 (או גרסה מתקדמת יותר), בעוד שהספק נשאר ב-N, יכול להיות שהספק יאבד את הגישה לסוגי המחיצות system_ext ו-product. כדי למנוע שגיאות, צריך לספק קבצי מיפוי מהפלחים system_ext ו-product מהטיפוסים הספציפיים למאפייני type_N. כל שותף אחראי על ניהול קובצי המיפוי, אם הוא תומך בספק N עם מחיצות system_ext ו-product בגרסה N+1 ואילך.

כדי לעשות זאת, השותפים נדרשים:

  1. מעתיקים את קובצי המיפוי הבסיסיים שנוצרו מהמחיצות N,‏ system_ext ו-product אל עץ המקור שלהן.
  2. משנים את קובצי המיפוי לפי הצורך.
  3. מתקינים את קובצי המיפוי במחיצות system_ext ו-product של N+1 (או גרסה מתקדמת יותר).

לדוגמה, נניח של-N system_ext יש טיפוס ציבורי אחד בשם foo_type. לאחר מכן, system_ext/etc/selinux/mapping/N.cil במחיצה N system_ext ייראה כך:

(typeattributeset foo_type_N (foo_type))
(expandtypeattribute foo_type_N true)
(typeattribute foo_type_N)

אם bar_type מתווסף ל-N+1 system_ext, ואם צריך למפות את bar_type ל-foo_type עבור הספק N, אפשר לעדכן את N.cil מ-

(typeattributeset foo_type_N (foo_type))

עד

(typeattributeset foo_type_N (foo_type bar_type))

ולאחר מכן מותקנים במחיצה N+1 system_ext. הספק של N יכול להמשיך לגשת ל-foo_type ול-bar_type של N+1 ב-system_ext.

תוויות של הקשרים ב-SELinux

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

הקשרים של קבצים

ב-Android 8.0 הוכנסו השינויים הבאים ב-file_contexts:

  • כדי למנוע תקורה נוספת של הידור במכשיר במהלך האתחול, file_contexts מפסיק להתקיים בפורמט הבינארי. במקום זאת, הם קובצי טקסט של ביטויים רגולריים שאפשר לקרוא אותם, כמו {property, service}_contexts (כפי שהיו לפני גרסה 7.0).
  • הקבצים file_contexts מחולקים לשני קבצים:
    • plat_file_contexts
      • פלטפורמת Android‏ file_context ללא תוויות ספציפיות למכשיר, מלבד תוויות של חלקים במחיצה /vendor שצריך לתייג במדויק כדי להבטיח את הפעולה התקינה של קובצי המדיניות.
      • צריך להיות במחיצה system ב-/system/etc/selinux/plat_file_contexts במכשיר, ו-init צריך לטעון אותו בהתחלה יחד עם הספק file_context.
    • vendor_file_contexts
      • file_context ספציפי למכשיר שנוצר על ידי שילוב של file_contexts שנמצאים בספריות שמפניות אל BOARD_SEPOLICY_DIRS בקבצים Boardconfig.mk של המכשיר.
      • צריך להתקין אותו ב-/vendor/etc/selinux/vendor_file_contexts במחיצה vendor, ו-init צריך לטעון אותו בהתחלה יחד עם הפלטפורמה file_context.

הקשרים של נכסים

ב-Android 8.0, ה-property_contexts מפוצל לשני קבצים:

  • plat_property_contexts
    • פלטפורמת Android property_context ללא תוויות ספציפיות למכשיר.
    • צריך להיות בפורמט system ב-/system/etc/selinux/plat_property_contexts, ו-init צריך לטעון אותו בהתחלה יחד עם הספק property_contexts.
  • vendor_property_contexts
    • property_context ספציפי למכשיר שנוצר על ידי שילוב של property_contexts שנמצא בתיקיות ש-BOARD_SEPOLICY_DIRS מפנה אליהן בקבצים Boardconfig.mk של המכשיר.
    • חייב להיות במחיצה vendor ב-/vendor/etc/selinux/vendor_property_contexts, ו-init צריך לטעון אותו בהתחלה יחד עם הפלטפורמה property_context

הקשרים של שירותים

ב-Android מגרסה 8.0, ה-service_contexts מחולק בין הקבצים הבאים:

  • plat_service_contexts
    • service_context ספציפי לפלטפורמת Android עבור servicemanager. ל-service_context אין תוויות ספציפיות למכשיר.
    • חייב להיות במחיצה system ב-/system/etc/selinux/plat_service_contexts, ו-servicemanager צריך לטעון אותו בהתחלה יחד עם הספק service_contexts.
  • vendor_service_contexts
    • service_context ספציפי למכשיר שנוצר על ידי שילוב של service_contexts שנמצאים בספריות שמוצגות על ידי BOARD_SEPOLICY_DIRS בקבצים Boardconfig.mk של המכשיר.
    • צריך להיות במחיצה vendor ב-/vendor/etc/selinux/vendor_service_contexts, ו-servicemanager צריך לטעון אותו בהתחלה יחד עם הפלטפורמה service_contexts.
    • אמנם servicemanager מחפש את הקובץ הזה בזמן האתחול, אבל במכשיר TREBLE תואם לחלוטין, הקובץ vendor_service_contexts לא יכול להתקיים. הסיבה לכך היא שכל האינטראקציה בין התהליכים vendor ו-system חייבת לעבור דרך hwservicemanager/hwbinder.
  • plat_hwservice_contexts
    • פלטפורמת Android hwservice_context עבור hwservicemanager ללא תוויות ספציפיות למכשיר.
    • צריך להיות במחיצה system ב-/system/etc/selinux/plat_hwservice_contexts, ו-hwservicemanager צריך לטעון אותו בהתחלה יחד עם vendor_hwservice_contexts.
  • vendor_hwservice_contexts
    • hwservice_context ספציפי למכשיר שנוצר על ידי שילוב של hwservice_contexts שנמצאים בספריות שמוצגות על ידי BOARD_SEPOLICY_DIRS בקבצים Boardconfig.mk של המכשיר.
    • צריך להיות במחיצה vendor ב-/vendor/etc/selinux/vendor_hwservice_contexts, ו-hwservicemanager צריך לטעון אותו בהתחלה יחד עם ה-plat_service_contexts.
  • vndservice_contexts
    • service_context ספציפי למכשיר של vndservicemanager שנוצר על ידי שילוב של vndservice_contexts שנמצא בספריות שBOARD_SEPOLICY_DIRS מפנה אליהן ב-Boardconfig.mk של המכשיר.
    • הקובץ הזה צריך להיות במחיצה vendor ב-/vendor/etc/selinux/vndservice_contexts, ו-vndservicemanager צריך לטעון אותו בהתחלה.

הקשרים של Seapp

ב-Android 8.0, ה-seapp_contexts מפוצל לשני קבצים:

  • plat_seapp_contexts
    • פלטפורמת Android seapp_context ללא שינויים ספציפיים למכשיר.
    • חייב להיות במחיצה system בכתובת /system/etc/selinux/plat_seapp_contexts.
  • vendor_seapp_contexts
    • תוסף ספציפי למכשיר לפלטפורמה seapp_context שנוצר על ידי שילוב של seapp_contexts שנמצא בתיקיות שמפנות אליו BOARD_SEPOLICY_DIRS בקובצי Boardconfig.mk של המכשיר.
    • חייב להיות במחיצה vendor בכתובת /vendor/etc/selinux/vendor_seapp_contexts.

הרשאות MAC

ב-Android 8.0, ה-mac_permissions.xml מפוצל לשני קבצים:

  • פלטפורמה mac_permissions.xml
    • פלטפורמת Android mac_permissions.xml ללא שינויים ספציפיים למכשיר.
    • חייב להיות במחיצה system בכתובת /system/etc/selinux/.
  • ללא פלטפורמה mac_permissions.xml
    • תוסף ספציפי למכשיר לפלטפורמה mac_permissions.xml שנוצר מ-mac_permissions.xml שנמצא בתיקיות שמפניות אליו BOARD_SEPOLICY_DIRS בקבצים Boardconfig.mk של המכשיר.
    • חייב להיות במחיצה vendor בכתובת /vendor/etc/selinux/.