first commit

This commit is contained in:
cpeng 2025-08-25 08:01:50 +08:00
commit fdf2a6817f
13455 changed files with 2068140 additions and 0 deletions

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim_not_pre_installed.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim_not_pre_installed.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v1.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v1.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_additional_file.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_additional_file.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_additional_folder.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_additional_folder.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_apk_in_apex_upgrades.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_apk_in_apex_upgrades.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_different_certificate.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_different_certificate.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_different_package_name.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_different_package_name.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_no_hashtree.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_no_hashtree.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_rebootless.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_rebootless.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_sdk_target_p.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_sdk_target_p.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_signed_bob.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_signed_bob.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_signed_bob_rot.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_signed_bob_rot.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,12 @@
drops {
android_build_drop {
build_id: "6508977"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_unsigned_apk_container.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_unsigned_apk_container.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "rvc-dev"
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_unsigned_payload.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_unsigned_payload.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_with_post_install_hook.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_with_post_install_hook.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_with_pre_install_hook.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_with_pre_install_hook.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_without_apk_in_apex.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_without_apk_in_apex.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v2_wrong_sha.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v2_wrong_sha.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v3.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v3.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v3_rebootless.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v3_rebootless.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v3_signed_bob.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v3_signed_bob.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_arm64/com.android.apex.cts.shim.v3_signed_bob_rot.apex"
}
dest_file: "shim/prebuilts//arm/com.android.apex.cts.shim.v3_signed_bob_rot.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim_not_pre_installed.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim_not_pre_installed.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v1.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v1.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_additional_file.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_additional_file.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_additional_folder.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_additional_folder.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_apk_in_apex_upgrades.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_apk_in_apex_upgrades.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_different_certificate.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_different_certificate.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_different_package_name.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_different_package_name.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_no_hashtree.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_no_hashtree.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_rebootless.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_rebootless.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_sdk_target_p.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_sdk_target_p.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_sign_payload_with_different_key.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_signed_bob.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_signed_bob.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_signed_bob_rot.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_signed_bob_rot.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,12 @@
drops {
android_build_drop {
build_id: "6508977"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_unsigned_apk_container.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_unsigned_apk_container.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "rvc-dev"
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_unsigned_payload.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_unsigned_payload.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_with_post_install_hook.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_with_post_install_hook.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_with_pre_install_hook.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_with_pre_install_hook.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_without_apk_in_apex.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_without_apk_in_apex.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v2_wrong_sha.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v2_wrong_sha.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v3.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v3.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v3_rebootless.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v3_rebootless.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v3_signed_bob.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v3_signed_bob.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

View File

@ -0,0 +1,15 @@
drops {
android_build_drop {
build_id: "8572644"
target: "CtsShim"
source_file: "aosp_x86_64/com.android.apex.cts.shim.v3_signed_bob_rot.apex"
}
dest_file: "shim/prebuilts//x86/com.android.apex.cts.shim.v3_signed_bob_rot.apex"
version: ""
version_group: ""
git_project: "platform/system/apex"
git_branch: "tm-dev"
transform: TRANSFORM_NONE
transform_options {
}
}

67
apex/Android.bp Normal file
View File

@ -0,0 +1,67 @@
// This introduces the module type library_linking_strategy_cc_defaults
// To use in other Android.bp files, add the following lines:
// soong_config_module_type_import {
// from: "system/apex/Android.bp",
// module_types: ["library_linking_strategy_cc_defaults"],
// }
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
soong_config_string_variable {
name: "library_linking_strategy",
values: [
"prefer_static",
],
}
soong_config_module_type {
name: "library_linking_strategy_cc_defaults",
module_type: "cc_defaults",
config_namespace: "ANDROID",
variables: ["library_linking_strategy"],
properties: [
"shared_libs",
"static_libs",
"stl",
],
}
soong_config_module_type {
name: "library_linking_strategy_apex_defaults",
module_type: "apex_defaults",
config_namespace: "ANDROID",
variables: ["library_linking_strategy"],
properties: [
"manifest",
"min_sdk_version",
],
}
library_linking_strategy_cc_defaults {
name: "library_linking_strategy_sample_defaults",
soong_config_variables: {
library_linking_strategy: {
prefer_static: {
static_libs: [
"libbase",
"liblog",
],
stl: "c++_static",
},
conditions_default: {
shared_libs: [
"libbase",
"liblog",
],
},
},
},
}
cc_binary {
name: "library_linking_strategy_sample_binary",
srcs: ["library_linking_strategy.cc"],
defaults: ["library_linking_strategy_sample_defaults"],
}

53
apex/CleanSpec.mk Normal file
View File

@ -0,0 +1,53 @@
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# If you don't need to do a full clean build but would like to touch
# a file or delete some intermediate files, add a clean step to the end
# of the list. These steps will only be run once, if they haven't been
# run before.
#
# E.g.:
# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
#
# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
# files that are missing or have been moved.
#
# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
# Use $(OUT_DIR) to refer to the "out" directory.
#
# If you need to re-do something that's already mentioned, just copy
# the command and add it to the bottom of the list. E.g., if a change
# that you made last week required touching a file and a change you
# made today requires touching the same file, just copy the old
# touch step and add it to the end of the list.
#
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
# For example:
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
# APEX keys are moved from /*/etc/security/apex to inside of APEXes
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/etc/security/apex)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/security/apex)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/security/apex)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************

6
apex/OWNERS Normal file
View File

@ -0,0 +1,6 @@
dariofreni@google.com
ioffe@google.com
jiyong@google.com
maco@google.com
malchev@google.com
narayan@google.com

12
apex/PREUPLOAD.cfg Normal file
View File

@ -0,0 +1,12 @@
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
[Builtin Hooks]
clang_format = true
commit_msg_changeid_field = true
commit_msg_test_field = true
gofmt = true
pylint3 = true
[Builtin Hooks Options]
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp

1
apex/apexd/.clang-format Normal file
View File

@ -0,0 +1 @@
BasedOnStyle: Google

573
apex/apexd/Android.bp Normal file
View File

@ -0,0 +1,573 @@
// List of clang-tidy checks that are reported as errors.
// Please keep this list ordered lexicographically.
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
tidy_errors = [
"android-*",
"bugprone-infinite-loop",
"bugprone-macro-parentheses",
"bugprone-misplaced-widening-cast",
"bugprone-move-forwarding-reference",
"bugprone-sizeof-container",
"bugprone-sizeof-expression",
"bugprone-string-constructor",
"bugprone-terminating-continue",
"bugprone-undefined-memory-manipulation",
"bugprone-undelegated-constructor",
// "bugprone-unhandled-self-assignment", // found in apex_manifest.proto
"bugprone-unused-raii",
"cert-err34-c",
"google-default-arguments",
// "google-explicit-constructor", // found in com_android_apex.h
"google-readability-avoid-underscore-in-googletest-name",
"google-readability-todo",
"google-runtime-int",
"google-runtime-member-string-references",
"misc-move-const-arg",
"misc-move-forwarding-reference",
// "misc-unused-parameters", // found in apexd_utils.h
"misc-unused-using-decls",
"misc-use-after-move",
// "modernize-pass-by-value", // found in apex_database.h
"performance-faster-string-find",
"performance-for-range-copy",
"performance-implicit-conversion-in-loop",
"performance-inefficient-vector-operation",
"performance-move-const-arg",
// "performance-move-constructor-init", // found in apexd_loop.h
"performance-noexcept-move-constructor",
"performance-unnecessary-copy-initialization",
"performance-unnecessary-value-param",
// "readability-avoid-const-params-in-decls", // found in apexd.h
]
cc_defaults {
name: "apex_flags_defaults",
cflags: [
"-Wall",
"-Wextra",
"-Werror",
"-Wno-unused-parameter",
// Some extra flags.
"-fstrict-aliasing",
"-Wredundant-decls",
"-Wshadow",
"-Wstrict-aliasing",
"-Wthread-safety",
"-Wthread-safety-negative",
"-Wunreachable-code",
"-Wunreachable-code-break",
"-Wunreachable-code-return",
"-Wunused",
"-Wused-but-marked-unused",
],
tidy: true,
tidy_checks: tidy_errors,
tidy_checks_as_errors: tidy_errors,
tidy_flags: [
"-format-style=file",
"-header-filter=system/apex/",
],
}
soong_config_module_type {
name: "apexd_cc_defaults",
module_type: "cc_defaults",
config_namespace: "ANDROID",
bool_variables: [
"target_board_auto",
],
properties: [
"cppflags",
],
}
apexd_cc_defaults {
name: "libapexd-deps",
defaults: ["libapex-deps"],
shared_libs: [
"libbinder",
"liblog",
"liblogwrap",
],
static_libs: [
"libapex",
"libavb",
"libdm",
"libext2_uuid",
"libsigningutils",
"libverity_tree",
"libvold_binder",
"libxml2",
],
whole_static_libs: ["com.android.sysprop.apex"],
soong_config_variables: {
target_board_auto: {
cppflags: ["-DDISABLE_LOOP_IO_CONFIG"],
},
},
}
aidl_interface {
name: "apex_aidl_interface",
unstable: true,
srcs: [
"aidl/android/apex/ApexInfo.aidl",
"aidl/android/apex/ApexInfoList.aidl",
"aidl/android/apex/ApexSessionInfo.aidl",
"aidl/android/apex/ApexSessionParams.aidl",
"aidl/android/apex/CompressedApexInfo.aidl",
"aidl/android/apex/CompressedApexInfoList.aidl",
"aidl/android/apex/IApexService.aidl",
],
local_include_dir: "aidl",
backend: {
java: {
sdk_version: "28",
},
ndk: {
enabled: false,
},
},
}
cc_binary {
name: "apexd",
defaults: [
"apex_flags_defaults",
"libapex-deps",
"libapexd-deps",
"libapexservice-deps",
],
srcs: [
"apexd_main.cpp",
],
static_libs: [
"libapex",
"libapexd",
"libapexd_checkpoint_vold",
"libapexservice",
],
init_rc: ["apexd.rc"],
// Just like the init, apexd should be able to run without
// any APEX activated. To do so, it uses the bootstrap linker
// and the bootstrap bionic libraries.
bootstrap: true,
}
cc_library_static {
name: "libapexd",
defaults: [
"apex_flags_defaults",
"libapexd-deps",
],
srcs: [
"apex_classpath.cpp",
"apex_database.cpp",
"apexd.cpp",
"apexd_lifecycle.cpp",
"apexd_loop.cpp",
"apexd_private.cpp",
"apexd_session.cpp",
"apexd_verity.cpp",
],
export_include_dirs: ["."],
generated_sources: ["apex-info-list"],
// Don't add shared/static libs here; add to libapexd_defaults instead.
}
cc_library_static {
name: "libapexd_checkpoint_vold",
defaults: ["apex_flags_defaults"],
srcs: [ "apexd_checkpoint_vold.cpp" ],
static_libs: [
"libbase",
"libutils",
"libvold_binder",
],
export_include_dirs: ["."],
}
cc_defaults {
name: "libapexservice-deps",
shared_libs: [
"apex_aidl_interface-cpp",
"libbinder",
"libutils",
],
}
cc_library_static {
name: "libapexservice",
defaults: [
"apex_flags_defaults",
"libapexd-deps",
"libapexservice-deps",
],
srcs: ["apexservice.cpp"],
static_libs: [
"libapexd",
],
cflags: [
"-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
],
}
cc_defaults {
name: "libapex-deps",
shared_libs: [
"libbase",
"libcrypto",
"libcutils",
"libprotobuf-cpp-full",
"libziparchive",
"libselinux",
],
static_libs: [
"lib_apex_session_state_proto",
"lib_apex_manifest_proto",
"lib_microdroid_metadata_proto",
"libavb",
"libverity_tree",
],
static: {
whole_static_libs: ["libc++fs"],
},
cpp_std: "experimental",
shared: {
static_libs: ["libc++fs"],
},
}
cc_library_static {
name: "libapex",
defaults: [
"apex_flags_defaults",
"libapex-deps"
],
srcs: [
"apex_file.cpp",
"apex_file_repository.cpp",
"apex_manifest.cpp",
"apex_shim.cpp",
"apexd_verity.cpp",
],
host_supported: true,
target: {
darwin: {
enabled: false,
},
},
header_libs: [
"libutils_headers",
],
export_header_lib_headers: [
"libutils_headers",
],
export_include_dirs: ["."],
}
genrule {
// Generates an apex which has a different manifest outside the filesystem
// image.
name: "gen_manifest_mismatch_apex",
out: ["apex.apexd_test_manifest_mismatch.apex"],
srcs: [":apex.apexd_test"],
tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
cmd: "unzip -q $(in) -d $(genDir) && " +
"$(location conv_apex_manifest) setprop version 137 $(genDir)/apex_manifest.pb && " +
"$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
"-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
"-o $(genDir)/unaligned.apex && " +
"$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
"$(genDir)/apex.apexd_test_manifest_mismatch.apex"
}
genrule {
// Generates an apex which has a different manifest outside the filesystem
// image.
name: "gen_manifest_mismatch_apex_no_hashtree",
out: ["apex.apexd_test_no_hashtree_manifest_mismatch.apex"],
srcs: [":apex.apexd_test_no_hashtree"],
tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
cmd: "unzip -q $(in) -d $(genDir) && " +
"$(location conv_apex_manifest) setprop version 137 $(genDir)/apex_manifest.pb && " +
"$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
"-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
"-o $(genDir)/unaligned.apex && " +
"$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
"$(genDir)/apex.apexd_test_no_hashtree_manifest_mismatch.apex"
}
genrule {
// Generates an apex with a corrupted filesystem superblock, which should cause
// Apex::Open to fail
name: "gen_corrupt_superblock_apex",
out: ["apex.apexd_test_corrupt_superblock_apex.apex"],
srcs: [":apex.apexd_test"],
tools: ["soong_zip", "zipalign"],
cmd: "unzip -q $(in) -d $(genDir) && " +
"dd if=/dev/zero of=$(genDir)/apex_payload.img conv=notrunc bs=1024 seek=1 count=1 && " +
"$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
"-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
"-o $(genDir)/unaligned.apex && " +
"$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
"$(genDir)/apex.apexd_test_corrupt_superblock_apex.apex"
}
genrule {
// Generates an apex with a corrupted filesystem image, which should cause
// dm-verity verification to fail
name: "gen_corrupt_apex",
out: ["apex.apexd_test_corrupt_apex.apex"],
srcs: [":apex.apexd_test"],
tools: ["soong_zip", "zipalign"],
cmd: "unzip -q $(in) -d $(genDir) && " +
"dd if=/dev/zero of=$(genDir)/apex_payload.img conv=notrunc bs=1024 seek=16 count=1 && " +
"$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
"-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
"-o $(genDir)/unaligned.apex && " +
"$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
"$(genDir)/apex.apexd_test_corrupt_apex.apex"
}
genrule {
// Extract the root digest with avbtool
name: "apex.apexd_test_digest",
out: ["apex.apexd_test_digest.txt"],
srcs: [":apex.apexd_test"],
tools: ["avbtool"],
cmd: "unzip -q $(in) -d $(genDir) apex_payload.img && " +
"$(location avbtool) print_partition_digests --image $(genDir)/apex_payload.img " +
"| cut -c 3-| tee $(out)"
}
genrule {
// Extract the root digest with avbtool
name: "apex.apexd_test_f2fs_digest",
out: ["apex.apexd_test_f2fs_digest.txt"],
srcs: [":apex.apexd_test_f2fs"],
tools: ["avbtool"],
cmd: "unzip -q $(in) -d $(genDir) apex_payload.img && " +
"$(location avbtool) print_partition_digests --image $(genDir)/apex_payload.img " +
"| cut -c 3-| tee $(out)"
}
genrule {
// Extract the root digest with avbtool
name: "apex.apexd_test_erofs_digest",
out: ["apex.apexd_test_erofs_digest.txt"],
srcs: [":apex.apexd_test_erofs"],
tools: ["avbtool"],
cmd: "unzip -q $(in) -d $(genDir) apex_payload.img && " +
"$(location avbtool) print_partition_digests --image $(genDir)/apex_payload.img " +
"| cut -c 3-| tee $(out)"
}
genrule {
// Generates an apex which has same module name as apex.apexd_test.apex, but
// is actually signed with a different key.
name: "gen_key_mismatch_apex",
out: ["apex.apexd_test_different_key.apex"],
srcs: [":apex.apexd_test_no_inst_key"],
tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
cmd: "unzip -q $(in) -d $(genDir) && " +
"$(location conv_apex_manifest) setprop name com.android.apex.test_package $(genDir)/apex_manifest.pb && " +
"$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
"-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
"-o $(genDir)/unaligned.apex && " +
"$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
"$(genDir)/apex.apexd_test_different_key.apex"
}
genrule {
// Generates an apex which has same module name as apex.apexd_test.apex, but
// is actually signed with a different key.
name: "gen_key_mismatch_apex_v2",
out: ["apex.apexd_test_different_key_v2.apex"],
srcs: [":apex.apexd_test_no_inst_key"],
tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
cmd: "unzip -q $(in) -d $(genDir) && " +
"$(location conv_apex_manifest) setprop name com.android.apex.test_package $(genDir)/apex_manifest.pb && " +
"$(location conv_apex_manifest) setprop version 2 $(genDir)/apex_manifest.pb && " +
"$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
"-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
"-o $(genDir)/unaligned.apex && " +
"$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
"$(genDir)/apex.apexd_test_different_key_v2.apex"
}
genrule {
// Generates an apex which has a different manifest outside the filesystem
// image.
name: "gen_manifest_mismatch_rebootless_apex",
out: ["test.rebootless_apex_manifest_mismatch.apex"],
srcs: [":test.rebootless_apex_v1"],
tools: ["soong_zip", "zipalign", "conv_apex_manifest"],
cmd: "unzip -q $(in) -d $(genDir) && " +
"$(location conv_apex_manifest) setprop version 137 $(genDir)/apex_manifest.pb && " +
"$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
"-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
"-o $(genDir)/unaligned.apex && " +
"$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
"$(genDir)/test.rebootless_apex_manifest_mismatch.apex"
}
genrule {
// Generates an apex with a corrupted filesystem image, which should cause
// dm-verity verification to fail
name: "gen_corrupt_rebootless_apex",
out: ["test.rebootless_apex_corrupted.apex"],
srcs: [":test.rebootless_apex_v1"],
tools: ["soong_zip", "zipalign"],
cmd: "unzip -q $(in) -d $(genDir) && " +
"dd if=/dev/zero of=$(genDir)/apex_payload.img conv=notrunc bs=1024 seek=16 count=1 && " +
"$(location soong_zip) -d -C $(genDir) -D $(genDir) " +
"-s apex_manifest.pb -s apex_payload.img -s apex_pubkey " +
"-o $(genDir)/unaligned.apex && " +
"$(location zipalign) -f 4096 $(genDir)/unaligned.apex " +
"$(genDir)/test.rebootless_apex_corrupted.apex"
}
cc_test {
name: "ApexTestCases",
defaults: [
"apex_flags_defaults",
"libapex-deps",
"libapexd-deps"
],
require_root: true,
cflags: [
// Otherwise libgmock won't compile.
"-Wno-used-but-marked-unused",
],
data: [
":apex.apexd_test",
":apex.apexd_test_erofs",
":apex.apexd_test_f2fs",
":apex.apexd_test_digest",
":apex.apexd_test_erofs_digest",
":apex.apexd_test_f2fs_digest",
":apex.apexd_test_classpath",
":apex.apexd_test_different_app",
":apex.apexd_test_no_hashtree",
":apex.apexd_test_no_hashtree_2",
":apex.apexd_test_no_inst_key",
":apex.apexd_test_f2fs_no_inst_key",
":apex.apexd_test_nocode",
":apex.apexd_test_v2",
":apex.corrupted_b146895998",
":apex.banned_name",
":gen_key_mismatch_apex",
":gen_key_mismatch_apex_v2",
":gen_key_mismatch_capex",
":gen_manifest_mismatch_apex",
":gen_manifest_mismatch_apex_no_hashtree",
":gen_corrupt_superblock_apex",
":gen_corrupt_apex",
":gen_capex_not_decompressible",
":gen_capex_without_apex",
":gen_capex_with_v2_apex",
":gen_key_mismatch_with_original_capex",
":com.android.apex.cts.shim.v1_prebuilt",
":com.android.apex.cts.shim.v2_prebuilt",
":com.android.apex.cts.shim.v2_wrong_sha_prebuilt",
":com.android.apex.cts.shim.v2_additional_file_prebuilt",
":com.android.apex.cts.shim.v2_additional_folder_prebuilt",
":com.android.apex.cts.shim.v2_with_pre_install_hook_prebuilt",
":com.android.apex.cts.shim.v2_with_post_install_hook_prebuilt",
":com.android.apex.compressed_sharedlibs",
":com.android.apex.compressed.v1",
":com.android.apex.compressed.v1_different_digest",
":com.android.apex.compressed.v1_different_digest_original",
":com.android.apex.compressed.v1_original",
":com.android.apex.compressed.v2",
":com.android.apex.compressed.v2_original",
":com.android.sepolicy",
":gen_manifest_mismatch_compressed_apex_v2",
"apexd_testdata/com.android.apex.test_package.avbpubkey",
"apexd_testdata/com.android.apex.compressed.avbpubkey",
":com.android.apex.test.sharedlibs_generated.v1.libvX_prebuilt",
":com.android.apex.test.sharedlibs_generated.v2.libvY_prebuilt",
":test.rebootless_apex_v1",
":test.rebootless_apex_v2",
":test.rebootless_apex_v2_no_hashtree",
":gen_manifest_mismatch_rebootless_apex",
":gen_corrupt_rebootless_apex",
":test.rebootless_apex_provides_sharedlibs",
":test.rebootless_apex_provides_native_libs",
":test.rebootless_apex_requires_shared_apex_libs",
":test.rebootless_apex_jni_libs",
":test.rebootless_apex_add_native_lib",
":test.rebootless_apex_remove_native_lib",
":test.rebootless_apex_app_in_apex",
":test.rebootless_apex_priv_app_in_apex",
],
srcs: [
"apex_classpath_test.cpp",
"apex_database_test.cpp",
"apex_file_test.cpp",
"apex_file_repository_test.cpp",
"apex_manifest_test.cpp",
"apexd_test.cpp",
"apexd_session_test.cpp",
"apexd_verity_test.cpp",
"apexd_utils_test.cpp",
"apexservice_test.cpp",
],
host_supported: false,
compile_multilib: "first",
static_libs: [
"apex_aidl_interface-cpp",
"libapex",
"libapexd",
"libfstab",
"libgmock",
],
shared_libs: [
"libbinder",
"libfs_mgr",
"libutils",
],
generated_sources: ["apex-info-list"],
test_suites: ["device-tests"],
test_config: "AndroidTest.xml",
}
cc_test {
name: "flattened_apex_test",
defaults: [
"apex_flags_defaults",
"libapex-deps",
"libapexd-deps"
],
srcs: ["flattened_apex_test.cpp"],
host_supported: false,
compile_multilib: "first",
static_libs: [
"libapex",
"libapexd",
],
test_suites: ["device-tests"],
test_config: "flattened_apex_test_config.xml",
}
xsd_config {
name: "apex-info-list",
srcs: ["ApexInfoList.xsd"],
package_name: "com.android.apex",
api_dir: "apex-info-list-api",
gen_writer: true,
}
xsd_config {
name: "apex-info-list-tinyxml",
srcs: ["ApexInfoList.xsd"],
package_name: "com.android.apex",
api_dir: "apex-info-list-api",
gen_writer: true,
tinyxml: true,
}

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration description="Runs ApexTestCases">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-native" />
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<!-- Note: despite how this line reads, it will push the complete testcase directory, thus
all apexes that are required by the blueprint's data[] tag. -->
<option name="push" value="ApexTestCases->/data/local/tmp/ApexTestCases" />
</target_preparer>
<!-- The test runs as root to prepare the temporary directory, make selinux adjustments
and so on to provide files that apexd can consume. This is done to avoid dependencies
on higher levels (e.g., PackageInstaller). -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="remount-system" value="true" />
<option name="push" value="apex.apexd_test.apex->/system_ext/apex/apex.apexd_test.apex" />
<option name="push" value="apex.apexd_test_different_app.apex->/system_ext/apex/apex.apexd_test_different_app.apex" />
</target_preparer>
<!-- system_server might still hold a reference to apexservice. This means that apexd is still
running, and test apexes pushed in the PushFilePreparer above are not yet scanned.
One way to solve this is to reboot a device, but that would significantly increase
execution of this test module. Instead, force a GC in system_server by sending kill -10. -->
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="shell kill -10 $(pidof system_server)" />
<option name="teardown-command" value="shell kill -10 $(pidof system_server)" />
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
<!-- Note: despite how these lines read, the test will run nicely separated out
of a subfolder. -->
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="ApexTestCases" />
</test>
</configuration>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="apex-info-list">
<xs:complexType>
<xs:sequence>
<xs:element ref="apex-info" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="apex-info">
<xs:complexType>
<xs:attribute name="moduleName" type="xs:string" use="required"/>
<xs:attribute name="modulePath" type="xs:string" use="required"/>
<xs:attribute name="preinstalledModulePath" type="xs:string"/>
<xs:attribute name="versionCode" type="xs:long" use="required"/>
<xs:attribute name="versionName" type="xs:string" use="required"/>
<xs:attribute name="isFactory" type="xs:boolean" use="required"/>
<xs:attribute name="isActive" type="xs:boolean" use="required"/>
<xs:attribute name="lastUpdateMillis" type="xs:long"/>
<xs:attribute name="provideSharedApexLibs" type="xs:boolean" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>

18
apex/apexd/TEST_MAPPING Normal file
View File

