הטמעת dm-verity

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

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

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

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

dm-verity-hash-table

איור 1. טבלת גיבוב של dm-verity

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

פעולה

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

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

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

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

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

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

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

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

הטמעה

סיכום

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

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

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

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

<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 ל-4,096 בלוקים, לכל אחד מהם מוקצה גיבוב SHA256. שכבה 1 נוצרת על ידי צירוף של גיבובי ה-SHA256 האלה בלבד לבלוקים של 4K, וכתוצאה מכך נוצרת תמונה קטנה בהרבה. שכבה 2 נוצרת באופן זהה, עם גיבובי SHA256 של שכבה 1.

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

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

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

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

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

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

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

יצירת טבלת המיפוי של dm-verity

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

תיאור מפורט של השדות בטבלת המיפוי של יעד ה-Verity זמין במאמר cryptsetup.

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

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

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

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

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

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

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

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

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

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

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

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

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

טבלה 1. שדות מטא-נתונים של Verity

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

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

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

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