הטמעת dm-verity

Android 4.4 ואילך תומך ב'הפעלה מאומתת' באמצעות תכונת ליבה (kernel) של מיפוי מכשירים (dm-verity), שמספקת שקיפות בדיקת תקינות של מכשירים חוסמים. dm-verity עוזר במניעת Rootkits מתמידים שיכול לשמור על הרשאות בסיס ולהגן על מכשירים. הזה תכונה שעוזרת למשתמשי Android לוודא שכאשר מפעילים מכשיר, המכשיר נמצא באותו מיקום כשהשימוש האחרון היה בשימוש.

אפליקציות שעלולות להזיק (PHA) עם הרשאות שורש יכולות להסתתר מפני תוכנות זיהוי ואנונימיזציה בדרך אחרת. לתוכנת השורש (Root) יש אפשרות כי לרוב יש לו יותר הרשאות מהמזהים, דבר שמאפשר תוכנה ל"שקר" לתוכנות האיתור.

התכונה dm-verity מאפשרת לך לצפות במכשיר בלוק, האחסון הבסיסי של מערכת הקבצים, ולקבוע אם היא תואמת הגדרה אישית. לשם כך נעשה שימוש בעץ גיבוב (hash) קריפטוגרפי. לכל בלוק (בדרך כלל 4k), יש גיבוב מסוג SHA256.

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

dm-verity-hash-table

איור 1. טבלת גיבוב (hash) מסוג dm-verity

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

פעולה

הגנת ה-dm-verity נמצאת בליבה (kernel). כך שאם תוכנת רוט (Root) מהווה סיכון לפני שהליבה עולה, היא שומרת על הגישה הזו. כדי לצמצם את הסיכון רוב היצרנים מאמתים את הליבה באמצעות מפתח שצורף במכשיר. לא ניתן לשנות את המפתח הזה כשהמכשיר יוצא מהמפעל.

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

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

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

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

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

תיקון שגיאת העברה

מערכת Android 7.0 ואילך משפרת את האמינות של dm-ver עם שגיאות הרצה תיקון (FEC). הטמעת ה-AOSP מתחילה קוד לתיקון שגיאה של Reed-Solomon ומחיל שנקראת 'interleaving' כדי לצמצם את תקורת החלל ולהגדיל את מספר הבלוקים הפגומים שאפשר לשחזר. פרטים נוספים על FEC זמינים במאמר אתחול מאומת באכיפה מחמירה עם תיקון שגיאה.

הטמעה

סיכום

  1. יצירת תמונת מערכת ext4.
  2. יוצרים עץ גיבוב (hash) של התמונה הזו.
  3. יוצרים טבלת dm-verity לעץ הגיבוב הזה.
  4. מסמנים את טבלת ה-dm-verity כדי ליצור טבלה. לחתימה.
  5. קיבוץ של חתימת הטבלה וטבלה של dm-verity למטא-נתונים אמיתיים.
  6. משרשרים את תמונת המערכת, את המטא-נתונים של האימותים ואת עץ הגיבוב.

לעיון במאמר The Chromium Projects – הפעלה מאומתת לתיאור מפורט של עץ הגיבוב והטבלה dm-verity.

יצירת עץ הגיבוב

כפי שמתואר במבוא, עץ הגיבוב הוא חלק בלתי נפרד מ-dm-verity. הכלי cryptsetup ליצור בשבילכם עץ גיבוב (hash). לחלופין, אפשרות תואמת מוגדרת כאן:

<your block device name> <your block device name> <block size> <block size> <image size in blocks> <image size in blocks + 8> <root hash> <salt>

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

התהליך הזה מתבצע עד שגיבובי SHA256 של השכבה הקודמת יוכלו להיכנס חסימה. כשמקבלים את ה-SHA256 של הבלוק הזה, מקבלים את הגיבוב הבסיסי (root) של העץ.

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

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