@ -0,0 +1,18 @@
{
"presubmit": [
{
"name": "ApexTestCases"
},
{
"name": "MicrodroidHostTestCases"
}
],
"imports": [
{
"path": "system/apex/tests"
},
{
"path": "vendor/xts/gts-tests/hostsidetests/stagedinstall"
}
]
}

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.apex;
parcelable ApexInfo {
@utf8InCpp String moduleName;
@utf8InCpp String modulePath;
@utf8InCpp String preinstalledModulePath;
long versionCode;
@utf8InCpp String versionName;
boolean isFactory;
boolean isActive;
// Populated only for getStagedApex() API
boolean hasClassPathJars;
// Will be set to true if during this boot a different APEX package of the APEX was
// activated, than in the previous boot.
// This can happen in the following situations:
// 1. It was part of the staged session that was applied during this boot.
// 2. A compressed system APEX was decompressed during this boot.
// 3. apexd failed to activate an APEX on /data/apex/active (that was successfully
// activated during last boot) and needed to fallback to pre-installed counterpart.
// Note: this field can only be set to true during boot, after boot is completed
// (sys.boot_completed = 1) value of this field will always be false.
boolean activeApexChanged;
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.apex;
import android.apex.ApexInfo;
parcelable ApexInfoList {
ApexInfo[] apexInfos;
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.apex;
parcelable ApexSessionInfo {
int sessionId;
// Maps to apex::proto::SessionState::State enum.
boolean isUnknown;
boolean isVerified;
boolean isStaged;
boolean isActivated;
boolean isRevertInProgress;
boolean isActivationFailed;
boolean isSuccess;
boolean isReverted;
boolean isRevertFailed;
@utf8InCpp String crashingNativeProcess;
@utf8InCpp String errorMessage;
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.apex;
parcelable ApexSessionParams {
int sessionId = 0;
int[] childSessionIds = {};
boolean hasRollbackEnabled = false;
boolean isRollback = false;
int rollbackId = 0;
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.apex;
parcelable CompressedApexInfo {
@utf8InCpp String moduleName;
long versionCode;
long decompressedSize;
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.apex;
import android.apex.CompressedApexInfo;
parcelable CompressedApexInfoList {
CompressedApexInfo[] apexInfos;
}

View File

@ -0,0 +1,143 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.apex;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.ApexSessionParams;
import android.apex.CompressedApexInfoList;
interface IApexService {
void submitStagedSession(in ApexSessionParams params, out ApexInfoList packages);
void markStagedSessionReady(int session_id);
void markStagedSessionSuccessful(int session_id);
ApexSessionInfo[] getSessions();
ApexSessionInfo getStagedSessionInfo(int session_id);
ApexInfo[] getStagedApexInfos(in ApexSessionParams params);
ApexInfo[] getActivePackages();
ApexInfo[] getAllPackages();
void abortStagedSession(int session_id);
void revertActiveSessions();
/**
* Copies the CE apex data directory for the given user to the backup
* location.
*/
void snapshotCeData(int user_id, int rollback_id, in @utf8InCpp String apex_name);
/**
* Restores the snapshot of the CE apex data directory for the given user and
* apex. Note the snapshot will be deleted after restoration succeeded.
*/
void restoreCeData(int user_id, int rollback_id, in @utf8InCpp String apex_name);
/**
* Deletes device-encrypted snapshots for the given rollback id.
*/
void destroyDeSnapshots(int rollback_id);
/**
* Deletes credential-encrypted snapshots for the given user, for the given rollback id.
*/
void destroyCeSnapshots(int user_id, int rollback_id);
/**
* Deletes all credential-encrypted snapshots for the given user, except for
* those listed in retain_rollback_ids.
*/
void destroyCeSnapshotsNotSpecified(int user_id, in int[] retain_rollback_ids);
void unstagePackages(in @utf8InCpp List<String> active_package_paths);
/**
* Returns the active package corresponding to |package_name| and null
* if none exists.
*/
ApexInfo getActivePackage(in @utf8InCpp String package_name);
/**
* Not meant for use outside of testing. The call will not be
* functional on user builds.
*/
void stagePackages(in @utf8InCpp List<String> package_tmp_paths);
/**
* Not meant for use outside of testing. The call will not be
* functional on user builds.
*/
void resumeRevertIfNeeded();
/**
* Forces apexd to remount all active packages.
*
* This call is mostly useful for speeding up development of APEXes.
* Instead of going through a full APEX installation that requires a reboot,
* developers can incorporate this method in much faster `adb sync` based
* workflow:
*
* 1. adb shell stop
* 2. adb sync
* 3. adb shell cmd -w apexservice remountPackages
* 4. adb shell start
*
* Note, that for an APEX package will be successfully remounted only if
* there are no alive processes holding a reference to it.
*
* Not meant for use outside of testing. This call will not be functional
* on user builds. Only root is allowed to call this method.
*/
void remountPackages();
/**
* Forces apexd to recollect pre-installed data from the given |paths|.
*
* Not meant for use outside of testing. This call will not be functional
* on user builds. Only root is allowed to call this method.
*/
void recollectPreinstalledData(in @utf8InCpp List<String> paths);
/**
* Forces apexd to recollect data apex from the given |path|.
*
* Not meant for use outside of testing. This call will not be functional
* on user builds. Only root is allowed to call this method.
*/
void recollectDataApex(in @utf8InCpp String path, in@utf8InCpp String decompression_dir);
/**
* Informs apexd that the boot has completed.
*/
void markBootCompleted();
/**
* Assuming the provided compressed APEX will be installed on next boot,
* calculate how much space will be required for decompression
*/
long calculateSizeForCompressedApex(in CompressedApexInfoList compressed_apex_info_list);
/**
* Reserve space on /data partition for compressed APEX decompression. Returns error if
* reservation fails. If empty list is passed, then reserved space is deallocated.
*/
void reserveSpaceForCompressedApex(in CompressedApexInfoList compressed_apex_info_list);
/**
* Performs a non-staged install of the given APEX.
* Note: don't confuse this to preInstall and postInstall binder calls which are only used to
* test corresponding features of APEX packages.
*/
ApexInfo installAndActivatePackage(in @utf8InCpp String packagePath);
}

View File

@ -0,0 +1,47 @@
// Signature format: 2.0
package com.android.apex {
public class ApexInfo {
ctor public ApexInfo();
method public boolean getIsActive();
method public boolean getIsFactory();
method public long getLastUpdateMillis();
method public String getModuleName();
method public String getModulePath();
method public String getPreinstalledModulePath();
method public boolean getProvideSharedApexLibs();
method public long getVersionCode();
method public String getVersionName();
method public void setIsActive(boolean);
method public void setIsFactory(boolean);
method public void setLastUpdateMillis(long);
method public void setModuleName(String);
method public void setModulePath(String);
method public void setPreinstalledModulePath(String);
method public void setProvideSharedApexLibs(boolean);
method public void setVersionCode(long);
method public void setVersionName(String);
}
public class ApexInfoList {
ctor public ApexInfoList();
method public java.util.List<com.android.apex.ApexInfo> getApexInfo();
}
public class XmlParser {
ctor public XmlParser();
method public static com.android.apex.ApexInfo readApexInfo(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static com.android.apex.ApexInfoList readApexInfoList(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
}
public class XmlWriter implements java.io.Closeable {
ctor public XmlWriter(java.io.PrintWriter);
method public void close();
method public static void write(com.android.apex.XmlWriter, com.android.apex.ApexInfoList) throws java.io.IOException;
method public static void write(com.android.apex.XmlWriter, com.android.apex.ApexInfo) throws java.io.IOException;
}
}

View File

@ -0,0 +1,45 @@
// Signature format: 2.0
package com.android.apex {
public class ApexInfo {
ctor public ApexInfo();
method public boolean getIsActive();
method public boolean getIsFactory();
method public long getLastUpdateMillis();
method public String getModuleName();
method public String getModulePath();
method public String getPreinstalledModulePath();
method public long getVersionCode();
method public String getVersionName();
method public void setIsActive(boolean);
method public void setIsFactory(boolean);
method public void setLastUpdateMillis(long);
method public void setModuleName(String);
method public void setModulePath(String);
method public void setPreinstalledModulePath(String);
method public void setVersionCode(long);
method public void setVersionName(String);
}
public class ApexInfoList {
ctor public ApexInfoList();
method public java.util.List<com.android.apex.ApexInfo> getApexInfo();
}
public class XmlParser {
ctor public XmlParser();
method public static com.android.apex.ApexInfo readApexInfo(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static com.android.apex.ApexInfoList readApexInfoList(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
}
public class XmlWriter implements java.io.Closeable {
ctor public XmlWriter(java.io.PrintWriter);
method public void close();
method public static void write(com.android.apex.XmlWriter, com.android.apex.ApexInfoList) throws java.io.IOException;
method public static void write(com.android.apex.XmlWriter, com.android.apex.ApexInfo) throws java.io.IOException;
}
}

View File

@ -0,0 +1 @@
// Signature format: 2.0

View File

@ -0,0 +1,131 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "apex_classpath.h"
#include <android-base/file.h>
#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <logwrap/logwrap.h>
#include <fstream>
#include <regex>
namespace android {
namespace apex {
using ::android::base::Error;
using ::android::base::StringPrintf;
android::base::Result<ClassPath> ClassPath::DeriveClassPath(
const std::vector<std::string>& temp_mounted_apex_paths,
const std::string& sdkext_module_name) {
if (temp_mounted_apex_paths.empty()) {
return Error()
<< "Invalid argument: There are no APEX to derive claspath from";
}
// Call derive_classpath binary to generate required information
// Prefer using the binary from staged session if possible
std::string apex_of_binary =
StringPrintf("/apex/%s", sdkext_module_name.c_str());
for (const auto& temp_mounted_apex_path : temp_mounted_apex_paths) {
if (temp_mounted_apex_path.starts_with(apex_of_binary + "@")) {
apex_of_binary = temp_mounted_apex_path;
break;
}
}
std::string binary_path =
StringPrintf("%s/bin/derive_classpath", apex_of_binary.c_str());
std::string scan_dirs_flag =
StringPrintf("--scan-dirs=%s",
android::base::Join(temp_mounted_apex_paths, ",").c_str());
// Create a temp file to write output
auto temp_output_path = "/apex/derive_classpath_temp";
auto cleanup = [temp_output_path]() {
android::base::RemoveFileIfExists(temp_output_path);
};
auto scope_guard = android::base::make_scope_guard(cleanup);
// Cleanup to ensure we are creating an empty file
cleanup();
// Create the empty file where derive_classpath will write into
std::ofstream _(temp_output_path);
const char* const argv[] = {binary_path.c_str(), scan_dirs_flag.c_str(),
temp_output_path};
auto rc = logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG,
false, nullptr);
if (rc != 0) {
return Error() << "Running derive_classpath failed; binary path: " +
binary_path;
}
return ClassPath::ParseFromFile(temp_output_path);
}
// Parse the string output into structured information
// The raw output from derive_classpath has the following format:
// ```
// export BOOTCLASSPATH path/to/jar1:/path/to/jar2
// export DEX2OATBOOTCLASSPATH
// export SYSTEMSERVERCLASSPATH path/to/some/jar
android::base::Result<ClassPath> ClassPath::ParseFromFile(
const std::string& file_path) {
ClassPath result;
std::string contents;
auto read_status = android::base::ReadFileToString(file_path, &contents,
/*follow_symlinks=*/false);
if (!read_status) {
return Error() << "Failed to read classpath info from file";
}
// Jars in apex have the following format: /apex/<package-name>/*
const std::regex capture_apex_package_name("^/apex/([^/]+)/");
for (const auto& line : android::base::Split(contents, "\n")) {
// Split the line by space. The second element determines which type of
// classpath we are dealing with and the third element are the jars
// separated by :
auto tokens = android::base::Split(line, " ");
if (tokens.size() < 3) {
continue;
}
auto jars_list = tokens[2];
for (const auto& jar_path : android::base::Split(jars_list, ":")) {
std::smatch match;
if (std::regex_search(jar_path, match, capture_apex_package_name)) {
auto package_name = match[1];
result.AddPackageWithClasspathJars(package_name);
}
}
}
return result;
}
void ClassPath::AddPackageWithClasspathJars(const std::string& package) {
packages_with_classpath_jars.insert(package);
}
bool ClassPath::HasClassPathJars(const std::string& package) {
return packages_with_classpath_jars.find(package) !=
packages_with_classpath_jars.end();
}
} // namespace apex
} // namespace android

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_APEXD_APEX_CLASSPATH_H_
#define ANDROID_APEXD_APEX_CLASSPATH_H_
#include <android-base/result.h>
#include <set>
#include <string>
namespace android {
namespace apex {
/**
* An utility class that contains logic to extract classpath fragments
* information from mounted APEX.
*
* The bulk of the work is done by derive_classpath binary, which is found
* inside sdkext module. This class is a wrapper for calling that binary and
* parsing its string output into a structured object.
*/
class ClassPath {
static constexpr const char* kSdkExtModuleName = "com.android.sdkext";
public:
static android::base::Result<ClassPath> DeriveClassPath(
const std::vector<std::string>& temp_mounted_apex_paths,
const std::string& sdkext_module_name = kSdkExtModuleName);
bool HasClassPathJars(const std::string& package);
// Exposed for testing only
static android::base::Result<ClassPath> ParseFromFile(
const std::string& file_path);
private:
void AddPackageWithClasspathJars(const std::string& package);
std::set<std::string> packages_with_classpath_jars;
};
} // namespace apex
} // namespace android
#endif // ANDROID_APEXD_APEXD_CHECKPOINT_H_

View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "apex_classpath.h"
#include <android-base/file.h>
#include <android-base/result-gmock.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <fstream>
#include <string>
namespace android {
namespace apex {
using android::base::WriteStringToFile;
using android::base::testing::Ok;
using android::base::testing::WithMessage;
using ::testing::HasSubstr;
TEST(ApexClassPathUnitTest, ParseFromFile) {
TemporaryFile output;
WriteStringToFile(
"export BOOTCLASSPATH /apex/a/jar1:/apex/b/jar2\n"
"export SYSTEMSERVERCLASSPATH\n"
"export UNEXPECTED /apex/c/\n",
output.path);
auto result = ClassPath::ParseFromFile(output.path);
ASSERT_THAT(result, Ok());
ASSERT_THAT(result->HasClassPathJars("a"), true);
ASSERT_THAT(result->HasClassPathJars("b"), true);
ASSERT_THAT(result->HasClassPathJars("c"), true);
ASSERT_THAT(result->HasClassPathJars("d"), false);
}
TEST(ApexClassPathUnitTest, ParseFromFileJarsNotInApex) {
TemporaryFile output;
// We accept jars with regex: /apex/<package-name>/*
WriteStringToFile("export BOOTCLASSPATH a:b\n", output.path);
auto result = ClassPath::ParseFromFile(output.path);
ASSERT_THAT(result, Ok());
ASSERT_THAT(result->HasClassPathJars("a"), false);
ASSERT_THAT(result->HasClassPathJars("b"), false);
}
TEST(ApexClassPathUnitTest, ParseFromFilePackagesWithSamePrefix) {
TemporaryFile output;
WriteStringToFile(
"export BOOTCLASSPATH /apex/media/:/apex/mediaprovider\n"
"export SYSTEMSERVERCLASSPATH /apex/mediafoo/\n",
output.path);
auto result = ClassPath::ParseFromFile(output.path);
ASSERT_THAT(result, Ok());
ASSERT_THAT(result->HasClassPathJars("media"), true);
// "/apex/mediaprovider" did not end with /
ASSERT_THAT(result->HasClassPathJars("mediaprovider"), false);
// A prefix of an apex name present should not be accepted
ASSERT_THAT(result->HasClassPathJars("m"), false);
}
TEST(ApexClassPathUnitTest, ParseFromFileDoesNotExist) {
auto result = ClassPath::ParseFromFile("/file/does/not/exist");
ASSERT_THAT(result, HasError(WithMessage(HasSubstr(
"Failed to read classpath info from file"))));
}
TEST(ApexClassPathUnitTest, ParseFromFileEmptyJars) {
TemporaryFile output;
WriteStringToFile(
"export BOOTCLASSPATH\n"
"export SYSTEMSERVERCLASSPATH \n"
"export DEX2OATBOOTCLASSPATH \n",
output.path);
auto result = ClassPath::ParseFromFile(output.path);
ASSERT_THAT(result, Ok());
}
TEST(ApexClassPathUnitTest, DeriveClassPathNoStagedApex) {
auto result = ClassPath::DeriveClassPath({});
ASSERT_THAT(
result,
HasError(WithMessage(HasSubstr(
"Invalid argument: There are no APEX to derive claspath from"))));
}
TEST(ApexClassPathUnitTest, DeriveClassPathPreferBinaryInStagedApex) {
// Default location uses provided package name to compose binary path
auto result = ClassPath::DeriveClassPath({"/apex/temp@123"}, "different");
ASSERT_THAT(result,
HasError(WithMessage(HasSubstr(
"binary path: /apex/different/bin/derive_classpath"))));
// When staged apex has same package name, we use that location instead
result = ClassPath::DeriveClassPath({"/apex/temp@123"}, "temp");
ASSERT_THAT(result,
HasError(WithMessage(HasSubstr(
"binary path: /apex/temp@123/bin/derive_classpath"))));
}
} // namespace apex
} // namespace android

View File

@ -0,0 +1,87 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <string>
#include <unordered_set>
#include <vector>
namespace android {
namespace apex {
static constexpr const char* kApexDataDir = "/data/apex";
static constexpr const char* kActiveApexPackagesDataDir = "/data/apex/active";
static constexpr const char* kApexBackupDir = "/data/apex/backup";
static constexpr const char* kApexHashTreeDir = "/data/apex/hashtree";
static constexpr const char* kApexDecompressedDir = "/data/apex/decompressed";
static constexpr const char* kOtaReservedDir = "/data/apex/ota_reserved";
static constexpr const char* kApexPackageSystemDir = "/system/apex";
static constexpr const char* kApexPackageSystemExtDir = "/system_ext/apex";
static constexpr const char* kApexPackageVendorDir = "/vendor/apex";
static const std::vector<std::string> kApexPackageBuiltinDirs = {
kApexPackageSystemDir,
kApexPackageSystemExtDir,
"/product/apex",
kApexPackageVendorDir,
};
static constexpr const char* kApexRoot = "/apex";
static constexpr const char* kStagedSessionsDir = "/data/app-staging";
static constexpr const char* kApexDataSubDir = "apexdata";
static constexpr const char* kApexSharedLibsSubDir = "sharedlibs";
static constexpr const char* kApexSnapshotSubDir = "apexrollback";
static constexpr const char* kPreRestoreSuffix = "-prerestore";
static constexpr const char* kDeSysDataDir = "/data/misc";
static constexpr const char* kDeNDataDir = "/data/misc_de";
static constexpr const char* kCeDataDir = "/data/misc_ce";
static constexpr const char* kApexPackageSuffix = ".apex";
static constexpr const char* kCompressedApexPackageSuffix = ".capex";
static constexpr const char* kDecompressedApexPackageSuffix =
".decompressed.apex";
static constexpr const char* kOtaApexPackageSuffix = ".ota.apex";
static constexpr const char* kManifestFilenameJson = "apex_manifest.json";
static constexpr const char* kManifestFilenamePb = "apex_manifest.pb";
static constexpr const char* kApexInfoList = "apex-info-list.xml";
// These should be in-sync with system/sepolicy/private/property_contexts
static constexpr const char* kApexStatusSysprop = "apexd.status";
static constexpr const char* kApexStatusStarting = "starting";
static constexpr const char* kApexStatusActivated = "activated";
static constexpr const char* kApexStatusReady = "ready";
static constexpr const char* kMultiApexSelectPersistPrefix =
"persist.vendor.apex.";
static constexpr const char* kMultiApexSelectBootconfigPrefix =
"ro.boot.vendor.apex.";
static constexpr const char* kVmPayloadMetadataPartitionProp =
"apexd.payload_metadata.path";
static constexpr const std::chrono::seconds kBlockApexWaitTime(10);
static constexpr const char* kMetadataSepolicyStagedDir =
"/metadata/sepolicy/staged";
// Banned APEX names
static const std::unordered_set<std::string> kBannedApexName = {
kApexSharedLibsSubDir, // To avoid conflicts with predefined
// /apex/sharedlibs directory
};
} // namespace apex
} // namespace android

View File

@ -0,0 +1,311 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "apexd"
#include "apex_database.h"
#include "apex_constants.h"
#include "apex_file.h"
#include "apexd_utils.h"
#include "string_log.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/result.h>
#include <android-base/strings.h>
#include <filesystem>
#include <fstream>
#include <string>
#include <unordered_map>
#include <utility>
using android::base::ConsumeSuffix;
using android::base::EndsWith;
using android::base::ErrnoError;
using android::base::Error;
using android::base::ParseInt;
using android::base::ReadFileToString;
using android::base::Result;
using android::base::Split;
using android::base::StartsWith;
using android::base::Trim;
namespace fs = std::filesystem;
namespace android {
namespace apex {
namespace {
using MountedApexData = MountedApexDatabase::MountedApexData;
enum BlockDeviceType {
UnknownDevice,
LoopDevice,
DeviceMapperDevice,
};
const fs::path kDevBlock = "/dev/block";
const fs::path kSysBlock = "/sys/block";
class BlockDevice {
std::string name; // loopN, dm-N, ...
public:
explicit BlockDevice(const fs::path& path) { name = path.filename(); }
BlockDeviceType GetType() const {
if (StartsWith(name, "loop")) return LoopDevice;
if (StartsWith(name, "dm-")) return DeviceMapperDevice;
return UnknownDevice;
}
fs::path SysPath() const { return kSysBlock / name; }
fs::path DevPath() const { return kDevBlock / name; }
Result<std::string> GetProperty(const std::string& property) const {
auto property_file = SysPath() / property;
std::string property_value;
if (!ReadFileToString(property_file, &property_value)) {
return ErrnoError() << "Fail to read";
}
return Trim(property_value);
}
std::vector<BlockDevice> GetSlaves() const {
std::vector<BlockDevice> slaves;
std::error_code ec;
auto status = WalkDir(SysPath() / "slaves", [&](const auto& entry) {
BlockDevice dev(entry);
if (fs::is_block_file(dev.DevPath(), ec)) {
slaves.push_back(dev);
}
});
if (!status.ok()) {
LOG(WARNING) << status.error();
}
return slaves;
}
};
std::pair<fs::path, fs::path> ParseMountInfo(const std::string& mount_info) {
const auto& tokens = Split(mount_info, " ");
if (tokens.size() < 2) {
return std::make_pair("", "");
}
return std::make_pair(tokens[0], tokens[1]);
}
std::pair<std::string, int> ParseMountPoint(const std::string& mount_point) {
auto package_id = fs::path(mount_point).filename();
auto split = Split(package_id, "@");
if (split.size() == 2) {
int version;
if (!ParseInt(split[1], &version)) {
version = -1;
}
return std::make_pair(split[0], version);
}
return std::make_pair(package_id, -1);
}
bool IsActiveMountPoint(const std::string& mount_point) {
return (mount_point.find('@') == std::string::npos);
}
Result<void> PopulateLoopInfo(const BlockDevice& top_device,
const std::string& active_apex_dir,
const std::string& decompression_dir,
const std::string& apex_hash_tree_dir,
MountedApexData* apex_data) {
std::vector<BlockDevice> slaves = top_device.GetSlaves();
if (slaves.size() != 1 && slaves.size() != 2) {
return Error() << "dm device " << top_device.DevPath()
<< " has unexpected number of slaves : " << slaves.size();
}
std::vector<std::string> backing_files;
backing_files.reserve(slaves.size());
for (const auto& dev : slaves) {
if (dev.GetType() != LoopDevice) {
return Error() << dev.DevPath() << " is not a loop device";
}
auto backing_file = dev.GetProperty("loop/backing_file");
if (!backing_file.ok()) {
return backing_file.error();
}
backing_files.push_back(std::move(*backing_file));
}
// Enforce following invariant:
// * slaves[0] always represents a data loop device
// * if size = 2 then slaves[1] represents an external hashtree loop device
auto is_data_loop_device = [&](const std::string& backing_file) {
return StartsWith(backing_file, active_apex_dir) ||
StartsWith(backing_file, decompression_dir);
};
if (slaves.size() == 2) {
if (!is_data_loop_device(backing_files[0])) {
std::swap(slaves[0], slaves[1]);
std::swap(backing_files[0], backing_files[1]);
}
}
if (!is_data_loop_device(backing_files[0])) {
return Error() << "Data loop device " << slaves[0].DevPath()
<< " has unexpected backing file " << backing_files[0];
}
if (slaves.size() == 2) {
if (!StartsWith(backing_files[1], apex_hash_tree_dir)) {
return Error() << "Hashtree loop device " << slaves[1].DevPath()
<< " has unexpected backing file " << backing_files[1];
}
apex_data->hashtree_loop_name = slaves[1].DevPath();
}
apex_data->loop_name = slaves[0].DevPath();
apex_data->full_path = backing_files[0];
return {};
}
// This is not the right place to do this normalization, but proper solution
// will require some refactoring first. :(
// TODO(b/158469911): introduce MountedApexDataBuilder and delegate all
// building/normalization logic to it.
void NormalizeIfDeleted(MountedApexData* apex_data) {
std::string_view full_path = apex_data->full_path;
if (ConsumeSuffix(&full_path, "(deleted)")) {
apex_data->deleted = true;
auto it = full_path.rbegin();
while (it != full_path.rend() && isspace(*it)) {
it++;
}
full_path.remove_suffix(it - full_path.rbegin());
} else {
apex_data->deleted = false;
}
apex_data->full_path = full_path;
}
Result<MountedApexData> ResolveMountInfo(
const BlockDevice& block, const std::string& mount_point,
const std::string& active_apex_dir, const std::string& decompression_dir,
const std::string& apex_hash_tree_dir) {
bool temp_mount = EndsWith(mount_point, ".tmp");
// Now, see if it is dm-verity or loop mounted
switch (block.GetType()) {
case LoopDevice: {
auto backing_file = block.GetProperty("loop/backing_file");
if (!backing_file.ok()) {
return backing_file.error();
}
auto result = MountedApexData(block.DevPath(), *backing_file, mount_point,
/* device_name= */ "",
/* hashtree_loop_name= */ "",
/* is_temp_mount */ temp_mount);
NormalizeIfDeleted(&result);
return result;
}
case DeviceMapperDevice: {
auto name = block.GetProperty("dm/name");
if (!name.ok()) {
return name.error();
}
MountedApexData result;
result.mount_point = mount_point;
result.device_name = *name;
result.is_temp_mount = temp_mount;
auto status = PopulateLoopInfo(block, active_apex_dir, decompression_dir,
apex_hash_tree_dir, &result);
if (!status.ok()) {
return status.error();
}
NormalizeIfDeleted(&result);
return result;
}
case UnknownDevice: {
return Errorf("Can't resolve {}", block.DevPath().string());
}
}
}
} // namespace
// On startup, APEX database is populated from /proc/mounts.
// /apex/<package-id> can be mounted from
// - /dev/block/loopX : loop device
// - /dev/block/dm-X : dm-verity
// In case of loop device, it is from a non-flattened
// APEX file. This original APEX file can be tracked
// by /sys/block/loopX/loop/backing_file.
// In case of dm-verity, it is mapped to a loop device.
// This mapped loop device can be traced by
// /sys/block/dm-X/slaves/ directory which contains
// a symlink to /sys/block/loopY, which leads to
// the original APEX file.
// Device name can be retrieved from
// /sys/block/dm-Y/dm/name.
// By synchronizing the mounts info with Database on startup,
// Apexd serves the correct package list even on the devices
// which are not ro.apex.updatable.
void MountedApexDatabase::PopulateFromMounts(
const std::string& active_apex_dir, const std::string& decompression_dir,
const std::string& apex_hash_tree_dir) REQUIRES(!mounted_apexes_mutex_) {
LOG(INFO) << "Populating APEX database from mounts...";
std::unordered_map<std::string, int> active_versions;
std::ifstream mounts("/proc/mounts");
std::string line;
std::lock_guard lock(mounted_apexes_mutex_);
while (std::getline(mounts, line)) {
auto [block, mount_point] = ParseMountInfo(line);
// TODO(b/158469914): distinguish between temp and non-temp mounts
if (fs::path(mount_point).parent_path() != kApexRoot) {
continue;
}
if (IsActiveMountPoint(mount_point)) {
continue;
}
auto mount_data =
ResolveMountInfo(BlockDevice(block), mount_point, active_apex_dir,
decompression_dir, apex_hash_tree_dir);
if (!mount_data.ok()) {
LOG(WARNING) << "Can't resolve mount info " << mount_data.error();
continue;
}
auto [package, version] = ParseMountPoint(mount_point);
AddMountedApexLocked(package, false, *mount_data);
auto active = active_versions[package] < version;
if (active) {
active_versions[package] = version;
SetLatestLocked(package, mount_data->full_path);
}
LOG(INFO) << "Found " << mount_point << " backed by"
<< (mount_data->deleted ? " deleted " : " ") << "file "
<< mount_data->full_path;
}
LOG(INFO) << mounted_apexes_.size() << " packages restored.";
}
} // namespace apex
} // namespace android

278
apex/apexd/apex_database.h Normal file
View File

@ -0,0 +1,278 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_APEXD_APEX_DATABASE_H_
#define ANDROID_APEXD_APEX_DATABASE_H_
#include <map>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_set>
#include <android-base/logging.h>
#include <android-base/result.h>
#include <android-base/thread_annotations.h>
namespace android {
namespace apex {
class MountedApexDatabase {
public:
// Stores associated low-level data for a mounted APEX. To conserve memory,
// the APEX file isn't stored, but must be opened to retrieve specific data.
struct MountedApexData {
std::string loop_name; // Loop device used (fs path).
std::string full_path; // Full path to the apex file.
std::string mount_point; // Path this apex is mounted on.
std::string device_name; // Name of the dm verity device.
// Name of the loop device backing up hashtree or empty string in case
// hashtree is embedded inside an APEX.
std::string hashtree_loop_name;
// Whenever apex file specified in full_path was deleted.
bool deleted;
// Whether the mount is a temp mount or not.
bool is_temp_mount;
MountedApexData() {}
MountedApexData(const std::string& loop_name, const std::string& full_path,
const std::string& mount_point,
const std::string& device_name,
const std::string& hashtree_loop_name,
bool is_temp_mount = false)
: loop_name(loop_name),
full_path(full_path),
mount_point(mount_point),
device_name(device_name),
hashtree_loop_name(hashtree_loop_name),
is_temp_mount(is_temp_mount) {}
inline bool operator<(const MountedApexData& rhs) const {
int compare_val = loop_name.compare(rhs.loop_name);
if (compare_val < 0) {
return true;
} else if (compare_val > 0) {
return false;
}
compare_val = full_path.compare(rhs.full_path);
if (compare_val < 0) {
return true;
} else if (compare_val > 0) {
return false;
}
compare_val = mount_point.compare(rhs.mount_point);
if (compare_val < 0) {
return true;
} else if (compare_val > 0) {
return false;
}
compare_val = device_name.compare(rhs.device_name);
if (compare_val < 0) {
return true;
} else if (compare_val > 0) {
return false;
}
return hashtree_loop_name < rhs.hashtree_loop_name;
}
};
template <typename... Args>
inline void AddMountedApexLocked(const std::string& package, bool latest,
Args&&... args)
REQUIRES(mounted_apexes_mutex_) {
auto it = mounted_apexes_.find(package);
if (it == mounted_apexes_.end()) {
auto insert_it =
mounted_apexes_.emplace(package, std::map<MountedApexData, bool>());
CHECK(insert_it.second);
it = insert_it.first;
}
auto check_it = it->second.emplace(
MountedApexData(std::forward<Args>(args)...), latest);
CHECK(check_it.second);
CheckAtMostOneLatest();
CheckUniqueLoopDm();
}
template <typename... Args>
inline void AddMountedApex(const std::string& package, bool latest,
Args&&... args) REQUIRES(!mounted_apexes_mutex_) {
std::lock_guard lock(mounted_apexes_mutex_);
AddMountedApexLocked(package, latest, args...);
}
inline void RemoveMountedApex(const std::string& package,
const std::string& full_path,
bool match_temp_mounts = false)
REQUIRES(!mounted_apexes_mutex_) {
std::lock_guard lock(mounted_apexes_mutex_);
auto it = mounted_apexes_.find(package);
if (it == mounted_apexes_.end()) {
return;
}
auto& pkg_map = it->second;
for (auto pkg_it = pkg_map.begin(); pkg_it != pkg_map.end(); ++pkg_it) {
if (pkg_it->first.full_path == full_path &&
pkg_it->first.is_temp_mount == match_temp_mounts) {
pkg_map.erase(pkg_it);
return;
}
}
}
inline void SetLatest(const std::string& package,
const std::string& full_path)
REQUIRES(!mounted_apexes_mutex_) {
std::lock_guard lock(mounted_apexes_mutex_);
SetLatestLocked(package, full_path);
}
inline void SetLatestLocked(const std::string& package,
const std::string& full_path)
REQUIRES(mounted_apexes_mutex_) {
auto it = mounted_apexes_.find(package);
CHECK(it != mounted_apexes_.end());
auto& pkg_map = it->second;
for (auto pkg_it = pkg_map.begin(); pkg_it != pkg_map.end(); ++pkg_it) {
if (pkg_it->first.full_path == full_path) {
pkg_it->second = true;
for (auto reset_it = pkg_map.begin(); reset_it != pkg_map.end();
++reset_it) {
if (reset_it != pkg_it) {
reset_it->second = false;
}
}
return;
}
}
LOG(FATAL) << "Did not find " << package << " " << full_path;
}
template <typename T>
inline void ForallMountedApexes(const std::string& package, const T& handler,
bool match_temp_mounts = false) const
REQUIRES(!mounted_apexes_mutex_) {
std::lock_guard lock(mounted_apexes_mutex_);
auto it = mounted_apexes_.find(package);
if (it == mounted_apexes_.end()) {
return;
}
for (auto& pair : it->second) {
if (pair.first.is_temp_mount == match_temp_mounts) {
handler(pair.first, pair.second);
}
}
}
template <typename T>
inline void ForallMountedApexes(const T& handler,
bool match_temp_mounts = false) const
REQUIRES(!mounted_apexes_mutex_) {
std::lock_guard lock(mounted_apexes_mutex_);
for (const auto& pkg : mounted_apexes_) {
for (const auto& pair : pkg.second) {
if (pair.first.is_temp_mount == match_temp_mounts) {
handler(pkg.first, pair.first, pair.second);
}
}
}
}
inline std::optional<MountedApexData> GetLatestMountedApex(
const std::string& package) REQUIRES(!mounted_apexes_mutex_) {
std::optional<MountedApexData> ret;
ForallMountedApexes(package,
[&ret](const MountedApexData& data, bool latest) {
if (latest) {
ret.emplace(data);
}
});
return ret;
}
void PopulateFromMounts(const std::string& active_apex_dir,
const std::string& decompression_dir,
const std::string& apex_hash_tree_dir);
// Resets state of the database. Should only be used in testing.
inline void Reset() REQUIRES(!mounted_apexes_mutex_) {
std::lock_guard lock(mounted_apexes_mutex_);
mounted_apexes_.clear();
}
private:
// A map from package name to mounted apexes.
// Note: using std::maps to
// a) so we do not have to worry about iterator invalidation.
// b) do not have to const_cast (over std::set)
// TODO(b/158467745): This structure (and functions) need to be guarded by
// locks.
std::map<std::string, std::map<MountedApexData, bool>> mounted_apexes_
GUARDED_BY(mounted_apexes_mutex_);
// To fix thread safety negative capability warning
class Mutex : public std::mutex {
public:
// for negative capabilities
const Mutex& operator!() const { return *this; }
};
mutable Mutex mounted_apexes_mutex_;
inline void CheckAtMostOneLatest() REQUIRES(mounted_apexes_mutex_) {
for (const auto& apex_set : mounted_apexes_) {
size_t count = 0;
for (const auto& pair : apex_set.second) {
if (pair.second) {
count++;
}
}
CHECK_LE(count, 1u) << apex_set.first;
}
}
inline void CheckUniqueLoopDm() REQUIRES(mounted_apexes_mutex_) {
std::unordered_set<std::string> loop_devices;
std::unordered_set<std::string> dm_devices;
for (const auto& apex_set : mounted_apexes_) {
for (const auto& pair : apex_set.second) {
if (pair.first.loop_name != "") {
CHECK(loop_devices.insert(pair.first.loop_name).second)
<< "Duplicate loop device: " << pair.first.loop_name;
}
if (pair.first.device_name != "") {
CHECK(dm_devices.insert(pair.first.device_name).second)
<< "Duplicate dm device: " << pair.first.device_name;
}
if (pair.first.hashtree_loop_name != "") {
CHECK(loop_devices.insert(pair.first.hashtree_loop_name).second)
<< "Duplicate loop device: " << pair.first.hashtree_loop_name;
}
}
}
}
};
} // namespace apex
} // namespace android
#endif // ANDROID_APEXD_APEX_DATABASE_H_

View File

@ -0,0 +1,292 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string>
#include <tuple>
#include <android-base/macros.h>
#include <gtest/gtest.h>
#include "apex_database.h"
namespace android {
namespace apex {
namespace {
using MountedApexData = MountedApexDatabase::MountedApexData;
TEST(MountedApexDataTest, LinearOrder) {
constexpr const char* kLoopName[] = {"loop1", "loop2", "loop3"};
constexpr const char* kPath[] = {"path1", "path2", "path3"};
constexpr const char* kMount[] = {"mount1", "mount2", "mount3"};
constexpr const char* kDm[] = {"dm1", "dm2", "dm3"};
constexpr const char* kHashtreeLoopName[] = {"hash-loop1", "hash-loop2",
"hash-loop3"};
// NOLINTNEXTLINE(bugprone-sizeof-expression)
constexpr size_t kCount = arraysize(kLoopName) * arraysize(kPath) *
arraysize(kMount) * arraysize(kDm);
auto index_fn = [&](size_t i) {
const size_t loop_index = i % arraysize(kLoopName);
const size_t loop_rest = i / arraysize(kLoopName);
const size_t path_index = loop_rest % arraysize(kPath);
const size_t path_rest = loop_rest / arraysize(kPath);
const size_t mount_index = path_rest % arraysize(kMount);
const size_t mount_rest = path_rest / arraysize(kMount);
const size_t dm_index = mount_rest % arraysize(kDm);
const size_t dm_rest = mount_rest / arraysize(kHashtreeLoopName);
const size_t hashtree_loop_index = dm_rest % arraysize(kHashtreeLoopName);
CHECK_EQ(dm_rest / arraysize(kHashtreeLoopName), 0u);
return std::make_tuple(loop_index, path_index, mount_index, dm_index,
hashtree_loop_index);
};
MountedApexData data[kCount];
for (size_t i = 0; i < kCount; ++i) {
size_t loop_idx, path_idx, mount_idx, dm_idx, hash_loop_idx;
std::tie(loop_idx, path_idx, mount_idx, dm_idx, hash_loop_idx) =
index_fn(i);
data[i] =
MountedApexData(kLoopName[loop_idx], kPath[path_idx], kMount[mount_idx],
kDm[dm_idx], kHashtreeLoopName[hash_loop_idx]);
}
for (size_t i = 0; i < kCount; ++i) {
size_t loop_idx_i, path_idx_i, mount_idx_i, dm_idx_i, hash_loop_idx_i;
std::tie(loop_idx_i, path_idx_i, mount_idx_i, dm_idx_i, hash_loop_idx_i) =
index_fn(i);
for (size_t j = i; j < kCount; ++j) {
size_t loop_idx_j, path_idx_j, mount_idx_j, dm_idx_j, hash_loop_idx_j;
std::tie(loop_idx_j, path_idx_j, mount_idx_j, dm_idx_j, hash_loop_idx_j) =
index_fn(j);
if (loop_idx_i != loop_idx_j) {
EXPECT_EQ(loop_idx_i < loop_idx_j, data[i] < data[j]);
continue;
}
if (path_idx_i != path_idx_j) {
EXPECT_EQ(path_idx_i < path_idx_j, data[i] < data[j]);
continue;
}
if (mount_idx_i != mount_idx_j) {
EXPECT_EQ(mount_idx_i < mount_idx_j, data[i] < data[j]);
continue;
}
if (dm_idx_i != dm_idx_j) {
EXPECT_EQ(dm_idx_i < dm_idx_j, data[i] < data[j]);
continue;
}
EXPECT_EQ(hash_loop_idx_i < hash_loop_idx_j, data[i] < data[j]);
}
}
}
size_t CountPackages(const MountedApexDatabase& db) {
size_t ret = 0;
db.ForallMountedApexes([&ret](const std::string& a ATTRIBUTE_UNUSED,
const MountedApexData& b ATTRIBUTE_UNUSED,
bool c ATTRIBUTE_UNUSED) { ++ret; });
return ret;
}
bool Contains(const MountedApexDatabase& db, const std::string& package,
const std::string& loop_name, const std::string& full_path,
const std::string& mount_point, const std::string& device_name,
const std::string& hashtree_loop_name) {
bool found = false;
db.ForallMountedApexes([&](const std::string& p, const MountedApexData& d,
bool b ATTRIBUTE_UNUSED) {
if (package == p && loop_name == d.loop_name && full_path == d.full_path &&
mount_point == d.mount_point && device_name == d.device_name &&
hashtree_loop_name == d.hashtree_loop_name) {
found = true;
}
});
return found;
}
bool ContainsPackage(const MountedApexDatabase& db, const std::string& package,
const std::string& loop_name, const std::string& full_path,
const std::string& dm,
const std::string& hashtree_loop_name) {
bool found = false;
db.ForallMountedApexes(
package, [&](const MountedApexData& d, bool b ATTRIBUTE_UNUSED) {
if (loop_name == d.loop_name && full_path == d.full_path &&
dm == d.device_name && hashtree_loop_name == d.hashtree_loop_name) {
found = true;
}
});
return found;
}
TEST(ApexDatabaseTest, AddRemovedMountedApex) {
constexpr const char* kPackage = "package";
constexpr const char* kLoopName = "loop";
constexpr const char* kPath = "path";
constexpr const char* kMountPoint = "mount";
constexpr const char* kDeviceName = "dev";
constexpr const char* kHashtreeLoopName = "hash-loop";
MountedApexDatabase db;
ASSERT_EQ(CountPackages(db), 0u);
db.AddMountedApex(kPackage, false, kLoopName, kPath, kMountPoint, kDeviceName,
kHashtreeLoopName);
ASSERT_TRUE(Contains(db, kPackage, kLoopName, kPath, kMountPoint, kDeviceName,
kHashtreeLoopName));
ASSERT_TRUE(ContainsPackage(db, kPackage, kLoopName, kPath, kDeviceName,
kHashtreeLoopName));
db.RemoveMountedApex(kPackage, kPath);
EXPECT_FALSE(Contains(db, kPackage, kLoopName, kPath, kMountPoint,
kDeviceName, kHashtreeLoopName));
EXPECT_FALSE(ContainsPackage(db, kPackage, kLoopName, kPath, kDeviceName,
kHashtreeLoopName));
}
TEST(ApexDatabaseTest, MountMultiple) {
constexpr const char* kPackage[] = {"package", "package", "package",
"package"};
constexpr const char* kLoopName[] = {"loop", "loop2", "loop3", "loop4"};
constexpr const char* kPath[] = {"path", "path2", "path", "path4"};
constexpr const char* kMountPoint[] = {"mount", "mount2", "mount", "mount4"};
constexpr const char* kDeviceName[] = {"dev", "dev2", "dev3", "dev4"};
constexpr const char* kHashtreeLoopName[] = {"hash-loop", "hash-loop2",
"hash-loop3", "hash-loop4"};
MountedApexDatabase db;
ASSERT_EQ(CountPackages(db), 0u);
for (size_t i = 0; i < arraysize(kPackage); ++i) {
db.AddMountedApex(kPackage[i], false, kLoopName[i], kPath[i],
kMountPoint[i], kDeviceName[i], kHashtreeLoopName[i]);
}
ASSERT_EQ(CountPackages(db), 4u);
for (size_t i = 0; i < arraysize(kPackage); ++i) {
ASSERT_TRUE(Contains(db, kPackage[i], kLoopName[i], kPath[i],
kMountPoint[i], kDeviceName[i], kHashtreeLoopName[i]));
ASSERT_TRUE(ContainsPackage(db, kPackage[i], kLoopName[i], kPath[i],
kDeviceName[i], kHashtreeLoopName[i]));
}
db.RemoveMountedApex(kPackage[0], kPath[0]);
EXPECT_FALSE(Contains(db, kPackage[0], kLoopName[0], kPath[0], kMountPoint[0],
kDeviceName[0], kHashtreeLoopName[0]));
EXPECT_FALSE(ContainsPackage(db, kPackage[0], kLoopName[0], kPath[0],
kDeviceName[0], kHashtreeLoopName[0]));
EXPECT_TRUE(Contains(db, kPackage[1], kLoopName[1], kPath[1], kMountPoint[1],
kDeviceName[1], kHashtreeLoopName[1]));
EXPECT_TRUE(ContainsPackage(db, kPackage[1], kLoopName[1], kPath[1],
kDeviceName[1], kHashtreeLoopName[1]));
EXPECT_TRUE(Contains(db, kPackage[2], kLoopName[2], kPath[2], kMountPoint[2],
kDeviceName[2], kHashtreeLoopName[2]));
EXPECT_TRUE(ContainsPackage(db, kPackage[2], kLoopName[2], kPath[2],
kDeviceName[2], kHashtreeLoopName[2]));
EXPECT_TRUE(Contains(db, kPackage[3], kLoopName[3], kPath[3], kMountPoint[3],
kDeviceName[3], kHashtreeLoopName[3]));
EXPECT_TRUE(ContainsPackage(db, kPackage[3], kLoopName[3], kPath[3],
kDeviceName[3], kHashtreeLoopName[3]));
}
TEST(ApexDatabaseTest, GetLatestMountedApex) {
constexpr const char* kPackage = "package";
constexpr const char* kLoopName = "loop";
constexpr const char* kPath = "path";
constexpr const char* kMountPoint = "mount";
constexpr const char* kDeviceName = "dev";
constexpr const char* kHashtreeLoopName = "hash-loop";
MountedApexDatabase db;
ASSERT_EQ(CountPackages(db), 0u);
db.AddMountedApex(kPackage, true, kLoopName, kPath, kMountPoint, kDeviceName,
kHashtreeLoopName);
auto ret = db.GetLatestMountedApex(kPackage);
MountedApexData expected(kLoopName, kPath, kMountPoint, kDeviceName,
kHashtreeLoopName);
ASSERT_TRUE(ret.has_value());
ASSERT_EQ(ret->loop_name, std::string(kLoopName));
ASSERT_EQ(ret->full_path, std::string(kPath));
ASSERT_EQ(ret->mount_point, std::string(kMountPoint));
ASSERT_EQ(ret->device_name, std::string(kDeviceName));
ASSERT_EQ(ret->hashtree_loop_name, std::string(kHashtreeLoopName));
}
TEST(ApexDatabaseTest, GetLatestMountedApexReturnsNullopt) {
MountedApexDatabase db;
auto ret = db.GetLatestMountedApex("no-such-name");
ASSERT_FALSE(ret.has_value());
}
#pragma clang diagnostic push
// error: 'ReturnSentinel' was marked unused but was used
// [-Werror,-Wused-but-marked-unused]
#pragma clang diagnostic ignored "-Wused-but-marked-unused"
TEST(MountedApexDataTest, NoDuplicateLoopDataLoopDevices) {
ASSERT_DEATH(
{
MountedApexDatabase db;
db.AddMountedApex("package", false, "loop", "path", "mount", "dm",
"hashtree-loop1");
db.AddMountedApex("package2", false, "loop", "path2", "mount2", "dm2",
"hashtree-loop2");
},
"Duplicate loop device: loop");
}
TEST(MountedApexDataTest, NoDuplicateLoopHashtreeLoopDevices) {
ASSERT_DEATH(
{
MountedApexDatabase db;
db.AddMountedApex("package", false, "loop1", "path", "mount", "dm",
"hashtree-loop");
db.AddMountedApex("package2", false, "loop2", "path2", "mount2", "dm2",
"hashtree-loop");
},
"Duplicate loop device: hashtree-loop");
}
TEST(MountedApexDataTest, NoDuplicateLoopHashtreeAndDataLoopDevices) {
ASSERT_DEATH(
{
MountedApexDatabase db;
db.AddMountedApex("package", false, "loop", "path", "mount", "dm",
"hashtree-loop1");
db.AddMountedApex("package2", false, "loop2", "path2", "mount2", "dm2",
"loop");
},
"Duplicate loop device: loop");
}
TEST(MountedApexDataTest, NoDuplicateDm) {
ASSERT_DEATH(
{
MountedApexDatabase db;
db.AddMountedApex("package", false, "loop", "path", "mount", "dm",
/* hashtree_loop_name= */ "");
db.AddMountedApex("package2", false, "loop2", "path2", "mount2", "dm",
/* hashtree_loop_name= */ "");
},
"Duplicate dm device: dm");
}
#pragma clang diagnostic pop
} // namespace
} // namespace apex
} // namespace android

458
apex/apexd/apex_file.cpp Normal file
View File

@ -0,0 +1,458 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "apex_file.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/scopeguard.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <fcntl.h>
#include <libavb/libavb.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <ziparchive/zip_archive.h>
#include <filesystem>
#include <fstream>
#include <span>
#include "apex_constants.h"
#include "apexd_utils.h"
#include "apexd_verity.h"
using android::base::borrowed_fd;
using android::base::ErrnoError;
using android::base::Error;
using android::base::ReadFullyAtOffset;
using android::base::RemoveFileIfExists;
using android::base::Result;
using android::base::unique_fd;
using ::apex::proto::ApexManifest;
namespace android {
namespace apex {
namespace {
constexpr const char* kImageFilename = "apex_payload.img";
constexpr const char* kCompressedApexFilename = "original_apex";
constexpr const char* kBundledPublicKeyFilename = "apex_pubkey";
struct FsMagic {
const char* type;
int32_t offset;
int16_t len;
const char* magic;
};
constexpr const FsMagic kFsType[] = {{"f2fs", 1024, 4, "\x10\x20\xf5\xf2"},
{"ext4", 1024 + 0x38, 2, "\123\357"},
{"erofs", 1024, 4, "\xe2\xe1\xf5\xe0"}};
Result<std::string> RetrieveFsType(borrowed_fd fd, uint32_t image_offset) {
for (const auto& fs : kFsType) {
char buf[fs.len];
if (!ReadFullyAtOffset(fd, buf, fs.len, image_offset + fs.offset)) {
return ErrnoError() << "Couldn't read filesystem magic";
}
if (memcmp(buf, fs.magic, fs.len) == 0) {
return std::string(fs.type);
}
}
return Error() << "Couldn't find filesystem magic";
}
} // namespace
Result<ApexFile> ApexFile::Open(const std::string& path) {
std::optional<uint32_t> image_offset;
std::optional<size_t> image_size;
std::string manifest_content;
std::string pubkey;
std::optional<std::string> fs_type;
ZipEntry entry;
unique_fd fd(open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
if (fd < 0) {
return ErrnoError() << "Failed to open package " << path << ": "
<< "I/O error";
}
ZipArchiveHandle handle;
auto handle_guard =
android::base::make_scope_guard([&handle] { CloseArchive(handle); });
int ret = OpenArchiveFd(fd.get(), path.c_str(), &handle,
/*assume_ownership=*/false);
if (ret < 0) {
return Error() << "Failed to open package " << path << ": "
<< ErrorCodeString(ret);
}
bool is_compressed = true;
ret = FindEntry(handle, kCompressedApexFilename, &entry);
if (ret < 0) {
is_compressed = false;
}
if (!is_compressed) {
// Locate the mountable image within the zipfile and store offset and size.
ret = FindEntry(handle, kImageFilename, &entry);
if (ret < 0) {
return Error() << "Could not find entry \"" << kImageFilename
<< "\" or \"" << kCompressedApexFilename
<< "\" in package " << path << ": "
<< ErrorCodeString(ret);
}
image_offset = entry.offset;
image_size = entry.uncompressed_length;
auto fs_type_result = RetrieveFsType(fd, image_offset.value());
if (!fs_type_result.ok()) {
return Error() << "Failed to retrieve filesystem type for " << path
<< ": " << fs_type_result.error();
}
fs_type = std::move(*fs_type_result);
}
ret = FindEntry(handle, kManifestFilenamePb, &entry);
if (ret < 0) {
return Error() << "Could not find entry \"" << kManifestFilenamePb
<< "\" in package " << path << ": " << ErrorCodeString(ret);
}
uint32_t length = entry.uncompressed_length;
manifest_content.resize(length, '\0');
ret = ExtractToMemory(handle, &entry,
reinterpret_cast<uint8_t*>(&(manifest_content)[0]),
length);
if (ret != 0) {
return Error() << "Failed to extract manifest from package " << path << ": "
<< ErrorCodeString(ret);
}
ret = FindEntry(handle, kBundledPublicKeyFilename, &entry);
if (ret >= 0) {
length = entry.uncompressed_length;
pubkey.resize(length, '\0');
ret = ExtractToMemory(handle, &entry,
reinterpret_cast<uint8_t*>(&(pubkey)[0]), length);
if (ret != 0) {
return Error() << "Failed to extract public key from package " << path
<< ": " << ErrorCodeString(ret);
}
}
Result<ApexManifest> manifest = ParseManifest(manifest_content);
if (!manifest.ok()) {
return manifest.error();
}
if (is_compressed && manifest->providesharedapexlibs()) {
return Error() << "Apex providing sharedlibs shouldn't be compressed";
}
// b/179211712 the stored path should be the realpath, otherwise the path we
// get by scanning the directory would be different from the path we get
// by reading /proc/mounts, if the apex file is on a symlink dir.
std::string realpath;
if (!android::base::Realpath(path, &realpath)) {
return ErrnoError() << "can't get realpath of " << path;
}
return ApexFile(realpath, image_offset, image_size, std::move(*manifest),
pubkey, fs_type, is_compressed);
}
// AVB-related code.
namespace {
static constexpr int kVbMetaMaxSize = 64 * 1024;
std::string GetSalt(const AvbHashtreeDescriptor& desc,
const uint8_t* trailing_data) {
const uint8_t* desc_salt = trailing_data + desc.partition_name_len;
return BytesToHex(desc_salt, desc.salt_len);
}
std::string GetDigest(const AvbHashtreeDescriptor& desc,
const uint8_t* trailing_data) {
const uint8_t* desc_digest =
trailing_data + desc.partition_name_len + desc.salt_len;
return BytesToHex(desc_digest, desc.root_digest_len);
}
Result<std::unique_ptr<AvbFooter>> GetAvbFooter(const ApexFile& apex,
const unique_fd& fd) {
std::array<uint8_t, AVB_FOOTER_SIZE> footer_data;
auto footer = std::make_unique<AvbFooter>();
// The AVB footer is located in the last part of the image
if (!apex.GetImageOffset() || !apex.GetImageSize()) {
return Error() << "Cannot check avb footer without image offset and size";
}
off_t offset = apex.GetImageSize().value() + apex.GetImageOffset().value() -
AVB_FOOTER_SIZE;
int ret = lseek(fd, offset, SEEK_SET);
if (ret == -1) {
return ErrnoError() << "Couldn't seek to AVB footer";
}
ret = read(fd, footer_data.data(), AVB_FOOTER_SIZE);
if (ret != AVB_FOOTER_SIZE) {
return ErrnoError() << "Couldn't read AVB footer";
}
if (!avb_footer_validate_and_byteswap((const AvbFooter*)footer_data.data(),
footer.get())) {
return Error() << "AVB footer verification failed.";
}
LOG(VERBOSE) << "AVB footer verification successful.";
return footer;
}
bool CompareKeys(const uint8_t* key, size_t length,
const std::string& public_key_content) {
return public_key_content.length() == length &&
memcmp(&public_key_content[0], key, length) == 0;
}
// Verifies correctness of vbmeta and returns public key it was signed with.
Result<std::span<const uint8_t>> VerifyVbMetaSignature(const ApexFile& apex,
const uint8_t* data,
size_t length) {
const uint8_t* pk;
size_t pk_len;
AvbVBMetaVerifyResult res;
res = avb_vbmeta_image_verify(data, length, &pk, &pk_len);
switch (res) {
case AVB_VBMETA_VERIFY_RESULT_OK:
break;
case AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED:
case AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH:
case AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH:
return Error() << "Error verifying " << apex.GetPath() << ": "
<< avb_vbmeta_verify_result_to_string(res);
case AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER:
return Error() << "Error verifying " << apex.GetPath() << ": "
<< "invalid vbmeta header";
case AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION:
return Error() << "Error verifying " << apex.GetPath() << ": "
<< "unsupported version";
default:
return Error() << "Unknown vmbeta_image_verify return value : " << res;
}
return std::span<const uint8_t>(pk, pk_len);
}
Result<std::unique_ptr<uint8_t[]>> VerifyVbMeta(const ApexFile& apex,
const unique_fd& fd,
const AvbFooter& footer,
const std::string& public_key) {
if (footer.vbmeta_size > kVbMetaMaxSize) {
return Errorf("VbMeta size in footer exceeds kVbMetaMaxSize.");
}
if (!apex.GetImageOffset()) {
return Error() << "Cannot check VbMeta size without image offset";
}
off_t offset = apex.GetImageOffset().value() + footer.vbmeta_offset;
std::unique_ptr<uint8_t[]> vbmeta_buf(new uint8_t[footer.vbmeta_size]);
if (!ReadFullyAtOffset(fd, vbmeta_buf.get(), footer.vbmeta_size, offset)) {
return ErrnoError() << "Couldn't read AVB meta-data";
}
Result<std::span<const uint8_t>> st =
VerifyVbMetaSignature(apex, vbmeta_buf.get(), footer.vbmeta_size);
if (!st.ok()) {
return st.error();
}
if (!CompareKeys(st->data(), st->size(), public_key)) {
return Error() << "Error verifying " << apex.GetPath() << " : "
<< "public key doesn't match the pre-installed one";
}
return vbmeta_buf;
}
Result<const AvbHashtreeDescriptor*> FindDescriptor(uint8_t* vbmeta_data,
size_t vbmeta_size) {
const AvbDescriptor** descriptors;
size_t num_descriptors;
descriptors =
avb_descriptor_get_all(vbmeta_data, vbmeta_size, &num_descriptors);
// avb_descriptor_get_all() returns an internally allocated array
// of pointers and it needs to be avb_free()ed after using it.
auto guard = android::base::ScopeGuard(std::bind(avb_free, descriptors));
for (size_t i = 0; i < num_descriptors; i++) {
AvbDescriptor desc;
if (!avb_descriptor_validate_and_byteswap(descriptors[i], &desc)) {
return Errorf("Couldn't validate AvbDescriptor.");
}
if (desc.tag != AVB_DESCRIPTOR_TAG_HASHTREE) {
// Ignore other descriptors
continue;
}
// Check that hashtree descriptor actually fits into memory.
const uint8_t* vbmeta_end = vbmeta_data + vbmeta_size;
if ((uint8_t*)descriptors[i] + sizeof(AvbHashtreeDescriptor) > vbmeta_end) {
return Errorf("Invalid length for AvbHashtreeDescriptor");
}
return (const AvbHashtreeDescriptor*)descriptors[i];
}
return Errorf("Couldn't find any AVB hashtree descriptors.");
}
Result<std::unique_ptr<AvbHashtreeDescriptor>> VerifyDescriptor(
const AvbHashtreeDescriptor* desc) {
auto verified_desc = std::make_unique<AvbHashtreeDescriptor>();
if (!avb_hashtree_descriptor_validate_and_byteswap(desc,
verified_desc.get())) {
return Errorf("Couldn't validate AvbDescriptor.");
}
return verified_desc;
}
} // namespace
Result<ApexVerityData> ApexFile::VerifyApexVerity(
const std::string& public_key) const {
if (IsCompressed()) {
return Error() << "Cannot verify ApexVerity of compressed APEX";
}
ApexVerityData verity_data;
unique_fd fd(open(GetPath().c_str(), O_RDONLY | O_CLOEXEC));
if (fd.get() == -1) {
return ErrnoError() << "Failed to open " << GetPath();
}
Result<std::unique_ptr<AvbFooter>> footer = GetAvbFooter(*this, fd);
if (!footer.ok()) {
return footer.error();
}
Result<std::unique_ptr<uint8_t[]>> vbmeta_data =
VerifyVbMeta(*this, fd, **footer, public_key);
if (!vbmeta_data.ok()) {
return vbmeta_data.error();
}
Result<const AvbHashtreeDescriptor*> descriptor =
FindDescriptor(vbmeta_data->get(), (*footer)->vbmeta_size);
if (!descriptor.ok()) {
return descriptor.error();
}
Result<std::unique_ptr<AvbHashtreeDescriptor>> verified_descriptor =
VerifyDescriptor(*descriptor);
if (!verified_descriptor.ok()) {
return verified_descriptor.error();
}
verity_data.desc = std::move(*verified_descriptor);
// This area is now safe to access, because we just verified it
const uint8_t* trailing_data =
(const uint8_t*)*descriptor + sizeof(AvbHashtreeDescriptor);
verity_data.hash_algorithm =
reinterpret_cast<const char*>((*descriptor)->hash_algorithm);
verity_data.salt = GetSalt(*verity_data.desc, trailing_data);
verity_data.root_digest = GetDigest(*verity_data.desc, trailing_data);
return verity_data;
}
Result<void> ApexFile::Decompress(const std::string& dest_path) const {
const std::string& src_path = GetPath();
LOG(INFO) << "Decompressing" << src_path << " to " << dest_path;
// We should decompress compressed APEX files only
if (!IsCompressed()) {
return ErrnoError() << "Cannot decompress an uncompressed APEX";
}
// Get file descriptor of the compressed apex file
unique_fd src_fd(open(src_path.c_str(), O_RDONLY | O_CLOEXEC));
if (src_fd.get() == -1) {
return ErrnoError() << "Failed to open compressed APEX " << GetPath();
}
// Open it as a zip file
ZipArchiveHandle handle;
int ret = OpenArchiveFd(src_fd.get(), src_path.c_str(), &handle, false);
if (ret < 0) {
return Error() << "Failed to open package " << src_path << ": "
<< ErrorCodeString(ret);
}
auto handle_guard =
android::base::make_scope_guard([&handle] { CloseArchive(handle); });
// Find the original apex file inside the zip and extract to dest
ZipEntry entry;
ret = FindEntry(handle, kCompressedApexFilename, &entry);
if (ret < 0) {
return Error() << "Could not find entry \"" << kCompressedApexFilename
<< "\" in package " << src_path << ": "
<< ErrorCodeString(ret);
}
// Open destination file descriptor
unique_fd dest_fd(
open(dest_path.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_EXCL, 0644));
if (dest_fd.get() == -1) {
return ErrnoError() << "Failed to open decompression destination "
<< dest_path.c_str();
}
// Prepare a guard that deletes the extracted file if anything goes wrong
auto decompressed_guard = android::base::make_scope_guard(
[&dest_path] { RemoveFileIfExists(dest_path); });
// Extract the original_apex to dest_path
ret = ExtractEntryToFile(handle, &entry, dest_fd.get());
if (ret < 0) {
return Error() << "Could not decompress to file " << dest_path << " "
<< ErrorCodeString(ret);
}
// Verification complete. Accept the decompressed file
decompressed_guard.Disable();
LOG(VERBOSE) << "Decompressed " << src_path << " to " << dest_path;
return {};
}
} // namespace apex
} // namespace android

89
apex/apexd/apex_file.h Normal file
View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_APEXD_APEX_FILE_H_
#define ANDROID_APEXD_APEX_FILE_H_
#include <memory>
#include <string>
#include <vector>
#include <android-base/result.h>
#include <libavb/libavb.h>
#include "apex_manifest.h"
namespace android {
namespace apex {
// Data needed to construct a valid VerityTable
struct ApexVerityData {
std::unique_ptr<AvbHashtreeDescriptor> desc;
std::string hash_algorithm;
std::string salt;
std::string root_digest;
};
// Manages the content of an APEX package and provides utilities to navigate
// the content.
class ApexFile {
public:
static android::base::Result<ApexFile> Open(const std::string& path);
ApexFile() = delete;
ApexFile(ApexFile&&) = default;
ApexFile& operator=(ApexFile&&) = default;
const std::string& GetPath() const { return apex_path_; }
const std::optional<uint32_t>& GetImageOffset() const {
return image_offset_;
}
const std::optional<size_t>& GetImageSize() const { return image_size_; }
const ::apex::proto::ApexManifest& GetManifest() const { return manifest_; }
const std::string& GetBundledPublicKey() const { return apex_pubkey_; }
const std::optional<std::string>& GetFsType() const { return fs_type_; }
android::base::Result<ApexVerityData> VerifyApexVerity(
const std::string& public_key) const;
bool IsCompressed() const { return is_compressed_; }
android::base::Result<void> Decompress(const std::string& output_path) const;
private:
ApexFile(const std::string& apex_path,
const std::optional<uint32_t>& image_offset,
const std::optional<size_t>& image_size,
::apex::proto::ApexManifest manifest, const std::string& apex_pubkey,
const std::optional<std::string>& fs_type, bool is_compressed)
: apex_path_(apex_path),
image_offset_(image_offset),
image_size_(image_size),
manifest_(std::move(manifest)),
apex_pubkey_(apex_pubkey),
fs_type_(fs_type),
is_compressed_(is_compressed) {}
std::string apex_path_;
std::optional<uint32_t> image_offset_;
std::optional<size_t> image_size_;
::apex::proto::ApexManifest manifest_;
std::string apex_pubkey_;
std::optional<std::string> fs_type_;
bool is_compressed_;
};
} // namespace apex
} // namespace android
#endif // ANDROID_APEXD_APEX_FILE_H_

View File

@ -0,0 +1,515 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "apexd"
#include "apex_file_repository.h"
#include <android-base/file.h>
#include <android-base/properties.h>
#include <android-base/result.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <microdroid/metadata.h>
#include <unordered_map>
#include "apex_constants.h"
#include "apex_file.h"
#include "apexd_utils.h"
#include "apexd_verity.h"
using android::base::EndsWith;
using android::base::Error;
using android::base::GetProperty;
using android::base::Result;
namespace android {
namespace apex {
std::string ConsumeApexPackageSuffix(const std::string& path) {
std::string_view path_view(path);
android::base::ConsumeSuffix(&path_view, kApexPackageSuffix);
android::base::ConsumeSuffix(&path_view, kCompressedApexPackageSuffix);
return std::string(path_view);
}
std::string GetApexSelectFilenameFromProp(
const std::vector<std::string>& prefixes, const std::string& apex_name) {
for (const std::string& prefix : prefixes) {
const std::string& filename = GetProperty(prefix + apex_name, "");
if (filename != "") {
return ConsumeApexPackageSuffix(filename);
}
}
return "";
}
Result<void> ApexFileRepository::ScanBuiltInDir(const std::string& dir) {
LOG(INFO) << "Scanning " << dir << " for pre-installed ApexFiles";
if (access(dir.c_str(), F_OK) != 0 && errno == ENOENT) {
LOG(WARNING) << dir << " does not exist. Skipping";
return {};
}
Result<std::vector<std::string>> all_apex_files = FindFilesBySuffix(
dir, {kApexPackageSuffix, kCompressedApexPackageSuffix});
if (!all_apex_files.ok()) {
return all_apex_files.error();
}
// TODO(b/179248390): scan parallelly if possible
for (const auto& file : *all_apex_files) {
LOG(INFO) << "Found pre-installed APEX " << file;
Result<ApexFile> apex_file = ApexFile::Open(file);
if (!apex_file.ok()) {
return Error() << "Failed to open " << file << " : " << apex_file.error();
}
const std::string& name = apex_file->GetManifest().name();
// Check if this APEX name is treated as a multi-install APEX.
//
// Note: apexd is a oneshot service which runs at boot, but can be restarted
// when needed (such as staging an APEX update). If a multi-install select
// property changes between boot and when apexd restarts, the LOG messages
// below will report the version that will be activated on next reboot,
// which may differ from the currently-active version.
std::string select_filename = GetApexSelectFilenameFromProp(
multi_install_select_prop_prefixes_, name);
if (!select_filename.empty()) {
std::string path;
if (!android::base::Realpath(apex_file->GetPath(), &path)) {
LOG(ERROR) << "Unable to resolve realpath of APEX with path "
<< apex_file->GetPath();
continue;
}
if (enforce_multi_install_partition_ &&
!android::base::StartsWith(path, "/vendor/apex/")) {
LOG(ERROR) << "Multi-install APEX " << path
<< " can only be preinstalled on /vendor/apex/.";
continue;
}
auto& keys = multi_install_public_keys_[name];
keys.insert(apex_file->GetBundledPublicKey());
if (keys.size() > 1) {
LOG(ERROR) << "Multi-install APEXes for " << name
<< " have different public keys.";
// If any versions of a multi-installed APEX differ in public key,
// then no version should be installed.
if (auto it = pre_installed_store_.find(name);
it != pre_installed_store_.end()) {
pre_installed_store_.erase(it);
}
continue;
}
if (ConsumeApexPackageSuffix(android::base::Basename(path)) ==
select_filename) {
LOG(INFO) << "Found APEX at path " << path << " for multi-install APEX "
<< name;
// Add the APEX file to the store if its filename matches the property.
pre_installed_store_.emplace(name, std::move(*apex_file));
} else {
LOG(INFO) << "Skipping APEX at path " << path
<< " because it does not match expected multi-install"
<< " APEX property for " << name;
}
continue;
}
auto it = pre_installed_store_.find(name);
if (it == pre_installed_store_.end()) {
pre_installed_store_.emplace(name, std::move(*apex_file));
} else if (it->second.GetPath() != apex_file->GetPath()) {
auto level = base::FATAL;
// On some development (non-REL) builds the VNDK apex could be in /vendor.
// When testing CTS-on-GSI on these builds, there would be two VNDK apexes
// in the system, one in /system and one in /vendor.
static constexpr char kVndkApexModuleNamePrefix[] = "com.android.vndk.";
static constexpr char kPlatformVersionCodenameProperty[] =
"ro.build.version.codename";
if (android::base::StartsWith(name, kVndkApexModuleNamePrefix) &&
GetProperty(kPlatformVersionCodenameProperty, "REL") != "REL") {
level = android::base::INFO;
}
LOG(level) << "Found two apex packages " << it->second.GetPath()
<< " and " << apex_file->GetPath()
<< " with the same module name " << name;
} else if (it->second.GetBundledPublicKey() !=
apex_file->GetBundledPublicKey()) {
LOG(FATAL) << "Public key of apex package " << it->second.GetPath()
<< " (" << name << ") has unexpectedly changed";
}
}
multi_install_public_keys_.clear();
return {};
}
ApexFileRepository& ApexFileRepository::GetInstance() {
static ApexFileRepository instance;
return instance;
}
android::base::Result<void> ApexFileRepository::AddPreInstalledApex(
const std::vector<std::string>& prebuilt_dirs) {
for (const auto& dir : prebuilt_dirs) {
if (auto result = ScanBuiltInDir(dir); !result.ok()) {
return result.error();
}
}
return {};
}
Result<int> ApexFileRepository::AddBlockApex(
const std::string& metadata_partition) {
CHECK(!block_disk_path_.has_value())
<< "AddBlockApex() can't be called twice.";
auto metadata_ready = WaitForFile(metadata_partition, kBlockApexWaitTime);
if (!metadata_ready.ok()) {
LOG(ERROR) << "Error waiting for metadata_partition : "
<< metadata_ready.error();
return {};
}
// TODO(b/185069443) consider moving the logic to find disk_path from
// metadata_partition to its own library
LOG(INFO) << "Scanning " << metadata_partition << " for host apexes";
if (access(metadata_partition.c_str(), F_OK) != 0 && errno == ENOENT) {
LOG(WARNING) << metadata_partition << " does not exist. Skipping";
return {};
}
std::string metadata_realpath;
if (!android::base::Realpath(metadata_partition, &metadata_realpath)) {
LOG(WARNING) << "Can't get realpath of " << metadata_partition
<< ". Skipping";
return {};
}
std::string_view metadata_path_view(metadata_realpath);
if (!android::base::ConsumeSuffix(&metadata_path_view, "1")) {
LOG(WARNING) << metadata_realpath << " is not a first partition. Skipping";
return {};
}
block_disk_path_ = std::string(metadata_path_view);
// Read the payload metadata.
// "metadata" can be overridden by microdroid_manager. To ensure that
// "microdroid" is started with the same/unmodified set of host APEXes,
// microdroid stores APEXes' pubkeys in its encrypted instance disk. Next
// time, microdroid checks if there's pubkeys in the instance disk and use
// them to activate APEXes. Microdroid_manager passes pubkeys in instance.img
// via the following file.
if (auto exists = PathExists("/apex/vm-payload-metadata");
exists.ok() && *exists) {
metadata_realpath = "/apex/vm-payload-metadata";
LOG(INFO) << "Overriding metadata to " << metadata_realpath;
}
auto metadata = android::microdroid::ReadMetadata(metadata_realpath);
if (!metadata.ok()) {
LOG(WARNING) << "Failed to load metadata from " << metadata_realpath
<< ". Skipping: " << metadata.error();
return {};
}
int ret = 0;
// subsequent partitions are APEX archives.
static constexpr const int kFirstApexPartition = 2;
for (int i = 0; i < metadata->apexes_size(); i++) {
const auto& apex_config = metadata->apexes(i);
const std::string apex_path =
*block_disk_path_ + std::to_string(i + kFirstApexPartition);
auto apex_ready = WaitForFile(apex_path, kBlockApexWaitTime);
if (!apex_ready.ok()) {
return Error() << "Error waiting for apex file : " << apex_ready.error();
}
auto apex_file = ApexFile::Open(apex_path);
if (!apex_file.ok()) {
return Error() << "Failed to open " << apex_path << " : "
<< apex_file.error();
}
// When metadata specifies the public key of the apex, it should match the
// bundled key. Otherwise we accept it.
if (apex_config.public_key() != "" &&
apex_config.public_key() != apex_file->GetBundledPublicKey()) {
return Error() << "public key doesn't match: " << apex_path;
}
const std::string& name = apex_file->GetManifest().name();
BlockApexOverride overrides;
// A block device doesn't have an inherent timestamp, so it is carried in
// the metadata.
if (int64_t last_update_seconds = apex_config.last_update_seconds();
last_update_seconds != 0) {
overrides.last_update_seconds = last_update_seconds;
}
// When metadata specifies the root digest of the apex, it should be used
// when activating the apex. So we need to keep it.
if (auto root_digest = apex_config.root_digest(); root_digest != "") {
overrides.block_apex_root_digest =
BytesToHex(reinterpret_cast<const uint8_t*>(root_digest.data()),
root_digest.size());
}
if (overrides.last_update_seconds.has_value() ||
overrides.block_apex_root_digest.has_value()) {
block_apex_overrides_.emplace(apex_path, std::move(overrides));
}
// Depending on whether the APEX was a factory version in the host or not,
// put it to different stores.
auto& store = apex_config.is_factory() ? pre_installed_store_ : data_store_;
// We want "uniqueness" in each store.
if (auto it = store.find(name); it != store.end()) {
return Error() << "duplicate of " << name << " found in "
<< it->second.GetPath();
}
store.emplace(name, std::move(*apex_file));
ret++;
}
return {ret};
}
// TODO(b/179497746): AddDataApex should not concern with filtering out invalid
// apex.
Result<void> ApexFileRepository::AddDataApex(const std::string& data_dir) {
LOG(INFO) << "Scanning " << data_dir << " for data ApexFiles";
if (access(data_dir.c_str(), F_OK) != 0 && errno == ENOENT) {
LOG(WARNING) << data_dir << " does not exist. Skipping";
return {};
}
Result<std::vector<std::string>> active_apex =
FindFilesBySuffix(data_dir, {kApexPackageSuffix});
if (!active_apex.ok()) {
return active_apex.error();
}
// TODO(b/179248390): scan parallelly if possible
for (const auto& file : *active_apex) {
LOG(INFO) << "Found updated apex " << file;
Result<ApexFile> apex_file = ApexFile::Open(file);
if (!apex_file.ok()) {
LOG(ERROR) << "Failed to open " << file << " : " << apex_file.error();
continue;
}
const std::string& name = apex_file->GetManifest().name();
if (!HasPreInstalledVersion(name)) {
LOG(ERROR) << "Skipping " << file << " : no preinstalled apex";
// Ignore data apex without corresponding pre-installed apex
continue;
}
std::string select_filename = GetApexSelectFilenameFromProp(
multi_install_select_prop_prefixes_, name);
if (!select_filename.empty()) {
LOG(WARNING) << "APEX " << name << " is a multi-installed APEX."
<< " Any updated version in /data will always overwrite"
<< " the multi-installed preinstalled version, if possible.";
}
auto pre_installed_public_key = GetPublicKey(name);
if (!pre_installed_public_key.ok() ||
apex_file->GetBundledPublicKey() != *pre_installed_public_key) {
// Ignore data apex if public key doesn't match with pre-installed apex
LOG(ERROR) << "Skipping " << file
<< " : public key doesn't match pre-installed one";
continue;
}
if (EndsWith(apex_file->GetPath(), kDecompressedApexPackageSuffix)) {
LOG(WARNING) << "Skipping " << file
<< " : Non-decompressed APEX should not have "
<< kDecompressedApexPackageSuffix << " suffix";
continue;
}
auto it = data_store_.find(name);
if (it == data_store_.end()) {
data_store_.emplace(name, std::move(*apex_file));
continue;
}
const auto& existing_version = it->second.GetManifest().version();
const auto new_version = apex_file->GetManifest().version();
// If multiple data apexs are preset, select the one with highest version
bool prioritize_higher_version = new_version > existing_version;
// For same version, non-decompressed apex gets priority
if (prioritize_higher_version) {
it->second = std::move(*apex_file);
}
}
return {};
}
// TODO(b/179497746): remove this method when we add api for fetching ApexFile
// by name
Result<const std::string> ApexFileRepository::GetPublicKey(
const std::string& name) const {
auto it = pre_installed_store_.find(name);
if (it == pre_installed_store_.end()) {
// Special casing for APEXes backed by block devices, i.e. APEXes in VM.
// Inside a VM, we fall back to find the key from data_store_. This is
// because an APEX is put to either pre_installed_store_ or data_store,
// depending on whether it was a factory APEX or not in the host.
it = data_store_.find(name);
if (it != data_store_.end() && IsBlockApex(it->second)) {
return it->second.GetBundledPublicKey();
}
return Error() << "No preinstalled apex found for package " << name;
}
return it->second.GetBundledPublicKey();
}
// TODO(b/179497746): remove this method when we add api for fetching ApexFile
// by name
Result<const std::string> ApexFileRepository::GetPreinstalledPath(
const std::string& name) const {
auto it = pre_installed_store_.find(name);
if (it == pre_installed_store_.end()) {
return Error() << "No preinstalled data found for package " << name;
}
return it->second.GetPath();
}
// TODO(b/179497746): remove this method when we add api for fetching ApexFile
// by name
Result<const std::string> ApexFileRepository::GetDataPath(
const std::string& name) const {
auto it = data_store_.find(name);
if (it == data_store_.end()) {
return Error() << "No data apex found for package " << name;
}
return it->second.GetPath();
}
std::optional<std::string> ApexFileRepository::GetBlockApexRootDigest(
const std::string& path) const {
auto it = block_apex_overrides_.find(path);
if (it == block_apex_overrides_.end()) {
return std::nullopt;
}
return it->second.block_apex_root_digest;
}
std::optional<int64_t> ApexFileRepository::GetBlockApexLastUpdateSeconds(
const std::string& path) const {
auto it = block_apex_overrides_.find(path);
if (it == block_apex_overrides_.end()) {
return std::nullopt;
}
return it->second.last_update_seconds;
}
bool ApexFileRepository::HasPreInstalledVersion(const std::string& name) const {
return pre_installed_store_.find(name) != pre_installed_store_.end();
}
bool ApexFileRepository::HasDataVersion(const std::string& name) const {
return data_store_.find(name) != data_store_.end();
}
// ApexFile is considered a decompressed APEX if it is located in decompression
// dir
bool ApexFileRepository::IsDecompressedApex(const ApexFile& apex) const {
return apex.GetPath().starts_with(decompression_dir_);
}
bool ApexFileRepository::IsPreInstalledApex(const ApexFile& apex) const {
auto it = pre_installed_store_.find(apex.GetManifest().name());
if (it == pre_installed_store_.end()) {
return false;
}
return it->second.GetPath() == apex.GetPath() || IsDecompressedApex(apex);
}
bool ApexFileRepository::IsBlockApex(const ApexFile& apex) const {
return block_disk_path_.has_value() &&
apex.GetPath().starts_with(*block_disk_path_);
}
std::vector<ApexFileRef> ApexFileRepository::GetPreInstalledApexFiles() const {
std::vector<ApexFileRef> result;
for (const auto& it : pre_installed_store_) {
result.emplace_back(std::cref(it.second));
}
return std::move(result);
}
std::vector<ApexFileRef> ApexFileRepository::GetDataApexFiles() const {
std::vector<ApexFileRef> result;
for (const auto& it : data_store_) {
result.emplace_back(std::cref(it.second));
}
return std::move(result);
}
// Group pre-installed APEX and data APEX by name
std::unordered_map<std::string, std::vector<ApexFileRef>>
ApexFileRepository::AllApexFilesByName() const {
// Collect all apex files
std::vector<ApexFileRef> all_apex_files;
auto pre_installed_apexs = GetPreInstalledApexFiles();
auto data_apexs = GetDataApexFiles();
std::move(pre_installed_apexs.begin(), pre_installed_apexs.end(),
std::back_inserter(all_apex_files));
std::move(data_apexs.begin(), data_apexs.end(),
std::back_inserter(all_apex_files));
// Group them by name
std::unordered_map<std::string, std::vector<ApexFileRef>> result;
for (const auto& apex_file_ref : all_apex_files) {
const ApexFile& apex_file = apex_file_ref.get();
const std::string& package_name = apex_file.GetManifest().name();
if (result.find(package_name) == result.end()) {
result[package_name] = std::vector<ApexFileRef>{};
}
result[package_name].emplace_back(apex_file_ref);
}
return std::move(result);
}
ApexFileRef ApexFileRepository::GetDataApex(const std::string& name) const {
auto it = data_store_.find(name);
CHECK(it != data_store_.end());
return std::cref(it->second);
}
ApexFileRef ApexFileRepository::GetPreInstalledApex(
const std::string& name) const {
auto it = pre_installed_store_.find(name);
CHECK(it != pre_installed_store_.end());
return std::cref(it->second);
}
} // namespace apex
} // namespace android

View File

@ -0,0 +1,212 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <android-base/result.h>
#include <functional>
#include <optional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "apex_constants.h"
#include "apex_file.h"
namespace android {
namespace apex {
using ApexFileRef = std::reference_wrapper<const android::apex::ApexFile>;
// This class serves as a ApexFile repository for all apexes on device. It also
// provides information about the ApexFiles it hosts, such as which are
// pre-installed and which are data. Such information can be used, for example,
// to verify validity of an apex before trying to mount it.
//
// It's expected to have a single instance of this class in a process that
// mounts apexes (e.g. apexd, otapreopt_chroot).
class ApexFileRepository final {
public:
// c-tors and d-tor are exposed for testing.
explicit ApexFileRepository(
const std::string& decompression_dir = kApexDecompressedDir)
: decompression_dir_(decompression_dir){};
explicit ApexFileRepository(
bool enforce_multi_install_partition,
const std::vector<std::string>& multi_install_select_prop_prefixes)
: multi_install_select_prop_prefixes_(multi_install_select_prop_prefixes),
enforce_multi_install_partition_(enforce_multi_install_partition){};
~ApexFileRepository() {
pre_installed_store_.clear();
data_store_.clear();
};
// Returns a singletone instance of this class.
static ApexFileRepository& GetInstance();
// Populate instance by collecting pre-installed apex files from the given
// |prebuilt_dirs|.
// Note: this call is **not thread safe** and is expected to be performed in a
// single thread during initialization of apexd. After initialization is
// finished, all queries to the instance are thread safe.
android::base::Result<void> AddPreInstalledApex(
const std::vector<std::string>& prebuilt_dirs);
// Populate instance by collecting host-provided apex files via
// |metadata_partition|. Host can provide its apexes to a VM instance via the
// virtual disk image which has partitions: (see
// /packages/modules/Virtualization/microdroid for the details)
// - metadata partition(/dev/block/vd*1) should be accessed by
// setting the system property apexd.payload_metadata.prop. On microdroid,
// this is /dev/block/by-name/payload-metadata.
// - each subsequence partition(/dev/block/vd*{2,3,..}) represents an APEX
// archive.
// It will fail if there is more than one apex with the same name in
// pre-installed and block apexes. Note: this call is **not thread safe** and
// is expected to be performed in a single thread during initialization of
// apexd. After initialization is finished, all queries to the instance are
// thread safe.
// This will return the number of block apexes that were added.
android::base::Result<int> AddBlockApex(
const std::string& metadata_partition);
// Populate instance by collecting data apex files from the given |data_dir|.
// Note: this call is **not thread safe** and is expected to be performed in a
// single thread during initialization of apexd. After initialization is
// finished, all queries to the instance are thread safe.
android::base::Result<void> AddDataApex(const std::string& data_dir);
// Returns trusted public key for an apex with the given |name|.
android::base::Result<const std::string> GetPublicKey(
const std::string& name) const;
// Returns path to the pre-installed version of an apex with the given |name|.
android::base::Result<const std::string> GetPreinstalledPath(
const std::string& name) const;
// Returns path to the data version of an apex with the given |name|.
android::base::Result<const std::string> GetDataPath(
const std::string& name) const;
// Returns root digest of an apex with the given |path| for block apexes.
std::optional<std::string> GetBlockApexRootDigest(
const std::string& path) const;
// Returns timestamp to be used for the block apex of the given |path|.
std::optional<int64_t> GetBlockApexLastUpdateSeconds(
const std::string& path) const;
// Checks whether there is a pre-installed version of an apex with the given
// |name|.
bool HasPreInstalledVersion(const std::string& name) const;
// Checks whether there is a data version of an apex with the given |name|.
bool HasDataVersion(const std::string& name) const;
// Checks if given |apex| is pre-installed.
bool IsPreInstalledApex(const ApexFile& apex) const;
// Checks if given |apex| is decompressed from a pre-installed APEX
bool IsDecompressedApex(const ApexFile& apex) const;
// Checks if given |apex| is loaded from block device.
bool IsBlockApex(const ApexFile& apex) const;
// Returns reference to all pre-installed APEX on device
std::vector<ApexFileRef> GetPreInstalledApexFiles() const;
// Returns reference to all data APEX on device
std::vector<ApexFileRef> GetDataApexFiles() const;
// Group all ApexFiles on device by their package name
std::unordered_map<std::string, std::vector<ApexFileRef>> AllApexFilesByName()
const;
// Returns a pre-installed version of apex with the given name. Caller is
// expected to check if there is a pre-installed apex with the given name
// using |HasPreinstalledVersion| function.
ApexFileRef GetPreInstalledApex(const std::string& name) const;
// Returns a data version of apex with the given name. Caller is
// expected to check if there is a data apex with the given name
// using |HasDataVersion| function.
ApexFileRef GetDataApex(const std::string& name) const;
// Clears ApexFileRepostiry.
// Only use in tests.
void Reset(const std::string& decompression_dir = kApexDecompressedDir) {
pre_installed_store_.clear();
data_store_.clear();
block_apex_overrides_.clear();
decompression_dir_ = decompression_dir;
block_disk_path_.reset();
}
private:
// Non-copyable && non-moveable.
ApexFileRepository(const ApexFileRepository&) = delete;
ApexFileRepository& operator=(const ApexFileRepository&) = delete;
ApexFileRepository& operator=(ApexFileRepository&&) = delete;
ApexFileRepository(ApexFileRepository&&) = delete;
// Scans apexes in the given directory and adds collected data into
// |pre_installed_store_|.
android::base::Result<void> ScanBuiltInDir(const std::string& dir);
std::unordered_map<std::string, ApexFile> pre_installed_store_, data_store_;
// Multi-installed APEX name -> all encountered public keys for this APEX.
std::unordered_map<std::string, std::unordered_set<std::string>>
multi_install_public_keys_;
// Prefixes used when looking for multi-installed APEX sysprops.
// Order matters: the first non-empty prop value is returned.
std::vector<std::string> multi_install_select_prop_prefixes_ = {
// Check persist props first, to allow users to override bootconfig.
kMultiApexSelectPersistPrefix,
kMultiApexSelectBootconfigPrefix,
};
// Allows multi-install APEXes outside of expected partitions.
// Only set false in tests.
bool enforce_multi_install_partition_ = true;
// Decompression directory which will be used to determine if apex is
// decompressed or not
std::string decompression_dir_;
// Disk path where block apexes are read from. AddBlockApex() sets this.
std::optional<std::string> block_disk_path_;
// Information from the metadata for block apexes, overriding the file data.
struct BlockApexOverride {
// Root digest for the APEX. When specified in block apex config, it
// should be used/checked when activating the apex to avoid
// TOCTOU(time-of-check to time-of-use).
std::optional<std::string> block_apex_root_digest;
// The last update time of the APEX.
std::optional<int64_t> last_update_seconds;
};
// Use "path" as key instead of APEX name because there can be multiple
// versions of sharedlibs APEXes.
std::unordered_map<std::string, BlockApexOverride> block_apex_overrides_;
};
} // namespace apex
} // namespace android

View File

@ -0,0 +1,822 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "apex_file_repository.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <errno.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <microdroid/metadata.h>
#include <sys/stat.h>
#include <filesystem>
#include <string>
#include "apex_file.h"
#include "apexd_test_utils.h"
#include "apexd_verity.h"
namespace android {
namespace apex {
using namespace std::literals;
namespace fs = std::filesystem;
using android::apex::testing::ApexFileEq;
using android::apex::testing::IsOk;
using android::base::GetExecutableDirectory;
using android::base::StringPrintf;
using ::testing::ByRef;
using ::testing::UnorderedElementsAre;
static std::string GetTestDataDir() { return GetExecutableDirectory(); }
static std::string GetTestFile(const std::string& name) {
return GetTestDataDir() + "/" + name;
}
namespace {
// Copies the compressed apex to |built_in_dir| and decompresses it to
// |decompression_dir
void PrepareCompressedApex(const std::string& name,
const std::string& built_in_dir,
const std::string& decompression_dir) {
fs::copy(GetTestFile(name), built_in_dir);
auto compressed_apex =
ApexFile::Open(StringPrintf("%s/%s", built_in_dir.c_str(), name.c_str()));
const auto& pkg_name = compressed_apex->GetManifest().name();
const int version = compressed_apex->GetManifest().version();
auto decompression_path =
StringPrintf("%s/%s@%d%s", decompression_dir.c_str(), pkg_name.c_str(),
version, kDecompressedApexPackageSuffix);
compressed_apex->Decompress(decompression_path);
}
} // namespace
TEST(ApexFileRepositoryTest, InitializeSuccess) {
// Prepare test data.
TemporaryDir built_in_dir, data_dir, decompression_dir;
fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
fs::copy(GetTestFile("apex.apexd_test_different_app.apex"),
built_in_dir.path);
fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path);
fs::copy(GetTestFile("apex.apexd_test_different_app.apex"), data_dir.path);
ApexFileRepository instance;
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
// Now test that apexes were scanned correctly;
auto test_fn = [&](const std::string& apex_name) {
auto apex = ApexFile::Open(GetTestFile(apex_name));
ASSERT_TRUE(IsOk(apex));
{
auto ret = instance.GetPublicKey(apex->GetManifest().name());
ASSERT_TRUE(IsOk(ret));
ASSERT_EQ(apex->GetBundledPublicKey(), *ret);
}
{
auto ret = instance.GetPreinstalledPath(apex->GetManifest().name());
ASSERT_TRUE(IsOk(ret));
ASSERT_EQ(StringPrintf("%s/%s", built_in_dir.path, apex_name.c_str()),
*ret);
}
{
auto ret = instance.GetDataPath(apex->GetManifest().name());
ASSERT_TRUE(IsOk(ret));
ASSERT_EQ(StringPrintf("%s/%s", data_dir.path, apex_name.c_str()), *ret);
}
ASSERT_TRUE(instance.HasPreInstalledVersion(apex->GetManifest().name()));
ASSERT_TRUE(instance.HasDataVersion(apex->GetManifest().name()));
};
test_fn("apex.apexd_test.apex");
test_fn("apex.apexd_test_different_app.apex");
// Check that second call will succeed as well.
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
test_fn("apex.apexd_test.apex");
test_fn("apex.apexd_test_different_app.apex");
}
TEST(ApexFileRepositoryTest, InitializeFailureCorruptApex) {
// Prepare test data.
TemporaryDir td;
fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
fs::copy(GetTestFile("apex.apexd_test_corrupt_superblock_apex.apex"),
td.path);
ApexFileRepository instance;
ASSERT_FALSE(IsOk(instance.AddPreInstalledApex({td.path})));
}
TEST(ApexFileRepositoryTest, InitializeCompressedApexWithoutApex) {
// Prepare test data.
TemporaryDir td;
fs::copy(GetTestFile("com.android.apex.compressed.v1_without_apex.capex"),
td.path);
ApexFileRepository instance;
// Compressed APEX without APEX cannot be opened
ASSERT_FALSE(IsOk(instance.AddPreInstalledApex({td.path})));
}
TEST(ApexFileRepositoryTest, InitializeSameNameDifferentPathAborts) {
// Prepare test data.
TemporaryDir td;
fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
fs::copy(GetTestFile("apex.apexd_test.apex"),
StringPrintf("%s/other.apex", td.path));
ASSERT_DEATH(
{
ApexFileRepository instance;
instance.AddPreInstalledApex({td.path});
},
"");
}
TEST(ApexFileRepositoryTest, InitializeMultiInstalledSuccess) {
// Prepare test data.
TemporaryDir td;
std::string apex_file = GetTestFile("apex.apexd_test.apex");
fs::copy(apex_file, StringPrintf("%s/version_a.apex", td.path));
fs::copy(apex_file, StringPrintf("%s/version_b.apex", td.path));
std::string apex_name = ApexFile::Open(apex_file)->GetManifest().name();
std::string persist_prefix = "debug.apexd.test.persistprefix.";
std::string bootconfig_prefix = "debug.apexd.test.bootconfigprefix.";
ApexFileRepository instance(/*enforce_multi_install_partition=*/false,
/*multi_install_select_prop_prefixes=*/{
persist_prefix, bootconfig_prefix});
auto test_fn = [&](const std::string& selected_filename) {
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
auto ret = instance.GetPreinstalledPath(apex_name);
ASSERT_TRUE(IsOk(ret));
ASSERT_EQ(StringPrintf("%s/%s", td.path, selected_filename.c_str()), *ret);
instance.Reset();
};
// Start with version_a in bootconfig.
android::base::SetProperty(bootconfig_prefix + apex_name, "version_a.apex");
test_fn("version_a.apex");
// Developer chooses version_b with persist prop.
android::base::SetProperty(persist_prefix + apex_name, "version_b.apex");
test_fn("version_b.apex");
// Developer goes back to version_a with persist prop.
android::base::SetProperty(persist_prefix + apex_name, "version_a.apex");
test_fn("version_a.apex");
android::base::SetProperty(persist_prefix + apex_name, "");
android::base::SetProperty(bootconfig_prefix + apex_name, "");
}
TEST(ApexFileRepositoryTest, InitializeMultiInstalledSkipsForDifferingKeys) {
// Prepare test data.
TemporaryDir td;
fs::copy(GetTestFile("apex.apexd_test.apex"),
StringPrintf("%s/version_a.apex", td.path));
fs::copy(GetTestFile("apex.apexd_test_different_key.apex"),
StringPrintf("%s/version_b.apex", td.path));
std::string apex_name =
ApexFile::Open(GetTestFile("apex.apexd_test.apex"))->GetManifest().name();
std::string prop_prefix = "debug.apexd.test.bootconfigprefix.";
std::string prop = prop_prefix + apex_name;
android::base::SetProperty(prop, "version_a.apex");
ApexFileRepository instance(
/*enforce_multi_install_partition=*/false,
/*multi_install_select_prop_prefixes=*/{prop_prefix});
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
// Neither version should be have been installed.
ASSERT_FALSE(IsOk(instance.GetPreinstalledPath(apex_name)));
android::base::SetProperty(prop, "");
}
TEST(ApexFileRepositoryTest, InitializeMultiInstalledSkipsForInvalidPartition) {
// Prepare test data.
TemporaryDir td;
// Note: These test files are on /data, which is not a valid partition for
// multi-installed APEXes.
fs::copy(GetTestFile("apex.apexd_test.apex"),
StringPrintf("%s/version_a.apex", td.path));
fs::copy(GetTestFile("apex.apexd_test.apex"),
StringPrintf("%s/version_b.apex", td.path));
std::string apex_name =
ApexFile::Open(GetTestFile("apex.apexd_test.apex"))->GetManifest().name();
std::string prop_prefix = "debug.apexd.test.bootconfigprefix.";
std::string prop = prop_prefix + apex_name;
android::base::SetProperty(prop, "version_a.apex");
ApexFileRepository instance(
/*enforce_multi_install_partition=*/true,
/*multi_install_select_prop_prefixes=*/{prop_prefix});
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
// Neither version should be have been installed.
ASSERT_FALSE(IsOk(instance.GetPreinstalledPath(apex_name)));
android::base::SetProperty(prop, "");
}
TEST(ApexFileRepositoryTest,
InitializeSameNameDifferentPathAbortsCompressedApex) {
// Prepare test data.
TemporaryDir td;
fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path);
fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"),
StringPrintf("%s/other.capex", td.path));
ASSERT_DEATH(
{
ApexFileRepository instance;
instance.AddPreInstalledApex({td.path});
},
"");
}
TEST(ApexFileRepositoryTest, InitializePublicKeyUnexpectdlyChangedAborts) {
// Prepare test data.
TemporaryDir td;
fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
ApexFileRepository instance;
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
// Check that apex was loaded.
auto path = instance.GetPreinstalledPath("com.android.apex.test_package");
ASSERT_TRUE(IsOk(path));
ASSERT_EQ(StringPrintf("%s/apex.apexd_test.apex", td.path), *path);
auto public_key = instance.GetPublicKey("com.android.apex.test_package");
ASSERT_TRUE(IsOk(public_key));
// Substitute it with another apex with the same name, but different public
// key.
fs::copy(GetTestFile("apex.apexd_test_different_key.apex"), *path,
fs::copy_options::overwrite_existing);
{
auto apex = ApexFile::Open(*path);
ASSERT_TRUE(IsOk(apex));
// Check module name hasn't changed.
ASSERT_EQ("com.android.apex.test_package", apex->GetManifest().name());
// Check public key has changed.
ASSERT_NE(*public_key, apex->GetBundledPublicKey());
}
ASSERT_DEATH({ instance.AddPreInstalledApex({td.path}); }, "");
}
TEST(ApexFileRepositoryTest,
InitializePublicKeyUnexpectdlyChangedAbortsCompressedApex) {
// Prepare test data.
TemporaryDir td;
fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path);
ApexFileRepository instance;
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
// Check that apex was loaded.
auto path = instance.GetPreinstalledPath("com.android.apex.compressed");
ASSERT_TRUE(IsOk(path));
ASSERT_EQ(StringPrintf("%s/com.android.apex.compressed.v1.capex", td.path),
*path);
auto public_key = instance.GetPublicKey("com.android.apex.compressed");
ASSERT_TRUE(IsOk(public_key));
// Substitute it with another apex with the same name, but different public
// key.
fs::copy(GetTestFile("com.android.apex.compressed_different_key.capex"),
*path, fs::copy_options::overwrite_existing);
{
auto apex = ApexFile::Open(*path);
ASSERT_TRUE(IsOk(apex));
// Check module name hasn't changed.
ASSERT_EQ("com.android.apex.compressed", apex->GetManifest().name());
// Check public key has changed.
ASSERT_NE(*public_key, apex->GetBundledPublicKey());
}
ASSERT_DEATH({ instance.AddPreInstalledApex({td.path}); }, "");
}
TEST(ApexFileRepositoryTest, IsPreInstalledApex) {
// Prepare test data.
TemporaryDir td;
fs::copy(GetTestFile("apex.apexd_test.apex"), td.path);
fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), td.path);
ApexFileRepository instance;
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({td.path})));
auto compressed_apex = ApexFile::Open(
StringPrintf("%s/com.android.apex.compressed.v1.capex", td.path));
ASSERT_TRUE(IsOk(compressed_apex));
ASSERT_TRUE(instance.IsPreInstalledApex(*compressed_apex));
auto apex1 = ApexFile::Open(StringPrintf("%s/apex.apexd_test.apex", td.path));
ASSERT_TRUE(IsOk(apex1));
ASSERT_TRUE(instance.IsPreInstalledApex(*apex1));
// It's same apex, but path is different. Shouldn't be treated as
// pre-installed.
auto apex2 = ApexFile::Open(GetTestFile("apex.apexd_test.apex"));
ASSERT_TRUE(IsOk(apex2));
ASSERT_FALSE(instance.IsPreInstalledApex(*apex2));
auto apex3 =
ApexFile::Open(GetTestFile("apex.apexd_test_different_app.apex"));
ASSERT_TRUE(IsOk(apex3));
ASSERT_FALSE(instance.IsPreInstalledApex(*apex3));
}
TEST(ApexFileRepositoryTest, IsDecompressedApex) {
// Prepare instance
TemporaryDir decompression_dir;
ApexFileRepository instance(decompression_dir.path);
// Prepare decompressed apex
std::string filename = "com.android.apex.compressed.v1_original.apex";
fs::copy(GetTestFile(filename), decompression_dir.path);
auto decompressed_path =
StringPrintf("%s/%s", decompression_dir.path, filename.c_str());
auto decompressed_apex = ApexFile::Open(decompressed_path);
// Any file which is already located in |decompression_dir| should be
// considered decompressed
ASSERT_TRUE(instance.IsDecompressedApex(*decompressed_apex));
// Hard links with same file name is not considered decompressed
TemporaryDir active_dir;
auto active_path = StringPrintf("%s/%s", active_dir.path, filename.c_str());
std::error_code ec;
fs::create_hard_link(decompressed_path, active_path, ec);
ASSERT_FALSE(ec) << "Failed to create hardlink";
auto active_apex = ApexFile::Open(active_path);
ASSERT_FALSE(instance.IsDecompressedApex(*active_apex));
}
TEST(ApexFileRepositoryTest, AddAndGetDataApex) {
// Prepare test data.
TemporaryDir built_in_dir, data_dir, decompression_dir;
fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path);
PrepareCompressedApex("com.android.apex.compressed.v1.capex",
built_in_dir.path, decompression_dir.path);
// Add a data apex that has kDecompressedApexPackageSuffix
fs::copy(GetTestFile("com.android.apex.compressed.v1_original.apex"),
StringPrintf("%s/com.android.apex.compressed@1%s", data_dir.path,
kDecompressedApexPackageSuffix));
ApexFileRepository instance(decompression_dir.path);
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
// ApexFileRepository should only deal with APEX in /data/apex/active.
// Decompressed APEX should not be included
auto data_apexs = instance.GetDataApexFiles();
auto normal_apex =
ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path));
ASSERT_THAT(data_apexs,
UnorderedElementsAre(ApexFileEq(ByRef(*normal_apex))));
}
TEST(ApexFileRepositoryTest, AddDataApexIgnoreCompressedApex) {
// Prepare test data.
TemporaryDir data_dir, decompression_dir;
fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"), data_dir.path);
ApexFileRepository instance;
ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
auto data_apexs = instance.GetDataApexFiles();
ASSERT_EQ(data_apexs.size(), 0u);
}
TEST(ApexFileRepositoryTest, AddDataApexIgnoreIfNotPreInstalled) {
// Prepare test data.
TemporaryDir data_dir, decompression_dir;
fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path);
ApexFileRepository instance;
ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
auto data_apexs = instance.GetDataApexFiles();
ASSERT_EQ(data_apexs.size(), 0u);
}
TEST(ApexFileRepositoryTest, AddDataApexPrioritizeHigherVersionApex) {
// Prepare test data.
TemporaryDir built_in_dir, data_dir, decompression_dir;
fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
fs::copy(GetTestFile("apex.apexd_test.apex"), data_dir.path);
fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path);
ApexFileRepository instance;
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
auto data_apexs = instance.GetDataApexFiles();
auto normal_apex =
ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path));
ASSERT_THAT(data_apexs,
UnorderedElementsAre(ApexFileEq(ByRef(*normal_apex))));
}
TEST(ApexFileRepositoryTest, AddDataApexDoesNotScanDecompressedApex) {
// Prepare test data.
TemporaryDir built_in_dir, data_dir, decompression_dir;
PrepareCompressedApex("com.android.apex.compressed.v1.capex",
built_in_dir.path, decompression_dir.path);
ApexFileRepository instance(decompression_dir.path);
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
auto data_apexs = instance.GetDataApexFiles();
ASSERT_EQ(data_apexs.size(), 0u);
}
TEST(ApexFileRepositoryTest, AddDataApexIgnoreWrongPublicKey) {
// Prepare test data.
TemporaryDir built_in_dir, data_dir, decompression_dir;
fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
fs::copy(GetTestFile("apex.apexd_test_different_key.apex"), data_dir.path);
ApexFileRepository instance;
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
auto data_apexs = instance.GetDataApexFiles();
ASSERT_EQ(data_apexs.size(), 0u);
}
TEST(ApexFileRepositoryTest, GetPreInstalledApexFiles) {
// Prepare test data.
TemporaryDir built_in_dir;
fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"),
built_in_dir.path);
ApexFileRepository instance;
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
auto pre_installed_apexs = instance.GetPreInstalledApexFiles();
auto pre_apex_1 = ApexFile::Open(
StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path));
auto pre_apex_2 = ApexFile::Open(StringPrintf(
"%s/com.android.apex.compressed.v1.capex", built_in_dir.path));
ASSERT_THAT(pre_installed_apexs,
UnorderedElementsAre(ApexFileEq(ByRef(*pre_apex_1)),
ApexFileEq(ByRef(*pre_apex_2))));
}
TEST(ApexFileRepositoryTest, AllApexFilesByName) {
TemporaryDir built_in_dir, decompression_dir;
fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
fs::copy(GetTestFile("com.android.apex.cts.shim.apex"), built_in_dir.path);
fs::copy(GetTestFile("com.android.apex.compressed.v1.capex"),
built_in_dir.path);
ApexFileRepository instance;
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
TemporaryDir data_dir;
fs::copy(GetTestFile("com.android.apex.cts.shim.v2.apex"), data_dir.path);
ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
auto result = instance.AllApexFilesByName();
// Verify the contents of result
auto apexd_test_file = ApexFile::Open(
StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path));
auto shim_v1 = ApexFile::Open(
StringPrintf("%s/com.android.apex.cts.shim.apex", built_in_dir.path));
auto compressed_apex = ApexFile::Open(StringPrintf(
"%s/com.android.apex.compressed.v1.capex", built_in_dir.path));
auto shim_v2 = ApexFile::Open(
StringPrintf("%s/com.android.apex.cts.shim.v2.apex", data_dir.path));
ASSERT_EQ(result.size(), 3u);
ASSERT_THAT(result[apexd_test_file->GetManifest().name()],
UnorderedElementsAre(ApexFileEq(ByRef(*apexd_test_file))));
ASSERT_THAT(result[shim_v1->GetManifest().name()],
UnorderedElementsAre(ApexFileEq(ByRef(*shim_v1)),
ApexFileEq(ByRef(*shim_v2))));
ASSERT_THAT(result[compressed_apex->GetManifest().name()],
UnorderedElementsAre(ApexFileEq(ByRef(*compressed_apex))));
}
TEST(ApexFileRepositoryTest, GetDataApex) {
// Prepare test data.
TemporaryDir built_in_dir, data_dir;
fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
fs::copy(GetTestFile("apex.apexd_test_v2.apex"), data_dir.path);
ApexFileRepository instance;
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
ASSERT_TRUE(IsOk(instance.AddDataApex(data_dir.path)));
auto apex =
ApexFile::Open(StringPrintf("%s/apex.apexd_test_v2.apex", data_dir.path));
ASSERT_RESULT_OK(apex);
auto ret = instance.GetDataApex("com.android.apex.test_package");
ASSERT_THAT(ret, ApexFileEq(ByRef(*apex)));
}
TEST(ApexFileRepositoryTest, GetDataApexNoSuchApexAborts) {
ASSERT_DEATH(
{
ApexFileRepository instance;
instance.GetDataApex("whatever");
},
"");
}
TEST(ApexFileRepositoryTest, GetPreInstalledApex) {
// Prepare test data.
TemporaryDir built_in_dir;
fs::copy(GetTestFile("apex.apexd_test.apex"), built_in_dir.path);
ApexFileRepository instance;
ASSERT_TRUE(IsOk(instance.AddPreInstalledApex({built_in_dir.path})));
auto apex = ApexFile::Open(
StringPrintf("%s/apex.apexd_test.apex", built_in_dir.path));
ASSERT_RESULT_OK(apex);
auto ret = instance.GetPreInstalledApex("com.android.apex.test_package");
ASSERT_THAT(ret, ApexFileEq(ByRef(*apex)));
}
TEST(ApexFileRepositoryTest, GetPreInstalledApexNoSuchApexAborts) {
ASSERT_DEATH(
{
ApexFileRepository instance;
instance.GetPreInstalledApex("whatever");
},
"");
}
struct ApexFileRepositoryTestAddBlockApex : public ::testing::Test {
TemporaryDir test_dir;
struct PayloadMetadata {
android::microdroid::Metadata metadata;
std::string path;
PayloadMetadata(const std::string& path) : path(path) {}
PayloadMetadata& apex(const std::string& name,
const std::string& public_key = "",
const std::string& root_digest = "",
int64_t last_update_seconds = 0,
bool is_factory = true) {
auto apex = metadata.add_apexes();
apex->set_name(name);
apex->set_public_key(public_key);
apex->set_root_digest(root_digest);
apex->set_last_update_seconds(last_update_seconds);
apex->set_is_factory(is_factory);
return *this;
}
~PayloadMetadata() {
metadata.set_version(1);
std::ofstream out(path);
android::microdroid::WriteMetadata(metadata, out);
}
};
};
TEST_F(ApexFileRepositoryTestAddBlockApex,
ScansPayloadDisksAndAddApexFilesToPreInstalled) {
// prepare payload disk
// <test-dir>/vdc1 : metadata
// /vdc2 : apex.apexd_test.apex
// /vdc3 : apex.apexd_test_different_app.apex
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
const auto& test_apex_bar = GetTestFile("apex.apexd_test_different_app.apex");
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
const std::string apex_bar_path = test_dir.path + "/vdc3"s;
PayloadMetadata(metadata_partition_path)
.apex(test_apex_foo)
.apex(test_apex_bar);
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
auto loop_device2 = WriteBlockApex(test_apex_bar, apex_bar_path);
// call ApexFileRepository::AddBlockApex()
ApexFileRepository instance;
auto status = instance.AddBlockApex(metadata_partition_path);
ASSERT_RESULT_OK(status);
auto apex_foo = ApexFile::Open(apex_foo_path);
ASSERT_RESULT_OK(apex_foo);
// block apexes can be identified with IsBlockApex
ASSERT_TRUE(instance.IsBlockApex(*apex_foo));
// "block" apexes are treated as "pre-installed"
auto ret_foo = instance.GetPreInstalledApex("com.android.apex.test_package");
ASSERT_THAT(ret_foo, ApexFileEq(ByRef(*apex_foo)));
auto apex_bar = ApexFile::Open(apex_bar_path);
ASSERT_RESULT_OK(apex_bar);
auto ret_bar =
instance.GetPreInstalledApex("com.android.apex.test_package_2");
ASSERT_THAT(ret_bar, ApexFileEq(ByRef(*apex_bar)));
}
TEST_F(ApexFileRepositoryTestAddBlockApex,
ScansOnlySpecifiedInMetadataPartition) {
// prepare payload disk
// <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
// /vdc2 : apex.apexd_test.apex
// /vdc3 : apex.apexd_test_different_app.apex
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
const auto& test_apex_bar = GetTestFile("apex.apexd_test_different_app.apex");
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
const std::string apex_bar_path = test_dir.path + "/vdc3"s;
// metadata lists only "foo"
PayloadMetadata(metadata_partition_path).apex(test_apex_foo);
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
auto loop_device2 = WriteBlockApex(test_apex_bar, apex_bar_path);
// call ApexFileRepository::AddBlockApex()
ApexFileRepository instance;
auto status = instance.AddBlockApex(metadata_partition_path);
ASSERT_RESULT_OK(status);
// foo is added, but bar is not
auto ret_foo = instance.GetPreinstalledPath("com.android.apex.test_package");
ASSERT_TRUE(IsOk(ret_foo));
ASSERT_EQ(apex_foo_path, *ret_foo);
auto ret_bar =
instance.GetPreinstalledPath("com.android.apex.test_package_2");
ASSERT_FALSE(IsOk(ret_bar));
}
TEST_F(ApexFileRepositoryTestAddBlockApex, FailsWhenTheresDuplicateNames) {
// prepare payload disk
// <test-dir>/vdc1 : metadata with v1 and v2 of apex.apexd_test
// /vdc2 : apex.apexd_test.apex
// /vdc3 : apex.apexd_test_v2.apex
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
const auto& test_apex_bar = GetTestFile("apex.apexd_test_v2.apex");
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
const std::string apex_bar_path = test_dir.path + "/vdc3"s;
PayloadMetadata(metadata_partition_path)
.apex(test_apex_foo)
.apex(test_apex_bar);
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
auto loop_device2 = WriteBlockApex(test_apex_bar, apex_bar_path);
ApexFileRepository instance;
auto status = instance.AddBlockApex(metadata_partition_path);
ASSERT_FALSE(IsOk(status));
}
TEST_F(ApexFileRepositoryTestAddBlockApex, GetBlockApexRootDigest) {
// prepare payload disk with root digest
// <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
// /vdc2 : apex.apexd_test.apex
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
// root digest is stored as bytes in metadata and as hexadecimal in
// ApexFileRepository
const std::string root_digest = "root_digest";
const std::string hex_root_digest = BytesToHex(
reinterpret_cast<const uint8_t*>(root_digest.data()), root_digest.size());
// metadata lists "foo"
PayloadMetadata(metadata_partition_path)
.apex(test_apex_foo, /*public_key=*/"", root_digest);
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
// call ApexFileRepository::AddBlockApex()
ApexFileRepository instance;
auto status = instance.AddBlockApex(metadata_partition_path);
ASSERT_TRUE(IsOk(status));
ASSERT_EQ(hex_root_digest, instance.GetBlockApexRootDigest(apex_foo_path));
}
TEST_F(ApexFileRepositoryTestAddBlockApex, GetBlockApexLastUpdateSeconds) {
// prepare payload disk with last update time
// <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
// /vdc2 : apex.apexd_test.apex
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
const int64_t last_update_seconds = 123456789;
// metadata lists "foo"
PayloadMetadata(metadata_partition_path)
.apex(test_apex_foo, /*public_key=*/"", /*root_digest=*/"",
last_update_seconds);
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
// call ApexFileRepository::AddBlockApex()
ApexFileRepository instance;
auto status = instance.AddBlockApex(metadata_partition_path);
ASSERT_TRUE(IsOk(status));
ASSERT_EQ(last_update_seconds,
instance.GetBlockApexLastUpdateSeconds(apex_foo_path));
}
TEST_F(ApexFileRepositoryTestAddBlockApex, VerifyPublicKeyWhenAddingBlockApex) {
// prepare payload disk
// <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
// /vdc2 : apex.apexd_test.apex
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
// metadata lists "foo"
PayloadMetadata(metadata_partition_path)
.apex(test_apex_foo, /*public_key=*/"wrong public key");
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
// call ApexFileRepository::AddBlockApex()
ApexFileRepository instance;
auto status = instance.AddBlockApex(metadata_partition_path);
ASSERT_FALSE(IsOk(status));
}
TEST_F(ApexFileRepositoryTestAddBlockApex, RespectIsFactoryBitFromMetadata) {
// prepare payload disk
// <test-dir>/vdc1 : metadata with apex.apexd_test.apex only
// /vdc2 : apex.apexd_test.apex
const auto& test_apex_foo = GetTestFile("apex.apexd_test.apex");
const std::string metadata_partition_path = test_dir.path + "/vdc1"s;
const std::string apex_foo_path = test_dir.path + "/vdc2"s;
auto loop_device1 = WriteBlockApex(test_apex_foo, apex_foo_path);
for (const bool is_factory : {true, false}) {
// metadata lists "foo"
PayloadMetadata(metadata_partition_path)
.apex(test_apex_foo, /*public_key=*/"", /*root_digest=*/"",
/*last_update_seconds=*/0, is_factory);
// call ApexFileRepository::AddBlockApex()
ApexFileRepository instance;
auto status = instance.AddBlockApex(metadata_partition_path);
ASSERT_TRUE(IsOk(status))
<< "failed to add block apex with is_factory=" << is_factory;
ASSERT_EQ(is_factory,
instance.HasPreInstalledVersion("com.android.apex.test_package"));
}
}
} // namespace apex
} // namespace android

