Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

APEX 파일 형식

Android Pony EXpress(APEX) 컨테이너 형식은 Android 10에 도입된 것으로, 하위 수준 시스템 모듈의 설치 흐름에 사용됩니다. 이 형식은 표준 Android 애플리케이션 모델에 맞지 않는 시스템 구성요소의 업데이트를 지원합니다. 구성요소의 예로는 네이티브 서비스 및 라이브러리, 하드웨어 추상화 계층(HAL), 런타임(ART), 클래스 라이브러리 등이 있습니다.

'APEX'란 용어는 APEX 파일을 지칭할 수도 있습니다.

배경

Android는 Google Play 스토어 앱과 같은 패키지 설치 프로그램 앱을 통해 서비스, 활동과 같은 표준 앱 모델 내에 적합한 모듈의 업데이트를 지원하지만, 하위 수준 OS 구성요소에 유사한 모델을 사용할 경우 아래와 같은 단점이 있습니다.

  • APK 기반 모듈은 부팅 시퀀스 초반에 사용할 수 없습니다. 패키지 관리자는 앱에 관한 정보로 구성된 중앙 저장소이며 활동 관리자에서만 시작할 수 있습니다. 활동 관리자는 부팅 절차의 후반 단계에서 준비됩니다.
  • APK 형식(특히 매니페스트)은 Android 앱을 위해 설계되었으며 시스템 모듈은 경우에 따라 적합하지 않을 수 있습니다.

설계

이 섹션에서는 APEX 파일 형식의 대략적인 설계와 APEX 파일 관리 서비스인 APEX 관리자에 관해 설명합니다.

왜 이 APEX 설계가 선택되었는지에 관한 자세한 내용은 APEX 개발 시에 고려된 대안을 참고하세요.

APEX 형식

이는 APEX 파일의 형식입니다.

APEX 파일 형식

그림 1. APEX 파일 형식

상위 수준에서의 APEX 파일은 파일이 압축되지 않은 상태로 4KB 경계에 저장되는 ZIP 파일입니다.

APEX 파일에 포함되는 4개의 파일은 다음과 같습니다.

  • apex_manifest.json
  • AndroidManifest.xml
  • apex_payload.img
  • apex_pubkey

apex_manifest.json 파일에는 APEX 파일을 알려주는 패키지 이름과 버전이 포함됩니다.

APEX 파일은 AndroidManifest.xml 파일을 통해 ADB, PackageManager, 패키지 설치 프로그램 앱(예: Play 스토어) 등의 APK 관련 도구와 인프라를 사용할 수 있습니다. 예를 들어 APEX 파일은 aapt 등의 기존 도구를 사용하여 파일에서 기본 메타데이터를 검사할 수 있습니다. 파일에는 패키지 이름과 버전 정보가 포함됩니다. 이 정보는 보통 apex_manifest.json에서도 제공됩니다.

apex_manifest.json은 APEX를 다루는 새 코드 및 시스템에서 AndroidManifest.xml보다 더 권장됩니다. AndroidManifest.xml에는 기존 앱 게시 도구에서 사용할 수 있는 추가적인 타겟팅 정보가 포함될 수 있습니다.

apex_payload.img는 dm-verity가 지원하는 ext4 파일 시스템 이미지입니다. 이미지는 런타임 시 루프백 기기를 통해 마운트됩니다. 구체적으로는 libavb 라이브러리를 사용하여 해시 트리와 메타데이터 블록이 생성됩니다. 이미지를 제자리에 마운트할 수 있어야 하므로 파일 시스템 페이로드는 파싱되지 않습니다. 일반 파일은 apex_payload.img 파일 내에 포함됩니다.

apex_pubkey는 파일 시스템 이미지에 서명하는 데 사용되는 공개 키입니다. 런타임 시 이 키는 다운로드된 APEX가 기본 파티션의 동일한 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. APEX 파일에서 루프백 기기를 생성합니다.
    3. 루프백 기기 외에 기기 매퍼 블록 기기를 생성합니다.
    4. 기기 매퍼 블록 기기를 고유 경로(예: /apex/name@ver)에 마운트합니다.

내부 데이터베이스에 나열된 모든 APEX 파일이 마운트되면 APEX 관리자는 다른 시스템 구성요소에서 설치된 APEX 파일에 관한 정보를 쿼리할 수 있도록 바인더 서비스를 제공합니다. 예를 들어 다른 시스템 구성요소는 파일에 액세스할 수 있도록 기기에 설치된 APEX 파일 목록을 쿼리하거나 특정 APEX가 마운트된 정확한 경로를 쿼리할 수 있습니다.

