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

Commit ab183847 authored by Alex Buynytskyy's avatar Alex Buynytskyy Committed by Android (Google) Code Review
Browse files

Merge "Host side test corrupting fs-verity protected file."

parents adc9ba1a d4f24d4c
Loading
Loading
Loading
Loading
+10 −1
Original line number Original line Diff line number Diff line
@@ -23,7 +23,10 @@ package {


java_test_host {
java_test_host {
    name: "PackageManagerServiceHostTests",
    name: "PackageManagerServiceHostTests",
    srcs: ["src/**/*.kt"],
    srcs: [
        "src/**/*.java",
        "src/**/*.kt",
    ],
    libs: [
    libs: [
        "tradefed",
        "tradefed",
        "junit",
        "junit",
@@ -34,11 +37,17 @@ java_test_host {
        "cts-host-utils",
        "cts-host-utils",
        "frameworks-base-hostutils",
        "frameworks-base-hostutils",
        "PackageManagerServiceHostTestsIntentVerifyUtils",
        "PackageManagerServiceHostTestsIntentVerifyUtils",
        "block_device_writer_jar",
    ],
    ],
    test_suites: ["general-tests"],
    test_suites: ["general-tests"],
    data: [
    data: [
        ":PackageManagerTestApex",
        ":PackageManagerTestApex",
        ":PackageManagerTestApexApp",
        ":PackageManagerTestApexApp",
        ":PackageManagerServiceServerTests",
    ],
    data_device_bins_both: [
        "block_device_writer",
        "fsverity_multilib",
    ],
    ],
    java_resources: [
    java_resources: [
        ":PackageManagerTestOverlayActor",
        ":PackageManagerTestOverlayActor",
+16 −0
Original line number Original line Diff line number Diff line
@@ -27,6 +27,22 @@
            value="pm uninstall com.android.cts.install.lib.testapp.A" />
            value="pm uninstall com.android.cts.install.lib.testapp.A" />
    </target_preparer>
    </target_preparer>


    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
        <option name="cleanup-apks" value="true" />
        <option name="install-arg" value="-t" />
        <option name="test-file-name" value="PackageManagerServiceServerTests.apk" />
    </target_preparer>

    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
        <!-- The build system produces both 32 and 64 bit variants with bitness suffix. Let
             FilePusher find the filename with bitness and push to a remote name without bitness.
        -->
        <option name="append-bitness" value="true" />
        <option name="cleanup" value="true" />
        <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
        <option name="push" value="fsverity_multilib->/data/local/tmp/fsverity_multilib" />
    </target_preparer>

    <test class="com.android.tradefed.testtype.HostTest">
    <test class="com.android.tradefed.testtype.HostTest">
        <option name="jar" value="PackageManagerServiceHostTests.jar" />
        <option name="jar" value="PackageManagerServiceHostTests.jar" />
    </test>
    </test>
+106 −0
Original line number Original line 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import com.android.blockdevicewriter.BlockDeviceWriter;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.File;
import java.util.ArrayList;

/**
 * atest com.android.server.pm.test.SettingsTest
 */
@RunWith(DeviceJUnit4ClassRunner.class)
public class SettingsTest extends BaseHostJUnit4Test {
    private static final String DEVICE_TEST_PACKAGE_NAME =
            "com.android.server.pm.test.service.server";
    private static final String TEST_CLASS_NAME =
            "com.android.server.pm.PackageManagerSettingsDeviceTests";

    private static final String FSVERITY_EXECUTABLE = "/data/local/tmp/fsverity_multilib";
    private static final String DAMAGING_EXECUTABLE = "/data/local/tmp/block_device_writer";

    @Test
    public void testWriteCorruptHeaderBinaryXml() throws Exception {
        // Corrupt 1st page, this will trigger error in initial resolve* call.
        performTest("testWriteBinaryXmlSettings", 0);
    }

    @Test
    public void testWriteCorruptHeaderTextXml() throws Exception {
        // Corrupt 1st page, this will trigger error in initial resolve* call.
        performTest("testWriteTextXmlSettings", 0);
    }

    @Test
    public void testWriteCorruptDataBinaryXml() throws Exception {
        // Corrupt 2nd page, this will trigger error in the XML parser.
        performTest("testWriteBinaryXmlSettings", 1);
    }

    @Test
    public void testWriteCorruptDataTextXml() throws Exception {
        // Corrupt 2nd page, this will trigger error in the XML parser.
        performTest("testWriteTextXmlSettings", 1);
    }

    private void performTest(String writeTest, int pageToCorrupt) throws Exception {
        assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
                writeTest));

        int userId = getDevice().getCurrentUser();
        File filesDir = new File(
                "/data/user/" + userId + "/" + DEVICE_TEST_PACKAGE_NAME + "/files");
        File packagesXml = new File(filesDir, "system/packages.xml");

        // Make sure fs-verity is enabled.
        enableFsVerity(packagesXml.getAbsolutePath());

        // Damage the file directly against the block device.
        BlockDeviceWriter.damageFileAgainstBlockDevice(getDevice(), packagesXml.getAbsolutePath(),
                pageToCorrupt * 4096 + 1);
        BlockDeviceWriter.dropCaches(getDevice());

        assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
                "testReadSettings"));
    }

    private void enableFsVerity(String path) throws Exception {
        ITestDevice device = getDevice();
        ArrayList<String> args = new ArrayList<>();
        args.add(FSVERITY_EXECUTABLE);
        args.add("enable");
        args.add(path);

        String cmd = String.join(" ", args);
        CommandResult result = device.executeShellV2Command(cmd);
        assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS,
                result.getStatus());
    }

}
+157 −0
Original line number Original line 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;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.when;

import android.annotation.NonNull;
import android.app.PropertyInvalidatedCache;
import android.os.Message;
import android.platform.test.annotations.Presubmit;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.permission.persistence.RuntimePermissionsPersistence;
import com.android.server.LocalServices;
import com.android.server.pm.permission.LegacyPermissionDataProvider;
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
import com.android.server.testutils.TestHandler;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.UUID;

@Presubmit
@RunWith(AndroidJUnit4.class)
@SmallTest
public class PackageManagerSettingsDeviceTests {
    @Mock
    RuntimePermissionsPersistence mRuntimePermissionsPersistence;
    @Mock
    LegacyPermissionDataProvider mPermissionDataProvider;
    @Mock
    DomainVerificationManagerInternal mDomainVerificationManager;
    @Mock
    Computer mComputer;

    final TestHandler mHandler = new TestHandler((@NonNull Message msg) -> {
        return true;
    });

    @Before
    public void initializeMocks() {
        MockitoAnnotations.initMocks(this);
        when(mDomainVerificationManager.generateNewId())
                .thenAnswer(invocation -> UUID.randomUUID());
    }

    @Before
    public void setup() {
        // Disable binder caches in this process.
        PropertyInvalidatedCache.disableForTestMode();
    }

    @Before
    public void createUserManagerServiceRef() throws ReflectiveOperationException {
        InstrumentationRegistry.getInstrumentation().runOnMainSync((Runnable) () -> {
            try {
                // unregister the user manager from the local service
                LocalServices.removeServiceForTest(UserManagerInternal.class);
                new UserManagerService(InstrumentationRegistry.getContext());
            } catch (Exception e) {
                e.printStackTrace();
                fail("Could not create user manager service; " + e);
            }
        });
    }

    // Write valid packages.xml, compare with the reserve copy.
    @Test
    public void testWriteBinaryXmlSettings() throws Exception {
        // write out files and read
        PackageManagerSettingsTests.writeOldFiles();
        Settings settings = makeSettings();
        assertTrue(settings.readLPw(mComputer, PackageManagerSettingsTests.createFakeUsers()));

        // write out
        settings.writeLPr(mComputer, /*sync=*/true);

        File filesDir = InstrumentationRegistry.getContext().getFilesDir();
        File packageXml = new File(filesDir, "system/packages.xml");
        File packagesReserveCopyXml = new File(filesDir, "system/packages.xml.reservecopy");
        // Primary.
        assertTrue(packageXml.exists());
        // For the test, we need a file with at least 2 pages.
        assertThat(packageXml.length(), greaterThan(4096L));
        // Reserve copy.
        assertTrue(packagesReserveCopyXml.exists());
        // Temporary backup.
        assertFalse(new File(filesDir, "packages-backup.xml").exists());

        // compare two copies, make sure they are the same
        assertTrue(Arrays.equals(Files.readAllBytes(Path.of(packageXml.getAbsolutePath())),
                Files.readAllBytes(Path.of(packagesReserveCopyXml.getAbsolutePath()))));
    }

    @Test
    public void testWriteTextXmlSettings() throws Exception {
        testWriteBinaryXmlSettings();

        PackageManagerSettingsTests.writePackagesXml("system/packages.xml");

        File filesDir = InstrumentationRegistry.getContext().getFilesDir();
        File packageXml = new File(filesDir, "system/packages.xml");

        assertTrue(packageXml.exists());
        // For the test, we need a file with at least 2 pages.
        assertThat(packageXml.length(), greaterThan(4096L));
    }

    // Read settings, verify.
    @Test
    public void testReadSettings() throws Exception {
        // This test can be run separately. In this case packages.xml is missing.
        assumeTrue(new File(InstrumentationRegistry.getContext().getFilesDir(),
                "system/packages.xml").exists());
        Settings settings = makeSettings();
        assertTrue(settings.readLPw(mComputer, PackageManagerSettingsTests.createFakeUsers()));
        PackageManagerSettingsTests.verifyKeySetMetaData(settings);
    }

    private Settings makeSettings() {
        return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
                mRuntimePermissionsPersistence, mPermissionDataProvider,
                mDomainVerificationManager, mHandler,
                new PackageManagerTracedLock());
    }
}
+28 −30
Original line number Original line Diff line number Diff line
@@ -56,7 +56,6 @@ import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.AtomicFile;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.LongSparseArray;