View File

@ -0,0 +1,354 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <limits>
#include <string>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/scopeguard.h>
#include <android-base/strings.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libavb/libavb.h>
#include <ziparchive/zip_archive.h>
#include "apex_file.h"
#include "apexd_test_utils.h"
#include "apexd_utils.h"
using android::base::GetExecutableDirectory;
using android::base::Result;
static const std::string kTestDataDir = GetExecutableDirectory() + "/";
namespace android {
namespace apex {
namespace {
struct ApexFileTestParam {
const char* type;
const char* prefix;
};
constexpr const ApexFileTestParam kParameters[] = {
{"ext4", "apex.apexd_test"},
{"f2fs", "apex.apexd_test_f2fs"},
{"erofs", "apex.apexd_test_erofs"}};
class ApexFileTest : public ::testing::TestWithParam<ApexFileTestParam> {};
INSTANTIATE_TEST_SUITE_P(Apex, ApexFileTest, ::testing::ValuesIn(kParameters));
TEST_P(ApexFileTest, GetOffsetOfSimplePackage) {
const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_TRUE(apex_file.ok());
uint32_t zip_image_offset;
size_t zip_image_size;
{
ZipArchiveHandle handle;
int32_t rc = OpenArchive(file_path.c_str(), &handle);
ASSERT_EQ(0, rc);
auto close_guard =
android::base::make_scope_guard([&handle]() { CloseArchive(handle); });
ZipEntry entry;
rc = FindEntry(handle, "apex_payload.img", &entry);
ASSERT_EQ(0, rc);
zip_image_offset = entry.offset;
EXPECT_EQ(zip_image_offset % 4096, 0U);
zip_image_size = entry.uncompressed_length;
EXPECT_EQ(zip_image_size, entry.compressed_length);
}
EXPECT_EQ(zip_image_offset, apex_file->GetImageOffset().value());
EXPECT_EQ(zip_image_size, apex_file->GetImageSize().value());
}
TEST_P(ApexFileTest, OpenBlockApex) {
const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_RESULT_OK(apex_file);
TemporaryFile temp_file;
auto loop_device = WriteBlockApex(file_path, temp_file.path);
Result<ApexFile> apex_file_sized = ApexFile::Open(temp_file.path);
ASSERT_RESULT_OK(apex_file_sized);
EXPECT_EQ(apex_file->GetImageOffset(), apex_file_sized->GetImageOffset());
EXPECT_EQ(apex_file->GetImageSize(), apex_file_sized->GetImageSize());
}
TEST(ApexFileTest, GetOffsetMissingFile) {
const std::string file_path = kTestDataDir + "missing.apex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_FALSE(apex_file.ok());
ASSERT_THAT(apex_file.error().message(),
::testing::HasSubstr("Failed to open package"));
}
TEST_P(ApexFileTest, GetApexManifest) {
const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_RESULT_OK(apex_file);
EXPECT_EQ("com.android.apex.test_package", apex_file->GetManifest().name());
EXPECT_EQ(1u, apex_file->GetManifest().version());
}
TEST_P(ApexFileTest, VerifyApexVerity) {
const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_RESULT_OK(apex_file);
auto verity_or =
apex_file->VerifyApexVerity(apex_file->GetBundledPublicKey());
ASSERT_RESULT_OK(verity_or);
const ApexVerityData& data = *verity_or;
EXPECT_NE(nullptr, data.desc.get());
EXPECT_EQ(std::string("368a22e64858647bc45498e92f749f85482ac468"
"50ca7ec8071f49dfa47a243c"),
data.salt);
const std::string digest_path =
kTestDataDir + GetParam().prefix + "_digest.txt";
std::string root_digest;
ASSERT_TRUE(android::base::ReadFileToString(digest_path, &root_digest))
<< "Failed to read " << digest_path;
root_digest = android::base::Trim(root_digest);
EXPECT_EQ(std::string(root_digest), data.root_digest);
}
TEST_P(ApexFileTest, VerifyApexVerityWrongKey) {
const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_RESULT_OK(apex_file);
auto verity_or = apex_file->VerifyApexVerity("wrong-key");
ASSERT_FALSE(verity_or.ok());
}
TEST_P(ApexFileTest, GetBundledPublicKey) {
const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_RESULT_OK(apex_file);
const std::string key_path =
kTestDataDir + "apexd_testdata/com.android.apex.test_package.avbpubkey";
std::string key_content;
ASSERT_TRUE(android::base::ReadFileToString(key_path, &key_content))
<< "Failed to read " << key_path;
EXPECT_EQ(key_content, apex_file->GetBundledPublicKey());
}
TEST(ApexFileTest, CorrutedApexB146895998) {
const std::string apex_path = kTestDataDir + "corrupted_b146895998.apex";
Result<ApexFile> apex = ApexFile::Open(apex_path);
ASSERT_RESULT_OK(apex);
ASSERT_FALSE(apex->VerifyApexVerity("ignored").ok());
}
TEST_P(ApexFileTest, RetrieveFsType) {
const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_TRUE(apex_file.ok());
EXPECT_EQ(std::string(GetParam().type), apex_file->GetFsType().value());
}
TEST(ApexFileTest, OpenInvalidFilesystem) {
const std::string file_path =
kTestDataDir + "apex.apexd_test_corrupt_superblock_apex.apex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_FALSE(apex_file.ok());
ASSERT_THAT(apex_file.error().message(),
::testing::HasSubstr("Failed to retrieve filesystem type"));
}
TEST(ApexFileTest, OpenCompressedApexFile) {
const std::string file_path =
kTestDataDir + "com.android.apex.compressed.v1.capex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_TRUE(apex_file.ok());
ASSERT_TRUE(apex_file->IsCompressed());
ASSERT_FALSE(apex_file->GetImageOffset().has_value());
ASSERT_FALSE(apex_file->GetImageSize().has_value());
ASSERT_FALSE(apex_file->GetFsType().has_value());
}
TEST(ApexFileTest, OpenFailureForCompressedApexWithoutApex) {
const std::string file_path =
kTestDataDir + "com.android.apex.compressed.v1_without_apex.capex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_FALSE(apex_file.ok());
ASSERT_THAT(apex_file.error().message(),
::testing::HasSubstr("Could not find entry"));
}
TEST(ApexFileTest, GetCompressedApexManifest) {
const std::string file_path =
kTestDataDir + "com.android.apex.compressed.v1.capex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_RESULT_OK(apex_file);
EXPECT_EQ("com.android.apex.compressed", apex_file->GetManifest().name());
EXPECT_EQ(1u, apex_file->GetManifest().version());
}
TEST(ApexFileTest, GetBundledPublicKeyOfCompressedApex) {
const std::string file_path =
kTestDataDir + "com.android.apex.compressed.v1.capex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_RESULT_OK(apex_file);
const std::string key_path =
kTestDataDir + "apexd_testdata/com.android.apex.compressed.avbpubkey";
std::string key_content;
ASSERT_TRUE(android::base::ReadFileToString(key_path, &key_content))
<< "Failed to read " << key_path;
EXPECT_EQ(key_content, apex_file->GetBundledPublicKey());
}
TEST(ApexFileTest, CannotVerifyApexVerityForCompressedApex) {
const std::string file_path =
kTestDataDir + "com.android.apex.compressed.v1.capex";
auto apex = ApexFile::Open(file_path);
ASSERT_RESULT_OK(apex);
auto result = apex->VerifyApexVerity(apex->GetBundledPublicKey());
ASSERT_FALSE(result.ok());
ASSERT_THAT(
result.error().message(),
::testing::HasSubstr("Cannot verify ApexVerity of compressed APEX"));
}
TEST(ApexFileTest, DISABLED_DecompressCompressedApex) {
const std::string file_path =
kTestDataDir + "com.android.apex.compressed.v1.capex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_RESULT_OK(apex_file);
// Create a temp dir for decompression
TemporaryDir tmp_dir;
const std::string package_name = apex_file->GetManifest().name();
const std::string decompression_file_path =
tmp_dir.path + package_name + ".capex";
auto result = apex_file->Decompress(decompression_file_path);
ASSERT_RESULT_OK(result);
// Assert output path is not empty
auto exists = PathExists(decompression_file_path);
ASSERT_RESULT_OK(exists);
ASSERT_TRUE(*exists) << decompression_file_path << " does not exist";
// Assert that decompressed apex is same as original apex
const std::string original_apex_file_path =
kTestDataDir + "com.android.apex.compressed.v1_original.apex";
auto comparison_result =
CompareFiles(original_apex_file_path, decompression_file_path);
ASSERT_RESULT_OK(comparison_result);
ASSERT_TRUE(*comparison_result);
}
TEST(ApexFileTest, DecompressFailForNormalApex) {
const std::string file_path =
kTestDataDir + "com.android.apex.compressed.v1_original.apex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_RESULT_OK(apex_file);
TemporaryFile decompression_file;
auto result = apex_file->Decompress(decompression_file.path);
ASSERT_FALSE(result.ok());
ASSERT_THAT(result.error().message(),
::testing::HasSubstr("Cannot decompress an uncompressed APEX"));
}
TEST(ApexFileTest, DecompressFailIfDecompressionPathExists) {
const std::string file_path =
kTestDataDir + "com.android.apex.compressed.v1.capex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
// Attempt to decompress in a path that already exists
TemporaryFile decompression_file;
auto exists = PathExists(decompression_file.path);
ASSERT_RESULT_OK(exists);
ASSERT_TRUE(*exists) << decompression_file.path << " does not exist";
auto result = apex_file->Decompress(decompression_file.path);
ASSERT_FALSE(result.ok());
ASSERT_THAT(result.error().message(),
::testing::HasSubstr("Failed to open decompression destination"));
}
TEST(ApexFileTest, GetPathReturnsRealpath) {
const std::string real_path = kTestDataDir + "apex.apexd_test.apex";
const std::string symlink_path =
kTestDataDir + "apex.apexd_test.symlink.apex";
// In case the link already exists
int ret = unlink(symlink_path.c_str());
ASSERT_TRUE(ret == 0 || errno == ENOENT)
<< "failed to unlink " << symlink_path;
ret = symlink(real_path.c_str(), symlink_path.c_str());
ASSERT_EQ(0, ret) << "failed to create symlink at " << symlink_path;
// Open with the symlink. Realpath is expected.
Result<ApexFile> apex_file = ApexFile::Open(symlink_path);
ASSERT_RESULT_OK(apex_file);
ASSERT_EQ(real_path, apex_file->GetPath());
}
TEST(ApexFileTest, CompressedSharedLibsApexIsRejected) {
const std::string file_path =
kTestDataDir + "com.android.apex.compressed_sharedlibs.capex";
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_FALSE(apex_file.ok());
ASSERT_THAT(apex_file.error().message(),
::testing::HasSubstr("Apex providing sharedlibs shouldn't "
"be compressed"));
}
// Check if CAPEX contains originalApexDigest in its manifest
TEST(ApexFileTest, OriginalApexDigest) {
const std::string capex_path =
kTestDataDir + "com.android.apex.compressed.v1.capex";
auto capex = ApexFile::Open(capex_path);
ASSERT_TRUE(capex.ok());
const std::string decompressed_apex_path =
kTestDataDir + "com.android.apex.compressed.v1_original.apex";
auto decompressed_apex = ApexFile::Open(decompressed_apex_path);
ASSERT_TRUE(decompressed_apex.ok());
// Validate root digest
auto digest = decompressed_apex->VerifyApexVerity(
decompressed_apex->GetBundledPublicKey());
ASSERT_TRUE(digest.ok());
ASSERT_EQ(digest->root_digest,
capex->GetManifest().capexmetadata().originalapexdigest());
}
} // namespace
} // namespace apex
} // namespace android

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "apex_manifest.h"
#include <android-base/file.h>
#include <memory>
#include <string>
using android::base::Error;
using android::base::Result;
using ::apex::proto::ApexManifest;
namespace android {
namespace apex {
Result<ApexManifest> ParseManifest(const std::string& content) {
ApexManifest apex_manifest;
if (!apex_manifest.ParseFromString(content)) {
return Error() << "Can't parse APEX manifest.";
}
// Verifying required fields.
// name
if (apex_manifest.name().empty()) {
return Error() << "Missing required field \"name\" from APEX manifest.";
}
// version
if (apex_manifest.version() == 0) {
return Error() << "Missing required field \"version\" from APEX manifest.";
}
return apex_manifest;
}
std::string GetPackageId(const ApexManifest& apex_manifest) {
return apex_manifest.name() + "@" + std::to_string(apex_manifest.version());
}
Result<ApexManifest> ReadManifest(const std::string& path) {
std::string content;
if (!android::base::ReadFileToString(path, &content)) {
return Error() << "Failed to read manifest file: " << path;
}
return ParseManifest(content);
}
} // namespace apex
} // namespace android

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_APEXD_APEX_MANIFEST_H_
#define ANDROID_APEXD_APEX_MANIFEST_H_
#include <android-base/result.h>
#include "apex_manifest.pb.h"
#include <string>
namespace android {
namespace apex {
// Parses and validates APEX manifest.
android::base::Result<::apex::proto::ApexManifest> ParseManifest(
const std::string& content);
// Returns package id of an ApexManifest
std::string GetPackageId(const ::apex::proto::ApexManifest& apex_manifest);
// Reads and parses APEX manifest from the file on disk.
android::base::Result<::apex::proto::ApexManifest> ReadManifest(
const std::string& path);
} // namespace apex
} // namespace android
#endif // ANDROID_APEXD_APEX_MANIFEST_H_

