פורמט המכולה Android Pony EXpress (APEX) הוצג ב-Android 10 והוא משמש בתהליך ההתקנה של מודולים ברמה נמוכה יותר במערכת. הפורמט הזה מאפשר לעדכן רכיבי מערכת שלא מתאימים למודל האפליקציות הרגיל של Android. דוגמאות לרכיבים כאלה הן ספריות ושירותים מקוריים, שכבות הפשטה של חומרה (HAL), זמן ריצה (ART) וספריות מחלקות.
המונח APEX יכול להתייחס גם לקובץ APEX.
רקע
למרות שמערכת Android תומכת בעדכונים של מודולים שמתאימים למודל האפליקציה הרגיל (לדוגמה, שירותים, פעילויות) באמצעות אפליקציות להתקנת חבילות (כמו אפליקציית חנות Google Play), לשימוש במודל דומה לרכיבי מערכת הפעלה ברמה נמוכה יותר יש את החסרונות הבאים:
- אי אפשר להשתמש במודולים מבוססי APK בשלב מוקדם ברצף האתחול. מנהל החבילות הוא המאגר המרכזי של מידע על אפליקציות, ואפשר להפעיל אותו רק ממנהל הפעילות, שמוכן בשלב מאוחר יותר של תהליך האתחול.
- פורמט ה-APK (ובמיוחד המניפסט) מיועד לאפליקציות ל-Android, ולא תמיד מתאים למודולים של המערכת.
עיצוב
בקטע הזה מתואר העיצוב הכללי של פורמט קובץ APEX ומנהל APEX, שהוא שירות שמנהל קובצי APEX.
מידע נוסף על הסיבות לבחירת העיצוב הזה של APEX זמין במאמר חלופות שנשקלו במהלך הפיתוח של APEX.
פורמט APEX
זהו הפורמט של קובץ APEX.

