diff options
6 files changed, 184 insertions, 18 deletions
diff --git a/Android.bp b/Android.bp index c1f6860980de..3c08056b7db9 100644 --- a/Android.bp +++ b/Android.bp @@ -444,6 +444,7 @@ java_library { "services-platform-compat-config", "media-provider-platform-compat-config", "services-devicepolicy-platform-compat-config", + "services-core-platform-compat-config", ], static_libs: [ // If MimeMap ever becomes its own APEX, then this dependency would need to be removed diff --git a/services/core/Android.bp b/services/core/Android.bp index 2eb2f334981c..203bc61c2022 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -162,3 +162,9 @@ prebuilt_etc { name: "protolog.conf.json.gz", src: ":services.core.json.gz", } + +platform_compat_config { + name: "services-core-platform-compat-config", + src: ":services.core.unboosted", +} + diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8439a0de3a06..4e0e4ffb3a13 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2364,6 +2364,37 @@ public class PackageManagerService extends IPackageManager.Stub PackageManagerService m = new PackageManagerService(injector, factoryTest, onlyCore); t.traceEnd(); // "create package manager" + injector.getCompatibility().registerListener(SELinuxMMAC.SELINUX_LATEST_CHANGES, + packageName -> { + synchronized (m.mInstallLock) { + final PackageParser.Package pkg; + final SharedUserSetting sharedUser; + synchronized (m.mLock) { + PackageSetting ps = m.mSettings.getPackageLPr(packageName); + if (ps == null) { + Slog.e(TAG, "Failed to find package setting " + packageName); + return; + } + pkg = ps.pkg; + sharedUser = ps.sharedUser; + } + + if (pkg == null) { + Slog.e(TAG, "Failed to find package " + packageName); + return; + } + final String newSeInfo = SELinuxMMAC.getSeInfo(pkg, sharedUser, + m.mInjector.getCompatibility()); + + if (!newSeInfo.equals(pkg.applicationInfo.seInfo)) { + Slog.i(TAG, "Updating seInfo for package " + packageName + " from: " + + pkg.applicationInfo.seInfo + " to: " + newSeInfo); + pkg.applicationInfo.seInfo = newSeInfo; + m.prepareAppDataAfterInstallLIF(pkg); + } + } + }); + m.installWhitelistedSystemPackages(); ServiceManager.addService("package", m); final PackageManagerNative pmn = m.new PackageManagerNative(); @@ -10784,24 +10815,8 @@ public class PackageManagerService extends IPackageManager.Stub pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; } - // Apps which share a sharedUserId must be placed in the same selinux domain. If this - // package is the first app installed as this shared user, set seInfoTargetSdkVersion to its - // targetSdkVersion. These are later adjusted in PackageManagerService's constructor to be - // the lowest targetSdkVersion of all apps within the shared user, which corresponds to the - // least restrictive selinux domain. - // NOTE: As new packages are installed / updated, the shared user's seinfoTargetSdkVersion - // will NOT be modified until next boot, even if a lower targetSdkVersion is used. This - // ensures that all packages continue to run in the same selinux domain. - final int targetSdkVersion = - ((sharedUserSetting != null) && (sharedUserSetting.packages.size() != 0)) ? - sharedUserSetting.seInfoTargetSdkVersion : pkg.applicationInfo.targetSdkVersion; - // TODO(b/71593002): isPrivileged for sharedUser and appInfo should never be out of sync. - // They currently can be if the sharedUser apps are signed with the platform key. - final boolean isPrivileged = (sharedUserSetting != null) ? - sharedUserSetting.isPrivileged() | pkg.isPrivileged() : pkg.isPrivileged(); - - pkg.applicationInfo.seInfo = SELinuxMMAC.getSeInfo(pkg, isPrivileged, - targetSdkVersion); + pkg.applicationInfo.seInfo = SELinuxMMAC.getSeInfo(pkg, sharedUserSetting, + injector.getCompatibility()); pkg.applicationInfo.seInfoUser = SELinuxUtil.assignSeinfoUser(pkgSetting.readUserState( userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId)); diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index b464988d5871..d20f20ffec46 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.pm.PackageParser; import android.content.pm.PackageParser.SigningDetails; import android.content.pm.Signature; @@ -23,6 +25,8 @@ import android.os.Environment; import android.util.Slog; import android.util.Xml; +import com.android.server.compat.PlatformCompat; + import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; @@ -72,6 +76,19 @@ public final class SELinuxMMAC { // Append targetSdkVersion=n to existing seinfo label where n is the app's targetSdkVersion private static final String TARGETSDKVERSION_STR = ":targetSdkVersion="; + /** + * This change gates apps access to untrusted_app_R-targetSDk SELinux domain. Allows opt-in + * to R targetSdkVersion enforced changes without changing target SDK. Turning this change + * off for an app targeting R is a no-op. + * + * <p>Has no effect for apps using shared user id. + * + * TODO(b/143539591): Update description with relevant SELINUX changes this opts in to. + */ + @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.Q) + @ChangeId + static final long SELINUX_LATEST_CHANGES = 143539591L; + // Only initialize sMacPermissions once. static { // Platform mac permissions. @@ -319,6 +336,48 @@ public final class SELinuxMMAC { } } + private static int getTargetSdkVersionForSeInfo(PackageParser.Package pkg, + SharedUserSetting sharedUserSetting, PlatformCompat compatibility) { + // Apps which share a sharedUserId must be placed in the same selinux domain. If this + // package is the first app installed as this shared user, set seInfoTargetSdkVersion to its + // targetSdkVersion. These are later adjusted in PackageManagerService's constructor to be + // the lowest targetSdkVersion of all apps within the shared user, which corresponds to the + // least restrictive selinux domain. + // NOTE: As new packages are installed / updated, the shared user's seinfoTargetSdkVersion + // will NOT be modified until next boot, even if a lower targetSdkVersion is used. This + // ensures that all packages continue to run in the same selinux domain. + if ((sharedUserSetting != null) && (sharedUserSetting.packages.size() != 0)) { + return sharedUserSetting.seInfoTargetSdkVersion; + } + if (compatibility.isChangeEnabled(SELINUX_LATEST_CHANGES, pkg.applicationInfo)) { + return android.os.Build.VERSION_CODES.R; + } + + return pkg.applicationInfo.targetSdkVersion; + } + + /** + * Selects a security label to a package based on input parameters and the seinfo tag taken + * from a matched policy. All signature based policy stanzas are consulted and, if no match + * is found, the default seinfo label of 'default' is used. The security label is attached to + * the ApplicationInfo instance of the package. + * + * @param pkg object representing the package to be labeled. + * @param sharedUserSetting if the app shares a sharedUserId, then this has the shared setting. + * @param compatibility the PlatformCompat service to ask about state of compat changes. + * @return String representing the resulting seinfo. + */ + public static String getSeInfo(PackageParser.Package pkg, SharedUserSetting sharedUserSetting, + PlatformCompat compatibility) { + final int targetSdkVersion = getTargetSdkVersionForSeInfo(pkg, sharedUserSetting, + compatibility); + // TODO(b/71593002): isPrivileged for sharedUser and appInfo should never be out of sync. + // They currently can be if the sharedUser apps are signed with the platform key. + final boolean isPrivileged = (sharedUserSetting != null) + ? sharedUserSetting.isPrivileged() | pkg.isPrivileged() : pkg.isPrivileged(); + return getSeInfo(pkg, isPrivileged, targetSdkVersion); + } + /** * Selects a security label to a package based on input parameters and the seinfo tag taken * from a matched policy. All signature based policy stanzas are consulted and, if no match diff --git a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java new file mode 100644 index 000000000000..4b7dd36a382a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java @@ -0,0 +1,80 @@ +/* + * 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.pm; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.mockito.Mockito.when; + +import android.content.pm.PackageParser; +import android.platform.test.annotations.Presubmit; + +import com.android.server.compat.PlatformCompat; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + + +/** + * {@link SELinuxMMAC} tests. + */ +@RunWith(MockitoJUnitRunner.class) +@Presubmit +public class SELinuxMMACTest { + + private static final String PACKAGE_NAME = "my.package"; + private static final int OPT_IN_VERSION = android.os.Build.VERSION_CODES.R; + + @Mock + PlatformCompat mMockCompatibility; + + PackageParser.Package mPkg; + + @Before + public void setUp() { + mPkg = new PackageParser.Package(PACKAGE_NAME); + mPkg.applicationInfo.targetSdkVersion = 28; + } + + @Test + public void getSeInfoOptInToLatest() { + when(mMockCompatibility.isChangeEnabled(SELinuxMMAC.SELINUX_LATEST_CHANGES, + mPkg.applicationInfo)).thenReturn(true); + assertThat(SELinuxMMAC.getSeInfo(mPkg, null, mMockCompatibility), + is("default:targetSdkVersion=" + OPT_IN_VERSION)); + } + + @Test + public void getSeInfoNoOptIn() { + when(mMockCompatibility.isChangeEnabled(SELinuxMMAC.SELINUX_LATEST_CHANGES, + mPkg.applicationInfo)).thenReturn(false); + assertThat(SELinuxMMAC.getSeInfo(mPkg, null, mMockCompatibility), + is("default:targetSdkVersion=28")); + } + + @Test + public void getSeInfoNoOptInButAlreadyR() { + mPkg.applicationInfo.targetSdkVersion = OPT_IN_VERSION; + when(mMockCompatibility.isChangeEnabled(SELinuxMMAC.SELINUX_LATEST_CHANGES, + mPkg.applicationInfo)).thenReturn(false); + assertThat(SELinuxMMAC.getSeInfo(mPkg, null, mMockCompatibility), + is("default:targetSdkVersion=" + OPT_IN_VERSION)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java index 3ea3b3cff7e8..74ef034082a0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java @@ -48,6 +48,8 @@ import android.os.UserManagerInternal; import android.platform.test.annotations.Presubmit; import android.util.Pair; +import com.android.server.compat.PlatformCompat; + import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; @@ -71,12 +73,15 @@ public class ScanTests { @Mock UserManagerInternal mMockUserManager; @Mock + PlatformCompat mMockCompatibility; + @Mock PackageManagerService.Injector mMockInjector; @Before public void setupInjector() { when(mMockInjector.getAbiHelper()).thenReturn(mMockPackageAbiHelper); when(mMockInjector.getUserManagerInternal()).thenReturn(mMockUserManager); + when(mMockInjector.getCompatibility()).thenReturn(mMockCompatibility); } @Before |