APEX 파일은 APK 파일임

APEX 파일은 AndroidManifest.xml 파일을 포함하는 서명(APK 서명 체계 사용)된 ZIP 파일이므로 유효한 APK 파일입니다. 따라서 APEX 파일은 패키지 설치 프로그램 앱, 서명 유틸리티 및 패키지 관리자와 같은 APK 파일의 인프라를 사용할 수 있습니다.

APEX 파일 내의 AndroidManifest.xml 파일은 패키지 name, versionCode, 그리고 세부 타겟팅을 위한 선택적 targetSdkVersion, minSdkVersion, maxSdkVersion으로 구성된 최소 파일입니다. 이 정보를 사용하여 패키지 설치 프로그램 앱과 ADB 등의 기존 채널을 통해 APEX 파일을 전달할 수 있습니다.

지원되는 파일 형식

APEX 형식은 다음과 같은 파일 형식을 지원합니다.

  • 네이티브 공유 라이브러리
  • 네이티브 실행 파일
  • JAR 파일
  • 데이터 파일
  • 구성 파일

그렇다고 해서 APEX가 이러한 모든 파일 형식을 업데이트할 수 있는 것은 아닙니다. 파일 형식의 업데이트 가능 여부는 어떤 플랫폼인지, 그리고 파일 형식의 인터페이스가 얼마나 안정적으로 정의되는지에 따라 다릅니다.

서명

APEX 파일은 두 가지 방식으로 서명됩니다. 우선 apex_payload.img(구체적으로는 apex_payload.img에 추가되는 vbmeta 설명자) 파일은 키로 서명됩니다. 이어서 전체 APEX가 APK 서명 체계 v3을 사용하여 서명됩니다. 이 과정에서 두 가지 키가 사용됩니다.

기기 측에서는 vbmeta 설명자에 서명하는 데 사용된 비공개 키에 상응하는 공개 키가 설치됩니다. APEX 관리자는 공개 키를 사용하여 설치가 요청된 APEX를 인증합니다. 각 APEX는 다른 키로 서명되어야 하며 빌드 시와 런타임에 적용됩니다.

내장 파티션의 APEX

APEX 파일은 /system과 같은 내장 파티션에 위치할 수 있습니다. 파티션이 이미 dm-verity 위에 있어서 APEX 파일이 루프백 기기 바로 위에 마운트됩니다.

APEX가 내장 파티션에 존재하는 경우 패키지 이름이 같고 버전 코드가 같거나 높은 APEX 패키지를 제공하여 APEX를 업데이트할 수 있습니다. 새 APEX는 /data에 저장되며, APK와 마찬가지로 최신 설치 버전이 내장 파티션에 이미 존재하는 버전을 덮습니다. 그러나 APK와 달리 새로 설치된 APEX 버전은 재부팅 후에만 활성화됩니다.

커널 요구사항

Android 기기에서 APEX 메인라인 모듈을 지원하려면 Linux 커널 기능(루프백 드라이버 및 dm-verity)이 필요합니다. 루프백 드라이버는 APEX 모듈에 파일 시스템 이미지를 마운트하고 dm-verity는 APEX 모듈을 인증합니다.

루프백 드라이버와 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: 논리 블록 크기 변경을 위한 ioctl을 추가(4.4)
  • BACKPORT: block/loop: hw_sectors를 설정(4.4)
  • UPSTREAM: loop: LOOP_SET_BLOCK_SIZE를 compat ioctl에 추가(4.4)
  • ANDROID: mnt: next_descendent를 고정(4.4)
  • ANDROID: mnt: 재마운트 시 slave의 slave로 전파되어야 함(4.4)
  • ANDROID: mnt: 재마운트를 올바르게 전파(4.4)
  • Revert "ANDROID: dm verity: 최소 미리 가져오기 크기를 추가"(4.4)
  • UPSTREAM: loop: 오프셋 또는 block_size가 변경되면 캐시를 드롭(4.4)

커널 버전 4.9/4.14/4.19

커널 버전 4.9/4.14/4.19의 필수 패치를 가져오려면 android-common 분기에서 아래로 병합합니다.

필수 커널 구성 옵션

다음 목록은 Android 10에 도입되었던 APEX 모듈 지원을 위한 기본 구성 요구사항을 보여줍니다. 별표(*)가 표시된 항목은 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 빌드

