diff --git a/core/api/current.txt b/core/api/current.txt index 651669c0e1004..170a98548a19b 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 aa5b866798497..f653e132f7e30 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 e8f0a89930ad5..c8db0d8c958d9 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(); - 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 @@ 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 * Processes * and Threads. * - *
Note that there are no permissions needed for an application to + *
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.
+ * + *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.
* * @param uri the URI to query. * @return a MIME type string, or {@code null} if there is no type. @@ -1637,6 +1677,24 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall @Override 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. + * + *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}
+ * + * @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 diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index 47c9669908613..4ba3ff4fe3d3e 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,7 +535,25 @@ 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); + + 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 @@ final class ContentProviderProxy implements IContentProvider 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 37794531dfa3b..b84eb118b8d4e 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 e0315a3e171b6..eb80148250b94 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 e9771a9dcb7fb..e2431221d9959 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7604,6 +7604,13 @@