איור 1. פורמט קובץ APEX
ברמה העליונה, קובץ APEX הוא קובץ ZIP שבו הקבצים מאוחסנים ללא דחיסה וממוקמים בגבולות של 4 KB.
ארבעת הקבצים בקובץ APEX הם:
apex_manifest.jsonAndroidManifest.xmlapex_payload.imgapex_pubkey
קובץ apex_manifest.json מכיל את שם החבילה והגרסה, שמזהים קובץ APEX. זהו מאגר אחסון לפרוטוקולים של ApexManifest בפורמט JSON.
הקובץ AndroidManifest.xml מאפשר לקובץ ה-APEX להשתמש בכלים ובאינפראסטרוקטורה שקשורים ל-APK, כמו ADB, PackageManager ואפליקציות להתקנת חבילות (כמו חנות Play). לדוגמה, אפשר להשתמש בכלי קיים כמו aapt כדי לבדוק מטא-נתונים בסיסיים מהקובץ. הקובץ מכיל את שם החבילה ופרטי הגרסה. המידע הזה זמין בדרך כלל גם בapex_manifest.json.
מומלץ להשתמש ב-apex_manifest.json במקום ב-AndroidManifest.xml במערכות ובקוד חדשים שמתייחסים ל-APEX. יכול להיות ש-AndroidManifest.xml יכיל מידע נוסף על טירגוט שאפשר להשתמש בו בכלי פרסום האפליקציות הקיימים.
apex_payload.img היא קובץ אימג' של המערכת מסוג ext4 שמגובה על ידי dm-verity. התמונה מותקנת בזמן הריצה באמצעות מכשיר loopback. ספציפית, עץ הגיבוב ובלוק המטא-נתונים נוצרים באמצעות ספריית libavb. המטען הייעודי (payload) של מערכת הקבצים לא מנותח (כי התמונה אמורה להיות ניתנת להרכבה במקום). קבצים רגילים נכללים בקובץ apex_payload.img.
apex_pubkey הוא המפתח הציבורי שמשמש לחתימה על קובץ אימג' של המערכת. בזמן הריצה, המפתח הזה מוודא שחבילת ה-APEX שהורדה חתומה על ידי אותו גורם שחתם על אותה חבילת APEX במחיצות המובנות.
הנחיות למתן שמות ב-APEX
כדי למנוע התנגשויות בשמות בין רשומות APEX חדשות ככל שהפלטפורמה מתקדמת, מומלץ לפעול לפי ההנחיות הבאות למתן שמות:
com.android.*- שמור ל-APEX של AOSP. לא ייחודי לאף חברה או מכשיר.
com.<companyname>.*- שמור לחברה. יכול להיות שהמכשיר נמצא בשימוש של כמה מכשירים של אותה חברה.
com.<companyname>.<devicename>.*- שמורות לקובצי APEX ייחודיים למכשיר ספציפי (או לקבוצת משנה של מכשירים).
מנהל APEX
מנהל ה-APEX (או apexd) הוא תהליך מקורי עצמאי שאחראי על אימות, התקנה והסרה של קובצי APEX. התהליך הזה מופעל ומוכן בשלב מוקדם ברצף האתחול. בדרך כלל קובצי APEX מותקנים מראש במכשיר בתיקייה /system/apex. אם אין עדכונים זמינים, מנהל APEX משתמש בחבילות האלה כברירת מחדל.
רצף העדכון של APEX משתמש במחלקה PackageManager והוא מתבצע באופן הבא.
- קובץ APEX מורידים דרך אפליקציה להתקנת חבילות, דרך ADB או ממקור אחר.
- מנהל החבילות מתחיל את תהליך ההתקנה. כשמזהים שהקובץ הוא APEX, מנהל החבילות מעביר את השליטה למנהל ה-APEX.
- מנהל ה-APEX מאמת את קובץ ה-APEX.
- אם קובץ ה-APEX מאומת, מסד הנתונים הפנימי של מנהל ה-APEX מתעדכן כדי לשקף את העובדה שקובץ ה-APEX יופעל באתחול הבא.
- מבקש ההתקנה מקבל שידור לאחר אימות החבילה בהצלחה.
- כדי להמשיך בהתקנה, צריך להפעיל מחדש את המערכת.
בהפעלה הבאה, מנהל ה-APEX מתחיל, קורא את מסד הנתונים הפנימי ומבצע את הפעולות הבאות לכל קובץ APEX שמופיע ברשימה:
- מאמת את קובץ ה-APEX.
- יוצרת מכשיר loopback מקובץ ה-APEX.
- יוצר מכשיר בלוק של מיפוי מכשירים על גבי מכשיר הלולאה החוזרת.
- המערכת מבצעת Mount של מכשיר הבלוקים של מיפוי המכשירים לנתיב ייחודי (לדוגמה,
/apex/name@ver).
כשכל קובצי ה-APEX שמופיעים במסד הנתונים הפנימי מותקנים, מנהל ה-APEX מספק שירות binder לרכיבי מערכת אחרים כדי לשלוח שאילתות לגבי קובצי ה-APEX המותקנים. לדוגמה, רכיבי מערכת אחרים יכולים לשלוח שאילתה לרשימת קובצי ה-APEX שמותקנים במכשיר או לשלוח שאילתה לנתיב המדויק שבו קובץ APEX מסוים מותקן, כדי לקבל גישה לקבצים.
קובצי APEX הם קובצי APK
קובצי APEX הם קובצי APK תקינים כי הם ארכיוני ZIP חתומים (באמצעות סכמת החתימה על APK) שמכילים קובץ AndroidManifest.xml. כך קובצי APEX יכולים להשתמש בתשתית של קובצי APK, כמו אפליקציה להתקנת חבילות, כלי החתימה ומנהל החבילות.
קובץ ה-AndroidManifest.xml בתוך קובץ APEX הוא מינימלי, והוא כולל את החבילה name, versionCode ואת האפשרויות targetSdkVersion, minSdkVersion ו-maxSdkVersion (אופציונליות) לטירגוט מדויק. המידע הזה מאפשר להעביר קובצי APEX דרך ערוצים קיימים כמו אפליקציות להתקנת חבילות ו-ADB.
סוגי קבצים נתמכים
פורמט APEX תומך בסוגי הקבצים הבאים:
- ספריות משותפות מקוריות
- קובצי הפעלה מקוריים
- קובצי JAR
- קובצי נתונים
- קובצי תצורה
זה לא אומר ש-APEX יכול לעדכן את כל סוגי הקבצים האלה. האפשרות לעדכן סוג קובץ תלויה בפלטפורמה וביציבות של הגדרות הממשקים של סוגי הקבצים.
אפשרויות חתימה
קובצי APEX נחתמים בשתי דרכים. קודם כול, קובץ apex_payload.img (למעשה, מתאר ה-vbmeta שמצורף ל-apex_payload.img) נחתם באמצעות מפתח.
לאחר מכן, כל ה-APEX חתום באמצעות סכמת החתימה על APK v3. בתהליך הזה נעשה שימוש בשני מקשים שונים.
בצד המכשיר, מותקן מפתח ציבורי שמתאים למפתח הפרטי ששימש לחתימה על מתאר ה-vbmeta. מנהל ה-APEX משתמש במפתח הציבורי כדי לאמת קובצי APEX שמתבקשים להתקין. כל APEX צריך להיות חתום באמצעות מפתחות שונים, והאכיפה מתבצעת בזמן הבנייה ובזמן הריצה.
APEX במחיצות מובנות
קובצי APEX יכולים להיות ממוקמים במחיצות מובנות כמו /system. המחיצה כבר נמצאת מעל dm-verity, ולכן קובצי ה-APEX נטענים ישירות מעל מכשיר הלולאה החוזרת.
אם קובץ APEX קיים במחיצה מובנית, אפשר לעדכן אותו על ידי אספקת חבילת APEX עם אותו שם חבילה וקוד גרסה גדול או שווה. קובץ ה-APEX החדש מאוחסן ב-/data, ובדומה לקובצי APK, הגרסה החדשה שהותקנה מסתירה את הגרסה שכבר קיימת במחיצה המובנית. אבל בניגוד לקובצי APK, הגרסה החדשה של APEX מופעלת רק אחרי הפעלה מחדש.
דרישות ליבה
כדי לתמוך במודולים של APEX mainline במכשיר Android, נדרשות התכונות הבאות של ליבת Linux: מנהל ההתקן של loopback ו-dm-verity. מנהל ההתקן של ה-loopback טוען את קובץ אימג' של המערכת במודול APEX, ו-dm-verity מאמת את מודול ה-APEX.
הביצועים של מנהל ההתקן של ה-loopback ושל dm-verity חשובים להשגת ביצועים טובים של המערכת כשמשתמשים במודולי APEX.
גרסאות ליבה נתמכות
מודולים מרכזיים של APEX נתמכים במכשירים עם ליבה מגרסה 4.4 ומעלה. במכשירים חדשים עם Android מגרסה 10 ומעלה צריך להשתמש בגרסת ליבה 4.9 ומעלה כדי לתמוך במודולי APEX.
תיקוני ליבה נדרשים
תיקוני הליבה הנדרשים לתמיכה במודולי APEX כלולים בעץ המשותף של Android. כדי לקבל את תיקוני ה-patch לתמיכה ב-APEX, צריך להשתמש בגרסה העדכנית של עץ Android המשותף.
גרסת ליבה 4.4
הגרסה הזו נתמכת רק במכשירים שמשודרגים מ-Android 9 ל-Android 10 ורוצים לתמוך במודולי APEX. כדי לקבל את התיקונים הנדרשים, מומלץ לבצע מיזוג כלפי מטה מהענף android-4.4. בהמשך מפורטת רשימה של תיקוני האבטחה הנדרשים לגרסה 4.4 של ליבת המערכת.
- UPSTREAM: loop: add ioctl for changing logical block size (4.4)
- BACKPORT: block/loop: set hw_sectors (4.4)
- UPSTREAM: loop: Add LOOP_SET_BLOCK_SIZE in compat ioctl (4.4)
- ANDROID: mnt: Fix next_descendent (4.4)
- ANDROID: mnt: remount should propagate to slaves of slaves (4.4)
- ANDROID: mnt: Propagate remount correctly (4.4)
- Revert "ANDROID: dm verity: add minimum prefetch size" (4.4)
- UPSTREAM: loop: drop caches if offset or block_size are changed (4.4)
גרסאות ליבה 4.9/4.14/4.19
כדי לקבל את התיקונים הנדרשים לגרסאות ליבת 4.9/4.14/4.19, צריך לבצע מיזוג כלפי מטה מהענף android-common.
אפשרויות הגדרה נדרשות של ליבת המערכת
ברשימה הבאה מפורטות דרישות ההגדרה הבסיסיות לתמיכה במודולי APEX שהוצגו ב-Android 10. הפריטים שמסומנים בכוכבית (*) הם דרישות קיימות מ-Android 9 ומגרסאות קודמות.
(*) CONFIG_AIO=Y # AIO support (for direct I/O on loop devices)
CONFIG_BLK_DEV_LOOP=Y # for loop device support
CONFIG_BLK_DEV_LOOP_MIN_COUNT=16 # pre-create 16 loop devices
(*) CONFIG_CRYPTO_SHA1=Y # SHA1 hash for DM-verity
(*) CONFIG_CRYPTO_SHA256=Y # SHA256 hash for DM-verity
CONFIG_DM_VERITY=Y # DM-verity support
הדרישות לפרמטרים של שורת פקודה של ליבת המערכת
כדי לתמוך ב-APEX, צריך לוודא שהפרמטרים של שורת הפקודה של ליבת המערכת עומדים בדרישות הבאות:
- אסור להגדיר את
loop.max_loop - הערך של
loop.max_partחייב להיות 8 או פחות
פיתוח APEX
בקטע הזה מוסבר איך ליצור APEX באמצעות מערכת ה-Build של Android.
הדוגמה הבאה היא של Android.bp עבור APEX בשם apex.test.
apex {
name: "apex.test",
manifest: "apex_manifest.json",
file_contexts: "file_contexts",
// libc.so and libcutils.so are included in the apex
native_shared_libs: ["libc", "libcutils"],
binaries: ["vold"],
java_libs: ["core-all"],
prebuilts: ["my_prebuilt"],
compile_multilib: "both",
key: "apex.test.key",
certificate: "platform",
}
apex_manifest.json דוגמה:
{
"name": "com.android.example.apex",
"version": 1
}
file_contexts דוגמה:
(/.*)? u:object_r:system_file:s0
/sub(/.*)? u:object_r:sub_file:s0
/sub/file3 u:object_r:file3_file:s0
סוגי קבצים ומיקומים ב-APEX
| סוג הקובץ | מיקום ב-APEX |
|---|---|
| ספריות משותפות | /lib ו-/lib64 (/lib/arm ל-ARM מתורגם ב-x86) |
| קובצי הפעלה | /bin |
| ספריות Java | /javalib |
| תצורות מוכנות מראש | /etc |
יחסי תלות טרנזיטיביים
קבצי APEX כוללים באופן אוטומטי יחסי תלות טרנזיטיביים של ספריות משותפות מקוריות או קבצים הפעלה. לדוגמה, אם libFoo תלוי ב-libBar, שתי הספריות נכללות כשמופיע רק libFoo בנכס native_shared_libs.
טיפול בכמה ממשקי ABI
מתקינים את המאפיין native_shared_libs גם בממשק הבינארי הראשי וגם בממשק הבינארי המשני של המכשיר. אם חבילת APEX מטרגטת מכשירים עם ABI יחיד (כלומר, 32 ביט בלבד או 64 ביט בלבד), מותקנות רק ספריות עם ה-ABI המתאים.
מתקינים את המאפיין binaries רק עבור ממשק ה-ABI הראשי של המכשיר, כמו שמתואר בהמשך:
- אם המכשיר הוא 32 ביט בלבד, רק גרסת 32 ביט של הקובץ הבינארי מותקנת.
- אם המכשיר הוא 64 ביט בלבד, מותקנת רק גרסת 64 ביט של הקובץ הבינארי.
כדי להוסיף בקרת גישה פרטנית לממשקי ה-ABI של הספריות והקבצים הבינאריים המקוריים, משתמשים במאפיינים multilib.[first|lib32|lib64|prefer32|both].[native_shared_libs|binaries].
-
first: תואם לממשק ה-ABI הראשי של המכשיר. זו ברירת המחדל עבור קבצים בינאריים. -
lib32: תואם ל-ABI של 32 ביט של המכשיר, אם נתמך. -
lib64: תואם ל-ABI של 64 ביט במכשיר, אם הוא נתמך. -
prefer32: תואם ל-ABI של 32 ביט של המכשיר, אם נתמך. אם ממשק ה-ABI של 32 ביט לא נתמך, הוא תואם לממשק ה-ABI של 64 ביט. -
both: מתאים לשני ממשקי ה-ABI. זוהי ברירת המחדל שלnative_shared_libraries.
המאפיינים java, libraries ו-prebuilts לא תלויים ב-ABI.
הדוגמה הזו היא למכשיר שתומך ב-32/64 ולא מעדיף 32:
apex {
// other properties are omitted
native_shared_libs: ["libFoo"], // installed for 32 and 64
binaries: ["exec1"], // installed for 64, but not for 32
multilib: {
first: {
native_shared_libs: ["libBar"], // installed for 64, but not for 32
binaries: ["exec2"], // same as binaries without multilib.first
},
both: {
native_shared_libs: ["libBaz"], // same as native_shared_libs without multilib
binaries: ["exec3"], // installed for 32 and 64
},
prefer32: {
native_shared_libs: ["libX"], // installed for 32, but not for 64
},
lib64: {
native_shared_libs: ["libY"], // installed for 64, but not for 32
},
},
}
חתימת vbmeta
לחתום על כל APEX באמצעות מפתחות שונים. כשנדרש מפתח חדש, יוצרים זוג מפתחות ציבורי/פרטי ומכינים מודול apex_key. משתמשים במאפיין key כדי לחתום על ה-APEX באמצעות המפתח. המפתח הציבורי נכלל אוטומטית ב-APEX עם השם avb_pubkey.
# create an rsa key pairopenssl genrsa -out foo.pem 4096# extract the public key from the key pairavbtool extract_public_key --key foo.pem --output foo.avbpubkey# in Android.bpapex_key { name: "apex.test.key", public_key: "foo.avbpubkey", private_key: "foo.pem", }
בדוגמה שלמעלה, השם של המפתח הציבורי (foo) הופך למזהה של המפתח. המזהה של המפתח שמשמש לחתימה על APEX נכתב ב-APEX. בזמן הריצה, apexd מאמת את ה-APEX באמצעות מפתח ציבורי עם אותו מזהה במכשיר.
חתימת APEX
חותמים על קובצי APEX באותו אופן שבו חותמים על קובצי APK. חתימה על קובצי APEX פעמיים: פעם אחת עבור מערכת הקבצים הקטנה (קובץ apex_payload.img) ופעם אחת עבור הקובץ כולו.
כדי לחתום על APEX ברמת הקובץ, מגדירים את המאפיין certificate באחת משלוש הדרכים הבאות:
- לא מוגדר: אם לא מוגדר ערך, חבילת ה-APEX נחתמת באמצעות האישור שנמצא בכתובת
PRODUCT_DEFAULT_DEV_CERTIFICATE. אם לא מוגדר דגל, נתיב ברירת המחדל הואbuild/target/product/security/testkey. -
<name>: חתימת ה-APEX מתבצעת באמצעות אישור<name>באותה ספרייה כמוPRODUCT_DEFAULT_DEV_CERTIFICATE. -
:<name>: ה-APEX חתום באמצעות האישור שמוגדר על ידי מודול Soong שנקרא<name>. אפשר להגדיר את מודול האישורים באופן הבא.
android_app_certificate {
name: "my_key_name",
certificate: "dir/cert",
// this will use dir/cert.x509.pem (the cert) and dir/cert.pk8 (the private key)
}
ניהול מפתחות
מפתחות בדיקה משמשים לגרסאות build לפיתוח, ומפתחות הפצה משמשים לחתימה על גרסאות build ציבוריות. כמו שמתואר במאמר בנושא החלפת מפתח לחתימת APEX, לפני פרסום לציבור צריך להחליף את כל מפתחות הבדיקה במפתחות ההפצה המתאימים. שרתי build של OEM יכולים לשלב את כלי המארח sign_target_files_apks, שחותם מחדש על קובץ אימג' של המערכת ועל קובץ ה-APEX כולו עבור כל קובצי ה-APEX שנמצאים בארכיון ZIP של קובצי היעד.
כדי לשמור על האבטחה, חשוב לפעול לפי השיטות המומלצות הבאות לניהול מפתחות ולפעולות חתימה על גרסאות:
חשוב לשמור את מפתחות ההפצה בסביבה מאובטחת כדי להגביל את הגישה אליהם.
רשימת בקרת גישה (ACL) צריכה לשלוט בהפעלת פעולות חתימה של מהדורות.
חותמים על ארטיפקטים באמצעות מפתח הפצה רק אחרי שהם נבדקים ומאושרים להפצה.
אדם צריך להפעיל את הפעולות של חתימת הגרסה, ולא להפוך את התהליך הזה לאוטומטי.
פריטים חתומים באמצעות מפתח שחרור צריכים להיות מאוחסנים בסביבה מאובטחת.
הגישה לארטיפקטים שחתומים באמצעות מפתח ההפצה צריכה להיות מוגבלת לסיבות עסקיות תקפות.
שרת ה-build של ה-OEM חייב לשמור רשומה של כל בקשת חתימה במסד נתונים של חתימות.
התקנה של APEX
כדי להתקין APEX, משתמשים ב-ADB.
adb install apex_file_nameadb reboot
אם הערך של supportsRebootlessUpdate מוגדר כ-true ב-apex_manifest.json וחבילת ה-APEX שמותקנת כרגע לא נמצאת בשימוש (לדוגמה, אם כל השירותים שהיא מכילה הופסקו), אפשר להתקין חבילת APEX חדשה בלי הפעלה מחדש באמצעות הדגל --force-non-staged.
adb install --force-non-staged apex_file_nameשימוש ב-APEX
אחרי ההפעלה מחדש, חבילת ה-APEX נטענת בספרייה /apex/<apex_name>@<version>. אפשר להטמיע כמה גרסאות של אותו APEX בו-זמנית.
בין נתיבי ההרכבה, הנתיב שמתאים לגרסה האחרונה הוא /apex/<apex_name>.
אפליקציות הלקוח יכולות להשתמש בנתיב של הנפח המשותף כדי לקרוא או להפעיל קבצים מ-APEX.
בדרך כלל משתמשים ב-APEX באופן הבא:
- יצרן ציוד מקורי (OEM) או יצרן עיצוב מקורי (ODM) טוען מראש קובץ APEX בתיקייה
/system/apexכשהמכשיר נשלח. - הגישה לקבצים ב-APEX מתבצעת דרך הנתיב
/apex/<apex_name>/. - כשגרסה מעודכנת של APEX מותקנת ב-
/data/apex, הנתיב מצביע על ה-APEX החדש אחרי הפעלה מחדש.
עדכון שירות באמצעות APEX
כדי לעדכן שירות באמצעות APEX:
מסמנים את השירות במחיצת המערכת כניתן לעדכון. מוסיפים את האפשרות
updatableלהגדרת השירות./system/etc/init/myservice.rc: service myservice /system/bin/myservice class core user system ... updatableיוצרים קובץ
.rcחדש לשירות המעודכן. משתמשים באפשרותoverrideכדי להגדיר מחדש את השירות הקיים./apex/my.apex/etc/init.rc: service myservice /apex/my.apex/bin/myservice class core user system ... override
אפשר להגדיר הגדרות שירות רק בקובץ .rc של APEX. אין תמיכה בטריגרים של פעולות ב-APEX.
אם שירות שמסומן כניתן לעדכון מתחיל לפני שהמודולים של APEX מופעלים, ההתחלה מתעכבת עד שההפעלה של המודולים של APEX מסתיימת.
הגדרת המערכת לתמיכה בעדכוני APEX
כדי לתמוך בעדכונים של קובצי APEX, צריך להגדיר את מאפיין המערכת הבא לערך true:
<device.mk>:
PRODUCT_PROPERTY_OVERRIDES += ro.apex.updatable=true
BoardConfig.mk:
TARGET_FLATTEN_APEX := false
או פשוט
<device.mk>:
$(call inherit-product, $(SRC_TARGET_DIR)/product/updatable_apex.mk)
APEX מפוצל
במכשירים מדור קודם, לפעמים אי אפשר או לא מעשי לעדכן את ליבת המערכת הישנה כדי לתמוך באופן מלא ב-APEX. לדוגמה, יכול להיות שהליבה נוצרה ללא CONFIG_BLK_DEV_LOOP=Y, שהוא חיוני להרכבת תמונת מערכת הקבצים בתוך APEX.
חבילת APEX שטוחה היא חבילת APEX שנבנתה במיוחד ואפשר להפעיל אותה במכשירים עם ליבת מערכת הפעלה מדור קודם. קבצים ב-APEX שטוח מותקנים ישירות בספרייה במחיצה המובנית. לדוגמה, lib/libFoo.so ב-APEX שטוח my.apex מותקן ב-/system/apex/my.apex/lib/libFoo.so.
הפעלת APEX שטוח לא כוללת את מכשיר הלולאה. כל הספרייה /system/apex/my.apex מותקנת ישירות ב-/apex/name@ver.
אי אפשר לעדכן חבילות APEX שטוחות על ידי הורדת גרסאות מעודכנות של חבילות APEX מהרשת, כי אי אפשר לשטח את חבילות ה-APEX שהורדו. אפשר לעדכן קובצי APEX שטוחים רק באמצעות OTA רגיל.
הגדרת ברירת המחדל היא Flattened APEX. המשמעות היא שכל קובצי ה-APEX מושטחים כברירת מחדל, אלא אם מגדירים במפורש את המכשיר ליצור קובצי APEX לא מושטחים כדי לתמוך בעדכוני APEX (כמו שמוסבר למעלה).
לא ניתן לשלב קובצי APEX שטוחים וקובצי APEX לא שטוחים במכשיר. כל קובצי ה-APEX במכשיר צריכים להיות לא שטוחים או שטוחים.
זה חשוב במיוחד כששולחים מראש קבצים מוכנים מראש של APEX שחתומים מראש לפרויקטים כמו Mainline. חבילות APEX שלא נחתמו מראש (כלומר, שנבנו מהמקור) צריכות גם להיות לא שטוחות וחתומות באמצעות מפתחות מתאימים. המכשיר צריך לרשת מ-updatable_apex.mk כמו שמוסבר במאמר עדכון שירות באמצעות APEX.
קובצי APEX דחוסים
ב-Android מגרסה 12 ואילך יש דחיסת APEX כדי לצמצם את ההשפעה של חבילות APEX שניתנות לעדכון על נפח האחסון. אחרי שמתקינים עדכון ל-APEX, הגרסה המותקנת מראש שלו לא נמצאת יותר בשימוש, אבל היא עדיין תופסת את אותו נפח אחסון. השטח התפוס הזה לא יהיה זמין.
דחיסת APEX מצמצמת את ההשפעה הזו על האחסון באמצעות שימוש בקבוצה דחוסה מאוד של קובצי APEX במחיצות לקריאה בלבד (כמו מחיצת /system). Android מגרסה 12 ומעלה משתמש באלגוריתם דחיסה של Deflate zip.
דחיסה לא מספקת אופטימיזציה ל:
חבילות APEX שנדרשות להרכבה בשלב מוקדם מאוד ברצף האתחול.
חבילות APEX שלא ניתן לעדכן. הדחיסה מועילה רק אם מותקנת גרסה מעודכנת של APEX במחיצה
/data. רשימה מלאה של רכיבי APEX שאפשר לעדכן זמינה בדף Modular System Components.חבילות APEX של ספריות דינמיות משותפות. מכיוון שהמודול
apexdתמיד מפעיל את שתי הגרסאות של חבילות APEX כאלה (הגרסה שהותקנה מראש והגרסה המשודרגת), דחיסה שלהן לא מוסיפה ערך.
פורמט קובץ APEX דחוס
זהו הפורמט של קובץ APEX דחוס.
איור 2. פורמט קובץ APEX דחוס
ברמה העליונה, קובץ APEX דחוס הוא קובץ ZIP שמכיל את קובץ ה-APEX המקורי בצורה מנופחת עם רמת דחיסה של 9, ועם קבצים אחרים שמאוחסנים ללא דחיסה.
קובץ APEX מורכב מארבעה קבצים:
-
original_apex: deflated with compression level of 9 זהו קובץ APEX המקורי שלא עבר דחיסה. -
apex_manifest.pb: מאוחסן בלבד -
AndroidManifest.xml: מאוחסן בלבד -
apex_pubkey: מאוחסן בלבד
הקבצים apex_manifest.pb, AndroidManifest.xml ו-apex_pubkey הם עותקים של הקבצים התואמים להם ב-original_apex.
יצירת APEX דחוס
אפשר ליצור קובץ APEX דחוס באמצעות הכלי apex_compression_tool.py שנמצא בכתובת system/apex/tools.
במערכת build זמינים כמה פרמטרים שקשורים לדחיסת APEX.
ב-Android.bp, המאפיין compressible קובע אם אפשר לדחוס קובץ APEX:
apex {
name: "apex.test",
manifest: "apex_manifest.json",
file_contexts: "file_contexts",
compressible: true,
}
PRODUCT_COMPRESSED_APEX דגל מוצר קובע אם קובץ אימג' של המערכת שנבנה ממקור חייב להכיל קובצי APEX דחוסים.
כדי לבצע ניסויים מקומיים, אפשר להגדיר את OVERRIDE_PRODUCT_COMPRESSED_APEX= ל-true כדי לכפות על המערכת ליצור דחיסה של קובצי APEX.
לקובצי APEX דחוסים שנוצרו על ידי מערכת ה-build יש סיומת .capex.
התוסף מאפשר להבחין בקלות בין גרסאות דחוסות ולא דחוסות של קובץ APEX.
אלגוריתמים נתמכים לדחיסת נתונים
ב-Android 12 יש תמיכה רק בדחיסת zip בפורמט Deflate.
הפעלת קובץ APEX דחוס במהלך האתחול
לפני שאפשר להפעיל APEX דחוס, הקובץ original_apex בתוכו נפרס לתיקייה /data/apex/decompressed. קובץ ה-APEX שנוצר אחרי הפעולה מקושר באופן קשיח לספרייה /data/apex/active.
הדוגמה הבאה ממחישה את התהליך שמתואר למעלה.
כדאי להתייחס אל /system/apex/com.android.foo.capex כאל APEX דחוס שמבוצעת בו הפעלה, עם versionCode 37.
- הקובץ
original_apexבתוך/system/apex/com.android.foo.capexנפרס לתוך/data/apex/decompressed/com.android.foo@37.apex. - מתבצעת כדי לוודא שיש לה תווית SELinux נכונה.
restorecon /data/apex/decompressed/com.android.foo@37.apex - במסגרת האימות מתבצעות בדיקות ב-
/data/apex/decompressed/com.android.foo@37.apexכדי לוודא שהוא תקף: במסגרתapexdנבדק המפתח הציבורי שכלול ב-/data/apex/decompressed/com.android.foo@37.apexכדי לוודא שהוא זהה לזה שכלול ב-/system/apex/com.android.foo.capex. - קובץ
/data/apex/decompressed/com.android.foo@37.apexמקושר קשיח לספרייה/data/apex/active/com.android.foo@37.apex. - הלוגיקה הרגילה של הפעלת קובצי APEX לא דחוסים מבוצעת ב-
/data/apex/active/com.android.foo@37.apex.
אינטראקציה עם OTA
לקובצי APEX דחוסים יש השלכות על העברה והחלה של OTA. יכול להיות שעדכון OTA יכיל קובץ APEX דחוס עם רמת גרסה גבוהה יותר מזו שפעילה במכשיר. לכן, צריך להקצות כמות מסוימת של שטח פנוי לפני שמפעילים מחדש את המכשיר כדי להחיל עדכון OTA.
כדי לתמוך במערכת OTA, apexd חושף את שני ממשקי ה-API של Binder:
-
calculateSizeForCompressedApex– חישוב הגודל הנדרש לביטול הדחיסה של קובצי APEX בחבילת OTA. אפשר להשתמש בזה כדי לוודא שיש במכשיר מספיק מקום לפני שמורידים עדכון OTA. -
reserveSpaceForCompressedApex– שומר מקום בדיסק לשימוש עתידי שלapexdלצורך ביטול הדחיסה של קובצי APEX דחוסים בחבילת ה-OTA.
במקרה של עדכון OTA מסוג A/B, apexd מנסה לבצע דקומפרסיה ברקע כחלק משגרת ה-OTA אחרי ההתקנה. אם הפריסה נכשלת, apexd מבצע את הפריסה במהלך האתחול שבו מוחל עדכון ה-OTA.
חלופות שנשקלו במהלך פיתוח APEX
אלה כמה אפשרויות שנשקלו ב-AOSP כשעיצבו את פורמט קובץ ה-APEX, והסיבות לכך שהן נכללו או לא נכללו.
מערכות רגילות לניהול חבילות
בהפצות של Linux יש מערכות לניהול חבילות כמו dpkg ו-rpm, שהן עוצמתיות, מבוססות ואמינות. עם זאת, הם לא אומצו עבור APEX כי הם לא יכולים להגן על החבילות אחרי ההתקנה. האימות מתבצע רק כשמתקינים חבילות.
תוקפים יכולים לפגוע בשלמות של החבילות המותקנות בלי שזה יתגלה. זוהי רגרסיה ב-Android שבה כל רכיבי המערכת אוחסנו במערכות קבצים לקריאה בלבד, שהתקינות שלהן מוגנת על ידי dm-verity לכל קלט/פלט. צריך לאסור כל שינוי ברכיבי המערכת, או לאפשר זיהוי של שינויים כאלה כדי שהמכשיר יוכל לסרב לאתחל את עצמו אם הוא נפרץ.
dm-crypt לתקינות
הקבצים במאגר APEX הם ממחיצות מובנות (לדוגמה, מחיצת /system) שמוגנות על ידי dm-verity, כך שחל איסור על ביצוע שינויים בקבצים גם אחרי שהמחיצות מותקנות. כדי לספק את אותה רמת אבטחה לקבצים, כל הקבצים ב-APEX מאוחסנים בקובץ אימג' של המערכת שמשויך לעץ גיבוב ולתיאור vbmeta. בלי dm-verity, מודול APEX במחיצה /data חשוף לשינויים לא מכוונים שמתבצעים אחרי האימות וההתקנה שלו.
למעשה, המחיצה /data מוגנת גם על ידי שכבות הצפנה כמו dm-crypt. למרות שהשיטה הזו מספקת רמת הגנה מסוימת מפני שינויים לא מורשים, המטרה העיקרית שלה היא שמירה על הפרטיות ולא על השלמות. אם תוקף יקבל גישה למחיצה /data, לא תהיה הגנה נוספת. שוב, זו נסיגה בהשוואה למצב שבו כל רכיב במערכת נמצא במחיצה /system.
עץ הגיבוב בתוך קובץ APEX, יחד עם dm-verity, מספק את אותה רמה של הגנה על התוכן.
הפניות נתיבים מ- /system אל /apex
אפשר לגשת לקבצים של רכיבי מערכת שמופיעים בחבילת APEX דרך נתיבים חדשים כמו
/apex/<name>/lib/libfoo.so. כשהקבצים היו חלק ממחיצת /system, הייתה אליהם גישה דרך נתיבים כמו /system/lib/libfoo.so. לקוח של קובץ APEX (קובצי APEX אחרים או הפלטפורמה) חייב להשתמש בנתיבים החדשים. יכול להיות שתצטרכו לעדכן קוד קיים בעקבות השינוי בנתיב.
אחת הדרכים להימנע משינוי הנתיב היא להוסיף את תוכן הקובץ במחיצת /system לקובץ APEX, אבל צוות Android החליט לא להוסיף קבצים במחיצת /system כי זה עלול להשפיע על הביצועים ככל שמספר הקבצים שנוספים (אולי אפילו אחד אחרי השני) גדל.
אפשרות נוספת הייתה לחטוף פונקציות של גישה לקבצים, כמו open, stat ו-readlink, כך שנתיבים שמתחילים ב-/system יופנו לנתיבים התואמים שלהם ב-/apex. צוות Android פסל את האפשרות הזו כי אי אפשר לשנות את כל הפונקציות שמקבלות נתיבים.
לדוגמה, חלק מהאפליקציות מקשרות באופן סטטי את Bionic, שמטמיע את הפונקציות.
במקרים כאלה, לא מתבצעת הפניה אוטומטית לאפליקציות האלה.