import androidx.test.InstrumentationRegistry;
import androidx.test.InstrumentationRegistry;
@@ -108,7 +107,6 @@ import java.util.concurrent.CountDownLatch;
@RunWith(AndroidJUnit4.class)
@RunWith(AndroidJUnit4.class)
@SmallTest
@SmallTest
public class PackageManagerSettingsTests {
public class PackageManagerSettingsTests {
    private static final String TAG = "PackageManagerSettingsTests";
    private static final String PACKAGE_NAME_1 = "com.android.app1";
    private static final String PACKAGE_NAME_1 = "com.android.app1";
    private static final String PACKAGE_NAME_2 = "com.android.app2";
    private static final String PACKAGE_NAME_2 = "com.android.app2";
    private static final String PACKAGE_NAME_3 = "com.android.app3";
    private static final String PACKAGE_NAME_3 = "com.android.app3";
@@ -142,6 +140,25 @@ public class PackageManagerSettingsTests {
        PropertyInvalidatedCache.disableForTestMode();
        PropertyInvalidatedCache.disableForTestMode();
    }
    }


    @Before
    public void createUserManagerServiceRef() throws ReflectiveOperationException {
        InstrumentationRegistry.getInstrumentation().runOnMainSync((Runnable) () -> {
            try {
                // unregister the user manager from the local service
                LocalServices.removeServiceForTest(UserManagerInternal.class);
                new UserManagerService(InstrumentationRegistry.getContext());
            } catch (Exception e) {
                e.printStackTrace();
                fail("Could not create user manager service; " + e);
            }
        });
    }

