Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 573c9bdc authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Integrity test to recover allowlisted system app tampering" into main

parents 73c8acd4 ce6174fe
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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",
    ],
@@ -59,6 +62,7 @@ java_test_host {
        ":PackageManagerTestAppUsesStaticLibrary",
        ":PackageManagerTestAppVersion1",
        ":PackageManagerTestAppVersion2",
        ":PackageManagerTestAppVersion2AltKey",
        ":PackageManagerTestAppVersion3",
        ":PackageManagerTestAppVersion3Invalid",
        ":PackageManagerTestAppVersion4",
+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())
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -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,
}