이 섹션에서는 Android 빌드 시스템을 사용하여 APEX를 빌드하는 방법을 설명합니다. 다음은 apex.test라는 APEX의 Android.bp 예시입니다.

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(x86에서 해석된 arm의 /lib/arm)
실행 파일 /bin
자바 라이브러리 /javalib
사전 빌드 /etc

전이 종속 항목

APEX 파일은 네이티브 공유 libs 또는 실행 파일의 전이 종속 항목을 자동으로 포함합니다. 예를 들어 libFoolibBar에 종속되는 경우에는 libFoonative_shared_libs 속성에 나열되는 경우에만 2개의 libs가 포함됩니다.

여러 ABI 처리

기기의 기본 및 보조 애플리케이션 바이너리 인터페이스(ABI) 둘 다에 해당하는 native_shared_libs 속성을 설치합니다. APEX가 단일 ABI(32비트 또는 64비트로만)로 기기를 타겟팅하면 상응하는 ABI가 포함된 라이브러리만 설치됩니다.

아래의 설명처럼 기기의 기본 ABI에 해당하는 binaries 속성만 설치합니다.

  • 기기가 32비트 전용인 경우 바이너리의 32비트 변형만 설치됩니다.
  • 기기가 64비트 전용인 경우 바이너리의 64비트 변형만 설치됩니다.

네이티브 라이브러리 및 바이너리의 ABI에 관한 세부 제어를 추가하려면 multilib.[first|lib32|lib64|prefer32|both].[native_shared_libs|binaries] 속성을 사용하세요.

  • first: 기기의 기본 ABI를 매칭합니다. 이는 바이너리의 기본값입니다.
  • lib32: 지원되는 경우 기기의 32비트 ABI를 매칭합니다.
  • lib64: 지원되는 경우 기기의 64비트 ABI를 매칭합니다.
  • prefer32: 지원되는 경우 기기의 32비트 ABI를 매칭합니다. 32비트 ABI가 지원되지 않는 경우 64비트 ABI를 매칭합니다.
  • both: 두 ABI를 모두 매칭합니다. 이는 native_shared_libraries의 기본값입니다.

java, librariesprebuilts 속성은 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에 서명합니다. 공개 키가 이름이 avb_pubkey인 APEX에 자동으로 포함됩니다.

# 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)이 키의 ID가 됩니다. APEX에 서명하는 데 사용된 키의 ID는 APEX에서 작성됩니다. 런타임 시 apexd는 기기의 동일한 ID를 가진 공개 키를 사용하여 APEX를 인증합니다.

ZIP 서명

APK에 서명하는 것과 동일한 방식으로 APEX 서명 APEX가 미니 파일 시스템(apex_payload.img 파일)에 한 번, 전체 파일에 한 번, 이렇게 두 번 서명됩니다.

파일 수준에서 APEX에 서명하려면 아래와 같은 세 방식 중 하나를 사용하여 certificate 속성을 설정합니다.

  • 설정되지 않음: 설정된 값이 없으면 APEX가 PRODUCT_DEFAULT_DEV_CERTIFICATE에 위치한 인증서로 서명됩니다. 설정된 플래그가 없으면 경로가 기본값인 build/target/product/security/testkey로 설정됩니다.
  • <name>: APEX가 PRODUCT_DEFAULT_DEV_CERTIFICATE와 같은 디렉터리에 있는 <name> 인증서로 서명됩니다.
  • :<name>: APEX가 이름이 <name>인 Soong 모듈에 의해 정의된 인증서로 서명됩니다. 인증서 모듈은 다음과 같이 정의할 수 있습니다.
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

APEX 사용

재부팅 후에는 APEX가 /apex/<apex_name>@<version> 디렉터리에 마운트됩니다. 동일한 APEX의 여러 버전을 동시에 마운트할 수 있습니다. 마운트 경로 중에서 최신 버전에 상응하는 경로가 /apex/<apex_name>에서 제 위치에 마운트됩니다.

클라이언트는 제자리에 마운트된 경로를 사용하여 APEX에서 파일을 읽고 실행할 수 있습니다.

APEX는 일반적으로 다음과 같이 사용됩니다.

  1. 기기가 배송되면 OEM 또는 ODM이 /system/apex 아래에 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
    

서비스 정의는 APEX의 .rc 파일에서만 정의할 수 있습니다. 작업 트리거는 APEX에서 지원되지 않습니다.

업데이트 가능으로 표시된 서비스가 APEX 활성화 이전에 시작되면 APEX 활성화가 완료될 때까지 시작이 지연됩니다.

APEX 업데이트를 지원하도록 시스템 구성

다음 시스템 속성을 true로 설정하여 APEX 파일 업데이트를 지원합니다.

