summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Nikhil Kumar <nykkumar@google.com> 2023-12-12 16:48:36 +0000
committer Nikhil Kumar <nykkumar@google.com> 2024-01-10 11:01:09 +0000
commit1a735df717baef0740dbfecf8d2174d85b27674f (patch)
tree4a62da63d82b3c29ef88550e720f945956d1c85f
parent850a3f0066dbcce9af9aab364137130a6996533e (diff)
Extending systemUserOnly attribute to Providers and Services
systemUserOnly manifest attribute limits an android component to be initialized and available only for the SYSTEM user. This is different from the singleUser attribute which instantiate the component on the SYSTEM user as a singleton and provides a proxy for other secondary users to access the SYSTEM user component. Currently this attribute is only available for Activities and Broadcast receivers, In the following change the attribute is extended to support Providers and Services as well. Bug: 302354856 Test: atest PackageManagerShellCommandMultiUserTest#testInstallAppHavingSystemUserOnlyComponents -c Test: manually tested by applying this attribute on a Service and a provider and making sure it isn't accessible and initialized when running on a Secondary user. Change-Id: I3125bc0c336dfc11c40e327ce8992d5be3601922
-rw-r--r--core/api/current.txt1
-rw-r--r--core/java/android/content/ContentProvider.java31
-rw-r--r--core/java/android/content/pm/ProviderInfo.java9
-rw-r--r--core/java/android/content/pm/ServiceInfo.java8
-rw-r--r--core/java/android/content/pm/multiuser.aconfig9
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java5
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java6
-rw-r--r--core/res/res/values/attrs_manifest.xml10
-rw-r--r--core/res/res/values/public-staging.xml2
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java5
-rw-r--r--services/core/java/com/android/server/am/ContentProviderHelper.java18
12 files changed, 102 insertions, 8 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index ec8f0706379c..857cef361c00 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1600,6 +1600,7 @@ package android {
field public static final int switchTextOff = 16843628; // 0x101036c
field public static final int switchTextOn = 16843627; // 0x101036b
field public static final int syncable = 16842777; // 0x1010019
+ field @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") public static final int systemUserOnly;
field public static final int tabStripEnabled = 16843453; // 0x10102bd
field public static final int tabStripLeft = 16843451; // 0x10102bb
field public static final int tabStripRight = 16843452; // 0x10102bc
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index c7a75ed5ea9c..e9b94c9f5791 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -41,6 +41,7 @@ import android.content.res.Configuration;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.SQLException;
+import android.multiuser.Flags;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
@@ -146,6 +147,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
private boolean mExported;
private boolean mNoPerms;
private boolean mSingleUser;
+ private boolean mSystemUserOnly;
private SparseBooleanArray mUsersRedirectedToOwnerForMedia = new SparseBooleanArray();
private ThreadLocal<AttributionSource> mCallingAttributionSource;
@@ -377,7 +379,9 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
!= PermissionChecker.PERMISSION_GRANTED
&& getContext().checkUriPermission(userUri, Binder.getCallingPid(),
callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
- != PackageManager.PERMISSION_GRANTED) {
+ != PackageManager.PERMISSION_GRANTED
+ && !deniedAccessSystemUserOnlyProvider(callingUserId,
+ mSystemUserOnly)) {
FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
enumCheckUriPermission,
callingUid, uri.getAuthority(), type);
@@ -865,6 +869,10 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
boolean checkUser(int pid, int uid, Context context) {
final int callingUserId = UserHandle.getUserId(uid);
+ if (deniedAccessSystemUserOnlyProvider(callingUserId, mSystemUserOnly)) {
+ return false;
+ }
+
if (callingUserId == context.getUserId() || mSingleUser) {
return true;
}
@@ -987,6 +995,9 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
// last chance, check against any uri grants
final int callingUserId = UserHandle.getUserId(uid);
+ if (deniedAccessSystemUserOnlyProvider(callingUserId, mSystemUserOnly)) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
final Uri userUri = (mSingleUser && !UserHandle.isSameUser(mMyUid, uid))
? maybeAddUserId(uri, callingUserId) : uri;
if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
@@ -2623,6 +2634,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
setPathPermissions(info.pathPermissions);
mExported = info.exported;
mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
+ mSystemUserOnly = (info.flags & ProviderInfo.FLAG_SYSTEM_USER_ONLY) != 0;
setAuthorities(info.authority);
}
if (Build.IS_DEBUGGABLE) {
@@ -2756,6 +2768,11 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
String auth = uri.getAuthority();
if (!mSingleUser) {
int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
+ if (deniedAccessSystemUserOnlyProvider(mContext.getUserId(),
+ mSystemUserOnly)) {
+ throw new SecurityException("Trying to query a SYSTEM user only content"
+ + " provider from user:" + mContext.getUserId());
+ }
if (userId != UserHandle.USER_CURRENT
&& userId != mContext.getUserId()
// Since userId specified in content uri, the provider userId would be
@@ -2929,4 +2946,16 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
Trace.traceBegin(traceTag, methodName + subInfo);
}
}
+ /**
+ * Return true if access to content provider is denied because it's a SYSTEM user only
+ * provider and the calling user is not the SYSTEM user.
+ *
+ * @param callingUserId UserId of the caller accessing the content provider.
+ * @param systemUserOnly true when the content provider is only available for the SYSTEM user.
+ */
+ private static boolean deniedAccessSystemUserOnlyProvider(int callingUserId,
+ boolean systemUserOnly) {
+ return Flags.enableSystemUserOnlyForServicesAndProviders()
+ && (callingUserId != UserHandle.USER_SYSTEM && systemUserOnly);
+ }
}
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index 9e553dbfb719..de33fa8b2328 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -89,6 +89,15 @@ public final class ProviderInfo extends ComponentInfo
public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
/**
+ * Bit in {@link #flags}: If set, this provider will only be available
+ * for the system user.
+ * Set from the android.R.attr#systemUserOnly attribute.
+ * In Sync with {@link ActivityInfo#FLAG_SYSTEM_USER_ONLY}
+ * @hide
+ */
+ public static final int FLAG_SYSTEM_USER_ONLY = ActivityInfo.FLAG_SYSTEM_USER_ONLY;
+
+ /**
* Bit in {@link #flags}: If set, a single instance of the provider will
* run for all users on the device. Set from the
* {@link android.R.attr#singleUser} attribute.
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 4d704c34195f..2db59cc27237 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -100,6 +100,14 @@ public class ServiceInfo extends ComponentInfo
public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000;
/**
+ * @hide Bit in {@link #flags}: If set, this service will only be available
+ * for the system user.
+ * Set from the android.R.attr#systemUserOnly attribute.
+ * In Sync with {@link ActivityInfo#FLAG_SYSTEM_USER_ONLY}
+ */
+ public static final int FLAG_SYSTEM_USER_ONLY = ActivityInfo.FLAG_SYSTEM_USER_ONLY;
+
+ /**
* Bit in {@link #flags}: If set, a single instance of the service will
* run for all users on the device. Set from the
* {@link android.R.attr#singleUser} attribute.
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index c7797c719e2c..10368653f0c4 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -70,4 +70,11 @@ flag {
namespace: "profile_experiences"
description: "Add support for Private Space in resolver sheet"
bug: "307515485"
-} \ No newline at end of file
+}
+flag {
+ name: "enable_system_user_only_for_services_and_providers"
+ namespace: "multiuser"
+ description: "Enable systemUserOnly manifest attribute for services and providers."
+ bug: "302354856"
+ is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 5d82d0469d56..12aff1c6669f 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -29,6 +29,7 @@ import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.multiuser.Flags;
import android.os.Build;
import android.os.PatternMatcher;
import android.util.Slog;
@@ -126,6 +127,10 @@ public class ParsedProviderUtils {
.setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SINGLE_USER,
R.styleable.AndroidManifestProvider_singleUser, sa));
+ if (Flags.enableSystemUserOnlyForServicesAndProviders()) {
+ provider.setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SYSTEM_USER_ONLY,
+ R.styleable.AndroidManifestProvider_systemUserOnly, sa));
+ }
visibleToEphemeral = sa.getBoolean(
R.styleable.AndroidManifestProvider_visibleToInstantApps, false);
if (visibleToEphemeral) {
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index a1dd19a3bc90..4ac542f84226 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -29,6 +29,7 @@ import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.multiuser.Flags;
import android.os.Build;
import com.android.internal.R;
@@ -105,6 +106,11 @@ public class ParsedServiceUtils {
| flag(ServiceInfo.FLAG_SINGLE_USER,
R.styleable.AndroidManifestService_singleUser, sa)));
+ if (Flags.enableSystemUserOnlyForServicesAndProviders()) {
+ service.setFlags(service.getFlags() | flag(ServiceInfo.FLAG_SYSTEM_USER_ONLY,
+ R.styleable.AndroidManifestService_systemUserOnly, sa));
+ }
+
visibleToEphemeral = sa.getBoolean(
R.styleable.AndroidManifestService_visibleToInstantApps, false);
if (visibleToEphemeral) {
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d1143c43ad61..e1532c6c0097 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -508,6 +508,12 @@
receivers, and providers; it can not be used with activities. -->
<attr name="singleUser" format="boolean" />
+ <!-- If set to true, only a single instance of this component will
+ run and be available for the SYSTEM user. Non SYSTEM users will not be
+ allowed to access the component if this flag is enabled.
+ This flag can be used with services, receivers, providers and activities. -->
+ <attr name="systemUserOnly" format="boolean" />
+
<!-- Specify a specific process that the associated code is to run in.
Use with the application tag (to supply a default process for all
application components), or with the activity, receiver, service,
@@ -2855,6 +2861,7 @@
Context.createAttributionContext() using the first attribution tag
contained here. -->
<attr name="attributionTags" />
+ <attr name="systemUserOnly" format="boolean" />
</declare-styleable>
<!-- Attributes that can be supplied in an AndroidManifest.xml
@@ -3013,6 +3020,7 @@
ignored when the process is bound into a shared isolated process by a client.
-->
<attr name="allowSharedIsolatedProcess" format="boolean" />
+ <attr name="systemUserOnly" format="boolean" />
</declare-styleable>
<!-- @hide The <code>apex-system-service</code> tag declares an apex system service
@@ -3140,7 +3148,7 @@
<attr name="uiOptions" />
<attr name="parentActivityName" />
<attr name="singleUser" />
- <!-- @hide This broadcast receiver or activity will only receive broadcasts for the
+ <!-- This broadcast receiver or activity will only receive broadcasts for the
system user-->
<attr name="systemUserOnly" format="boolean" />
<attr name="persistableMode" />
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 540967d28d9b..f82a5c85d582 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -119,6 +119,8 @@
<public name="optional"/>
<!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") -->
<public name="adServiceTypes" />
+ <!-- @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") -->
+ <public name="systemUserOnly"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 96b1650d9575..fb36ad17f08e 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4463,6 +4463,12 @@ public final class ActiveServices {
}
}
if (userId > 0) {
+ if (mAm.isSystemUserOnly(sInfo.flags)) {
+ Slog.w(TAG_SERVICE, service + " is only available for the SYSTEM user,"
+ + " calling userId is: " + userId);
+ return null;
+ }
+
if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo,
sInfo.name, sInfo.flags)
&& mAm.isValidSingletonCall(callingUid, sInfo.applicationInfo.uid)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 671c8e984253..f7c8e8a69ddc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13737,6 +13737,11 @@ public class ActivityManagerService extends IActivityManager.Stub
return result;
}
+ boolean isSystemUserOnly(int flags) {
+ return android.multiuser.Flags.enableSystemUserOnlyForServicesAndProviders()
+ && (flags & ServiceInfo.FLAG_SYSTEM_USER_ONLY) != 0;
+ }
+
/**
* Checks to see if the caller is in the same app as the singleton
* component, or the component is in a special app. It allows special apps
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 095d907d7df6..30f21a65b5b1 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -1249,9 +1249,9 @@ public class ContentProviderHelper {
ProviderInfo cpi = providers.get(i);
boolean singleton = mService.isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags);
- if (singleton && app.userId != UserHandle.USER_SYSTEM) {
- // This is a singleton provider, but a user besides the
- // default user is asking to initialize a process it runs
+ if (isSingletonOrSystemUserOnly(cpi) && app.userId != UserHandle.USER_SYSTEM) {
+ // This is a singleton or a SYSTEM user only provider, but a user besides the
+ // SYSTEM user is asking to initialize a process it runs
// in... well, no, it doesn't actually run in this process,
// it runs in the process of the default user. Get rid of it.
providers.remove(i);
@@ -1398,8 +1398,7 @@ public class ContentProviderHelper {
final boolean processMatch =
Objects.equals(pi.processName, app.processName)
|| pi.multiprocess;
- final boolean userMatch = !mService.isSingleton(
- pi.processName, pi.applicationInfo, pi.name, pi.flags)
+ final boolean userMatch = !isSingletonOrSystemUserOnly(pi)
|| app.userId == UserHandle.USER_SYSTEM;
final boolean isInstantApp = pi.applicationInfo.isInstantApp();
final boolean splitInstalled = pi.splitName == null
@@ -1985,4 +1984,13 @@ public class ContentProviderHelper {
return isAuthRedirected;
}
}
+
+ /**
+ * Returns true if Provider is either singleUser or systemUserOnly provider.
+ */
+ private boolean isSingletonOrSystemUserOnly(ProviderInfo pi) {
+ return (android.multiuser.Flags.enableSystemUserOnlyForServicesAndProviders()
+ && mService.isSystemUserOnly(pi.flags))
+ || mService.isSingleton(pi.processName, pi.applicationInfo, pi.name, pi.flags);
+ }
}