/* * Copyright (C) 2019 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.systemconfig; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.testng.Assert.expectThrows; import android.os.Build; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.Xml; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.SystemConfig; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Arrays; import java.util.Map; import java.util.Scanner; import java.util.Set; /** * Tests for {@link SystemConfig}. * * Build/Install/Run: * atest FrameworksServicesTests:SystemConfigTest */ @SmallTest @Presubmit @RunWith(AndroidJUnit4.class) public class SystemConfigTest { private static final String LOG_TAG = "SystemConfigTest"; private SystemConfig mSysConfig; private File mFooJar; @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); @Before public void setUp() throws Exception { mSysConfig = new SystemConfigTestClass(); mFooJar = createTempFile( mTemporaryFolder.getRoot().getCanonicalFile(), "foo.jar", "JAR"); } /** * Subclass of SystemConfig without running the constructor. */ private class SystemConfigTestClass extends SystemConfig { SystemConfigTestClass() { super(false); } } private void readPermissions(File libraryDir, int permissionFlag) { final XmlPullParser parser = Xml.newPullParser(); mSysConfig.readPermissions(parser, libraryDir, permissionFlag); } /** * Tests that readPermissions works correctly for the tag: install-in-user-type */ @Test public void testInstallInUserType() throws Exception { final String contents1 = "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; final String contents2 = "\n" + " \n" + " \n" + " \n" + " \n" + ""; final String contents3 = "\n" + " \n" + " \n" // Ignore invalid attribute + " \n" + " \n" + " \n" // Valid + " \n" + " \n" // Ignored since missing package name + " \n" + " \n" + ""; Map> expectedWhite = new ArrayMap<>(); expectedWhite.put("com.android.package1", new ArraySet<>(Arrays.asList("FULL", "PROFILE"))); expectedWhite.put("com.android.package2", new ArraySet<>(Arrays.asList("FULL", "PROFILE", "RESTRICTED", "SYSTEM"))); Map> expectedBlack = new ArrayMap<>(); expectedBlack.put("com.android.package2", new ArraySet<>(Arrays.asList("GUEST", "PROFILE"))); final File folder1 = createTempSubfolder("folder1"); createTempFile(folder1, "permissionFile1.xml", contents1); final File folder2 = createTempSubfolder("folder2"); createTempFile(folder2, "permissionFile2.xml", contents2); // Also, make a third file, but with the name folder1/permissionFile2.xml, to prove no // conflicts. createTempFile(folder1, "permissionFile2.xml", contents3); readPermissions(folder1, /* No permission needed anyway */ 0); readPermissions(folder2, /* No permission needed anyway */ 0); Map> actualWhite = mSysConfig.getAndClearPackageToUserTypeWhitelist(); Map> actualBlack = mSysConfig.getAndClearPackageToUserTypeBlacklist(); assertEquals("Whitelist was not cleared", 0, mSysConfig.getAndClearPackageToUserTypeWhitelist().size()); assertEquals("Blacklist was not cleared", 0, mSysConfig.getAndClearPackageToUserTypeBlacklist().size()); assertEquals("Incorrect whitelist.", expectedWhite, actualWhite); assertEquals("Incorrect blacklist", expectedBlack, actualBlack); } @Test public void testComponentOverride() throws Exception { final String contents = "" + " \n" + " " + " \n" + " " + " \n" + " \n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "component-override.xml", contents); readPermissions(folder, /* No permission needed anyway */ 0); final ArrayMap packageOneExpected = new ArrayMap<>(); packageOneExpected.put("com.android.package1.Full", true); packageOneExpected.put("com.android.package1.Relative", false); final ArrayMap packageTwoExpected = new ArrayMap<>(); packageTwoExpected.put("com.android.package3.Relative2", true); final Map packageOne = mSysConfig.getComponentsEnabledStates( "com.android.package1"); assertEquals(packageOneExpected, packageOne); final Map packageTwo = mSysConfig.getComponentsEnabledStates( "com.android.package2"); assertEquals(packageTwoExpected, packageTwo); } /** * Tests that readPermissions works correctly with {@link SystemConfig#ALLOW_APP_CONFIGS} * permission flag for the tag: allowlisted-staged-installer. */ @Test public void readPermissions_allowAppConfigs_parsesStagedInstallerWhitelist() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "staged-installer-whitelist.xml", contents); readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(mSysConfig.getWhitelistedStagedInstallers()) .containsExactly("com.android.package1"); assertThat(mSysConfig.getModulesInstallerPackageName()).isNull(); } @Test public void readPermissions_parsesStagedInstallerWhitelist_modulesInstaller() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "staged-installer-whitelist.xml", contents); readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(mSysConfig.getWhitelistedStagedInstallers()) .containsExactly("com.android.package1"); assertThat(mSysConfig.getModulesInstallerPackageName()) .isEqualTo("com.android.package1"); } @Test public void readPermissions_parsesStagedInstallerWhitelist_multipleModulesInstallers() throws IOException { final String contents = "\n" + " \n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "staged-installer-whitelist.xml", contents); IllegalStateException e = expectThrows( IllegalStateException.class, () -> readPermissions(folder, /* Grant all permission flags */ ~0)); assertThat(e).hasMessageThat().contains("Multiple modules installers"); } /** * Tests that readPermissions works correctly without {@link SystemConfig#ALLOW_APP_CONFIGS} * permission flag for the tag: allowlisted-staged-installer. */ @Test public void readPermissions_notAllowAppConfigs_wontParseStagedInstallerWhitelist() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "staged-installer-whitelist.xml", contents); readPermissions(folder, /* Grant all but ALLOW_APP_CONFIGS flag */ ~0x08); assertThat(mSysConfig.getWhitelistedStagedInstallers()).isEmpty(); } /** * Tests that readPermissions works correctly with {@link SystemConfig#ALLOW_VENDOR_APEX} * permission flag for the tag: {@code allowed-vendor-apex}. */ @Test public void readPermissions_allowVendorApex_parsesVendorApexAllowList() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "vendor-apex-allowlist.xml", contents); readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(mSysConfig.getAllowedVendorApexes()) .containsExactly("com.android.apex1", "com.installer"); } /** * Tests that readPermissions works correctly with {@link SystemConfig#ALLOW_VENDOR_APEX} * permission flag for the tag: {@code allowed-vendor-apex}. */ @Test public void readPermissions_allowVendorApex_parsesVendorApexAllowList_noPackage() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "vendor-apex-allowlist.xml", contents); readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(mSysConfig.getAllowedVendorApexes()).isEmpty(); } /** * Tests that readPermissions works correctly without {@link SystemConfig#ALLOW_VENDOR_APEX} * permission flag for the tag: {@code allowed-oem-apex}. */ @Test public void readPermissions_notAllowVendorApex_doesNotParseVendorApexAllowList() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "vendor-apex-allowlist.xml", contents); readPermissions(folder, /* Grant all but ALLOW_VENDOR_APEX flag */ ~0x400); assertThat(mSysConfig.getAllowedVendorApexes()).isEmpty(); } /** * Tests that readPermissions works correctly for the tag: {@code install-constraints-allowed}. */ @Test public void readPermissions_installConstraints_successful() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "install-constraints-allowlist.xml", contents); readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(mSysConfig.getInstallConstraintsAllowlist()) .containsExactly("com.android.apex1"); } /** * Tests that readPermissions works correctly for the tag: {@code install-constraints-allowed}. */ @Test public void readPermissions_installConstraints_noPackage() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "install-constraints-allowlist.xml", contents); readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(mSysConfig.getInstallConstraintsAllowlist()).isEmpty(); } /** * Tests that readPermissions works correctly for the tag {@code install-constraints-allowed} * without {@link SystemConfig#ALLOW_VENDOR_APEX}. */ @Test public void readPermissions_installConstraints_noAppConfigs() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "install-constraints-allowlist.xml", contents); readPermissions(folder, /* Grant all but ALLOW_APP_CONFIGS flag */ ~0x08); assertThat(mSysConfig.getInstallConstraintsAllowlist()).isEmpty(); } @Test public void readApexPrivAppPermissions_addAllPermissions() throws Exception { final String contents = "" + "" + "" + ""; File apexDir = createTempSubfolder("apex"); File permissionFile = createTempFile( createTempSubfolder("apex/com.android.my_module/etc/permissions"), "permissions.xml", contents); XmlPullParser parser = readXmlUntilStartTag(permissionFile); mSysConfig.readApexPrivAppPermissions(parser, permissionFile, apexDir.toPath()); ArrayMap permissions = mSysConfig.getPermissionAllowlist() .getApexPrivilegedAppAllowlists().get("com.android.my_module") .get("com.android.apk_in_apex"); assertThat(permissions) .containsExactly("android.permission.FOO", true, "android.permission.BAR", false); } /** * Tests that readPermissions works correctly for a library with on-bootclasspath-before * and on-bootclasspath-since. */ @Test public void readPermissions_allowLibs_parsesSimpleLibrary() throws IOException { String contents = "\n" + " \n\n" + " "; parseSharedLibraries(contents); assertFooIsOnlySharedLibrary(); SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo"); assertThat(entry.onBootclasspathBefore).isEqualTo("10"); assertThat(entry.onBootclasspathSince).isEqualTo("20"); } /** * Tests that readPermissions works correctly for a library with on-bootclasspath-before * and on-bootclasspath-since that uses codenames. */ @Test public void readPermissions_allowLibs_parsesSimpleLibraryWithCodenames() throws IOException { String contents = "\n" + " \n\n" + " "; parseSharedLibraries(contents); assertFooIsOnlySharedLibrary(); SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo"); assertThat(entry.onBootclasspathBefore).isEqualTo("Q"); assertThat(entry.onBootclasspathSince).isEqualTo("W"); } /** * Tests that readPermissions works correctly for a library using the new * {@code apex-library} tag. */ @Test public void readPermissions_allowLibs_parsesUpdatableLibrary() throws IOException { String contents = "\n" + " \n\n" + " "; parseSharedLibraries(contents); assertFooIsOnlySharedLibrary(); SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo"); assertThat(entry.onBootclasspathBefore).isEqualTo("10"); assertThat(entry.onBootclasspathSince).isEqualTo("20"); } /** * Tests that readPermissions for a library with {@code min-device-sdk} lower than the current * SDK results in the library being added to the shared libraries. */ @Test public void readPermissions_allowLibs_allowsOldMinSdk() throws IOException { String contents = "\n" + " \n\n" + " "; parseSharedLibraries(contents); assertFooIsOnlySharedLibrary(); } /** * Tests that readPermissions for a library with {@code min-device-sdk} equal to the current * SDK results in the library being added to the shared libraries. */ @Test public void readPermissions_allowLibs_allowsCurrentMinSdk() throws IOException { String contents = "\n" + " \n\n" + " "; parseSharedLibraries(contents); assertFooIsOnlySharedLibrary(); } /** * Tests that readPermissions for a library with {@code min-device-sdk} greater than the current * SDK results in the library being ignored. */ @Test public void readPermissions_allowLibs_ignoresMinSdkInFuture() throws IOException { String contents = "\n" + " \n\n" + " "; parseSharedLibraries(contents); assertThat(mSysConfig.getSharedLibraries()).isEmpty(); } /** * Tests that readPermissions for a library with {@code max-device-sdk} less than the current * SDK results in the library being ignored. */ @Test public void readPermissions_allowLibs_ignoredOldMaxSdk() throws IOException { String contents = "\n" + " \n\n" + " "; parseSharedLibraries(contents); assertThat(mSysConfig.getSharedLibraries()).isEmpty(); } /** * Tests that readPermissions for a library with {@code max-device-sdk} equal to the current * SDK results in the library being added to the shared libraries. */ @Test public void readPermissions_allowLibs_allowsCurrentMaxSdk() throws IOException { // depending on whether this test is running before or after finalization, we need to // pass a different parameter String parameter; if ("REL".equals(Build.VERSION.CODENAME)) { parameter = "" + Build.VERSION.SDK_INT; } else { parameter = "ZZZ"; } String contents = "\n" + " \n\n" + " "; parseSharedLibraries(contents); assertFooIsOnlySharedLibrary(); } /** * Tests that readPermissions for a library with {@code max-device-sdk} greater than the current * SDK results in the library being added to the shared libraries. */ @Test public void readPermissions_allowLibs_allowsMaxSdkInFuture() throws IOException { String contents = "\n" + " \n\n" + " "; parseSharedLibraries(contents); assertFooIsOnlySharedLibrary(); } /** * Test that getRollbackDenylistedPackages works correctly for the tag: * {@code automatic-rollback-denylisted-app}. */ @Test public void automaticRollbackDeny_vending() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents); readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()) .containsExactly("com.android.vending"); } /** * Test that getRollbackDenylistedPackages works correctly for the tag: * {@code automatic-rollback-denylisted-app} without any packages. */ @Test public void automaticRollbackDeny_empty() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents); readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty(); } /** * Test that getRollbackDenylistedPackages works correctly for the tag: * {@code automatic-rollback-denylisted-app} without the corresponding config. */ @Test public void automaticRollbackDeny_noConfig() throws IOException { final File folder = createTempSubfolder("folder"); readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty(); } /** * Tests that readPermissions works correctly for the tag: {@code update-ownership}. */ @Test public void readPermissions_updateOwnership_successful() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "update_ownership.xml", contents); readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(mSysConfig.getSystemAppUpdateOwnerPackageName("com.foo")) .isEqualTo("com.bar"); } /** * Tests that readPermissions works correctly for the tag: {@code update-ownership}. */ @Test public void readPermissions_updateOwnership_noPackage() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "update_ownership.xml", contents); readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(mSysConfig.getSystemAppUpdateOwnerPackageName("com.foo")).isNull(); } /** * Tests that readPermissions works correctly for the tag: {@code update-ownership}. */ @Test public void readPermissions_updateOwnership_noInstaller() throws IOException { final String contents = "\n" + " \n" + ""; final File folder = createTempSubfolder("folder"); createTempFile(folder, "update_ownership.xml", contents); readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(mSysConfig.getSystemAppUpdateOwnerPackageName("com.foo")).isNull(); } private void parseSharedLibraries(String contents) throws IOException { File folder = createTempSubfolder("permissions_folder"); createTempFile(folder, "permissions.xml", contents); readPermissions(folder, /* permissionFlag = ALLOW_LIBS */ 0x02); } /** * Create an {@link XmlPullParser} for {@param permissionFile} and begin parsing it until * reaching the root tag. */ private XmlPullParser readXmlUntilStartTag(File permissionFile) throws IOException, XmlPullParserException { FileReader permReader = new FileReader(permissionFile); XmlPullParser parser = Xml.newPullParser(); parser.setInput(permReader); int type; do { type = parser.next(); } while (type != parser.START_TAG && type != parser.END_DOCUMENT); if (type != parser.START_TAG) { throw new XmlPullParserException("No start tag found"); } return parser; } /** * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. * * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed * @return the folder */ private File createTempSubfolder(String folderName) throws IOException { File folder = new File(mTemporaryFolder.getRoot(), folderName); folder.mkdirs(); return folder; } /** * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. * * @param folder pre-existing subdirectory of mTemporaryFolder to put the file * @param fileName name of the file (e.g. filename.xml) to create * @param contents contents to write to the file * @return the newly created file */ private File createTempFile(File folder, String fileName, String contents) throws IOException { File file = new File(folder, fileName); BufferedWriter bw = new BufferedWriter(new FileWriter(file)); bw.write(contents); bw.close(); // Print to logcat for test debugging. Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath()); Scanner input = new Scanner(file); while (input.hasNextLine()) { Log.d(LOG_TAG, input.nextLine()); } return file; } private void assertFooIsOnlySharedLibrary() { assertThat(mSysConfig.getSharedLibraries().size()).isEqualTo(1); SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo"); assertThat(entry.name).isEqualTo("foo"); assertThat(entry.filename).isEqualTo(mFooJar.toString()); } }