    @After
    public void tearDown() throws Exception {
        deleteFolder(InstrumentationRegistry.getContext().getFilesDir());
    }

    /** make sure our initialized KeySetManagerService metadata matches packages.xml */
    /** make sure our initialized KeySetManagerService metadata matches packages.xml */
    @Test
    @Test
    public void testReadKeySetSettings() throws Exception {
    public void testReadKeySetSettings() throws Exception {
@@ -1612,13 +1629,13 @@ public class PackageManagerSettingsTests {
                UUID.randomUUID());
                UUID.randomUUID());
    }
    }


    private @NonNull List<UserInfo> createFakeUsers() {
    static @NonNull List<UserInfo> createFakeUsers() {
        ArrayList<UserInfo> users = new ArrayList<>();
        ArrayList<UserInfo> users = new ArrayList<>();
        users.add(new UserInfo(UserHandle.USER_SYSTEM, "test user", UserInfo.FLAG_INITIALIZED));
        users.add(new UserInfo(UserHandle.USER_SYSTEM, "test user", UserInfo.FLAG_INITIALIZED));
        return users;
        return users;
    }
    }


    private void writeFile(File file, byte[] data) {
    private static void writeFile(File file, byte[] data) {
        file.mkdirs();
        file.mkdirs();
        try {
        try {
            AtomicFile aFile = new AtomicFile(file);
            AtomicFile aFile = new AtomicFile(file);
@@ -1626,7 +1643,7 @@ public class PackageManagerSettingsTests {
            fos.write(data);
            fos.write(data);
            aFile.finishWrite(fos);
            aFile.finishWrite(fos);
        } catch (IOException ioe) {
        } catch (IOException ioe) {
            Log.e(TAG, "Cannot write file " + file.getPath());
            throw new RuntimeException("Cannot write file " + file.getPath(), ioe);
        }
        }
    }
    }


@@ -1640,7 +1657,7 @@ public class PackageManagerSettingsTests {
                ).getBytes());
                ).getBytes());
    }
    }


    private void writePackagesXml(String fileName) {
    static void writePackagesXml(String fileName) {
        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), fileName),
        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), fileName),
                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                + "<packages>"
                + "<packages>"
