diff options
author | Jeff Sharkey <jsharkey@android.com> | 2013-09-05 18:11:45 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2013-09-06 08:08:18 -0700 |
commit | 911d7f411f36f2279aae44c89ff1d33a29140046 (patch) | |
tree | 98cdcb6ae4f9720b2096f252c8c0fe1d0726f56a | |
parent | a61dc8e03e6e863005b3a4629ca8f3801d33d3c4 (diff) | |
download | frameworks_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.txt | 2 | ||||
-rw-r--r-- | core/java/android/app/AppOpsManager.java | 17 | ||||
-rw-r--r-- | core/java/android/content/ContentProvider.java | 116 | ||||
-rw-r--r-- | core/java/android/provider/DocumentsProvider.java | 20 | ||||
-rw-r--r-- | core/java/com/android/internal/app/IAppOpsService.aidl | 1 | ||||
-rw-r--r-- | packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java | 5 | ||||
-rw-r--r-- | services/java/com/android/server/AppOpsService.java | 15 | ||||
-rw-r--r-- | services/java/com/android/server/DropBoxManagerService.java | 10 |
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) { |