summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-09-05 18:11:45 -0700
committerJeff Sharkey <jsharkey@android.com>2013-09-06 08:08:18 -0700
commit911d7f411f36f2279aae44c89ff1d33a29140046 (patch)
tree98cdcb6ae4f9720b2096f252c8c0fe1d0726f56a
parenta61dc8e03e6e863005b3a4629ca8f3801d33d3c4 (diff)
downloadframeworks_base-911d7f411f36f2279aae44c89ff1d33a29140046.zip
frameworks_base-911d7f411f36f2279aae44c89ff1d33a29140046.tar.gz
frameworks_base-911d7f411f36f2279aae44c89ff1d33a29140046.tar.bz2
Provide calling package to ContentProviders.
The calling package is important for ContentProviders that want to grant Uri permissions as a side effect of operations, so offer it through a new API. Validates the provided package against the calling UID before returning. Bug: 10626527 Change-Id: I7277880eebbd48444c024bcf5f69199133cd59e4
-rw-r--r--api/current.txt2
-rw-r--r--core/java/android/app/AppOpsManager.java17
-rw-r--r--core/java/android/content/ContentProvider.java116
-rw-r--r--core/java/android/provider/DocumentsProvider.java20
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java5
-rw-r--r--services/java/com/android/server/AppOpsService.java15
-rw-r--r--services/java/com/android/server/DropBoxManagerService.java10
8 files changed, 146 insertions, 40 deletions
diff --git a/api/current.txt b/api/current.txt
index fa29b8f..1443bee 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3172,6 +3172,7 @@ package android.app {
public class AppOpsManager {
method public int checkOp(int, int, java.lang.String);
method public int checkOpNoThrow(int, int, java.lang.String);
+ method public void checkPackage(int, java.lang.String);
method public void finishOp(int, int, java.lang.String);
method public void finishOp(int);
method public int noteOp(int, int, java.lang.String);
@@ -5624,6 +5625,7 @@ package android.content {
method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle);
method public abstract int delete(android.net.Uri, java.lang.String, java.lang.String[]);
method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method public final java.lang.String getCallingPackage();
method public final android.content.Context getContext();
method public final android.content.pm.PathPermission[] getPathPermissions();
method public final java.lang.String getReadPermission();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index fe09ce1..19a028d 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -616,6 +616,23 @@ public class AppOpsManager {
}
/**
+ * Do a quick check to validate if a package name belongs to a UID.
+ *
+ * @throws SecurityException if the package name doesn't belong to the given
+ * UID, or if ownership cannot be verified.
+ */
+ public void checkPackage(int uid, String packageName) {
+ try {
+ if (mService.checkPackage(uid, packageName) != MODE_ALLOWED) {
+ throw new SecurityException(
+ "Package " + packageName + " does not belong to " + uid);
+ }
+ } catch (RemoteException e) {
+ throw new SecurityException("Unable to verify package ownership", e);
+ }
+ }
+
+ /**
* Make note of an application performing an operation. Note that you must pass
* in both the uid and name of the application to be checked; this function will verify
* that these two match, and if not, return {@link #MODE_IGNORED}. If this call
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index b9121c7..24c396a 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -102,6 +102,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
private boolean mExported;
private boolean mNoPerms;
+ private final ThreadLocal<String> mCallingPackage = new ThreadLocal<String>();
+
private Transport mTransport = new Transport();
/**
@@ -194,8 +196,14 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
return rejectQuery(uri, projection, selection, selectionArgs, sortOrder,
CancellationSignal.fromTransport(cancellationSignal));
}
- return ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder,
- CancellationSignal.fromTransport(cancellationSignal));
+ mCallingPackage.set(callingPkg);
+ try {
+ return ContentProvider.this.query(
+ uri, projection, selection, selectionArgs, sortOrder,
+ CancellationSignal.fromTransport(cancellationSignal));
+ } finally {
+ mCallingPackage.set(null);
+ }
}
@Override
@@ -208,7 +216,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return rejectInsert(uri, initialValues);
}
- return ContentProvider.this.insert(uri, initialValues);
+ mCallingPackage.set(callingPkg);
+ try {
+ return ContentProvider.this.insert(uri, initialValues);
+ } finally {
+ mCallingPackage.set(null);
+ }
}
@Override
@@ -216,7 +229,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return 0;
}
- return ContentProvider.this.bulkInsert(uri, initialValues);
+ mCallingPackage.set(callingPkg);
+ try {
+ return ContentProvider.this.bulkInsert(uri, initialValues);
+ } finally {
+ mCallingPackage.set(null);
+ }
}
@Override
@@ -238,7 +256,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
}
}
- return ContentProvider.this.applyBatch(operations);
+ mCallingPackage.set(callingPkg);
+ try {
+ return ContentProvider.this.applyBatch(operations);
+ } finally {
+ mCallingPackage.set(null);
+ }
}
@Override
@@ -246,7 +269,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return 0;
}
- return ContentProvider.this.delete(uri, selection, selectionArgs);
+ mCallingPackage.set(callingPkg);
+ try {
+ return ContentProvider.this.delete(uri, selection, selectionArgs);
+ } finally {
+ mCallingPackage.set(null);
+ }
}
@Override
@@ -255,7 +283,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
return 0;
}
- return ContentProvider.this.update(uri, values, selection, selectionArgs);
+ mCallingPackage.set(callingPkg);
+ try {
+ return ContentProvider.this.update(uri, values, selection, selectionArgs);
+ } finally {
+ mCallingPackage.set(null);
+ }
}
@Override
@@ -263,8 +296,13 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
throws FileNotFoundException {
enforceFilePermission(callingPkg, uri, mode);
- return ContentProvider.this.openFile(
- uri, mode, CancellationSignal.fromTransport(cancellationSignal));
+ mCallingPackage.set(callingPkg);
+ try {
+ return ContentProvider.this.openFile(
+ uri, mode, CancellationSignal.fromTransport(cancellationSignal));
+ } finally {
+ mCallingPackage.set(null);
+ }
}
@Override
@@ -272,13 +310,23 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
throws FileNotFoundException {
enforceFilePermission(callingPkg, uri, mode);
- return ContentProvider.this.openAssetFile(
- uri, mode, CancellationSignal.fromTransport(cancellationSignal));
+ mCallingPackage.set(callingPkg);
+ try {
+ return ContentProvider.this.openAssetFile(
+ uri, mode, CancellationSignal.fromTransport(cancellationSignal));
+ } finally {
+ mCallingPackage.set(null);
+ }
}
@Override
public Bundle call(String callingPkg, String method, String arg, Bundle extras) {
- return ContentProvider.this.callFromPackage(callingPkg, method, arg, extras);
+ mCallingPackage.set(callingPkg);
+ try {
+ return ContentProvider.this.call(method, arg, extras);
+ } finally {
+ mCallingPackage.set(null);
+ }
}
@Override
@@ -290,8 +338,13 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
enforceFilePermission(callingPkg, uri, "r");
- return ContentProvider.this.openTypedAssetFile(
- uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal));
+ mCallingPackage.set(callingPkg);
+ try {
+ return ContentProvider.this.openTypedAssetFile(
+ uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal));
+ } finally {
+ mCallingPackage.set(null);
+ }
}
@Override
@@ -461,6 +514,28 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
/**
+ * Return the package name of the caller that initiated the request being
+ * processed on the current thread. The returned package will have been
+ * verified to belong to the calling UID. Returns {@code null} if not
+ * currently processing a request.
+ * <p>
+ * This will always return {@code null} when processing
+ * {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests.
+ *
+ * @see Binder#getCallingUid()
+ * @see Context#grantUriPermission(String, Uri, int)
+ * @throws SecurityException if the calling package doesn't belong to the
+ * calling UID.
+ */
+ public final String getCallingPackage() {
+ final String pkg = mCallingPackage.get();
+ if (pkg != null) {
+ mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg);
+ }
+ return pkg;
+ }
+
+ /**
* Change the permission required to read data from the content
* provider. This is normally set for you from its manifest information
* when the provider is first created.
@@ -529,8 +604,6 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
/** @hide */
public final void setAppOps(int readOp, int writeOp) {
if (!mNoPerms) {
- mTransport.mAppOpsManager = (AppOpsManager)mContext.getSystemService(
- Context.APP_OPS_SERVICE);
mTransport.mReadOp = readOp;
mTransport.mWriteOp = writeOp;
}
@@ -1413,6 +1486,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
*/
if (mContext == null) {
mContext = context;
+ mTransport.mAppOpsManager = (AppOpsManager) mContext.getSystemService(
+ Context.APP_OPS_SERVICE);
mMyUid = Process.myUid();
if (info != null) {
setReadPermission(info.readPermission);
@@ -1452,15 +1527,6 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
/**
- * @hide
- * Front-end to {@link #call(String, String, android.os.Bundle)} that provides the name
- * of the calling package.
- */
- public Bundle callFromPackage(String callingPackag, String method, String arg, Bundle extras) {
- return call(method, arg, extras);
- }
-
- /**
* Call a provider-defined method. This can be used to implement
* interfaces that are cheaper and/or unnatural for a table-like
* model.
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index bdfb776..1b0fc4d 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -34,7 +34,6 @@ import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Point;
import android.net.Uri;
-import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
@@ -42,8 +41,6 @@ import android.os.ParcelFileDescriptor.OnCloseListener;
import android.provider.DocumentsContract.Document;
import android.util.Log;
-import com.android.internal.util.ArrayUtils;
-
import libcore.io.IoUtils;
import java.io.FileNotFoundException;
@@ -332,15 +329,22 @@ public abstract class DocumentsProvider extends ContentProvider {
throw new UnsupportedOperationException("Update not supported");
}
- /** {@hide} */
+ /**
+ * Implementation is provided by the parent class. Can be overridden to
+ * provide additional functionality, but subclasses <em>must</em> always
+ * call the superclass. If the superclass returns {@code null}, the subclass
+ * may implement custom behavior.
+ *
+ * @see #openDocument(String, String, CancellationSignal)
+ * @see #deleteDocument(String)
+ */
@Override
- public final Bundle callFromPackage(
- String callingPackage, String method, String arg, Bundle extras) {
+ public Bundle call(String method, String arg, Bundle extras) {
final Context context = getContext();
if (!method.startsWith("android:")) {
// Let non-platform methods pass through
- return super.callFromPackage(callingPackage, method, arg, extras);
+ return super.call(method, arg, extras);
}
final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID);
@@ -368,7 +372,7 @@ public abstract class DocumentsProvider extends ContentProvider {
if (!callerHasManage) {
final Uri newDocumentUri = DocumentsContract.buildDocumentUri(
mAuthority, newDocumentId);
- context.grantUriPermission(callingPackage, newDocumentUri,
+ context.grantUriPermission(getCallingPackage(), newDocumentUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_PERSIST_GRANT_URI_PERMISSION);
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index b798a1a..9056641 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -23,6 +23,7 @@ interface IAppOpsService {
// These first methods are also called by native code, so must
// be kept in sync with frameworks/native/include/binder/IAppOpsService.h
int checkOperation(int code, int uid, String packageName);
+ int checkPackage(int uid, String packageName);
int noteOperation(int code, int uid, String packageName);
int startOperation(IBinder token, int code, int uid, String packageName);
void finishOperation(IBinder token, int code, int uid, String packageName);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index a5dab33..bc02b0d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -567,8 +567,7 @@ public class SettingsProvider extends ContentProvider {
* Fast path that avoids the use of chatty remoted Cursors.
*/
@Override
- public Bundle callFromPackage(String callingPackage, String method, String request,
- Bundle args) {
+ public Bundle call(String method, String request, Bundle args) {
int callingUser = UserHandle.getCallingUserId();
if (args != null) {
int reqUser = args.getInt(Settings.CALL_METHOD_USER_KEY, callingUser);
@@ -623,7 +622,7 @@ public class SettingsProvider extends ContentProvider {
// Also need to take care of app op.
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SETTINGS, Binder.getCallingUid(),
- callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ getCallingPackage()) != AppOpsManager.MODE_ALLOWED) {
return null;
}
diff --git a/services/java/com/android/server/AppOpsService.java b/services/java/com/android/server/AppOpsService.java
index 7af95f3..c6c4a94 100644
--- a/services/java/com/android/server/AppOpsService.java
+++ b/services/java/com/android/server/AppOpsService.java
@@ -552,6 +552,17 @@ public class AppOpsService extends IAppOpsService.Stub {
}
@Override
+ public int checkPackage(int uid, String packageName) {
+ synchronized (this) {
+ if (getOpsLocked(uid, packageName, true) != null) {
+ return AppOpsManager.MODE_ALLOWED;
+ } else {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ }
+ }
+
+ @Override
public int noteOperation(int code, int uid, String packageName) {
verifyIncomingUid(uid);
verifyIncomingOp(code);
@@ -560,7 +571,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (ops == null) {
if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ " package " + packageName);
- return AppOpsManager.MODE_IGNORED;
+ return AppOpsManager.MODE_ERRORED;
}
Op op = getOpLocked(ops, code, true);
if (op.duration == -1) {
@@ -594,7 +605,7 @@ public class AppOpsService extends IAppOpsService.Stub {
if (ops == null) {
if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ " package " + packageName);
- return AppOpsManager.MODE_IGNORED;
+ return AppOpsManager.MODE_ERRORED;
}
Op op = getOpLocked(ops, code, true);
final int switchCode = AppOpsManager.opToSwitch(code);
diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java
index 5008270..29b04da 100644
--- a/services/java/com/android/server/DropBoxManagerService.java
+++ b/services/java/com/android/server/DropBoxManagerService.java
@@ -24,6 +24,7 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.Binder;
import android.os.Debug;
import android.os.DropBoxManager;
import android.os.FileUtils;
@@ -265,8 +266,13 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
}
public boolean isTagEnabled(String tag) {
- return !"disabled".equals(Settings.Global.getString(
- mContentResolver, Settings.Global.DROPBOX_TAG_PREFIX + tag));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return !"disabled".equals(Settings.Global.getString(
+ mContentResolver, Settings.Global.DROPBOX_TAG_PREFIX + tag));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
public synchronized DropBoxManager.Entry getNextEntry(String tag, long millis) {