Merge "Add a compat change to opt-in to latest SELinux domain."
diff --git a/Android.bp b/Android.bp
index c1f6860..3c08056 100644
--- a/Android.bp
+++ b/Android.bp
@@ -444,6 +444,7 @@
         "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 2eb2f33..203bc61 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -162,3 +162,9 @@
     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 8439a0d..4e0e4ff 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 @@
         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 @@
             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 b464988..d20f20f 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.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 @@
     // 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 @@
         }
     }
 
+    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 0000000..4b7dd36
--- /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 3ea3b3c..74ef034 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.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 @@
     @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