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

This commit is contained in:
Alex Buynytskyy
2023-01-27 12:54:08 +00:00
committed by Android (Google) Code Review
5 changed files with 317 additions and 31 deletions

View File

@@ -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",

View File

@@ -27,6 +27,22 @@
value="pm uninstall com.android.cts.install.lib.testapp.A" />
</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">
<option name="jar" value="PackageManagerServiceHostTests.jar" />
</test>

View File

@@ -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<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());
}
}

View File

@@ -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());
}
}

View File

@@ -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<UserInfo> createFakeUsers() {
static @NonNull List<UserInfo> createFakeUsers() {
ArrayList<UserInfo> 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),
("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ "<packages>"
@@ -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"),
( "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ "<stopped-packages>"
@@ -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<String, PackageSetting> packages = settings.mPackages;
KeySetManagerService ksms = settings.getKeySetManagerService();