פורמט קובץ APEX

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

המונח APEX יכול להתייחס גם לקובץ APEX.

רקע

למרות שמערכת Android תומכת בעדכונים של מודולים שמתאימים למודל האפליקציה הרגיל (לדוגמה, שירותים, פעילויות) באמצעות אפליקציות להתקנת חבילות (כמו אפליקציית חנות Google Play), לשימוש במודל דומה לרכיבי מערכת הפעלה ברמה נמוכה יותר יש את החסרונות הבאים:

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

עיצוב

בקטע הזה מתואר העיצוב הכללי של פורמט קובץ APEX ומנהל APEX, שהוא שירות שמנהל קובצי APEX.

מידע נוסף על הסיבות לבחירת העיצוב הזה של APEX זמין במאמר חלופות שנשקלו במהלך הפיתוח של APEX.

פורמט APEX

זה הפורמט של קובץ APEX.

פורמט קובץ APEX

איור 1. פורמט קובץ APEX

ברמה העליונה, קובץ APEX הוא קובץ ZIP שבו הקבצים מאוחסנים ללא דחיסה וממוקמים בגבולות של 4 KB.

ארבעת הקבצים בקובץ APEX הם:

  • apex_manifest.json
  • AndroidManifest.xml
  • apex_payload.img
  • apex_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 והוא מתבצע באופן הבא.

  1. קובץ APEX מורידים דרך אפליקציה להתקנת חבילות, דרך ADB או ממקור אחר.
  2. מנהל החבילות מתחיל את תהליך ההתקנה. כשמנהל החבילות מזהה שהקובץ הוא APEX, הוא מעביר את השליטה למנהל ה-APEX.
  3. מנהל ה-APEX מאמת את קובץ ה-APEX.
  4. אם קובץ ה-APEX מאומת, מסד הנתונים הפנימי של מנהל ה-APEX מתעדכן כדי לשקף את העובדה שקובץ ה-APEX יופעל באתחול הבא.
  5. מגיש בקשת ההתקנה מקבל שידור אחרי אימות החבילה.
  6. כדי להמשיך בהתקנה, צריך להפעיל מחדש את המערכת.
  7. באתחול הבא, מנהל ה-APEX מתחיל, קורא את מסד הנתונים הפנימי ומבצע את הפעולות הבאות לכל קובץ APEX שמופיע ברשימה:

    1. מאמת את קובץ ה-APEX.
    2. יוצרת מכשיר loopback מקובץ ה-APEX.
    3. יוצר מכשיר בלוק של מיפוי מכשירים על גבי מכשיר הלולאה החוזרת.
    4. המערכת מבצעת 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. כדי לקבל את תיקוני האבטחה לתמיכה ב-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 בשני ממשקי ה-ABI (ממשקי יישום בינאריים) הראשיים והמשניים של המכשיר. אם חבילת 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 pair
openssl genrsa -out foo.pem 4096

# extract the public key from the key pair
avbtool extract_public_key --key foo.pem --output foo.avbpubkey

# in Android.bp
apex_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)
}

התקנה של APEX

כדי להתקין APEX, משתמשים ב-ADB.

adb install apex_file_name
adb 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>.

לקוחות יכולים להשתמש בנתיב של ה-bind-mount כדי לקרוא או להפעיל קבצים מ-APEX.

בדרך כלל משתמשים ב-APEX באופן הבא:

  1. יצרן ציוד מקורי (OEM) או יצרן עיצוב מקורי (ODM) טוען מראש קובץ APEX בתיקייה /system/apex כשהמכשיר נשלח.
  2. הגישה לקבצים ב-APEX מתבצעת דרך הנתיב /apex/<apex_name>/.
  3. כשגרסה מעודכנת של APEX מותקנת ב-/data/apex, הנתיב מצביע על ה-APEX החדש אחרי הפעלה מחדש.

עדכון שירות באמצעות APEX