<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를 완전히 지원하기가 불가능한 경우가 있습니다. 예를 들어 커널은 파일 시스템 이미지를 APEX 내에 마운트하는 데 있어 중요한 CONFIG_BLK_DEV_LOOP=Y 없이 빌드되었을 수 있습니다.

평면화된 APEX는 레거시 커널을 포함하는 기기에 활성화할 수 있는 특수 빌드된 APEX입니다. 평면화된 APEX의 파일은 내장 파티션 아래의 디렉터리에 직접적으로 설치됩니다. 예를 들어 평면화된 APEX my.apexlib/libFoo.so/system/apex/my.apex/lib/libFoo.so에 설치됩니다.

평면화된 APEX를 활성화할 때는 루프 기기가 사용되지 않으며, 전체 디렉터리 /system/apex/my.apex가 바로 /apex/name@ver의 제자리에 마운트됩니다.

평면화된 APEX는 네트워크에서 APEX의 업데이트된 버전을 다운로드하여 업데이트할 수 없습니다. 이는 다운로드된 APEX가 평면화될 수 없기 때문입니다. 평면화된 APEX는 일반 OTA를 통해서만 업데이트할 수 있습니다.

평면화된 APEX는 기본 구성입니다. 즉, 위에서 설명한 것처럼 기기가 평면화되지 않은 APEX를 빌드하여 APEX 업데이트를 지원하도록 명시적으로 구성하지 않는 이상 모든 APEX가 기본으로 평면화됩니다.

평면화되었거나 평면화되지 않은 APEX를 기기에서 함께 사용하는 기능은 지원되지 않습니다. 기기의 APEX는 모두 평면화되었거나 모두 평면화되지 않은 APEX여야 합니다. 이는 Mainline과 같은 프로젝트의 미리 서명된 APEX 사전 빌드를 배송하는 경우에 특히 중요합니다. 사전 서명되지 않은(즉, 소스에서 빌드된) APEX도 평면화되지 않아야 하며 올바른 키로 서명되어야 합니다. 기기는 APEX로 서비스 업데이트에 설명된 것처럼 updatable_apex.mk에서 상속해야 합니다.

압축된 APEX

Android 12 이상에는 업데이트 가능한 APEX 패키지의 저장용량 영향을 줄이기 위한 APEX 압축이 포함됩니다. APEX 업데이트가 설치된 후 사전 설치 버전은 더 이상 사용되지 않지만 여전히 동일한 공간을 차지합니다. 이러한 점유 공간은 사용할 수 없습니다.

APEX 압축은 읽기 전용 파티션(예: /system 파티션)에서 고도로 압축된 APEX 파일 집합을 사용하여 이러한 저장용량 영향을 최소화합니다. Android 12 이상에서는 DEFLATE ZIP 압축 알고리즘을 사용합니다.

압축 시 다음을 최적화하지 않습니다.

  • 부팅 시퀀스 초기에 마운트해야 하는 부트스트랩 APEX.

  • 업데이트 불가능한 APEX. 압축은 업데이트된 버전의 APEX가 /data 파티션에 설치된 경우에만 유용합니다. 업데이트 가능한 APEX의 전체 목록은 모듈식 시스템 구성요소 페이지에서 확인할 수 있습니다.

  • 동적 공유 라이브러리 APEX. apexd는 항상 이러한 두 버전(사전 설치 및 업그레이드)의 APEX를 활성화하므로 압축해도 가치가 추가되지 않습니다.

압축된 APEX 파일 형식

압축된 APEX 파일의 형식입니다.

압축된 APEX 파일의 형식을 보여주는 다이어그램

그림 2. 압축된 APEX 파일 형식

최상위 수준에서 보면 압축된 APEX 파일은 압축 수준 9로 압축된 형식의 원본 APEX 파일을 포함하고 비압축 상태로 저장된 다른 파일도 포함하는 ZIP 파일입니다.

APEX 파일은 4개의 파일로 구성됩니다.

  • original_apex: 압축 수준 9로 압축됩니다. 이는 압축되지 않은 원본 APEX 파일입니다.
  • apex_manifest.pb: 저장 전용
  • AndroidManifest.xml: 저장 전용
  • apex_pubkey: 저장 전용

apex_manifest.pb, AndroidManifest.xml, apex_pubkey 파일은 original_apex에 있는 상응하는 파일의 사본입니다.

압축된 APEX 빌드

system/apex/tools에 있는 apex_compression_tool.py 도구를 사용하여 압축된 APEX를 빌드할 수 있습니다.

