אימות SELinux

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

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

הפקודה הזו מדפיסה את מצב SELinux ברמת המערכת: Enforcing או Permissive. כדי לקבוע את מצב SELinux לכל דומיין, צריך לבדוק את הקבצים המתאימים או להריץ את הגרסה העדכנית ביותר של sepolicy-analyze עם הדגל המתאים (-p) שמופיע ב- /platform/system/sepolicy/tools/.

קריאת דחיות

בודקים אם יש שגיאות. השגיאות מנותבות כיומני אירועים אל dmesg ואל logcat, וניתן לראות אותן באופן מקומי במכשיר. היצרנים צריכים לבדוק את הפלט של SELinux ל-dmesg במכשירים האלה ולשפר את ההגדרות לפני הפרסום הציבורי במצב הרשאה, ולאחר מכן לעבור למצב אכיפה. הודעות ביומן של SELinux מכילות את avc:, ולכן אפשר למצוא אותן בקלות באמצעות grep. אפשר לתעד את יומני הדחייה המתמשכים על ידי הפעלת cat /proc/kmsg, או לתעד את יומני הדחייה מההפעלה הקודמת על ידי הפעלת cat /sys/fs/pstore/console-ramoops.

כדי למנוע הצפה של היומנים, הודעות השגיאה של SELinux מוגבלות בקצב אחרי שההפעלה מסתיימת. כדי לוודא שמוצגות לכם כל ההודעות הרלוונטיות, תוכלו להריץ את הפקודה adb shell auditctl -r 0.

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

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

avc: denied  { connectto } for  pid=2671 comm="ping" path="/dev/socket/dnsproxyd"
scontext=u:r:shell:s0 tcontext=u:r:netd:s0 tclass=unix_stream_socket

כך מפרשים את הפלט הזה:

  • הערך { connectto } שלמעלה מייצג את הפעולה שננקטה. יחד עם הערך tclass בסוף (unix_stream_socket), אפשר לדעת בערך מה בוצע ומה היה הגורם לכך. במקרה הזה, משהו ניסה להתחבר ליציאת יצירת הזרם של Unix.
  • השדה scontext (u:r:shell:s0) מציין את ההקשר שהפעיל את הפעולה. במקרה הזה, זהו משהו שפועל בתור המעטפת.
  • השדה tcontext (u:r:netd:s0) מציין את ההקשר של היעד של הפעולה. במקרה הזה, זהו unix_stream_socket בבעלות netd.
  • הערך comm="ping" בחלק העליון מספק רמז נוסף לגבי הפעולה שהייתה בשימוש בזמן יצירת הדחייה. במקרה הזה, זוהי עצה טובה למדי.

דוגמה נוספת:

adb shell su root dmesg | grep 'avc: '

אודיו יוצא:

<5> type=1400 audit: avc:  denied  { read write } for  pid=177
comm="rmt_storage" name="mem" dev="tmpfs" ino=6004 scontext=u:r:rmt:s0
tcontext=u:object_r:kmem_device:s0 tclass=chr_file

אלה הרכיבים העיקריים מהדחייה הזו:

  • פעולה – הפעולה שנעשתה בניסיון מודגשת בסוגריים, read write או setenforce.
  • Actor – הרשומה scontext (הקשר המקור) מייצגת את הגורם המבצע, במקרה הזה הדימון rmt_storage.
  • Object – הרשומה tcontext (הקשר היעד) מייצגת את האובייקט שבו מבוצעת הפעולה, במקרה הזה kmem.
  • תוצאה – הרשומה tclass (target class) מציינת את סוג האובייקט שבו מבוצעת הפעולה, במקרה הזה chr_file (character device).

יצירת גרסת dump של סטאקים של משתמשים ושל ליבה

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

ליבות עדכניות מגדירות נקודת מעקב בשם avc:selinux_audited. משתמשים ב-Android‏ simpleperf כדי להפעיל את נקודת המעקב הזו ולתעד את שרשור הקריאות.

תצורה נתמכת

  • יש תמיכה בליבה של Linux בגרסה 5.10 ואילך, במיוחד בהסתעפויות של Android Common Kernel‏ mainline ו-android12-5.10. יש גם תמיכה בהסתעפות android12-5.4. אפשר להשתמש ב-simpleperf כדי לקבוע אם נקודת המעקב מוגדרת במכשיר: adb root && adb shell simpleperf list | grep avc:selinux_audited. בגרסאות ליבה אחרות, אפשר לבחור את ההוספות dd81662 ו-30969bc.
  • צריך להיות אפשרי לשחזר את האירוע שאתם מנסים לנפות באגים בו. אירועים בזמן האתחול לא נתמכים באמצעות simpleperf, אבל יכול להיות שעדיין תוכלו להפעיל מחדש את השירות כדי להפעיל את האירוע.

איך מתעדים את שרשרת השיחות

השלב הראשון הוא לתעד את האירוע באמצעות simpleperf record:

adb shell -t "cd /data/local/tmp && su root simpleperf record -a -g -e avc:selinux_audited"

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

^Csimpleperf I cmd_record.cpp:751] Samples recorded: 1. Samples lost: 0.

לבסוף, אפשר להשתמש ב-simpleperf report כדי לבדוק את סטאק-טריי הנתון. לדוגמה:

adb shell -t "cd /data/local/tmp && su root simpleperf report -g --full-callgraph"
[...]
Children  Self     Command  Pid   Tid   Shared Object                                   Symbol
100.00%   0.00%    dmesg    3318  3318  /apex/com.android.runtime/lib64/bionic/libc.so  __libc_init
       |
       -- __libc_init
          |
           -- main
              toybox_main
              toy_exec_which
              dmesg_main
              klogctl
              entry_SYSCALL_64_after_hwframe
              do_syscall_64
              __x64_sys_syslog
              do_syslog
              selinux_syslog
              slow_avc_audit
              common_lsm_audit
              avc_audit_post_callback
              avc_audit_post_callback

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

מעבר למצב הרשאה רחבה

אפשר להשבית את אכיפת SELinux באמצעות adb ב-builds של userdebug או של eng. לשם כך, קודם צריך להעביר את ADB ל-root על ידי הפעלת adb root. לאחר מכן, כדי להשבית את האכיפה של SELinux, מריצים את הפקודה:

adb shell setenforce 0

או בשורת הפקודה של הליבה (במהלך ההפעלה המוקדמת של המכשיר):

androidboot.selinux=permissive
androidboot.selinux=enforcing

או דרך bootconfig ב-Android 12:

androidboot.selinux=permissive
androidboot.selinux=enforcing

שימוש ב-audit2allow

הכלי audit2allow מקבל דחיות dmesg וממיר אותן להצהרות מדיניות תואמות של SELinux. לכן, הוא יכול להאיץ מאוד את הפיתוח של SELinux.

כדי להשתמש בו, מריצים את הפקודה:

adb pull /sys/fs/selinux/policy
adb logcat -b events -d | audit2allow -p policy

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

#============= shell ==============
allow shell kernel:security setenforce;
#============= rmt ==============
allow rmt kmem_device:chr_file { read write };

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