Added a new api ContentProvider#getTypeAnonymous
- When an app is unable to acquire IContentProvider in contentResolver#getType,
they can only call getTypeAnonymous via activityManagerService.
- default getTypeUnchecked returns getType (to ensure less breaking)
- getType calls are protected by readPermission.
- updated logging of getType calls without readPermisison.
Bug: b/161370118
Test: Build, manual test and logging
Change-Id: Ia5fe69061de36ed70d93b30754d68b63ad791c83
diff --git a/core/api/current.txt b/core/api/current.txt
index eef16be..794d2c6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9848,6 +9848,7 @@
method @Nullable public final String getReadPermission();
method @Nullable public String[] getStreamTypes(@NonNull android.net.Uri, @NonNull String);
method @Nullable public abstract String getType(@NonNull android.net.Uri);
+ method @Nullable public String getTypeAnonymous(@NonNull android.net.Uri);
method @Nullable public final String getWritePermission();
method @Nullable public abstract android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues);
method @Nullable public android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable android.os.Bundle);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index aa5b866..f653e13 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -347,7 +347,7 @@
String getProviderMimeType(in Uri uri, int userId);
oneway void getProviderMimeTypeAsync(in Uri uri, int userId, in RemoteCallback resultCallback);
-
+ oneway void getMimeTypeFilterAsync(in Uri uri, int userId, in RemoteCallback resultCallback);
// Cause the specified process to dump the specified heap.
boolean dumpHeap(in String process, int userId, boolean managed, boolean mallocInfo,
boolean runGc, in String path, in ParcelFileDescriptor fd,
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index e8f0a89..c8db0d8 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -18,14 +18,15 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.os.Process.SYSTEM_UID;
import static android.os.Process.myUserHandle;
import static android.os.Trace.TRACE_TAG_DATABASE;
import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION;
import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_CHECK_URI_PERMISSION;
-import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_ERROR;
import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_FRAMEWORK_PERMISSION;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -300,17 +301,32 @@
}
@Override
- public String getType(Uri uri) {
+ public String getType(AttributionSource attributionSource, Uri uri) {
// getCallingPackage() isn't available in getType(), as the javadoc states.
uri = validateIncomingUri(uri);
uri = maybeGetUriWithoutUserId(uri);
traceBegin(TRACE_TAG_DATABASE, "getType: ", uri.getAuthority());
try {
- final String type = mInterface.getType(uri);
- if (type != null) {
- logGetTypeData(Binder.getCallingUid(), uri, type);
+ if (checkGetTypePermission(attributionSource, uri)
+ == PermissionChecker.PERMISSION_GRANTED) {
+ final String type = mInterface.getType(uri);
+ if (type != null) {
+ logGetTypeData(Binder.getCallingUid(), uri, type, true);
+ }
+ return type;
+ } else {
+ final int callingUid = Binder.getCallingUid();
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ final String type = getTypeAnonymous(uri);
+ if (type != null) {
+ logGetTypeData(callingUid, uri, type, false);
+ }
+ return type;
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
}
- return type;
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
@@ -319,59 +335,62 @@
}
// Utility function to log the getTypeData calls
- private void logGetTypeData(int callingUid, Uri uri, String type) {
+ private void logGetTypeData(int callingUid, Uri uri, String type,
+ boolean permissionCheckPassed) {
final int enumFrameworkPermission =
GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_FRAMEWORK_PERMISSION;
final int enumCheckUriPermission =
GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_CHECK_URI_PERMISSION;
- final int enumError = GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_ERROR;
-
- try {
- final AttributionSource attributionSource = new AttributionSource.Builder(
- callingUid).build();
- try {
- if (enforceReadPermission(attributionSource, uri)
- != PermissionChecker.PERMISSION_GRANTED) {
- FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
- enumFrameworkPermission,
- callingUid, uri.getAuthority(), type);
- } else {
- final ProviderInfo cpi = mContext.getPackageManager()
- .resolveContentProvider(uri.getAuthority(),
+ if (permissionCheckPassed) {
+ // Just for logging for mediaProvider cases
+ final ProviderInfo cpi = mContext.getPackageManager()
+ .resolveContentProvider(uri.getAuthority(),
PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
- final int callingUserId = UserHandle.getUserId(callingUid);
- final Uri userUri = (mSingleUser
- && !UserHandle.isSameUser(mMyUid, callingUid))
- ? maybeAddUserId(uri, callingUserId) : uri;
- if (cpi.forceUriPermissions
- && mInterface.checkUriPermission(uri,
- callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
- != PermissionChecker.PERMISSION_GRANTED
- && getContext().checkUriPermission(userUri, Binder.getCallingPid(),
- callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
- != PackageManager.PERMISSION_GRANTED) {
- FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
- enumCheckUriPermission,
- callingUid, uri.getAuthority(), type);
- }
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ final Uri userUri = (mSingleUser
+ && !UserHandle.isSameUser(mMyUid, callingUid))
+ ? maybeAddUserId(uri, callingUserId) : uri;
+ try {
+ if (cpi.forceUriPermissions
+ && mInterface.checkUriPermission(uri,
+ callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ != PermissionChecker.PERMISSION_GRANTED
+ && getContext().checkUriPermission(userUri, Binder.getCallingPid(),
+ callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ != PackageManager.PERMISSION_GRANTED) {
+ FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
+ enumCheckUriPermission,
+ callingUid, uri.getAuthority(), type);
}
- } catch (SecurityException e) {
- FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
- enumFrameworkPermission,
- callingUid, uri.getAuthority(), type);
+ } catch (RemoteException e) {
+ //does nothing
}
- } catch (Exception e) {
+ } else {
FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
- enumError,
+ enumFrameworkPermission,
callingUid, uri.getAuthority(), type);
}
}
@Override
- public void getTypeAsync(Uri uri, RemoteCallback callback) {
+ public void getTypeAsync(AttributionSource attributionSource,
+ Uri uri, RemoteCallback callback) {
final Bundle result = new Bundle();
try {
- result.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getType(uri));
+ result.putString(ContentResolver.REMOTE_CALLBACK_RESULT,
+ getType(attributionSource, uri));
+ } catch (Exception e) {
+ result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR,
+ new ParcelableException(e));
+ }
+ callback.sendResult(result);
+ }
+
+ @Override
+ public void getTypeAnonymousAsync(Uri uri, RemoteCallback callback) {
+ final Bundle result = new Bundle();
+ try {
+ result.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getTypeAnonymous(uri));
} catch (Exception e) {
result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR,
new ParcelableException(e));
@@ -795,6 +814,23 @@
}
return PermissionChecker.PERMISSION_GRANTED;
}
+
+ @PermissionCheckerManager.PermissionResult
+ private int checkGetTypePermission(@NonNull AttributionSource attributionSource,
+ Uri uri) {
+ final int callingUid = Binder.getCallingUid();
+ if (UserHandle.getAppId(callingUid) == SYSTEM_UID
+ || checkPermission(Manifest.permission.GET_ANY_PROVIDER_TYPE, attributionSource)
+ == PermissionChecker.PERMISSION_GRANTED) {
+ // Allowing System Uid and apps with permission to get any type, to access all types
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+ try {
+ return enforceReadPermission(attributionSource, uri);
+ } catch (SecurityException e) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+ }
}
boolean checkUser(int pid, int uid, Context context) {
@@ -1625,11 +1661,15 @@
* <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
* and Threads</a>.
*
- * <p>Note that there are no permissions needed for an application to
+ * <p>Note that by default there are no permissions needed for an application to
* access this information; if your content provider requires read and/or
* write permissions, or is not exported, all applications can still call
- * this method regardless of their access permissions. This allows them
- * to retrieve the MIME type for a URI when dispatching intents.
+ * this method regardless of their access permissions. </p>
+ *
+ * <p>If your mime type reveals details that should be protected,
+ * then you should protect this method by implementing {@link #getTypeAnonymous}.
+ * Implementing {@link #getTypeAnonymous} ensures your {@link #getType} can be
+ * only accessed by caller's having associated readPermission for the URI. </p>
*
* @param uri the URI to query.
* @return a MIME type string, or {@code null} if there is no type.
@@ -1638,6 +1678,24 @@
public abstract @Nullable String getType(@NonNull Uri uri);
/**
+ * Implement this to handle requests for MIME type of URIs, that does not need to
+ * reveal any internal information which should be protected by any permission.
+ *
+ * <p>If your mime type reveals details that should be protected, then you should protect those
+ * by implementing those in {@link #getType}, and in this function, only return types of
+ * URIs which can be obtained by anyone without any access.
+ *
+ * Implementing ths function will make sure {@link #getType} is protected by readPermission.
+ * This function by default works as the {@link #getType}</p>
+ *
+ * @param uri the URI to query.
+ * @return a MIME type string, or {@code null} if type needs to be protected.
+ */
+ public @Nullable String getTypeAnonymous(@NonNull Uri uri) {
+ return getType(uri);
+ }
+
+ /**
* Implement this to support canonicalization of URIs that refer to your
* content provider. A canonical URI is one that can be transported across
* devices, backup/restore, and other contexts, and still be able to refer
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index 47c96699..4ba3ff4 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -140,8 +140,10 @@
case GET_TYPE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
- String type = getType(url);
+ String type = getType(attributionSource, url);
reply.writeNoException();
reply.writeString(type);
@@ -150,9 +152,19 @@
case GET_TYPE_ASYNC_TRANSACTION: {
data.enforceInterface(IContentProvider.descriptor);
+ AttributionSource attributionSource = AttributionSource.CREATOR
+ .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);
RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data);
- getTypeAsync(url, callback);
+ getTypeAsync(attributionSource, url, callback);
+ return true;
+ }
+
+ case GET_TYPE_ANONYMOUS_ASYNC_TRANSACTION: {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data);
+ getTypeAnonymousAsync(url, callback);
return true;
}
@@ -502,13 +514,13 @@
}
@Override
- public String getType(Uri url) throws RemoteException
+ public String getType(AttributionSource attributionSource, Uri url) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
-
+ attributionSource.writeToParcel(data, 0);
url.writeToParcel(data, 0);
mRemote.transact(IContentProvider.GET_TYPE_TRANSACTION, data, reply, 0);
@@ -523,7 +535,25 @@
}
@Override
- /* oneway */ public void getTypeAsync(Uri uri, RemoteCallback callback) throws RemoteException {
+ /* oneway */ public void getTypeAsync(AttributionSource attributionSource,
+ Uri uri, RemoteCallback callback) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+ attributionSource.writeToParcel(data, 0);
+ uri.writeToParcel(data, 0);
+ callback.writeToParcel(data, 0);
+
+ mRemote.transact(IContentProvider.GET_TYPE_ASYNC_TRANSACTION, data, null,
+ IBinder.FLAG_ONEWAY);
+ } finally {
+ data.recycle();
+ }
+ }
+
+ @Override
+ /* oneway */ public void getTypeAnonymousAsync(Uri uri, RemoteCallback callback)
+ throws RemoteException {
Parcel data = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
@@ -531,7 +561,7 @@
uri.writeToParcel(data, 0);
callback.writeToParcel(data, 0);
- mRemote.transact(IContentProvider.GET_TYPE_ASYNC_TRANSACTION, data, null,
+ mRemote.transact(IContentProvider.GET_TYPE_ANONYMOUS_ASYNC_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
} finally {
data.recycle();
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 3779453..b84eb11 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -920,8 +920,13 @@
return null;
}
- // XXX would like to have an acquireExistingUnstableProvider for this.
- IContentProvider provider = acquireExistingProvider(url);
+ IContentProvider provider = null;
+ try {
+ provider = acquireProvider(url);
+ } catch (Exception e) {
+ // if unable to acquire the provider, then it should try to get the type
+ // using getTypeAnonymous via ActivityManagerService
+ }
if (provider != null) {
try {
final StringResultListener resultListener = new StringResultListener();
@@ -949,7 +954,7 @@
try {
final StringResultListener resultListener = new StringResultListener();
- ActivityManager.getService().getProviderMimeTypeAsync(
+ ActivityManager.getService().getMimeTypeFilterAsync(
ContentProvider.getUriWithoutUserId(url),
resolveUserId(url),
new RemoteCallback(resultListener));
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index e0315a3..eb80148 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppGlobals;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
@@ -44,14 +45,42 @@
@Nullable String[] projection,
@Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal)
throws RemoteException;
- String getType(Uri url) throws RemoteException;
+ /**
+ * getType function with AttributionSource
+ */
+ String getType(@NonNull AttributionSource attributionSource,
+ Uri url) throws RemoteException;
+ /**
+ * one way getType function with AttributionSource
+ */
+ void getTypeAsync(@NonNull AttributionSource attributionSource,
+ Uri url, RemoteCallback callback) throws RemoteException;
+ /**
+ * @deprecated -- use getType with AttributionSource
+ */
+ @Deprecated
+ default String getType(Uri url) throws RemoteException {
+ return getType(new AttributionSource(Binder.getCallingUid(),
+ AppGlobals.getPackageManager().getPackagesForUid(Binder.getCallingUid())[0],
+ null), url);
+ }
/**
* A oneway version of getType. The functionality is exactly the same, except that the
* call returns immediately, and the resulting type is returned when available via
* a binder callback.
+ *
+ * @deprecated -- use getTypeAsync with AttributionSource
*/
- void getTypeAsync(Uri uri, RemoteCallback callback) throws RemoteException;
+ @Deprecated
+ default void getTypeAsync(Uri uri, RemoteCallback callback) throws RemoteException {
+ getTypeAsync(new AttributionSource(Binder.getCallingUid(),
+ AppGlobals.getPackageManager().getPackagesForUid(Binder.getCallingUid())[0],
+ null), uri, callback);
+ }
+
+ /** oneway version of getTypeAnonymous*/
+ void getTypeAnonymousAsync(Uri uri, RemoteCallback callback) throws RemoteException;
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
@@ -185,4 +214,7 @@
int GET_TYPE_ASYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 28;
int CANONICALIZE_ASYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 29;
int UNCANONICALIZE_ASYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 30;
+ int GET_TYPE_ANONYMOUS_ASYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 31;
+
+
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e9771a9..e243122 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7604,6 +7604,13 @@
<permission android:name="android.permission.LOG_PROCESS_ACTIVITIES"
android:protectionLevel="signature|privileged" />
+ <!-- @hide Allows an application to get type of any provider uri.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.GET_ANY_PROVIDER_TYPE"
+ android:protectionLevel="signature" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 96d5f38..6aa49d2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -191,7 +191,6 @@
import android.app.ApplicationThreadConstants;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
-import android.app.ComponentOptions;
import android.app.ContentProviderHolder;
import android.app.ForegroundServiceDelegationOptions;
import android.app.IActivityController;
@@ -7031,6 +7030,17 @@
mCpHelper.getProviderMimeTypeAsync(uri, userId, resultCallback);
}
+ /**
+ * Filters calls to getType based on permission. If the caller has required permission,
+ * then it returns the contentProvider#getType.
+ * Else, it returns the contentProvider#getTypeAnonymous, which does not
+ * reveal any internal information which should be protected by any permission.
+ */
+ @Override
+ public void getMimeTypeFilterAsync(Uri uri, int userId, RemoteCallback resultCallback) {
+ mCpHelper.getMimeTypeFilterAsync(uri, userId, resultCallback);
+ }
+
// =========================================================
// GLOBAL MANAGEMENT
// =========================================================
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 3130166..f721d69 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -15,6 +15,7 @@
*/
package com.android.server.am;
+import static android.Manifest.permission.GET_ANY_PROVIDER_TYPE;
import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile;
import static android.os.Process.PROC_CHAR;
import static android.os.Process.PROC_OUT_LONG;
@@ -23,8 +24,6 @@
import static android.os.Process.SYSTEM_UID;
import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION;
-import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_CHECK_URI_PERMISSION;
-import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_ERROR;
import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_FRAMEWORK_PERMISSION;
import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
@@ -52,7 +51,6 @@
import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
-import android.content.PermissionChecker;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -1019,9 +1017,6 @@
mService.mHandler.postDelayed(providerNotResponding, 1000);
try {
final String type = holder.provider.getType(uri);
- if (type != null) {
- backgroundLogging(callingUid, callingPid, userId, uri, holder, type);
- }
return type;
} finally {
mService.mHandler.removeCallbacks(providerNotResponding);
@@ -1079,10 +1074,6 @@
Binder.restoreCallingIdentity(identity);
}
resultCallback.sendResult(result);
- final String type = result.getPairValue();
- if (type != null) {
- backgroundLogging(callingUid, callingPid, userId, uri, holder, type);
- }
}));
} else {
resultCallback.sendResult(Bundle.EMPTY);
@@ -1093,65 +1084,82 @@
}
}
- private void backgroundLogging(int callingUid, int callingPid, int userId, Uri uri,
- ContentProviderHolder holder, String type) {
- // Push the logging code in a different handlerThread.
+ /**
+ * Filters calls to getType based on permission. If the caller has required permission,
+ * then it returns the contentProvider#getType.
+ * Else, it returns the contentProvider#getTypeAnonymous, which does not
+ * reveal any internal information which should be protected by any permission.
+ */
+ void getMimeTypeFilterAsync(Uri uri, int userId, RemoteCallback resultCallback) {
+ mService.enforceNotIsolatedCaller("getProviderMimeTypeAsync");
+ final String name = uri.getAuthority();
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId);
+ final long ident = canClearIdentity(callingPid, callingUid, safeUserId)
+ ? Binder.clearCallingIdentity() : 0;
+ final ContentProviderHolder holder;
try {
- mService.mHandler.post(new Runnable() {
- @Override
- public void run() {
- logGetTypeData(callingUid, callingPid, userId,
- uri, holder, type);
+ holder = getContentProviderExternalUnchecked(name, null /* token */, callingUid,
+ "*getmimetype*", safeUserId);
+ } finally {
+ if (ident != 0) {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ try {
+ if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) {
+ if (checkGetAnyTypePermission(callingUid, callingPid)) {
+ final AttributionSource attributionSource =
+ new AttributionSource.Builder(callingUid).build();
+ holder.provider.getTypeAsync(attributionSource,
+ uri, new RemoteCallback(result -> {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ removeContentProviderExternalUnchecked(name, null, safeUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ resultCallback.sendResult(result);
+ }));
+ } else {
+ holder.provider.getTypeAnonymousAsync(uri, new RemoteCallback(result -> {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ removeContentProviderExternalUnchecked(name, null, safeUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ resultCallback.sendResult(result);
+ final String type = result.getPairValue();
+ if (type != null) {
+ logGetTypeData(callingUid, uri, type);
+ }
+ }));
}
- });
- } catch (Exception e) {
- // To ensure logging does not break the getType calls.
+ } else {
+ resultCallback.sendResult(Bundle.EMPTY);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Content provider dead retrieving " + uri, e);
+ resultCallback.sendResult(Bundle.EMPTY);
}
}
- // Utility function to log the getTypeData calls
- private void logGetTypeData(int callingUid, int callingPid, int userId, Uri uri,
- ContentProviderHolder holder, String type) {
- try {
- boolean checkUser = true;
- final String authority = uri.getAuthority();
- if (isAuthorityRedirectedForCloneProfile(authority)) {
- UserManagerInternal umInternal =
- LocalServices.getService(UserManagerInternal.class);
- UserInfo userInfo = umInternal.getUserInfo(userId);
-
- if (userInfo != null && userInfo.isCloneProfile()) {
- userId = umInternal.getProfileParentId(userId);
- checkUser = false;
- }
- }
- final ProviderInfo cpi = holder.info;
- final AttributionSource attributionSource =
- new AttributionSource.Builder(callingUid).build();
- final String permissionCheck =
- checkContentProviderPermission(cpi, callingPid, callingUid,
- userId, checkUser, null);
- final boolean grantCheck = mService.checkUriPermission(uri, callingPid, callingUid,
- Intent.FLAG_GRANT_READ_URI_PERMISSION , userId, null)
- == PackageManager.PERMISSION_GRANTED;
-
- if (!grantCheck && permissionCheck != null) {
- FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
- GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_FRAMEWORK_PERMISSION,
- callingUid, authority, type);
- } else if (!grantCheck && cpi.forceUriPermissions
- && holder.provider.checkUriPermission(attributionSource,
- uri, callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
- != PermissionChecker.PERMISSION_GRANTED) {
- FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
- GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_CHECK_URI_PERMISSION,
- callingUid, authority, type);
- }
- } catch (Exception e) {
- FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
- GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_ERROR, callingUid,
- uri.getAuthority(), type);
+ private boolean checkGetAnyTypePermission(int callingUid, int callingPid) {
+ if (mService.checkPermission(GET_ANY_PROVIDER_TYPE, callingPid, callingUid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
}
+ return false;
+ }
+
+ // Utility function to log the getTypeData calls
+ private void logGetTypeData(int callingUid, Uri uri, String type) {
+ FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
+ GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_FRAMEWORK_PERMISSION,
+ callingUid, uri.getAuthority(), type);
}
private boolean canClearIdentity(int callingPid, int callingUid, int userId) {
diff --git a/test-mock/src/android/test/mock/MockContentProvider.java b/test-mock/src/android/test/mock/MockContentProvider.java
index 7be42f4..7f084f89 100644
--- a/test-mock/src/android/test/mock/MockContentProvider.java
+++ b/test-mock/src/android/test/mock/MockContentProvider.java
@@ -80,16 +80,22 @@
}
@Override
- public String getType(Uri url) throws RemoteException {
+ public String getType(@NonNull AttributionSource attributionSource,
+ Uri url) throws RemoteException {
return MockContentProvider.this.getType(url);
}
@Override
- public void getTypeAsync(Uri uri, RemoteCallback callback) throws RemoteException {
+ public void getTypeAsync(@NonNull AttributionSource attributionSource,
+ Uri uri, RemoteCallback callback) throws RemoteException {
MockContentProvider.this.getTypeAsync(uri, callback);
}
@Override
+ public void getTypeAnonymousAsync(Uri uri, RemoteCallback callback) throws RemoteException {
+ MockContentProvider.this.getTypeAnonymousAsync(uri, callback);
+ }
+ @Override
public Uri insert(@NonNull AttributionSource attributionSource, Uri url,
ContentValues initialValues, Bundle extras) throws RemoteException {
return MockContentProvider.this.insert(url, initialValues, extras);
@@ -247,6 +253,23 @@
}
@Override
+ public String getTypeAnonymous(Uri uri) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ /**
+ * @hide
+ */
+ @SuppressWarnings("deprecation")
+ public void getTypeAnonymousAsync(Uri uri, RemoteCallback remoteCallback) {
+ AsyncTask.SERIAL_EXECUTOR.execute(() -> {
+ final Bundle bundle = new Bundle();
+ bundle.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getTypeAnonymous(uri));
+ remoteCallback.sendResult(bundle);
+ });
+ }
+
+ @Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException("unimplemented mock method");
}
diff --git a/test-mock/src/android/test/mock/MockIContentProvider.java b/test-mock/src/android/test/mock/MockIContentProvider.java
index b81c707..bb2996a 100644
--- a/test-mock/src/android/test/mock/MockIContentProvider.java
+++ b/test-mock/src/android/test/mock/MockIContentProvider.java
@@ -61,16 +61,30 @@
}
@Override
- public String getType(Uri url) {
+ public String getType(@NonNull AttributionSource attributionSource, Uri url) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
@SuppressWarnings("deprecation")
- public void getTypeAsync(Uri uri, RemoteCallback remoteCallback) {
+ public void getTypeAsync(@NonNull AttributionSource attributionSource,
+ Uri uri, RemoteCallback remoteCallback) {
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
final Bundle bundle = new Bundle();
- bundle.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getType(uri));
+ bundle.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getType(attributionSource,
+ uri));
+ remoteCallback.sendResult(bundle);
+ });
+ }
+ public String getTypeAnonymous(Uri url) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+ @Override
+ @SuppressWarnings("deprecation")
+ public void getTypeAnonymousAsync(Uri uri, RemoteCallback remoteCallback) {
+ AsyncTask.SERIAL_EXECUTOR.execute(() -> {
+ final Bundle bundle = new Bundle();
+ bundle.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getTypeAnonymous(uri));
remoteCallback.sendResult(bundle);
});
}