summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityManagerInternal.java7
-rw-r--r--core/java/android/content/ContentInterface.java3
-rw-r--r--core/java/android/content/ContentProvider.java23
-rw-r--r--core/java/android/content/ContentProviderClient.java19
-rw-r--r--core/java/android/content/ContentProviderNative.java36
-rw-r--r--core/java/android/content/ContentResolver.java19
-rw-r--r--core/java/android/content/IContentProvider.java5
-rw-r--r--core/java/android/content/LoggingContentInterface.java13
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java59
-rw-r--r--services/core/java/com/android/server/uri/UriGrantsManagerService.java20
-rw-r--r--test-mock/src/android/test/mock/MockContentProvider.java13
-rw-r--r--test-mock/src/android/test/mock/MockIContentProvider.java8
12 files changed, 224 insertions, 1 deletions
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index f8e6ae50d765..69e71185f228 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -26,6 +26,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ActivityPresentationInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.UserInfo;
+import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.TransactionTooLargeException;
@@ -52,6 +53,12 @@ public abstract class ActivityManagerInternal {
*/
public abstract String checkContentProviderAccess(String authority, int userId);
+ /**
+ * Verify that calling UID has access to the given provider.
+ */
+ public abstract int checkContentProviderUriPermission(Uri uri, int userId,
+ int callingUid, int modeFlags);
+
// Called by the power manager.
public abstract void onWakefulnessChanged(int wakefulness);
diff --git a/core/java/android/content/ContentInterface.java b/core/java/android/content/ContentInterface.java
index d41d8d9cc1e2..197de9711296 100644
--- a/core/java/android/content/ContentInterface.java
+++ b/core/java/android/content/ContentInterface.java
@@ -56,6 +56,9 @@ public interface ContentInterface {
public boolean refresh(@NonNull Uri uri, @Nullable Bundle args,
@Nullable CancellationSignal cancellationSignal) throws RemoteException;
+ public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags)
+ throws RemoteException;
+
public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues initialValues)
throws RemoteException;
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 7cdd2683905f..3c799915b871 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -28,6 +28,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.app.AppOpsManager;
+import android.content.pm.PackageManager;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
import android.content.res.AssetFileDescriptor;
@@ -582,6 +583,22 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
}
}
+ @Override
+ public int checkUriPermission(String callingPkg, Uri uri, int uid, int modeFlags) {
+ uri = validateIncomingUri(uri);
+ uri = maybeGetUriWithoutUserId(uri);
+ Trace.traceBegin(TRACE_TAG_DATABASE, "checkUriPermission");
+ final String original = setCallingPackage(callingPkg);
+ try {
+ return mInterface.checkUriPermission(uri, uid, modeFlags);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ setCallingPackage(original);
+ Trace.traceEnd(TRACE_TAG_DATABASE);
+ }
+ }
+
private void enforceFilePermission(String callingPkg, Uri uri, String mode,
IBinder callerToken) throws FileNotFoundException, SecurityException {
if (mode != null && mode.indexOf('w') != -1) {
@@ -1416,6 +1433,12 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
return false;
}
+ /** {@hide} */
+ @Override
+ public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
/**
* @hide
* Implementation when a caller has performed an insert on the content
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 93bf5188705c..8a4330ec0ede 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -307,6 +307,25 @@ public class ContentProviderClient implements ContentInterface, AutoCloseable {
}
}
+ /** {@hide} */
+ @Override
+ public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags)
+ throws RemoteException {
+ Preconditions.checkNotNull(uri, "uri");
+
+ beforeRemote();
+ try {
+ return mContentProvider.checkUriPermission(mPackageName, uri, uid, modeFlags);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ } finally {
+ afterRemote();
+ }
+ }
+
/** See {@link ContentProvider#insert ContentProvider.insert} */
@Override
public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues)
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index 994833866f40..cd735d4b10a3 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -363,6 +363,19 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
reply.writeInt(out ? 0 : -1);
return true;
}
+
+ case CHECK_URI_PERMISSION_TRANSACTION: {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ Uri uri = Uri.CREATOR.createFromParcel(data);
+ int uid = data.readInt();
+ int modeFlags = data.readInt();
+
+ int out = checkUriPermission(callingPkg, uri, uid, modeFlags);
+ reply.writeNoException();
+ reply.writeInt(out);
+ return true;
+ }
}
} catch (Exception e) {
DatabaseUtils.writeExceptionToParcel(reply, e);
@@ -800,6 +813,29 @@ final class ContentProviderProxy implements IContentProvider
}
}
+ @Override
+ public int checkUriPermission(String callingPkg, Uri url, int uid, int modeFlags)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ url.writeToParcel(data, 0);
+ data.writeInt(uid);
+ data.writeInt(modeFlags);
+
+ mRemote.transact(IContentProvider.CHECK_URI_PERMISSION_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ return reply.readInt();
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
@UnsupportedAppUsage
private IBinder mRemote;
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 0a1bc85202ff..9c863591f16f 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -31,6 +31,7 @@ import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.UriGrantsManager;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
@@ -1146,6 +1147,24 @@ public abstract class ContentResolver implements ContentInterface {
}
}
+ /** {@hide} */
+ @Override
+ public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags) {
+ Preconditions.checkNotNull(uri, "uri");
+
+ try {
+ if (mWrapped != null) return mWrapped.checkUriPermission(uri, uid, modeFlags);
+ } catch (RemoteException e) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ try (ContentProviderClient client = acquireUnstableContentProviderClient(uri)) {
+ return client.checkUriPermission(uri, uid, modeFlags);
+ } catch (RemoteException e) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ }
+
/**
* Open a stream on to the content associated with a content URI. If there
* is no data associated with the URI, FileNotFoundException is thrown.
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 0427c2f52415..fade0ab6bb5c 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -16,6 +16,7 @@
package android.content;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
@@ -82,6 +83,9 @@ public interface IContentProvider extends IInterface {
public Bundle call(String callingPkg, String authority, String method,
@Nullable String arg, @Nullable Bundle extras) throws RemoteException;
+ public int checkUriPermission(String callingPkg, Uri uri, int uid, int modeFlags)
+ throws RemoteException;
+
public ICancellationSignal createCancellationSignal() throws RemoteException;
public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException;
@@ -116,4 +120,5 @@ public interface IContentProvider extends IInterface {
static final int CANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 24;
static final int UNCANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 25;
static final int REFRESH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 26;
+ static final int CHECK_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 27;
}
diff --git a/core/java/android/content/LoggingContentInterface.java b/core/java/android/content/LoggingContentInterface.java
index 83c0c9112387..1df1c4faf2fe 100644
--- a/core/java/android/content/LoggingContentInterface.java
+++ b/core/java/android/content/LoggingContentInterface.java
@@ -165,6 +165,19 @@ public class LoggingContentInterface implements ContentInterface {
}
@Override
+ public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags)
+ throws RemoteException {
+ try (Logger l = new Logger("checkUriPermission", uri, uid, modeFlags)) {
+ try {
+ return l.setResult(delegate.checkUriPermission(uri, uid, modeFlags));
+ } catch (Exception res) {
+ l.setResult(res);
+ throw res;
+ }
+ }
+ }
+
+ @Override
public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues initialValues)
throws RemoteException {
try (Logger l = new Logger("insert", uri, initialValues)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index adb0909f46b8..54fc73993323 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -392,7 +392,9 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -7706,6 +7708,34 @@ public class ActivityManagerService extends IActivityManager.Stub
return null;
}
+ int checkContentProviderUriPermission(Uri uri, int userId, int callingUid, int modeFlags) {
+ final String name = uri.getAuthority();
+ final long ident = Binder.clearCallingIdentity();
+ ContentProviderHolder holder = null;
+ try {
+ holder = getContentProviderExternalUnchecked(name, null, callingUid,
+ "*checkContentProviderUriPermission*", userId);
+ if (holder != null) {
+ return holder.provider.checkUriPermission(null, uri, callingUid, modeFlags);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Content provider dead retrieving " + uri, e);
+ return PackageManager.PERMISSION_DENIED;
+ } catch (Exception e) {
+ Log.w(TAG, "Exception while determining type of " + uri, e);
+ return PackageManager.PERMISSION_DENIED;
+ } finally {
+ try {
+ if (holder != null) {
+ removeContentProviderExternalUnchecked(name, null, userId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ return PackageManager.PERMISSION_DENIED;
+ }
+
private boolean canClearIdentity(int callingPid, int callingUid, int userId) {
if (UserHandle.getUserId(callingUid) == userId) {
return true;
@@ -17826,6 +17856,35 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
+ public int checkContentProviderUriPermission(Uri uri, int userId,
+ int callingUid, int modeFlags) {
+ // We can find ourselves needing to check Uri permissions while
+ // already holding the WM lock, which means reaching back here for
+ // the AM lock would cause an inversion. The WM team has requested
+ // that we use the strategy below instead of shifting where Uri
+ // grants are calculated.
+
+ // Since we could also arrive here while holding the AM lock, we
+ // can't always delegate the call through the handler, and we need
+ // to delicately dance between the deadlocks.
+ if (Thread.currentThread().holdsLock(ActivityManagerService.this)) {
+ return ActivityManagerService.this.checkContentProviderUriPermission(uri,
+ userId, callingUid, modeFlags);
+ } else {
+ final CompletableFuture<Integer> res = new CompletableFuture<>();
+ mHandler.post(() -> {
+ res.complete(ActivityManagerService.this.checkContentProviderUriPermission(uri,
+ userId, callingUid, modeFlags));
+ });
+ try {
+ return res.get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
public void onWakefulnessChanged(int wakefulness) {
ActivityManagerService.this.onWakefulnessChanged(wakefulness);
}
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 55f062bca2d1..b2f115374305 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -949,7 +949,25 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
return false;
}
- return readMet && writeMet;
+ // If this provider says that grants are always required, we need to
+ // consult it directly to determine if the UID has permission
+ final boolean forceMet;
+ if (pi.forceUriPermissions) {
+ final int providerUserId = UserHandle.getUserId(pi.applicationInfo.uid);
+ final int clientUserId = UserHandle.getUserId(uid);
+ if (providerUserId == clientUserId) {
+ forceMet = (mAmInternal.checkContentProviderUriPermission(grantUri.uri,
+ providerUserId, uid, modeFlags) == PackageManager.PERMISSION_GRANTED);
+ } else {
+ // The provider can't track cross-user permissions, so we have
+ // to assume they're always denied
+ forceMet = false;
+ }
+ } else {
+ forceMet = true;
+ }
+
+ return readMet && writeMet && forceMet;
}
private void removeUriPermissionIfNeeded(UriPermission perm) {
diff --git a/test-mock/src/android/test/mock/MockContentProvider.java b/test-mock/src/android/test/mock/MockContentProvider.java
index e9a5ff70a7cc..4d8c7d930bde 100644
--- a/test-mock/src/android/test/mock/MockContentProvider.java
+++ b/test-mock/src/android/test/mock/MockContentProvider.java
@@ -16,6 +16,7 @@
package android.test.mock;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
@@ -23,6 +24,7 @@ import android.content.ContentProviderResult;
import android.content.ContentValues;
import android.content.Context;
import android.content.IContentProvider;
+import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
@@ -154,6 +156,11 @@ public class MockContentProvider extends ContentProvider {
ICancellationSignal cancellationSignal) throws RemoteException {
return MockContentProvider.this.refresh(url, args);
}
+
+ @Override
+ public int checkUriPermission(String callingPkg, Uri uri, int uid, int modeFlags) {
+ return MockContentProvider.this.checkUriPermission(uri, uid, modeFlags);
+ }
}
private final InversionIContentProvider mIContentProvider = new InversionIContentProvider();
@@ -266,6 +273,12 @@ public class MockContentProvider extends ContentProvider {
throw new UnsupportedOperationException("unimplemented mock method call");
}
+ /** {@hide} */
+ @Override
+ public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags) {
+ throw new UnsupportedOperationException("unimplemented mock method call");
+ }
+
/**
* Returns IContentProvider which calls back same methods in this class.
* By overriding this class, we avoid the mechanism hidden behind ContentProvider
diff --git a/test-mock/src/android/test/mock/MockIContentProvider.java b/test-mock/src/android/test/mock/MockIContentProvider.java
index fc2a4644b994..b072d7440de4 100644
--- a/test-mock/src/android/test/mock/MockIContentProvider.java
+++ b/test-mock/src/android/test/mock/MockIContentProvider.java
@@ -16,12 +16,14 @@
package android.test.mock;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentValues;
import android.content.EntityIterator;
import android.content.IContentProvider;
+import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
@@ -144,4 +146,10 @@ public class MockIContentProvider implements IContentProvider {
ICancellationSignal cancellationSignal) throws RemoteException {
throw new UnsupportedOperationException("unimplemented mock method");
}
+
+ /** {@hide} */
+ @Override
+ public int checkUriPermission(String callingPkg, Uri uri, int uid, int modeFlags) {
+ throw new UnsupportedOperationException("unimplemented mock method call");
+ }
}