@@ -1748,7 +1765,7 @@ public class PackageManagerSettingsTests {
                        .getBytes());
                        .getBytes());
    }
    }


    private void writeStoppedPackagesXml() {
    private static void writeStoppedPackagesXml() {
        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages-stopped.xml"),
        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages-stopped.xml"),
                ( "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                ( "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                + "<stopped-packages>"
                + "<stopped-packages>"
@@ -1758,7 +1775,7 @@ public class PackageManagerSettingsTests {
                .getBytes());
                .getBytes());
    }
    }


    private void writePackagesList() {
    private static void writePackagesList() {
        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages.list"),
        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages.list"),
                ( "com.android.app1 11000 0 /data/data/com.android.app1 seinfo1"
                ( "com.android.app1 11000 0 /data/data/com.android.app1 seinfo1"
                + "com.android.app2 11001 0 /data/data/com.android.app2 seinfo2"
                + "com.android.app2 11001 0 /data/data/com.android.app2 seinfo2"
@@ -1766,7 +1783,7 @@ public class PackageManagerSettingsTests {
                .getBytes());
                .getBytes());
    }
    }


    private void deleteSystemFolder() {
    private static void deleteSystemFolder() {
        File systemFolder = new File(InstrumentationRegistry.getContext().getFilesDir(), "system");
        File systemFolder = new File(InstrumentationRegistry.getContext().getFilesDir(), "system");
        deleteFolder(systemFolder);
        deleteFolder(systemFolder);
    }
    }
@@ -1781,7 +1798,7 @@ public class PackageManagerSettingsTests {
        folder.delete();
        folder.delete();
    }
    }


    private void writeOldFiles() {
    static void writeOldFiles() {
        deleteSystemFolder();
        deleteSystemFolder();
        writePackagesXml("system/packages.xml");
        writePackagesXml("system/packages.xml");
        writeStoppedPackagesXml();
        writeStoppedPackagesXml();
@@ -1795,25 +1812,6 @@ public class PackageManagerSettingsTests {
        writePackagesList();
        writePackagesList();
    }
    }


    @Before
    public void createUserManagerServiceRef() throws ReflectiveOperationException {
        InstrumentationRegistry.getInstrumentation().runOnMainSync((Runnable) () -> {
            try {
                // unregister the user manager from the local service
                LocalServices.removeServiceForTest(UserManagerInternal.class);
                new UserManagerService(InstrumentationRegistry.getContext());
            } catch (Exception e) {
                e.printStackTrace();
                fail("Could not create user manager service; " + e);
            }
        });
    }

    @After
    public void tearDown() throws Exception {
        deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
    }

    private Settings makeSettings() {
    private Settings makeSettings() {
        return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
        return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
                mRuntimePermissionsPersistence, mPermissionDataProvider,
                mRuntimePermissionsPersistence, mPermissionDataProvider,
@@ -1821,7 +1819,7 @@ public class PackageManagerSettingsTests {
                new PackageManagerTracedLock());
                new PackageManagerTracedLock());
    }
    }


    private void verifyKeySetMetaData(Settings settings)
    static void verifyKeySetMetaData(Settings settings)
            throws ReflectiveOperationException, IllegalAccessException {
            throws ReflectiveOperationException, IllegalAccessException {
        WatchedArrayMap<String, PackageSetting> packages = settings.mPackages;
        WatchedArrayMap<String, PackageSetting> packages = settings.mPackages;
        KeySetManagerService ksms = settings.getKeySetManagerService();
        KeySetManagerService ksms = settings.getKeySetManagerService();