כדי לעדכן שירות באמצעות APEX:

  1. סימון השירות במחיצת המערכת כניתן לעדכון. מוסיפים את האפשרות updatable להגדרת השירות.

    /system/etc/init/myservice.rc:
    
    service myservice /system/bin/myservice
        class core
        user system
        ...
        updatable
    
  2. יוצרים קובץ .rc חדש לשירות המעודכן. משתמשים באפשרות override כדי להגדיר מחדש את השירות הקיים.

    /apex/my.apex/etc/init.rc:
    
    service myservice /apex/my.apex/bin/myservice
        class core
        user system
        ...
        override
    

אפשר להגדיר הגדרות שירות רק בקובץ .rc של APEX. אין תמיכה בטריגרים של פעולות ב-APEX.

אם שירות שמסומן כשירות שאפשר לעדכן מתחיל לפני שה-APEXes מופעלים, ההתחלה מתעכבת עד שההפעלה של ה-APEXes מסתיימת.

הגדרת המערכת לתמיכה בעדכוני 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 דחוס.

תרשים שמציג את הפורמט של קובץ 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.

במערכת הבנייה זמינים כמה פרמטרים שקשורים לדחיסת APEX.

ב-Android.bp, האפשרות לדחיסת קובץ APEX נקבעת על ידי המאפיין compressible:

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 יש תמיכה רק בדחיסת deflate-zip.

הפעלת קובץ APEX דחוס במהלך האתחול

לפני שאפשר להפעיל קובץ APEX דחוס, הקובץ original_apex שבו נפרס לתיקייה /data/apex/decompressed. קובץ ה-APEX שנוצר אחרי הפריסה מקושר באופן קשיח לספרייה /data/apex/active.

הדוגמה הבאה ממחישה את התהליך שמתואר למעלה.

כדאי להתייחס אל /system/apex/com.android.foo.capex כאל APEX דחוס שמבוצעת בו הפעלה, עם versionCode‏ 37.

  1. הקובץ original_apex בתוך /system/apex/com.android.foo.capex נפרס לתוך /data/apex/decompressed/com.android.foo@37.apex.
  2. restorecon /data/apex/decompressed/com.android.foo@37.apex כדי לוודא שיש לו תווית SELinux נכונה.
  3. במהלך האימות מתבצעות בדיקות ב-/data/apex/decompressed/com.android.foo@37.apex כדי לוודא שהוא תקף: במסגרת הבדיקות, המפתח הציבורי שכלול ב-/data/apex/decompressed/com.android.foo@37.apex נבדק כדי לוודא שהוא זהה לזה שכלול ב-/system/apex/com.android.foo.capex.apexd
  4. הקובץ /data/apex/decompressed/com.android.foo@37.apex הוא קישור קשיח לספרייה /data/apex/active/com.android.foo@37.apex.
  5. הלוגיקה הרגילה של הפעלה לקובצי 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 אחרים או הפלטפורמה) חייב להשתמש בנתיבים החדשים. יכול להיות שתצטרכו לעדכן קוד קיים בעקבות השינוי בנתיב.

אחת הדרכים להימנע משינוי הנתיב היא להוסיף את תוכן הקובץ בקובץ APEX למחיצת /system. עם זאת, צוות Android החליט לא להוסיף קבצים למחיצת /system כי זה עלול להשפיע על הביצועים ככל שמספר הקבצים שנוספים (אולי אפילו אחד אחרי השני) גדל.

אפשרות נוספת הייתה לחטוף פונקציות של גישה לקבצים, כמו open, ‏ stat ו-readlink, כך שנתיבים שמתחילים ב-/system יופנו לנתיבים התואמים שלהם ב-/apex. צוות Android פסל את האפשרות הזו כי אי אפשר לשנות את כל הפונקציות שמקבלות נתיבים. לדוגמה, חלק מהאפליקציות מקשרות באופן סטטי את Bionic, שמטמיע את הפונקציות. במקרים כאלה, לא מתבצעת הפניה אוטומטית לאפליקציות האלה.