diff options
author | 2023-02-13 14:44:16 +0000 | |
---|---|---|
committer | 2023-02-17 18:20:24 +0000 | |
commit | 0ec406570d05cfe01db6da72b15b1282cfb794e6 (patch) | |
tree | d199a9896936488ebf46cdb97567c0ad15603891 | |
parent | 4526d3fb6fc9672e0d1cbbd620292a7af421cb02 (diff) |
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
-rw-r--r-- | core/api/current.txt | 1 | ||||
-rw-r--r-- | core/java/android/app/IActivityManager.aidl | 2 | ||||
-rw-r--r-- | core/java/android/content/ContentProvider.java | 148 | ||||
-rw-r--r-- | core/java/android/content/ContentProviderNative.java | 42 | ||||
-rw-r--r-- | core/java/android/content/ContentResolver.java | 11 | ||||
-rw-r--r-- | core/java/android/content/IContentProvider.java | 36 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 7 | ||||
-rw-r--r-- | services/core/java/com/android/server/am/ActivityManagerService.java | 12 | ||||
-rw-r--r-- | services/core/java/com/android/server/am/ContentProviderHelper.java | 132 | ||||
-rw-r--r-- | test-mock/src/android/test/mock/MockContentProvider.java | 27 | ||||
-rw-r--r-- | test-mock/src/android/test/mock/MockIContentProvider.java | 20 |
11 files changed, 313 insertions, 125 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index eef16bebea83..794d2c6c01b7 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9848,6 +9848,7 @@ package android.content { 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 aa5b86679849..f653e132f7e3 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -347,7 +347,7 @@ interface IActivityManager { 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 e8f0a89930ad..c8db0d8c958d 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -18,14 +18,15 @@ package android.content; 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } // 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(); + 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; try { - if (enforceReadPermission(attributionSource, uri) - != PermissionChecker.PERMISSION_GRANTED) { + 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, - enumFrameworkPermission, + enumCheckUriPermission, callingUid, uri.getAuthority(), type); - } else { - 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); - } } - } 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } 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 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * <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 class ContentProvider implements ContentInterface, ComponentCall 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 47c966990861..4ba3ff4fe3d3 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -140,8 +140,10 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ abstract public class ContentProviderNative extends Binder implements IContentPr 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 @@ final class ContentProviderProxy implements IContentProvider } @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,11 +535,12 @@ final class ContentProviderProxy implements IContentProvider } @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); @@ -539,6 +552,23 @@ final class ContentProviderProxy implements IContentProvider } @Override + /* oneway */ public void getTypeAnonymousAsync(Uri uri, RemoteCallback callback) + throws RemoteException { + Parcel data = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); + + uri.writeToParcel(data, 0); + callback.writeToParcel(data, 0); + + mRemote.transact(IContentProvider.GET_TYPE_ANONYMOUS_ASYNC_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + } finally { + data.recycle(); + } + } + + @Override public Uri insert(@NonNull AttributionSource attributionSource, Uri url, ContentValues values, Bundle extras) throws RemoteException { diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 37794531dfa3..b84eb118b8d4 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -920,8 +920,13 @@ public abstract class ContentResolver implements ContentInterface { 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 @@ public abstract class ContentResolver implements ContentInterface { 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 e0315a3e171b..eb80148250b9 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -18,6 +18,7 @@ package android.content; 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 @@ public interface IContentProvider extends IInterface { @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 @@ public interface IContentProvider extends IInterface { 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 e9771a9dcb7f..e2431221d995 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 96d5f38bd720..6aa49d276391 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.ApplicationStartInfo; 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 31301666ad53..f721d69958f5 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.PROC_SPACE_TERM; 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.ContentResolver; 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 @@ public class ContentProviderHelper { 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 @@ public class ContentProviderHelper { 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,67 +1084,84 @@ public class ContentProviderHelper { } } - 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); - } - }); - } catch (Exception e) { - // To ensure logging does not break the getType calls. + holder = getContentProviderExternalUnchecked(name, null /* token */, callingUid, + "*getmimetype*", safeUserId); + } finally { + if (ident != 0) { + Binder.restoreCallingIdentity(ident); + } } - } - // 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; + 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); + } + })); } + } else { + resultCallback.sendResult(Bundle.EMPTY); } - 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); + } catch (RemoteException e) { + Log.w(TAG, "Content provider dead retrieving " + uri, e); + resultCallback.sendResult(Bundle.EMPTY); } } + 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) { if (UserHandle.getUserId(callingUid) == userId) { return true; diff --git a/test-mock/src/android/test/mock/MockContentProvider.java b/test-mock/src/android/test/mock/MockContentProvider.java index 7be42f4f36f5..7f084f896e3f 100644 --- a/test-mock/src/android/test/mock/MockContentProvider.java +++ b/test-mock/src/android/test/mock/MockContentProvider.java @@ -80,16 +80,22 @@ public class MockContentProvider extends ContentProvider { } @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 @@ public class MockContentProvider extends ContentProvider { } @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 b81c70704d79..bb2996a2cb40 100644 --- a/test-mock/src/android/test/mock/MockIContentProvider.java +++ b/test-mock/src/android/test/mock/MockIContentProvider.java @@ -61,16 +61,30 @@ public class MockIContentProvider implements IContentProvider { } @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); }); } |