View File

@ -0,0 +1,130 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <gtest/gtest.h>
#include "apex_manifest.h"
using ::apex::proto::ApexManifest;
namespace android {
namespace apex {
namespace {
std::string ToString(const ApexManifest& manifest) {
std::string out;
manifest.SerializeToString(&out);
return out;
}
} // namespace
TEST(ApexManifestTest, SimpleTest) {
ApexManifest manifest;
manifest.set_name("com.android.example.apex");
manifest.set_version(1);
auto apex_manifest = ParseManifest(ToString(manifest));
ASSERT_RESULT_OK(apex_manifest);
EXPECT_EQ("com.android.example.apex", std::string(apex_manifest->name()));
EXPECT_EQ(1u, apex_manifest->version());
EXPECT_FALSE(apex_manifest->nocode());
}
TEST(ApexManifestTest, NameMissing) {
ApexManifest manifest;
manifest.set_version(1);
auto apex_manifest = ParseManifest(ToString(manifest));
ASSERT_FALSE(apex_manifest.ok());
EXPECT_EQ(apex_manifest.error().message(),
std::string("Missing required field \"name\" from APEX manifest."))
<< apex_manifest.error();
}
TEST(ApexManifestTest, VersionMissing) {
ApexManifest manifest;
manifest.set_name("com.android.example.apex");
auto apex_manifest = ParseManifest(ToString(manifest));
ASSERT_FALSE(apex_manifest.ok());
EXPECT_EQ(
apex_manifest.error().message(),
std::string("Missing required field \"version\" from APEX manifest."))
<< apex_manifest.error();
}
TEST(ApexManifestTest, NoPreInstallHook) {
ApexManifest manifest;
manifest.set_name("com.android.example.apex");
manifest.set_version(1);
auto apex_manifest = ParseManifest(ToString(manifest));
ASSERT_RESULT_OK(apex_manifest);
EXPECT_EQ("", std::string(apex_manifest->preinstallhook()));
}
TEST(ApexManifestTest, PreInstallHook) {
ApexManifest manifest;
manifest.set_name("com.android.example.apex");
manifest.set_version(1);
manifest.set_preinstallhook("bin/preInstallHook");
auto apex_manifest = ParseManifest(ToString(manifest));
ASSERT_RESULT_OK(apex_manifest);
EXPECT_EQ("bin/preInstallHook", std::string(apex_manifest->preinstallhook()));
}
TEST(ApexManifestTest, NoPostInstallHook) {
ApexManifest manifest;
manifest.set_name("com.android.example.apex");
manifest.set_version(1);
auto apex_manifest = ParseManifest(ToString(manifest));
ASSERT_RESULT_OK(apex_manifest);
EXPECT_EQ("", std::string(apex_manifest->postinstallhook()));
}
TEST(ApexManifestTest, PostInstallHook) {
ApexManifest manifest;
manifest.set_name("com.android.example.apex");
manifest.set_version(1);
manifest.set_postinstallhook("bin/postInstallHook");
auto apex_manifest = ParseManifest(ToString(manifest));
ASSERT_RESULT_OK(apex_manifest);
EXPECT_EQ("bin/postInstallHook",
std::string(apex_manifest->postinstallhook()));
}
TEST(ApexManifestTest, UnparsableManifest) {
auto apex_manifest = ParseManifest("This is an invalid pony");
ASSERT_FALSE(apex_manifest.ok());
EXPECT_EQ(apex_manifest.error().message(),
std::string("Can't parse APEX manifest."))
<< apex_manifest.error();
}
TEST(ApexManifestTest, NoCode) {
ApexManifest manifest;
manifest.set_name("com.android.example.apex");
manifest.set_version(1);
manifest.set_nocode(true);
auto apex_manifest = ParseManifest(ToString(manifest));
ASSERT_RESULT_OK(apex_manifest);
EXPECT_TRUE(apex_manifest->nocode());
}
} // namespace apex
} // namespace android