빌드 시스템에서 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 파일에는 .capex 확장자가 있습니다. 이 확장자를 통해 APEX 파일의 압축된 버전과 압축되지 않은 버전을 더 쉽게 구분할 수 있습니다.

지원되는 압축 알고리즘

Android 12는 deflate-zip 압축만 지원합니다.

부팅 중 압축된 APEX 파일 활성화

압축된 APEX를 활성화하기 전에 APEX 내부의 original_apex 파일이 /data/apex/decompressed 디렉터리로 압축 해제됩니다. 압축 해제된 APEX 파일은 /data/apex/active 디렉터리에 하드 링크됩니다.

다음 예시는 위에서 설명한 프로세스를 보여줍니다.

/system/apex/com.android.foo.capex는 versionCode 37을 사용하여 압축된 APEX를 활성화한다고 생각하세요.

  1. /system/apex/com.android.foo.capex 내의 original_apex 파일은 /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에서 인증 검사가 실행되어 유효성을 확인합니다. apexd/data/apex/decompressed/com.android.foo@37.apex에 번들된 공개 키를 확인하여 /system/apex/com.android.foo.capex에 번들된 공개 키와 같은지 확인합니다.
  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를 노출합니다.

  • calculateSizeForCompressedApex - OTA 패키지의 APEX 파일을 압축 해제하는 데 필요한 크기를 계산합니다. OTA 다운로드 전에 기기에 공간이 충분한지 확인하는 데 사용할 수 있습니다.
  • reserveSpaceForCompressedApex - 나중에 apexd가 OTA 패키지 내의 압축된 APEX 파일을 압축 해제할 수 있도록 디스크에 공간을 예약합니다.

A/B OTA 업데이트의 경우 apexd는 설치 후 OTA 루틴의 일부로 백그라운드에서 압축 해제를 시도합니다. 압축 해제에 실패하면 apexd는 OTA 업데이트를 적용하는 압축 해제를 부팅 중에 실행합니다.

APEX 개발 시 고려해야 하는 대안

다음은 AOSP에서 APEX 파일 형식을 설계할 때 고려해야 하는 몇 가지 옵션, 그리고 이러한 옵션을 포함하거나 제외해야 하는 이유입니다.

일반 패키지 관리 시스템

Linux 배포판에는 강력하고 성숙하고 안정적인 dpkgrpm 등의 패키지 관리 시스템이 포함되어 있습니다. 하지만 이러한 관리 시스템이 APEX에 채택되지 않은 이유는 설치 이후에 패키지를 보호할 수 없기 때문입니다. 인증은 패키지가 설치되고 있는 동안에만 실행됩니다. 공격자는 설치된 패키지의 무결성을 몰래 아무도 모르게 훼손할 수 있습니다. 이는 모든 I/O의 dm-verity에 의해 무결성이 보호되는 읽기 전용 파일 시스템에 모든 시스템 구성요소가 저장되는 Android에서는 회귀로 볼 수 있습니다. 시스템 구성요소의 모든 조작은 금지되어야 하거나 위험할 경우 기기에서 부팅을 거부할 수 있도록 감지 가능해야 합니다.

무결성을 위한 dm-crypt

APEX 컨테이너의 파일은 dm-verity에 의해 보호되는 내장 파티션(예: /system 파티션)에서 비롯됩니다. 여기서는 파티션이 마운트된 후에도 파일의 수정이 금지됩니다. 파일에 동일한 수준의 보안을 제공하기 위해 APEX의 모든 파일은 해시 트리 및 vbmeta 설명자로 페어링된 파일 시스템 이미지에 저장됩니다. dm-verity가 없으면 /data 파티션의 APEX는 인증 및 설치 후에 이루어진 의도되지 않은 수정에 취약해집니다.

실제로 /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 파티션에 파일을 오버레이하지 않기로 결정했습니다. 오버레이되거나 심지어는 계속해서 중첩되는 파일의 수가 늘어나면서 성능에 부정적인 영향을 미칠 수 있기 때문입니다.

또 다른 방법은 /system으로 시작하는 경로가 /apex 아래의 상응하는 경로로 리디렉션되도록 open, stat, readlink 등의 파일 액세스 함수를 도용하는 것입니다. 하지만 경로를 허용하는 모든 함수를 변경하기가 현실적으로 불가능하므로 Android팀은 이 방법을 폐기했습니다. 예를 들어 일부 앱은 함수를 구현하는 Bionic을 정적으로 연결하며 이 경우 리디렉션되지 않습니다.