From d4f24d4c523a582dcae23bfa630cae97055d9586 Mon Sep 17 00:00:00 2001 From: Alex Buynytskyy Date: Mon, 23 Jan 2023 20:28:45 -0800 Subject: [PATCH] Host side test corrupting fs-verity protected file. Bug: 253568736 Test: atest com.android.server.pm.test.SettingsTest Change-Id: I39817d6e2ac5e493c1e95a66da20e7be3a260b31 --- .../host/Android.bp | 11 +- .../host/AndroidTest.xml | 16 ++ .../android/server/pm/test/SettingsTest.java | 106 ++++++++++++ .../pm/PackageManagerSettingsDeviceTests.java | 157 ++++++++++++++++++ .../pm/PackageManagerSettingsTests.java | 58 ++++--- 5 files changed, 317 insertions(+), 31 deletions(-) create mode 100644 services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SettingsTest.java create mode 100644 services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsDeviceTests.java diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp index 47e7a37e9352b..d9467a5e1712b 100644 --- a/services/tests/PackageManagerServiceTests/host/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/Android.bp @@ -23,7 +23,10 @@ package { java_test_host { name: "PackageManagerServiceHostTests", - srcs: ["src/**/*.kt"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], libs: [ "tradefed", "junit", @@ -34,11 +37,17 @@ java_test_host { "cts-host-utils", "frameworks-base-hostutils", "PackageManagerServiceHostTestsIntentVerifyUtils", + "block_device_writer_jar", ], test_suites: ["general-tests"], data: [ ":PackageManagerTestApex", ":PackageManagerTestApexApp", + ":PackageManagerServiceServerTests", + ], + data_device_bins_both: [ + "block_device_writer", + "fsverity_multilib", ], java_resources: [ ":PackageManagerTestOverlayActor", diff --git a/services/tests/PackageManagerServiceTests/host/AndroidTest.xml b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml index f584599e81006..2382548192ad3 100644 --- a/services/tests/PackageManagerServiceTests/host/AndroidTest.xml +++ b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml @@ -27,6 +27,22 @@ value="pm uninstall com.android.cts.install.lib.testapp.A" /> + + + + + + + diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SettingsTest.java b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SettingsTest.java new file mode 100644 index 0000000000000..a66906e5cd1eb --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SettingsTest.java @@ -0,0 +1,106 @@ +/* + * 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 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()); + } + +} diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsDeviceTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsDeviceTests.java new file mode 100644 index 0000000000000..e43f40d7dfe10 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsDeviceTests.java @@ -0,0 +1,157 @@ +/* + * 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()); + } +} diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index 6a1ccc50df584..75484d162282c 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -56,7 +56,6 @@ import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; -import android.util.Log; import android.util.LongSparseArray; import androidx.test.InstrumentationRegistry; @@ -108,7 +107,6 @@ import java.util.concurrent.CountDownLatch; @RunWith(AndroidJUnit4.class) @SmallTest 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_2 = "com.android.app2"; private static final String PACKAGE_NAME_3 = "com.android.app3"; @@ -142,6 +140,25 @@ public class PackageManagerSettingsTests { 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 */ @Test public void testReadKeySetSettings() throws Exception { @@ -1612,13 +1629,13 @@ public class PackageManagerSettingsTests { UUID.randomUUID()); } - private @NonNull List createFakeUsers() { + static @NonNull List createFakeUsers() { ArrayList users = new ArrayList<>(); users.add(new UserInfo(UserHandle.USER_SYSTEM, "test user", UserInfo.FLAG_INITIALIZED)); return users; } - private void writeFile(File file, byte[] data) { + private static void writeFile(File file, byte[] data) { file.mkdirs(); try { AtomicFile aFile = new AtomicFile(file); @@ -1626,7 +1643,7 @@ public class PackageManagerSettingsTests { fos.write(data); aFile.finishWrite(fos); } 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()); } - private void writePackagesXml(String fileName) { + static void writePackagesXml(String fileName) { writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), fileName), ("" + "" @@ -1748,7 +1765,7 @@ public class PackageManagerSettingsTests { .getBytes()); } - private void writeStoppedPackagesXml() { + private static void writeStoppedPackagesXml() { writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages-stopped.xml"), ( "" + "" @@ -1758,7 +1775,7 @@ public class PackageManagerSettingsTests { .getBytes()); } - private void writePackagesList() { + private static void writePackagesList() { writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages.list"), ( "com.android.app1 11000 0 /data/data/com.android.app1 seinfo1" + "com.android.app2 11001 0 /data/data/com.android.app2 seinfo2" @@ -1766,7 +1783,7 @@ public class PackageManagerSettingsTests { .getBytes()); } - private void deleteSystemFolder() { + private static void deleteSystemFolder() { File systemFolder = new File(InstrumentationRegistry.getContext().getFilesDir(), "system"); deleteFolder(systemFolder); } @@ -1781,7 +1798,7 @@ public class PackageManagerSettingsTests { folder.delete(); } - private void writeOldFiles() { + static void writeOldFiles() { deleteSystemFolder(); writePackagesXml("system/packages.xml"); writeStoppedPackagesXml(); @@ -1795,25 +1812,6 @@ public class PackageManagerSettingsTests { 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() { return new Settings(InstrumentationRegistry.getContext().getFilesDir(), mRuntimePermissionsPersistence, mPermissionDataProvider, @@ -1821,7 +1819,7 @@ public class PackageManagerSettingsTests { new PackageManagerTracedLock()); } - private void verifyKeySetMetaData(Settings settings) + static void verifyKeySetMetaData(Settings settings) throws ReflectiveOperationException, IllegalAccessException { WatchedArrayMap packages = settings.mPackages; KeySetManagerService ksms = settings.getKeySetManagerService();