197
apex/apexd/apex_shim.cpp Normal file
View File

@ -0,0 +1,197 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "apex_shim.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <openssl/sha.h>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <unordered_set>
#include "apex_constants.h"
#include "apex_file.h"
#include "string_log.h"
using android::base::ErrnoError;
using android::base::Error;
using android::base::Result;
using ::apex::proto::ApexManifest;
namespace android {
namespace apex {
namespace shim {
namespace fs = std::filesystem;
namespace {
static constexpr const char* kApexCtsShimPackage = "com.android.apex.cts.shim";
static constexpr const char* kHashFilePath = "etc/hash.txt";
static constexpr const int kBufSize = 1024;
static constexpr const fs::perms kForbiddenFilePermissions =
fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec;
static constexpr const char* kExpectedCtsShimFiles[] = {
"apex_manifest.json",
"apex_manifest.pb",
"etc/hash.txt",
"app/CtsShim/CtsShim.apk",
"app/CtsShim@1/CtsShim.apk",
"app/CtsShim@2/CtsShim.apk",
"app/CtsShim@3/CtsShim.apk",
"app/CtsShimTargetPSdk/CtsShimTargetPSdk.apk",
"app/CtsShimTargetPSdk@1/CtsShimTargetPSdk.apk",
"app/CtsShimTargetPSdk@2/CtsShimTargetPSdk.apk",
"app/CtsShimTargetPSdk@3/CtsShimTargetPSdk.apk",
"priv-app/CtsShimPriv/CtsShimPriv.apk",
"priv-app/CtsShimPriv@1/CtsShimPriv.apk",
"priv-app/CtsShimPriv@2/CtsShimPriv.apk",
"priv-app/CtsShimPriv@3/CtsShimPriv.apk",
};
Result<std::string> CalculateSha512(const std::string& path) {
LOG(DEBUG) << "Calculating SHA512 of " << path;
SHA512_CTX ctx;
SHA512_Init(&ctx);
std::ifstream apex(path, std::ios::binary);
if (apex.bad()) {
return Error() << "Failed to open " << path;
}
char buf[kBufSize];
while (!apex.eof()) {
apex.read(buf, kBufSize);
if (apex.bad()) {
return Error() << "Failed to read " << path;
}
int bytes_read = apex.gcount();
SHA512_Update(&ctx, buf, bytes_read);
}
uint8_t hash[SHA512_DIGEST_LENGTH];
SHA512_Final(hash, &ctx);
std::stringstream ss;
ss << std::hex;
for (int i = 0; i < SHA512_DIGEST_LENGTH; i++) {
ss << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
}
return ss.str();
}
Result<std::vector<std::string>> GetAllowedHashes(const std::string& path) {
using android::base::ReadFileToString;
using android::base::StringPrintf;
const std::string& file_path =
StringPrintf("%s/%s", path.c_str(), kHashFilePath);
LOG(DEBUG) << "Reading SHA512 from " << file_path;
std::string hash;
if (!ReadFileToString(file_path, &hash, false /* follows symlinks */)) {
return ErrnoError() << "Failed to read " << file_path;
}
std::vector<std::string> allowed_hashes = android::base::Split(hash, "\n");
auto system_shim_hash = CalculateSha512(
StringPrintf("%s/%s", kApexPackageSystemDir, shim::kSystemShimApexName));
if (!system_shim_hash.ok()) {
return system_shim_hash.error();
}
allowed_hashes.push_back(std::move(*system_shim_hash));
return allowed_hashes;
}
} // namespace
bool IsShimApex(const ApexFile& apex_file) {
return apex_file.GetManifest().name() == kApexCtsShimPackage;
}
Result<void> ValidateShimApex(const std::string& mount_point,
const ApexFile& apex_file) {
LOG(DEBUG) << "Validating shim apex " << mount_point;
const ApexManifest& manifest = apex_file.GetManifest();
if (!manifest.preinstallhook().empty() ||
!manifest.postinstallhook().empty()) {
return Errorf("Shim apex is not allowed to have pre or post install hooks");
}
std::error_code ec;
std::unordered_set<std::string> expected_files;
for (auto file : kExpectedCtsShimFiles) {
expected_files.insert(file);
}
auto iter = fs::recursive_directory_iterator(mount_point, ec);
// Unfortunately fs::recursive_directory_iterator::operator++ can throw an
// exception, which means that it's impossible to use range-based for loop
// here.
while (iter != fs::end(iter)) {
auto path = iter->path();
// Resolve the mount point to ensure any trailing slash is removed.
auto resolved_mount_point = fs::path(mount_point).string();
auto local_path = path.string().substr(resolved_mount_point.length() + 1);
fs::file_status status = iter->status(ec);
if (fs::is_symlink(status)) {
return Error()
<< "Shim apex is not allowed to contain symbolic links, found "
<< path;
} else if (fs::is_regular_file(status)) {
if ((status.permissions() & kForbiddenFilePermissions) !=
fs::perms::none) {
return Error() << path << " has illegal permissions";
}
auto ex = expected_files.find(local_path);
if (ex != expected_files.end()) {
expected_files.erase(local_path);
} else {
return Error() << path << " is an unexpected file inside the shim apex";
}
} else if (!fs::is_directory(status)) {
// If this is not a symlink, a file or a directory, fail.
return Error() << "Unexpected file entry in shim apex: " << iter->path();
}
iter = iter.increment(ec);
if (ec) {
return Error() << "Failed to scan " << mount_point << " : "
<< ec.message();
}
}
return {};
}
Result<void> ValidateUpdate(const std::string& system_apex_path,
const std::string& new_apex_path) {
LOG(DEBUG) << "Validating update of shim apex to " << new_apex_path
<< " using system shim apex " << system_apex_path;
auto allowed = GetAllowedHashes(system_apex_path);
if (!allowed.ok()) {
return allowed.error();
}
auto actual = CalculateSha512(new_apex_path);
if (!actual.ok()) {
return actual.error();
}
auto it = std::find(allowed->begin(), allowed->end(), *actual);
if (it == allowed->end()) {
return Error() << new_apex_path << " has unexpected SHA512 hash "
<< *actual;
}
return {};
}
} // namespace shim
} // namespace apex
} // namespace android