כדי ליצור את עץ הגיבוב, משרשרים את הגיבובים של שכבה 2 לאלה של השכבה 1, שכבה 3 מבצעת 'גיבובים' על גבי שכבה 2, וכן הלאה. לכתוב את כל זה לדיסק. חשוב לזכור שהיא לא מפנה לשכבה 0 של הגיבוב של הרמה הבסיסית (root).

לסיכום, האלגוריתם הכללי לבניית עץ הגיבוב הוא באופן הבא:

  1. בוחרים נתוני salt אקראי (קידוד הקסדצימלי).
  2. מחלצים את תמונת המערכת לבלוקים של 4k.
  3. לכל בלוק, מקבלים את הגיבוב SHA256 (המולא) שלו.
  4. משרשרים את 'טביעות האצבע' האלה כדי ליצור רמה
  5. ממקמים את הרמה עם 0s עד לגבול של 4k.
  6. משרשרים את הרמה לעץ הגיבוב (hash).
  7. חוזרים על שלבים 2 עד 6 תוך שימוש ברמה הקודמת כמקור לשלב הבא, עד יש רק גיבוב אחד.

התוצאה היא גיבוב יחיד, שהוא גיבוב השורש שלכם. הפריט הזה וה-salt שלך משמשים במהלך בניית טבלת המיפוי dm-verity.

בניית טבלת המיפוי dm-verity

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

אפשר לקרוא מידע נוסף בקטע cryptsetup (הגדרה של קריפטו) תיאור מפורט של השדות בטבלת המיפוי של יעד האימות.

חתימה על טבלת ה-dm-verity

חותמים על הטבלה dm-verity כדי ליצור חתימת טבלה. כאשר מתבצע אימות המחיצה, חתימת הטבלה מאומתת קודם. הפעולה הזו מתבצעת מול מקש ב- קובץ אימג' האתחול במיקום קבוע. בדרך כלל מפתחות כלולים יצרנים לפתח מערכות להכללה אוטומטית במכשירים המיקום.

כדי לאמת את המחיצה עם השילוב הזה של חתימה ומקשים:

  1. מוסיפים מפתח RSA-2048 בפורמט שתואם ל-libmincrypt אל קטגוריה אחת (/boot) בשעה /verity_key. זיהוי המיקום של המפתח שמשמש לאימות את עץ הגיבוב (hash).
  2. ב-fstab של הרשומה הרלוונטית, מוסיפים verify לדגלים fs_mgr.

איחוד חתימת הטבלה למטא-נתונים

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

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

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

ערכי הבייטים הקסדצימליים הם:

  • בייט ראשון = b0
  • בייט שני = 01
  • בייט שלישי = b0
  • בייט רביעי = 01

בתרשים הבא מתואר פירוט של המטא-נתונים של אימות הנתונים:

<magic number>|<version>|<signature>|<table length>|<table>|<padding>
\-------------------------------------------------------------------/
\----------------------------------------------------------/   |
                            |                                  |
                            |                                 32K
                       block content

הטבלה הזו מתארת את שדות המטא-נתונים האלה.

טבלה 1. שדות של מטא-נתונים של קריטריון ההערכה

שדה המטרה גודל ערך
מספר קסום משמש את fs_mgr כבדיקת שפיות 4 בייטים 0xb001b001
גרסה שמשמשת לגרסה של בלוק המטא-נתונים 4 בייטים כרגע 0
חתימה החתימה של הטבלה בצורה מרופדת PKCS1.5 256 בייטים
אורך הטבלה אורך הטבלה dm-verity בבייטים 4 בייטים
שולחן הטבלה dm-verity שמתוארת קודם בייטים לאורך הטבלה
padding למבנה הזה יש מרווח של 0 עד 32 קראט 0

אופטימיזציה של dm-verity

כדי להשיג את הביצועים הטובים ביותר ב-dm-verity, יש לבצע את הפעולות הבאות:

  • בליבה (kernel), מפעילים את NEON SHA-2 עבור ARMv7 ואת SHA-2 ל-ARMv8.
  • התנסות עם תכונות שונות של Readahead ו-prefetch_cluster כדי למצוא את התצורה הטובה ביותר עבור המכשיר שלך.