הקשחת מסגרת המדיה

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

  • פיצול הרכיבים של צינור עיבוד הנתונים של AV לתהליכים בקופסה עם חול (sandbox) ספציפיים לאפליקציה.
  • הפעלת רכיבי מדיה שניתן לעדכן (חילוץ, קודיקים וכו').

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

יצרני ציוד מקורי (OEM) וספקי SoC צריכים לעדכן את השינויים ב-HAL ובמסגרת כדי שיתואמים לארכיטקטורה החדשה. באופן ספציפי, מכיוון שקוד Android שסופקו על ידי ספקים מניח לרוב שכל התהליכים פועלים באותו תהליך, הספקים צריכים לעדכן את הקוד שלהם כדי להעביר בין תהליכים מאחזים (native_handle) מקומיים שיש להם משמעות. להטמעה לדוגמה של שינויים שקשורים לחיזוק האבטחה של מדיה, אפשר לעיין במאמרים frameworks/av ו-frameworks/native.

שינויים ארכיטקטוניים

בגרסאות קודמות של Android נעשה שימוש בתהליך mediaserver יחיד ומונוליתי עם הרבה הרשאות (גישה למצלמה, גישה לאודיו, גישה למנהל הווידאו, גישה לקבצים, גישה לרשת וכו'). ב-Android 7.0, התהליך mediaserver מחולק לכמה תהליכים חדשים, לכל אחד מהם נדרשת קבוצה קטנה בהרבה של הרשאות:

הקשחת Mediaserver

איור 1. שינויים בארכיטקטורה לשיפור ההקשחה של Media Server

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

הערה: בגלל יחסי התלות של הספקים, חלק מהקודקים עדיין פועלים ב-mediaserver, וכתוצאה מכך הם מעניקים ל-mediaserver יותר הרשאות ממה שנחוץ. באופן ספציפי, Widevine Classic ממשיכה לפעול ב-mediaserver ל-Android 7.0.

שינויים ב-MediaServer

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

בסשן סטנדרטי של הפעלת קובץ מקומי, האפליקציה מעבירה מתאר קובץ (FD) אל mediaserver (בדרך כלל דרך MediaPlayer Java API), ו-mediaserver:

  1. האובייקט עוטף את ה-FD באובייקט DataSource של Binder, שמועברים לתהליך החילוץ, שמשתמש בו כדי לקרוא מהקובץ באמצעות Binder IPC. (ה-mediaextractor לא מקבל את ה-FD, אלא מבצע קריאות Binder חזרה ל-mediaserver כדי לקבל את הנתונים).
  2. השירות הזה בודק את הקובץ, יוצר את ה-extractor המתאים לסוג הקובץ (למשל MP3Extractor או MPEG4Extractor) ומחזיר למעבד mediaserver ממשק Binder ל-extractor.
  3. מבצעת קריאות IPC של Binder למחולל כדי לקבוע את סוג הנתונים בקובץ (למשל, נתוני MP3 או H.264).
  4. קריאה לתהליך mediacodec כדי ליצור קודיקים מהסוג הנדרש, קבלת ממשקי Binder לקודיקים האלה.
  5. מבצעת קריאות חוזרות של Binder IPC למחולל כדי לקרוא דגימות מוצפנות, משתמשת ב-Binder IPC כדי לשלוח נתונים מוצפנים לתהליך mediacodec לצורך פענוח ומקבלת נתונים מפוענחים.

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

שינויים ב-MediaCodecService

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

  • מקודדים של תוכנות ומפענחים לא מאובטחים נמצאים בתהליך הקודק.
  • מקודדים ומפענחים מאובטחים נמצאים ב-mediaserver (ללא שינוי).

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

שינויים ב-MediaDrmServer

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

שינויים ב-AudioServer

התהליך AudioServer מארח רכיבים שקשורים לאודיו, כמו קלט ופלט אודיו, שירות policymanager שמחליט איך לנתב את האודיו ושירות רדיו FM. פרטים על השינויים באודיו והנחיות להטמעה זמינים במאמר הטמעת אודיו.

שינויים ב-CameraServer

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

שינויים ב-ExtractorService

שירות החילוץ מארח את מחלצי הנתונים, רכיבים שמנתחים את פורמטים הקבצים השונים שנתמכים במסגרת המדיה. שירות החילוץ הוא בעל ההרשאות הנמוכות ביותר מבין כל השירותים – הוא לא יכול לקרוא קובצי FD, ולכן הוא מבצע קריאות לממשק Binder (שסופק לו על ידי mediaserver for בכל סשן הפעלה) כדי לגשת לקבצים.

אפליקציה (או mediaserver) מבצעת קריאה לתהליך החילוץ כדי לקבל IMediaExtractor, מבצעת קריאה ל-IMediaExtractor כדי לקבל את IMediaSources של הטראק שמכיל הקובץ, ואז מבצעת קריאה ל-IMediaSources כדי לקרוא ממנו נתונים.

כדי להעביר את הנתונים בין התהליכים, האפליקציה (או mediaserver) כוללת את הנתונים ב-reply-Parcel כחלק מהעסקה ב-Binder או משתמשת בזיכרון משותף:

  • השימוש בזיכרון משותף מחייב קריאה נוספת ל-Binder כדי לשחרר את הזיכרון המשותף, אבל הוא מהיר יותר ומשתמש בפחות חשמל במאגרי נתונים זמניים גדולים.
  • השימוש ב-in-Parcel מחייב העתקה נוספת, אבל הוא מהיר יותר ומשתמש בפחות חשמל למאגרים קטנים מ-64KB.

הטמעה

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

בגרסאות קודמות של Android, מאגרים מאובטחים מוקצים ב-mediaserver על ידי OMX::allocateBuffer, ומשמשים במהלך פענוח באותו תהליך, כפי שמוצג בהמשך:

איור 2. הקצאת מאגר ב-MediaServer ב-Android מגרסה 6.0 ומטה.

ב-Android 7.0, תהליך הקצאת המאגר השתנה למנגנון חדש שמספק גמישות תוך צמצום ההשפעה על הטמעות קיימות. כשמשתמשים בערימות MediaDrm ו-MediaCrypto בתהליך mediadrmserver החדש, המאגרים מוקצים באופן שונה והספקים צריכים לעדכן את הלחצנים המאובטחים של המאגרים כדי שניתן יהיה להעביר אותם דרך ה-binder כש-MediaCodec מפעיל פעולת פענוח ב-MediaCrypto.

איור 3. הקצאת מאגר ב-MediaServer ב-Android מגרסה 7.0 ואילך.

שימוש בכינויים מקומיים

הפונקציה OMX::allocateBuffer חייבת להחזיר הפניה למבנה native_handle, שמכיל מתארי קבצים (FD) ונתוני מספר שלמים נוספים. ל-native_handle יש את כל היתרונות של שימוש ב-FDs, כולל תמיכה קיימת ב-binder לסריאליזציה/דה-סריאליזציה, תוך מתן גמישות רבה יותר לספקים שלא משתמשים כרגע ב-FDs.

משתמשים ב-native_handle_create() כדי להקצות את הכינוי המקורי. קוד המסגרת מקבל בעלות על המבנה native_handle שהוקצה, והוא אחראי על שחרור המשאבים גם בתהליך שבו native_handle הוקצה במקור וגם בתהליך שבו הוא עבר סריאליזציה. המסגרת משחררת טיפולים (handles) מקומיים עם native_handle_close() ואחריו native_handle_delete(), ומבצעת סריאליזציה או דה-סריאליזציה של native_handle באמצעות Parcel::writeNativeHandle()/readNativeHandle().

ספקי SoC שמשתמשים ב-FDs כדי לייצג מאגרים מאובטחים יכולים לאכלס את ה-FD ב-native_handle באמצעות ה-FD שלהם. ספקים שלא משתמשים ב-FDs יכולים לייצג מאגרים מאובטחים באמצעות שדות נוספים ב-native_buffer.

הגדרת מיקום הפענוח

הספקים צריכים לעדכן את שיטת פענוח ה-OEMCrypto שפועלת ב-native_handle כדי לבצע פעולות ספציפיות לספק הנדרשות כדי שניתן יהיה להשתמש ב-native_handle במרחב התהליכים החדש (השינויים כוללים בדרך כלל עדכונים בספריות OEMCrypto).

מכיוון ש-allocateBuffer היא פעולה רגילה של OMX, Android 7.0 כולל תוסף OMX חדש (OMX.google.android.index.allocateNativeHandle) לשליחת שאילתה לגבי התמיכה הזו, וקריאה ל-OMX_SetParameter שמעדכנת את ההטמעה של OMX שצריך להשתמש במזהים מקומיים.