37
apex/apexd/apex_shim.h Normal file
View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "apex_file.h"
#include <android-base/result.h>
namespace android {
namespace apex {
namespace shim {
constexpr const char* kSystemShimApexName = "com.android.apex.cts.shim.apex";
bool IsShimApex(const ApexFile& apex_file);
android::base::Result<void> ValidateShimApex(const std::string& mount_point,
const ApexFile& apex_file);
android::base::Result<void> ValidateUpdate(const std::string& system_apex_path,
const std::string& new_apex_path);
} // namespace shim
} // namespace apex
} // namespace android

4058
apex/apexd/apexd.cpp Normal file

File diff suppressed because it is too large Load Diff

238
apex/apexd/apexd.h Normal file
View File

@ -0,0 +1,238 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_APEXD_APEXD_H_
#define ANDROID_APEXD_APEXD_H_
#include <android-base/macros.h>
#include <android-base/result.h>
#include <ostream>
#include <string>
#include <vector>
#include "apex_classpath.h"
#include "apex_constants.h"
#include "apex_database.h"
#include "apex_file.h"
#include "apex_file_repository.h"
#include "apexd_session.h"
namespace android {
namespace apex {
// A structure containing all the values that might need to be injected for
// testing (e.g. apexd status property, etc.)
//
// Ideally we want to introduce Apexd class and use dependency injection for
// such values, but that will require a sizeable refactoring. For the time being
// this config should do the trick.
struct ApexdConfig {
const char* apex_status_sysprop;
std::vector<std::string> apex_built_in_dirs;
const char* active_apex_data_dir;
const char* decompression_dir;
const char* ota_reserved_dir;
const char* apex_hash_tree_dir;
const char* staged_session_dir;
const char* metadata_sepolicy_staged_dir;
// Overrides the path to the "metadata" partition which is by default
// /dev/block/by-name/payload-metadata It should be a path pointing the first
// partition of the VM payload disk. So, realpath() of this path is checked if
// it has the suffix "1". For example, /test-dir/test-metadata-1 can be valid
// and the subsequent numbers should point APEX files.
const char* vm_payload_metadata_partition_prop;
const char* active_apex_selinux_ctx;
};
static const ApexdConfig kDefaultConfig = {
kApexStatusSysprop,
kApexPackageBuiltinDirs,
kActiveApexPackagesDataDir,
kApexDecompressedDir,
kOtaReservedDir,
kApexHashTreeDir,
kStagedSessionsDir,
kMetadataSepolicyStagedDir,
kVmPayloadMetadataPartitionProp,
"u:object_r:staging_data_file",
};
class CheckpointInterface;
void SetConfig(const ApexdConfig& config);
// Exposed only for testing.
android::base::Result<void> Unmount(
const MountedApexDatabase::MountedApexData& data, bool deferred);
android::base::Result<void> ResumeRevertIfNeeded();
android::base::Result<void> PreinstallPackages(
const std::vector<std::string>& paths) WARN_UNUSED;
android::base::Result<void> StagePackages(
const std::vector<std::string>& tmpPaths) WARN_UNUSED;
android::base::Result<void> UnstagePackages(
const std::vector<std::string>& paths) WARN_UNUSED;
android::base::Result<std::vector<ApexFile>> SubmitStagedSession(
const int session_id, const std::vector<int>& child_session_ids,
const bool has_rollback_enabled, const bool is_rollback,
const int rollback_id) WARN_UNUSED;
android::base::Result<std::vector<ApexFile>> GetStagedApexFiles(
const int session_id,
const std::vector<int>& child_session_ids) WARN_UNUSED;
android::base::Result<ClassPath> MountAndDeriveClassPath(
const std::vector<ApexFile>&) WARN_UNUSED;
android::base::Result<void> MarkStagedSessionReady(const int session_id)
WARN_UNUSED;
android::base::Result<void> MarkStagedSessionSuccessful(const int session_id)
WARN_UNUSED;
// Only only of the parameters should be passed during revert
android::base::Result<void> RevertActiveSessions(
const std::string& crashing_native_process,
const std::string& error_message);
// Only only of the parameters should be passed during revert
android::base::Result<void> RevertActiveSessionsAndReboot(
const std::string& crashing_native_process,
const std::string& error_message);
android::base::Result<void> ActivatePackage(const std::string& full_path)
WARN_UNUSED;
android::base::Result<void> DeactivatePackage(const std::string& full_path)
WARN_UNUSED;
std::vector<ApexFile> GetActivePackages();
android::base::Result<ApexFile> GetActivePackage(
const std::string& package_name);
std::vector<ApexFile> GetFactoryPackages();
android::base::Result<void> AbortStagedSession(const int session_id);
android::base::Result<void> SnapshotCeData(const int user_id,
const int rollback_id,
const std::string& apex_name);
android::base::Result<void> RestoreCeData(const int user_id,
const int rollback_id,
const std::string& apex_name);
android::base::Result<void> DestroyDeSnapshots(const int rollback_id);
android::base::Result<void> DestroyCeSnapshots(const int user_id,
const int rollback_id);
android::base::Result<void> DestroyCeSnapshotsNotSpecified(
int user_id, const std::vector<int>& retain_rollback_ids);
int OnBootstrap();
// Sets the values of gVoldService and gInFsCheckpointMode.
void InitializeVold(CheckpointInterface* checkpoint_service);
// Initializes in-memory state (e.g. pre-installed data, activated apexes).
// Must be called first before calling any other boot sequence related function.
void Initialize(CheckpointInterface* checkpoint_service);
// Initializes data apex as in-memory state. Should be called only if we are
// not booting, since initialization timing is different when booting
void InitializeDataApex();
// Migrates sessions from /data/apex/session to /metadata/session.i
// Must only be called during boot (i.e apexd.status is not "ready" or
// "activated").
android::base::Result<void> MigrateSessionsDirIfNeeded();
// Apex activation logic. Scans staged apex sessions and activates apexes.
// Must only be called during boot (i.e apexd.status is not "ready" or
// "activated").
void OnStart();
// For every package X, there can be at most two APEX, pre-installed vs
// installed on data. We decide which ones should be activated and return them
// as a list
std::vector<ApexFileRef> SelectApexForActivation(
const std::unordered_map<std::string, std::vector<ApexFileRef>>& all_apex,
const ApexFileRepository& instance);
std::vector<ApexFile> ProcessCompressedApex(
const std::vector<ApexFileRef>& compressed_apex, bool is_ota_chroot);
// Validate |apex| is same as |capex|
android::base::Result<void> ValidateDecompressedApex(const ApexFile& capex,
const ApexFile& apex);
// Notifies system that apexes are activated by setting apexd.status property to
// "activated".
// Must only be called during boot (i.e. apexd.status is not "ready" or
// "activated").
void OnAllPackagesActivated(bool is_bootstrap);
// Notifies system that apexes are ready by setting apexd.status property to
// "ready".
// Must only be called during boot (i.e. apexd.status is not "ready" or
// "activated").
void OnAllPackagesReady();
void OnBootCompleted();
// Exposed for testing
void RemoveInactiveDataApex();
void BootCompletedCleanup();
int SnapshotOrRestoreDeUserData();
int UnmountAll();
android::base::Result<MountedApexDatabase::MountedApexData>
GetTempMountedApexData(const std::string& package);
// Optimistically tries to remount as many APEX packages as possible.
// For more documentation see corresponding binder call in IApexService.aidl.
android::base::Result<void> RemountPackages();
// Exposed for unit tests
bool ShouldAllocateSpaceForDecompression(const std::string& new_apex_name,
int64_t new_apex_version,
const ApexFileRepository& instance);
int64_t CalculateSizeForCompressedApex(
const std::vector<std::tuple<std::string, int64_t, int64_t>>&
compressed_apexes,
const ApexFileRepository& instance);
void CollectApexInfoList(std::ostream& os,
const std::vector<ApexFile>& active_apexs,
const std::vector<ApexFile>& inactive_apexs);
// Reserve |size| bytes in |dest_dir| by creating a zero-filled file
android::base::Result<void> ReserveSpaceForCompressedApex(
int64_t size, const std::string& dest_dir);
// Entry point when running in the VM mode (with --vm arg)
int OnStartInVmMode();
// Activates apexes in otapreot_chroot environment.
// TODO(b/172911822): support compressed apexes.
int OnOtaChrootBootstrap();
// Activates flattened apexes
int ActivateFlattenedApex();
android::apex::MountedApexDatabase& GetApexDatabaseForTesting();
// Performs a non-staged install of an APEX specified by |package_path|.
// TODO(ioffe): add more documentation.
android::base::Result<ApexFile> InstallPackage(const std::string& package_path);
// Exposed for testing.
android::base::Result<int> AddBlockApex(ApexFileRepository& instance);
bool IsActiveApexChanged(const ApexFile& apex);
// Shouldn't be used outside of apexd_test.cpp
std::set<std::string>& GetChangedActiveApexesForTesting();
} // namespace apex
} // namespace android
#endif // ANDROID_APEXD_APEXD_H_

