Loading services/tests/PackageManagerServiceTests/host/Android.bp +4 −0 Original line number Diff line number Diff line Loading @@ -34,8 +34,11 @@ java_test_host { ], static_libs: [ "ApexInstallHelper", "android.security.flags-aconfig-java-host", "cts-host-utils", "flag-junit-host", "frameworks-base-hostutils", "kotlin-test", "PackageManagerServiceHostTestsIntentVerifyUtils", "block_device_writer_jar", ], Loading @@ -59,6 +62,7 @@ java_test_host { ":PackageManagerTestAppUsesStaticLibrary", ":PackageManagerTestAppVersion1", ":PackageManagerTestAppVersion2", ":PackageManagerTestAppVersion2AltKey", ":PackageManagerTestAppVersion3", ":PackageManagerTestAppVersion3Invalid", ":PackageManagerTestAppVersion4", Loading services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt 0 → 100644 +178 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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 com.android.server.pm.test import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.host.HostFlagsValueProvider import com.android.internal.util.test.SystemPreparer import com.android.tradefed.testtype.DeviceJUnit4ClassRunner import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test import com.google.common.truth.Truth.assertThat import java.io.File import java.io.RandomAccessFile import kotlin.test.assertNotNull import org.junit.After import org.junit.Before import org.junit.ClassRule import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith @RunWith(DeviceJUnit4ClassRunner::class) @RequiresFlagsEnabled(android.security.Flags.FLAG_EXTEND_VB_CHAIN_TO_UPDATED_APK) class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() { companion object { private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app" private const val VERSION_ONE = "PackageManagerTestAppVersion1.apk" private const val VERSION_TWO_ALT_KEY = "PackageManagerTestAppVersion2AltKey.apk" private const val VERSION_TWO_ALT_KEY_IDSIG = "PackageManagerTestAppVersion2AltKey.apk.idsig" private const val STRICT_SIGNATURE_CONFIG_PATH = "/system/etc/sysconfig/preinstalled-packages-strict-signature.xml" private const val TIMESTAMP_REFERENCE_FILE_PATH = "/data/local/tmp/timestamp.ref" @get:ClassRule val deviceRebootRule = SystemPreparer.TestRuleDelegate(true) } private val tempFolder = TemporaryFolder() private val preparer: SystemPreparer = SystemPreparer( tempFolder, SystemPreparer.RebootStrategy.FULL, deviceRebootRule ) { this.device } private val productPath = HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.PRODUCT) private lateinit var originalConfigFile: File @Rule @JvmField val checkFlagsRule = HostFlagsValueProvider.createCheckFlagsRule({ getDevice() }) @Rule @JvmField val rules = RuleChain.outerRule(tempFolder).around(preparer)!! @Before @After fun removeApk() { device.uninstallPackage(TEST_PKG_NAME) } @Before fun backupAndModifySystemFiles() { // Backup device.pullFile(STRICT_SIGNATURE_CONFIG_PATH).also { assertNotNull(it) originalConfigFile = it } // Modify to allowlist the target package on device for testing the feature val xml = tempFolder.newFile().apply { val newConfigText = originalConfigFile .readText() .replace( "</config>", "<require-strict-signature package=\"${TEST_PKG_NAME}\"/></config>" ) writeText(newConfigText) } device.remountSystemWritable() device.pushFile(xml, STRICT_SIGNATURE_CONFIG_PATH) } @After fun restoreSystemFiles() { device.remountSystemWritable() device.pushFile(originalConfigFile, STRICT_SIGNATURE_CONFIG_PATH) // Files pushed via a SystemPreparer are deleted automatically. } @Test fun detectApkAndXmlTamperingAtBoot() { // Set up the scenario where both APK and packages.xml are tampered by the attacker. // This is done by booting with the "bad" APK in a system partition, re-installing it to // /data. Then, replace the APK in the system partition with a "good" one. preparer.pushResourceFile(VERSION_TWO_ALT_KEY, productPath.toString()) .reboot() // Install the "bad" APK to /data. This will also update package manager's XML records. val versionTwoFile = HostUtils.copyResourceToHostFile( VERSION_TWO_ALT_KEY, tempFolder.newFile() ) assertThat(device.installPackage(versionTwoFile, true)).isNull() assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}")) .doesNotContain(productPath.toString()) // "Restore" the system partition is to a good state with correct APK. preparer.deleteFile(productPath.toString()) .pushResourceFile(VERSION_ONE, productPath.toString()) // Verify that upon the next boot, the system detect the problem and remove the problematic // APK in the /data. preparer.reboot() assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}")) .contains(productPath.toString()) } @Test fun detectApkTamperingAtBoot() { // Set up the scenario where APK is tampered but not the v4 signature. First, inject a // good APK as a system app. preparer.pushResourceFile(VERSION_TWO_ALT_KEY, productPath.toString()) .reboot() // Re-install the target APK to /data, with the corresponding .idsig from build time. val versionTwoFile = HostUtils.copyResourceToHostFile( VERSION_TWO_ALT_KEY, tempFolder.newFile() ) assertThat(device.installPackage(versionTwoFile, true)).isNull() val baseApkPath = device.executeShellCommand("pm path ${TEST_PKG_NAME}") .lineSequence() .first() .replace("package:", "") assertThat(baseApkPath).doesNotContain(productPath.toString()) preparer.pushResourceFile(VERSION_TWO_ALT_KEY_IDSIG, baseApkPath.toString() + ".idsig") // Replace the APK in /data with a tampered version. Restore fs-verity and attributes. RandomAccessFile(versionTwoFile, "rw").use { // Skip the zip local file header to keep it valid. Tamper with the file name field and // beyond, just so that it won't simply fail. it.seek(30) it.writeBytes("tamper") } device.executeShellCommand("touch ${TIMESTAMP_REFERENCE_FILE_PATH} -r $baseApkPath") preparer.pushFile(versionTwoFile, baseApkPath) device.executeShellCommand( "cd ${baseApkPath.replace("base.apk", "")}" + "&& chown system:system base.apk " + "&& /data/local/tmp/fsverity_multilib enable base.apk" + "&& touch base.apk -r ${TIMESTAMP_REFERENCE_FILE_PATH}" ) // Verify that upon the next boot, the system detect the problem and remove the problematic // APK in the /data. preparer.reboot() assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}")) .contains(productPath.toString()) } } services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp +10 −0 Original line number Diff line number Diff line Loading @@ -66,3 +66,13 @@ android_test_helper_app { "src/**/*.kt", ], } android_test_helper_app { name: "PackageManagerTestAppVersion2AltKey", manifest: "AndroidManifestVersion2.xml", srcs: [ "src/**/*.kt", ], certificate: ":FrameworksServicesTests_keyset_A_cert", v4_signature: true, } Loading
services/tests/PackageManagerServiceTests/host/Android.bp +4 −0 Original line number Diff line number Diff line Loading @@ -34,8 +34,11 @@ java_test_host { ], static_libs: [ "ApexInstallHelper", "android.security.flags-aconfig-java-host", "cts-host-utils", "flag-junit-host", "frameworks-base-hostutils", "kotlin-test", "PackageManagerServiceHostTestsIntentVerifyUtils", "block_device_writer_jar", ], Loading @@ -59,6 +62,7 @@ java_test_host { ":PackageManagerTestAppUsesStaticLibrary", ":PackageManagerTestAppVersion1", ":PackageManagerTestAppVersion2", ":PackageManagerTestAppVersion2AltKey", ":PackageManagerTestAppVersion3", ":PackageManagerTestAppVersion3Invalid", ":PackageManagerTestAppVersion4", Loading
services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt 0 → 100644 +178 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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 com.android.server.pm.test import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.host.HostFlagsValueProvider import com.android.internal.util.test.SystemPreparer import com.android.tradefed.testtype.DeviceJUnit4ClassRunner import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test import com.google.common.truth.Truth.assertThat import java.io.File import java.io.RandomAccessFile import kotlin.test.assertNotNull import org.junit.After import org.junit.Before import org.junit.ClassRule import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith @RunWith(DeviceJUnit4ClassRunner::class) @RequiresFlagsEnabled(android.security.Flags.FLAG_EXTEND_VB_CHAIN_TO_UPDATED_APK) class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() { companion object { private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app" private const val VERSION_ONE = "PackageManagerTestAppVersion1.apk" private const val VERSION_TWO_ALT_KEY = "PackageManagerTestAppVersion2AltKey.apk" private const val VERSION_TWO_ALT_KEY_IDSIG = "PackageManagerTestAppVersion2AltKey.apk.idsig" private const val STRICT_SIGNATURE_CONFIG_PATH = "/system/etc/sysconfig/preinstalled-packages-strict-signature.xml" private const val TIMESTAMP_REFERENCE_FILE_PATH = "/data/local/tmp/timestamp.ref" @get:ClassRule val deviceRebootRule = SystemPreparer.TestRuleDelegate(true) } private val tempFolder = TemporaryFolder() private val preparer: SystemPreparer = SystemPreparer( tempFolder, SystemPreparer.RebootStrategy.FULL, deviceRebootRule ) { this.device } private val productPath = HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.PRODUCT) private lateinit var originalConfigFile: File @Rule @JvmField val checkFlagsRule = HostFlagsValueProvider.createCheckFlagsRule({ getDevice() }) @Rule @JvmField val rules = RuleChain.outerRule(tempFolder).around(preparer)!! @Before @After fun removeApk() { device.uninstallPackage(TEST_PKG_NAME) } @Before fun backupAndModifySystemFiles() { // Backup device.pullFile(STRICT_SIGNATURE_CONFIG_PATH).also { assertNotNull(it) originalConfigFile = it } // Modify to allowlist the target package on device for testing the feature val xml = tempFolder.newFile().apply { val newConfigText = originalConfigFile .readText() .replace( "</config>", "<require-strict-signature package=\"${TEST_PKG_NAME}\"/></config>" ) writeText(newConfigText) } device.remountSystemWritable() device.pushFile(xml, STRICT_SIGNATURE_CONFIG_PATH) } @After fun restoreSystemFiles() { device.remountSystemWritable() device.pushFile(originalConfigFile, STRICT_SIGNATURE_CONFIG_PATH) // Files pushed via a SystemPreparer are deleted automatically. } @Test fun detectApkAndXmlTamperingAtBoot() { // Set up the scenario where both APK and packages.xml are tampered by the attacker. // This is done by booting with the "bad" APK in a system partition, re-installing it to // /data. Then, replace the APK in the system partition with a "good" one. preparer.pushResourceFile(VERSION_TWO_ALT_KEY, productPath.toString()) .reboot() // Install the "bad" APK to /data. This will also update package manager's XML records. val versionTwoFile = HostUtils.copyResourceToHostFile( VERSION_TWO_ALT_KEY, tempFolder.newFile() ) assertThat(device.installPackage(versionTwoFile, true)).isNull() assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}")) .doesNotContain(productPath.toString()) // "Restore" the system partition is to a good state with correct APK. preparer.deleteFile(productPath.toString()) .pushResourceFile(VERSION_ONE, productPath.toString()) // Verify that upon the next boot, the system detect the problem and remove the problematic // APK in the /data. preparer.reboot() assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}")) .contains(productPath.toString()) } @Test fun detectApkTamperingAtBoot() { // Set up the scenario where APK is tampered but not the v4 signature. First, inject a // good APK as a system app. preparer.pushResourceFile(VERSION_TWO_ALT_KEY, productPath.toString()) .reboot() // Re-install the target APK to /data, with the corresponding .idsig from build time. val versionTwoFile = HostUtils.copyResourceToHostFile( VERSION_TWO_ALT_KEY, tempFolder.newFile() ) assertThat(device.installPackage(versionTwoFile, true)).isNull() val baseApkPath = device.executeShellCommand("pm path ${TEST_PKG_NAME}") .lineSequence() .first() .replace("package:", "") assertThat(baseApkPath).doesNotContain(productPath.toString()) preparer.pushResourceFile(VERSION_TWO_ALT_KEY_IDSIG, baseApkPath.toString() + ".idsig") // Replace the APK in /data with a tampered version. Restore fs-verity and attributes. RandomAccessFile(versionTwoFile, "rw").use { // Skip the zip local file header to keep it valid. Tamper with the file name field and // beyond, just so that it won't simply fail. it.seek(30) it.writeBytes("tamper") } device.executeShellCommand("touch ${TIMESTAMP_REFERENCE_FILE_PATH} -r $baseApkPath") preparer.pushFile(versionTwoFile, baseApkPath) device.executeShellCommand( "cd ${baseApkPath.replace("base.apk", "")}" + "&& chown system:system base.apk " + "&& /data/local/tmp/fsverity_multilib enable base.apk" + "&& touch base.apk -r ${TIMESTAMP_REFERENCE_FILE_PATH}" ) // Verify that upon the next boot, the system detect the problem and remove the problematic // APK in the /data. preparer.reboot() assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}")) .contains(productPath.toString()) } }
services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp +10 −0 Original line number Diff line number Diff line Loading @@ -66,3 +66,13 @@ android_test_helper_app { "src/**/*.kt", ], } android_test_helper_app { name: "PackageManagerTestAppVersion2AltKey", manifest: "AndroidManifestVersion2.xml", srcs: [ "src/**/*.kt", ], certificate: ":FrameworksServicesTests_keyset_A_cert", v4_signature: true, }