21
apex/apexd/apexd.rc Normal file
View File

@ -0,0 +1,21 @@
service apexd /system/bin/apexd
interface aidl apexservice
class core
user root
group system
oneshot
disabled # does not start with the core class
reboot_on_failure reboot,apexd-failed
service apexd-bootstrap /system/bin/apexd --bootstrap
user root
group system
oneshot
disabled
reboot_on_failure reboot,bootloader,bootstrap-apexd-failed
service apexd-snapshotde /system/bin/apexd --snapshotde
user root
group system
oneshot
disabled

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_APEXD_APEXD_CHECKPOINT_H_
#define ANDROID_APEXD_APEXD_CHECKPOINT_H_
#include <string>
#include <android-base/result.h>
namespace android {
namespace apex {
class CheckpointInterface {
public:
virtual ~CheckpointInterface() {}
virtual android::base::Result<bool> SupportsFsCheckpoints() = 0;
virtual android::base::Result<bool> NeedsCheckpoint() = 0;
virtual android::base::Result<bool> NeedsRollback() = 0;
virtual android::base::Result<void> StartCheckpoint(int32_t num_retries) = 0;
virtual android::base::Result<void> AbortChanges(const std::string& msg,
bool retry) = 0;
};
} // namespace apex
} // namespace android
#endif // ANDROID_APEXD_APEXD_CHECKPOINT_H_

View File

@ -0,0 +1,113 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "apexd"
#include "apexd_checkpoint_vold.h"
#include <android-base/logging.h>
#include <android/os/IVold.h>
#include <binder/IServiceManager.h>
using android::sp;
using android::base::Error;
using android::base::Result;
using android::os::IVold;
namespace android {
namespace apex {
Result<VoldCheckpointInterface> VoldCheckpointInterface::Create() {
auto vold_service =
defaultServiceManager()->getService(android::String16("vold"));
if (vold_service != nullptr) {
return VoldCheckpointInterface(
android::interface_cast<android::os::IVold>(vold_service));
}
return Errorf("Failed to retrieve vold service.");
}
VoldCheckpointInterface::VoldCheckpointInterface(sp<IVold>&& vold_service) {
vold_service_ = vold_service;
supports_fs_checkpoints_ = false;
android::binder::Status status =
vold_service_->supportsCheckpoint(&supports_fs_checkpoints_);
if (!status.isOk()) {
LOG(ERROR) << "Failed to check if filesystem checkpoints are supported: "
<< status.toString8().c_str();
}
}
VoldCheckpointInterface::VoldCheckpointInterface(
VoldCheckpointInterface&& other) noexcept {
vold_service_ = std::move(other.vold_service_);
supports_fs_checkpoints_ = other.supports_fs_checkpoints_;
}
VoldCheckpointInterface::~VoldCheckpointInterface() {
// Just here to be able to forward-declare IVold.
}
Result<bool> VoldCheckpointInterface::SupportsFsCheckpoints() {
return supports_fs_checkpoints_;
}
Result<bool> VoldCheckpointInterface::NeedsCheckpoint() {
if (supports_fs_checkpoints_) {
bool needs_checkpoint = false;
android::binder::Status status =
vold_service_->needsCheckpoint(&needs_checkpoint);
if (!status.isOk()) {
return Error() << status.toString8().c_str();
}
return needs_checkpoint;
}
return false;
}
Result<bool> VoldCheckpointInterface::NeedsRollback() {
if (supports_fs_checkpoints_) {
bool needs_rollback = false;
android::binder::Status status =
vold_service_->needsRollback(&needs_rollback);
if (!status.isOk()) {
return Error() << status.toString8().c_str();
}
return needs_rollback;
}
return false;
}
Result<void> VoldCheckpointInterface::StartCheckpoint(int32_t num_retries) {
if (supports_fs_checkpoints_) {
android::binder::Status status =
vold_service_->startCheckpoint(num_retries);
if (!status.isOk()) {
return Error() << status.toString8().c_str();
}
return {};
}
return Errorf("Device does not support filesystem checkpointing");
}
Result<void> VoldCheckpointInterface::AbortChanges(const std::string& msg,
bool retry) {
vold_service_->abortChanges(msg, retry);
return {};
}
} // namespace apex
} // namespace android

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_APEXD_APEXD_CHECKPOINT_VOLD_H_
#define ANDROID_APEXD_APEXD_CHECKPOINT_VOLD_H_
#include <string>
#include <utils/StrongPointer.h>
#include "apexd_checkpoint.h"
#include <android-base/result.h>
namespace android {
namespace os {
class IVold;
}
namespace apex {
class VoldCheckpointInterface : public CheckpointInterface {
public:
~VoldCheckpointInterface();
android::base::Result<bool> SupportsFsCheckpoints() override;
android::base::Result<bool> NeedsCheckpoint() override;
android::base::Result<bool> NeedsRollback() override;
android::base::Result<void> StartCheckpoint(int32_t retry) override;
android::base::Result<void> AbortChanges(const std::string& msg,
bool num_retries) override;
static android::base::Result<VoldCheckpointInterface> Create();
VoldCheckpointInterface(VoldCheckpointInterface&& other) noexcept;
private:
explicit VoldCheckpointInterface(sp<os::IVold>&& vold_service);
sp<os::IVold> vold_service_;
bool supports_fs_checkpoints_;
};
} // namespace apex
} // namespace android
#endif // ANDROID_APEXD_APEXD_CHECKPOINT_VOLD_H_

View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <chrono>
#include <thread>
#include "apexd_lifecycle.h"
#include <android-base/logging.h>
#include <android-base/properties.h>
#include "apexd_utils.h"
#define LOG_TAG "apexd"
using android::base::GetProperty;
using android::base::Result;
using android::base::WaitForProperty;
namespace android {
namespace apex {
bool ApexdLifecycle::IsBooting() {
auto status = GetProperty(kApexStatusSysprop, "");
return status != kApexStatusReady && status != kApexStatusActivated;
}
void ApexdLifecycle::WaitForBootStatus(
Result<void> (&revert_fn)(const std::string&, const std::string&)) {
while (!boot_completed_) {
// Check for change in either crashing property or sys.boot_completed
// Wait for updatable_crashing property change for most of the time
// (arbitrary 10s), briefly check if boot has completed successfully,
// if not continue waiting for updatable_crashing.
// We use this strategy so that we can quickly detect if an updatable
// process is crashing.
if (WaitForProperty("sys.init.updatable_crashing", "1",
std::chrono::seconds(10))) {
auto name = GetProperty("sys.init.updatable_crashing_process_name", "");
LOG(ERROR) << "Native process '" << (name.empty() ? "[unknown]" : name)
<< "' is crashing. Attempting a revert";
auto result = revert_fn(name, "");
if (!result.ok()) {
LOG(ERROR) << "Revert failed : " << result.error();
return WaitForBootStatus();
} else {
// This should never be reached, since revert_fn should've rebooted
// the device.
LOG(FATAL) << "Active sessions were reverted, but reboot wasn't "
"triggered.";
}
}
}
}
void ApexdLifecycle::WaitForBootStatus() {
while (!boot_completed_) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
void ApexdLifecycle::MarkBootCompleted() { boot_completed_ = true; }
} // namespace apex
} // namespace android

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_APEXD_APEXD_LIFECYCLE_H_
#define ANDROID_APEXD_APEXD_LIFECYCLE_H_
#include <android-base/result.h>
namespace android {
namespace apex {
class ApexdLifecycle {
private:
ApexdLifecycle(){};
std::atomic<bool> boot_completed_;
// Non-copyable && non-moveable.
ApexdLifecycle(const ApexdLifecycle&) = delete;
ApexdLifecycle& operator=(const ApexdLifecycle&) = delete;
ApexdLifecycle& operator=(ApexdLifecycle&&) = delete;
void WaitForBootStatus();
public:
static ApexdLifecycle& GetInstance() {
static ApexdLifecycle instance;
return instance;
}
bool IsBooting();
void MarkBootCompleted();
void WaitForBootStatus(android::base::Result<void> (&rollback_fn)(
const std::string&, const std::string&));
};
} // namespace apex
} // namespace android
#endif // ANDROID_APEXD_APEXD_LIFECYCLE_H

566
apex/apexd/apexd_loop.cpp Normal file
View File

@ -0,0 +1,566 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "apexd"
#define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER
#include "apexd_loop.h"
#include <array>
#include <filesystem>
#include <mutex>
#include <string_view>
#include <dirent.h>
#include <fcntl.h>
#include <libdm/dm.h>
#include <linux/fs.h>
#include <linux/loop.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <utils/Trace.h>
#include "apexd_utils.h"
#include "string_log.h"
using android::base::Basename;
using android::base::ErrnoError;
using android::base::Error;
using android::base::GetBoolProperty;
using android::base::ParseUint;
using android::base::ReadFileToString;
using android::base::Result;
using android::base::StartsWith;
using android::base::StringPrintf;
using android::base::unique_fd;
using android::dm::DeviceMapper;
namespace android {
namespace apex {
namespace loop {
static constexpr const char* kApexLoopIdPrefix = "apex:";
// 128 kB read-ahead, which we currently use for /system as well
static constexpr const char* kReadAheadKb = "128";
// TODO(b/122059364): Even though the kernel has created the loop
// device, we still depend on ueventd to run to actually create the
// device node in userspace. To solve this properly we should listen on
// the netlink socket for uevents, or use inotify. For now, this will
// have to do.
static constexpr size_t kLoopDeviceRetryAttempts = 3u;
void LoopbackDeviceUniqueFd::MaybeCloseBad() {
if (device_fd.get() != -1) {
// Disassociate any files.
if (ioctl(device_fd.get(), LOOP_CLR_FD) == -1) {
PLOG(ERROR) << "Unable to clear fd for loopback device";
}
}
}
Result<void> ConfigureScheduler(const std::string& device_path) {
if (!StartsWith(device_path, "/dev/")) {
return Error() << "Invalid argument " << device_path;
}
const std::string device_name = Basename(device_path);
const std::string sysfs_path =
StringPrintf("/sys/block/%s/queue/scheduler", device_name.c_str());
unique_fd sysfs_fd(open(sysfs_path.c_str(), O_RDWR | O_CLOEXEC));
if (sysfs_fd.get() == -1) {
return ErrnoError() << "Failed to open " << sysfs_path;
}
// Kernels before v4.1 only support 'noop'. Kernels [v4.1, v5.0) support
// 'noop' and 'none'. Kernels v5.0 and later only support 'none'.
static constexpr const std::array<std::string_view, 2> kNoScheduler = {
"none", "noop"};
int ret = 0;
for (const std::string_view& scheduler : kNoScheduler) {
ret = write(sysfs_fd.get(), scheduler.data(), scheduler.size());
if (ret > 0) {
break;
}
}
if (ret <= 0) {
return ErrnoError() << "Failed to write to " << sysfs_path;
}
return {};
}
// Return the parent device of a partition. Converts e.g. "sda26" into "sda".
static Result<std::string> PartitionParent(const std::string& blockdev) {
if (blockdev.find('/') != std::string::npos) {
return Error() << "Invalid argument " << blockdev;
}
std::error_code ec;
for (const auto& entry :
std::filesystem::directory_iterator("/sys/class/block", ec)) {
const std::string path = entry.path().string();
if (std::filesystem::exists(
StringPrintf("%s/%s", path.c_str(), blockdev.c_str()))) {
return Basename(path);
}
}
return blockdev;
}
// Convert a major:minor pair into a block device name.
static std::string BlockdevName(dev_t dev) {
std::error_code ec;
for (const auto& entry :
std::filesystem::directory_iterator("/dev/block", ec)) {
struct stat statbuf;
if (stat(entry.path().string().c_str(), &statbuf) < 0) {
continue;
}
if (dev == statbuf.st_rdev) {
return Basename(entry.path().string());
}
}
return {};
}
// For file `file_path`, retrieve the block device backing the filesystem on
// which the file exists and return the queue depth of the block device. The
// loop in this function may e.g. traverse the following hierarchy:
// /dev/block/dm-9 (system-verity; dm-verity)
// -> /dev/block/dm-1 (system_b; dm-linear)
// -> /dev/sda26
Result<uint32_t> BlockDeviceQueueDepth(const std::string& file_path) {
struct stat statbuf;
int res = stat(file_path.c_str(), &statbuf);
if (res < 0) {
return ErrnoErrorf("stat({})", file_path.c_str());
}
std::string blockdev = "/dev/block/" + BlockdevName(statbuf.st_dev);
LOG(VERBOSE) << file_path << " -> " << blockdev;
if (blockdev.empty()) {
return Errorf("Failed to convert {}:{} (path {})", major(statbuf.st_dev),
minor(statbuf.st_dev), file_path.c_str());
}
auto& dm = DeviceMapper::Instance();
for (;;) {
std::optional<std::string> child = dm.GetParentBlockDeviceByPath(blockdev);
if (!child) {
break;
}
LOG(VERBOSE) << blockdev << " -> " << *child;
blockdev = *child;
}
std::optional<std::string> maybe_blockdev =
android::dm::ExtractBlockDeviceName(blockdev);
if (!maybe_blockdev) {
return Error() << "Failed to remove /dev/block/ prefix from " << blockdev;
}
Result<std::string> maybe_parent = PartitionParent(*maybe_blockdev);
if (!maybe_parent.ok()) {
return Error() << "Failed to determine parent of " << *maybe_blockdev;
}
blockdev = *maybe_parent;
LOG(VERBOSE) << "Partition parent: " << blockdev;
const std::string nr_tags_path =
StringPrintf("/sys/class/block/%s/mq/0/nr_tags", blockdev.c_str());
std::string nr_tags;
if (!ReadFileToString(nr_tags_path, &nr_tags)) {
return Error() << "Failed to read " << nr_tags_path;
}
nr_tags = android::base::Trim(nr_tags);
LOG(VERBOSE) << file_path << " is backed by /dev/" << blockdev
<< " and that block device supports queue depth " << nr_tags;
return strtol(nr_tags.c_str(), NULL, 0);
}
// Set 'nr_requests' of `loop_device_path` equal to the queue depth of
// the block device backing `file_path`.
Result<void> ConfigureQueueDepth(const std::string& loop_device_path,
const std::string& file_path) {
if (!StartsWith(loop_device_path, "/dev/")) {
return Error() << "Invalid argument " << loop_device_path;
}
const std::string loop_device_name = Basename(loop_device_path);
const std::string sysfs_path =
StringPrintf("/sys/block/%s/queue/nr_requests", loop_device_name.c_str());
std::string cur_nr_requests_str;
if (!ReadFileToString(sysfs_path, &cur_nr_requests_str)) {
return Error() << "Failed to read " << sysfs_path;
}
cur_nr_requests_str = android::base::Trim(cur_nr_requests_str);
uint32_t cur_nr_requests = 0;
if (!ParseUint(cur_nr_requests_str.c_str(), &cur_nr_requests)) {
return Error() << "Failed to parse " << cur_nr_requests_str;
}
unique_fd sysfs_fd(open(sysfs_path.c_str(), O_RDWR | O_CLOEXEC));
if (sysfs_fd.get() == -1) {
return ErrnoErrorf("Failed to open {}", sysfs_path);
}
const auto qd = BlockDeviceQueueDepth(file_path);
if (!qd.ok()) {
return qd.error();
}
if (*qd == cur_nr_requests) {
return {};
}
// Only report write failures if reducing the queue depth. Attempts to
// increase the queue depth are rejected by the kernel if no I/O scheduler
// is associated with the request queue.
if (!WriteStringToFd(StringPrintf("%u", *qd), sysfs_fd) &&
*qd < cur_nr_requests) {
return ErrnoErrorf("Failed to write {} to {}", *qd, sysfs_path);
}
return {};
}
Result<void> ConfigureReadAhead(const std::string& device_path) {
CHECK(StartsWith(device_path, "/dev/"));
std::string device_name = Basename(device_path);
std::string sysfs_device =
StringPrintf("/sys/block/%s/queue/read_ahead_kb", device_name.c_str());
unique_fd sysfs_fd(open(sysfs_device.c_str(), O_RDWR | O_CLOEXEC));
if (sysfs_fd.get() == -1) {
return ErrnoError() << "Failed to open " << sysfs_device;
}
int ret = TEMP_FAILURE_RETRY(
write(sysfs_fd.get(), kReadAheadKb, strlen(kReadAheadKb) + 1));
if (ret < 0) {
return ErrnoError() << "Failed to write to " << sysfs_device;
}
return {};
}
Result<void> PreAllocateLoopDevices(size_t num) {
Result<void> loop_ready = WaitForFile("/dev/loop-control", 20s);
if (!loop_ready.ok()) {
return loop_ready;
}
unique_fd ctl_fd(
TEMP_FAILURE_RETRY(open("/dev/loop-control", O_RDWR | O_CLOEXEC)));
if (ctl_fd.get() == -1) {
return ErrnoError() << "Failed to open loop-control";
}
bool found = false;
size_t start_id = 0;
constexpr const char* kLoopPrefix = "loop";
WalkDir("/dev/block", [&](const std::filesystem::directory_entry& entry) {
std::string devname = entry.path().filename().string();
if (StartsWith(devname, kLoopPrefix)) {
size_t id;
auto parse_ok = ParseUint(
devname.substr(std::char_traits<char>::length(kLoopPrefix)), &id);
if (parse_ok && id > start_id) {
start_id = id;
found = true;
}
}
});
if (found) ++start_id;
// Assumption: loop device ID [0..num) is valid.
// This is because pre-allocation happens during bootstrap.
// Anyway Kernel pre-allocated loop devices
// as many as CONFIG_BLK_DEV_LOOP_MIN_COUNT,
// Within the amount of kernel-pre-allocation,
// LOOP_CTL_ADD will fail with EEXIST
for (size_t id = start_id; id < num + start_id; ++id) {
int ret = ioctl(ctl_fd.get(), LOOP_CTL_ADD, id);
if (ret < 0 && errno != EEXIST) {
return ErrnoError() << "Failed LOOP_CTL_ADD";
}
}
// Don't wait until the dev nodes are actually created, which
// will delay the boot. By simply returing here, the creation of the dev
// nodes will be done in parallel with other boot processes, and we
// just optimistally hope that they are all created when we actually
// access them for activating APEXes. If the dev nodes are not ready
// even then, we wait 50ms and warning message will be printed (see below
// CreateLoopDevice()).
LOG(INFO) << "Pre-allocated " << num << " loopback devices";
return {};
}
Result<void> ConfigureLoopDevice(const int device_fd, const std::string& target,
const uint32_t image_offset,
const size_t image_size) {
static bool use_loop_configure;
static std::once_flag once_flag;
std::call_once(once_flag, [&]() {
// LOOP_CONFIGURE is a new ioctl in Linux 5.8 (and backported in Android
// common) that allows atomically configuring a loop device. It is a lot
// faster than the traditional LOOP_SET_FD/LOOP_SET_STATUS64 combo, but
// it may not be available on updating devices, so try once before
// deciding.
struct loop_config config;
memset(&config, 0, sizeof(config));
config.fd = -1;
if (ioctl(device_fd, LOOP_CONFIGURE, &config) == -1 && errno == EBADF) {
// If the IOCTL exists, it will fail with EBADF for the -1 fd
use_loop_configure = true;
}
});
/*
* Using O_DIRECT will tell the kernel that we want to use Direct I/O
* on the underlying file, which we want to do to avoid double caching.
* Note that Direct I/O won't be enabled immediately, because the block
* size of the underlying block device may not match the default loop
* device block size (512); when we call LOOP_SET_BLOCK_SIZE below, the
* kernel driver will automatically enable Direct I/O when it sees that
* condition is now met.
*/
bool use_buffered_io = false;
unique_fd target_fd(open(target.c_str(), O_RDONLY | O_CLOEXEC | O_DIRECT));
if (target_fd.get() == -1) {
struct statfs stbuf;
int saved_errno = errno;
// let's give another try with buffered I/O for EROFS and squashfs
if (statfs(target.c_str(), &stbuf) != 0 ||
(stbuf.f_type != EROFS_SUPER_MAGIC_V1 &&
stbuf.f_type != SQUASHFS_MAGIC &&
stbuf.f_type != OVERLAYFS_SUPER_MAGIC)) {
return Error(saved_errno) << "Failed to open " << target;
}
LOG(WARNING) << "Fallback to buffered I/O for " << target;
use_buffered_io = true;
target_fd.reset(open(target.c_str(), O_RDONLY | O_CLOEXEC));
if (target_fd.get() == -1) {
return ErrnoError() << "Failed to open " << target;
}
}
struct loop_info64 li;
memset(&li, 0, sizeof(li));
strlcpy((char*)li.lo_crypt_name, kApexLoopIdPrefix, LO_NAME_SIZE);
li.lo_offset = image_offset;
li.lo_sizelimit = image_size;
// Automatically free loop device on last close.
li.lo_flags |= LO_FLAGS_AUTOCLEAR;
if (use_loop_configure) {
struct loop_config config;
memset(&config, 0, sizeof(config));
config.fd = target_fd.get();
config.info = li;
config.block_size = 4096;
if (!use_buffered_io) {
li.lo_flags |= LO_FLAGS_DIRECT_IO;
}
if (ioctl(device_fd, LOOP_CONFIGURE, &config) == -1) {
return ErrnoError() << "Failed to LOOP_CONFIGURE";
}
return {};
} else {
if (ioctl(device_fd, LOOP_SET_FD, target_fd.get()) == -1) {
return ErrnoError() << "Failed to LOOP_SET_FD";
}
if (ioctl(device_fd, LOOP_SET_STATUS64, &li) == -1) {
return ErrnoError() << "Failed to LOOP_SET_STATUS64";
}
if (ioctl(device_fd, BLKFLSBUF, 0) == -1) {
// This works around a kernel bug where the following happens.
// 1) The device runs with a value of loop.max_part > 0
// 2) As part of LOOP_SET_FD above, we do a partition scan, which loads
// the first 2 pages of the underlying file into the buffer cache
// 3) When we then change the offset with LOOP_SET_STATUS64, those pages
// are not invalidated from the cache.
// 4) When we try to mount an ext4 filesystem on the loop device, the ext4
// code will try to find a superblock by reading 4k at offset 0; but,
// because we still have the old pages at offset 0 lying in the cache,
// those pages will be returned directly. However, those pages contain
// the data at offset 0 in the underlying file, not at the offset that
// we configured
// 5) the ext4 driver fails to find a superblock in the (wrong) data, and
// fails to mount the filesystem.
//
// To work around this, explicitly flush the block device, which will
// flush the buffer cache and make sure we actually read the data at the
// correct offset.
return ErrnoError() << "Failed to flush buffers on the loop device";
}
// Direct-IO requires the loop device to have the same block size as the
// underlying filesystem.
if (ioctl(device_fd, LOOP_SET_BLOCK_SIZE, 4096) == -1) {
PLOG(WARNING) << "Failed to LOOP_SET_BLOCK_SIZE";
}
}
return {};
}
Result<LoopbackDeviceUniqueFd> WaitForDevice(int num) {
std::string opened_device;
const std::vector<std::string> candidate_devices = {
StringPrintf("/dev/block/loop%d", num),
StringPrintf("/dev/loop%d", num),
};
// apexd-bootstrap runs in parallel with ueventd to optimize boot time. In
// rare cases apexd would try attempt to mount an apex before ueventd created
// a loop device for it. To work around this we keep polling for loop device
// to be created until ueventd's cold boot sequence is done.
// See comment on kLoopDeviceRetryAttempts.
bool cold_boot_done = GetBoolProperty("ro.cold_boot_done", false);
for (size_t i = 0; i != kLoopDeviceRetryAttempts; ++i) {
if (!cold_boot_done) {
cold_boot_done = GetBoolProperty("ro.cold_boot_done", false);
}
for (const auto& device : candidate_devices) {
unique_fd sysfs_fd(open(device.c_str(), O_RDWR | O_CLOEXEC));
if (sysfs_fd.get() != -1) {
return LoopbackDeviceUniqueFd(std::move(sysfs_fd), device);
}
}
PLOG(WARNING) << "Loopback device " << num << " not ready. Waiting 50ms...";
usleep(50000);
if (!cold_boot_done) {
// ueventd hasn't finished cold boot yet, keep trying.
i = 0;
}
}
return Error() << "Faled to open loopback device " << num;
}
Result<LoopbackDeviceUniqueFd> CreateLoopDevice(const std::string& target,
uint32_t image_offset,
size_t image_size) {
ATRACE_NAME("CreateLoopDevice");
unique_fd ctl_fd(open("/dev/loop-control", O_RDWR | O_CLOEXEC));
if (ctl_fd.get() == -1) {
return ErrnoError() << "Failed to open loop-control";
}
static std::mutex mtx;
std::lock_guard lock(mtx);
int num = ioctl(ctl_fd.get(), LOOP_CTL_GET_FREE);
if (num == -1) {
return ErrnoError() << "Failed LOOP_CTL_GET_FREE";
}
Result<LoopbackDeviceUniqueFd> loop_device = WaitForDevice(num);
if (!loop_device.ok()) {
return loop_device.error();
}
CHECK_NE(loop_device->device_fd.get(), -1);
Result<void> configure_status = ConfigureLoopDevice(
loop_device->device_fd.get(), target, image_offset, image_size);
if (!configure_status.ok()) {
return configure_status.error();
}
return loop_device;
}
Result<LoopbackDeviceUniqueFd> CreateAndConfigureLoopDevice(
const std::string& target, uint32_t image_offset, size_t image_size) {
ATRACE_NAME("CreateAndConfigureLoopDevice");
// Do minimal amount of work while holding a mutex. We need it because
// acquiring + configuring a loop device is not atomic. Ideally we should
// pre-acquire all the loop devices in advance, so that when we run APEX
// activation in-parallel, we can do it without holding any lock.
// Unfortunately, this will require some refactoring of how we manage loop
// devices, and probably some new loop-control ioctls, so for the time being
// we just limit the scope that requires locking.
auto loop_device = CreateLoopDevice(target, image_offset, image_size);
if (!loop_device.ok()) {
return loop_device.error();
}
// We skip confiruing scheduler and queue depth for automotive products.
// See: b/241473698.
#ifndef DISABLE_LOOP_IO_CONFIG
Result<void> sched_status = ConfigureScheduler(loop_device->name);
if (!sched_status.ok()) {
LOG(WARNING) << "Configuring I/O scheduler failed: "
<< sched_status.error();
}
Result<void> qd_status = ConfigureQueueDepth(loop_device->name, target);
if (!qd_status.ok()) {
LOG(WARNING) << qd_status.error();
}
#endif
Result<void> read_ahead_status = ConfigureReadAhead(loop_device->name);
if (!read_ahead_status.ok()) {
return read_ahead_status.error();
}
return loop_device;
}
void DestroyLoopDevice(const std::string& path, const DestroyLoopFn& extra) {
unique_fd fd(open(path.c_str(), O_RDWR | O_CLOEXEC));
if (fd.get() == -1) {
if (errno != ENOENT) {
PLOG(WARNING) << "Failed to open " << path;
}
return;
}
struct loop_info64 li;
if (ioctl(fd.get(), LOOP_GET_STATUS64, &li) < 0) {
if (errno != ENXIO) {
PLOG(WARNING) << "Failed to LOOP_GET_STATUS64 " << path;
}
return;
}
auto id = std::string((char*)li.lo_crypt_name);
if (StartsWith(id, kApexLoopIdPrefix)) {
extra(path, id);
if (ioctl(fd.get(), LOOP_CLR_FD, 0) < 0) {
PLOG(WARNING) << "Failed to LOOP_CLR_FD " << path;
}
}
}
} // namespace loop
} // namespace apex
} // namespace android

82
apex/apexd/apexd_loop.h Normal file
View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_APEXD_APEXD_LOOP_H_
#define ANDROID_APEXD_APEXD_LOOP_H_
#include <android-base/result.h>
#include <android-base/unique_fd.h>
#include <functional>
#include <string>
namespace android {
namespace apex {
namespace loop {
using android::base::unique_fd;
struct LoopbackDeviceUniqueFd {
unique_fd device_fd;
std::string name;
LoopbackDeviceUniqueFd() {}
LoopbackDeviceUniqueFd(unique_fd&& fd, const std::string& name)
: device_fd(std::move(fd)), name(name) {}
LoopbackDeviceUniqueFd(LoopbackDeviceUniqueFd&& fd) noexcept
: device_fd(std::move(fd.device_fd)), name(std::move(fd.name)) {}
LoopbackDeviceUniqueFd& operator=(LoopbackDeviceUniqueFd&& other) noexcept {
MaybeCloseBad();
device_fd = std::move(other.device_fd);
name = std::move(other.name);
return *this;
}
~LoopbackDeviceUniqueFd() { MaybeCloseBad(); }
void MaybeCloseBad();
void CloseGood() { device_fd.reset(-1); }
int Get() { return device_fd.get(); }
};
// Exposed only for testing
android::base::Result<uint32_t> BlockDeviceQueueDepth(
const std::string& file_path);
android::base::Result<LoopbackDeviceUniqueFd> WaitForDevice(int num);
android::base::Result<void> ConfigureQueueDepth(
const std::string& loop_device_path, const std::string& file_path);
android::base::Result<void> ConfigureReadAhead(const std::string& device_path);
android::base::Result<void> PreAllocateLoopDevices(size_t num);
android::base::Result<LoopbackDeviceUniqueFd> CreateAndConfigureLoopDevice(
const std::string& target, uint32_t image_offset, size_t image_size);
using DestroyLoopFn =
std::function<void(const std::string&, const std::string&)>;
void DestroyLoopDevice(const std::string& path, const DestroyLoopFn& extra);
} // namespace loop
} // namespace apex
} // namespace android
#endif // ANDROID_APEXD_APEXD_LOOP_H_

Some files were not shown because too many files have changed in this diff Show More