Merge "Add API to allow apps with location permission to access data blobs." into sc-dev

This commit is contained in:
Sudheer Shanka
2021-03-29 18:25:11 +00:00
committed by Android (Google) Code Review
9 changed files with 213 additions and 29 deletions

View File

@@ -258,7 +258,8 @@ public class BlobStoreManager {
public @NonNull ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle)
throws IOException {
try {
return mService.openBlob(blobHandle, mContext.getOpPackageName());
return mService.openBlob(blobHandle, mContext.getOpPackageName(),
mContext.getAttributionTag());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
@@ -315,7 +316,7 @@ public class BlobStoreManager {
@CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException {
try {
mService.acquireLease(blobHandle, descriptionResId, null, leaseExpiryTimeMillis,
mContext.getOpPackageName());
mContext.getOpPackageName(), mContext.getAttributionTag());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
e.maybeRethrow(LimitExceededException.class);
@@ -378,7 +379,7 @@ public class BlobStoreManager {
@CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException {
try {
mService.acquireLease(blobHandle, INVALID_RES_ID, description, leaseExpiryTimeMillis,
mContext.getOpPackageName());
mContext.getOpPackageName(), mContext.getAttributionTag());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
e.maybeRethrow(LimitExceededException.class);
@@ -497,7 +498,8 @@ public class BlobStoreManager {
*/
public void releaseLease(@NonNull BlobHandle blobHandle) throws IOException {
try {
mService.releaseLease(blobHandle, mContext.getOpPackageName());
mService.releaseLease(blobHandle, mContext.getOpPackageName(),
mContext.getAttributionTag());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
@@ -602,7 +604,8 @@ public class BlobStoreManager {
@Nullable
public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle) throws IOException {
try {
return mService.getLeaseInfo(blobHandle, mContext.getOpPackageName());
return mService.getLeaseInfo(blobHandle, mContext.getOpPackageName(),
mContext.getAttributionTag());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
@@ -896,6 +899,64 @@ public class BlobStoreManager {
}
}
/**
* Allow apps with location permission to access this blob data once it is committed using
* a {@link BlobHandle} representing the blob.
*
* <p> This needs to be called before committing the blob using
* {@link #commit(Executor, Consumer)}.
*
* Note that if a caller allows access to the blob using this API in addition to other APIs
* like {@link #allowPackageAccess(String, byte[])}, then apps satisfying any one of these
* access conditions will be allowed to access the blob.
*
* @param permissionName the name of the location permission that needs to be granted
* for the app. This can be either one of
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION} or
* {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
*
* @throws IOException when there is an I/O error while changing the access.
* @throws SecurityException when the caller is not the owner of the session.
* @throws IllegalStateException when the caller tries to change access for a blob which is
* already committed.
*/
public void allowPackagesWithLocationPermission(@NonNull String permissionName)
throws IOException {
try {
mSession.allowPackagesWithLocationPermission(permissionName);
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns {@code true} if access has been allowed for apps with location permission by
* using {@link #allowPackagesWithLocationPermission(String)}.
*
* @param permissionName the name of the location permission that needs to be granted
* for the app. This can be either one of
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION} or
* {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
*
* @throws IOException when there is an I/O error while getting the access type.
* @throws IllegalStateException when the caller tries to get access type from a session
* which is closed or abandoned.
*/
public boolean arePackagesWithLocationPermissionAllowed(@NonNull String permissionName)
throws IOException {
try {
return mSession.arePackagesWithLocationPermissionAllowed(permissionName);
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Commit the file that was written so far to this session to the blob store maintained by
* the system.

View File

@@ -25,12 +25,13 @@ import android.os.RemoteCallback;
interface IBlobStoreManager {
long createSession(in BlobHandle handle, in String packageName);
IBlobStoreSession openSession(long sessionId, in String packageName);
ParcelFileDescriptor openBlob(in BlobHandle handle, in String packageName);
ParcelFileDescriptor openBlob(in BlobHandle handle, in String packageName,
in String attributionTag);
void abandonSession(long sessionId, in String packageName);
void acquireLease(in BlobHandle handle, int descriptionResId, in CharSequence description,
long leaseTimeoutMillis, in String packageName);
void releaseLease(in BlobHandle handle, in String packageName);
long leaseTimeoutMillis, in String packageName, in String attributionTag);
void releaseLease(in BlobHandle handle, in String packageName, in String attributionTag);
long getRemainingLeaseQuotaBytes(String packageName);
void waitForIdle(in RemoteCallback callback);
@@ -39,5 +40,6 @@ interface IBlobStoreManager {
void deleteBlob(long blobId);
List<BlobHandle> getLeasedBlobs(in String packageName);
LeaseInfo getLeaseInfo(in BlobHandle blobHandle, in String packageName);
LeaseInfo getLeaseInfo(in BlobHandle blobHandle, in String packageName,
in String attributionTag);
}

View File

@@ -26,10 +26,12 @@ interface IBlobStoreSession {
void allowPackageAccess(in String packageName, in byte[] certificate);
void allowSameSignatureAccess();
void allowPublicAccess();
void allowPackagesWithLocationPermission(in String permissionName);
boolean isPackageAccessAllowed(in String packageName, in byte[] certificate);
boolean isSameSignatureAccessAllowed();
boolean isPublicAccessAllowed();
boolean arePackagesWithLocationPermissionAllowed(in String permissionName);
long getSize();
void close();

View File

@@ -38,6 +38,7 @@ public final class XmlTags {
public static final String ATTR_TYPE = "t";
public static final String TAG_ALLOWED_PACKAGE = "wl";
public static final String ATTR_CERTIFICATE = "ct";
public static final String TAG_ALLOWED_PERMISSION = "ap";
// For BlobHandle
public static final String TAG_BLOB_HANDLE = "bh";
@@ -55,4 +56,7 @@ public final class XmlTags {
public static final String TAG_LEASEE = "l";
public static final String ATTR_DESCRIPTION_RES_NAME = "rn";
public static final String ATTR_DESCRIPTION = "d";
// Generic
public static final String ATTR_VALUE = "val";
}

View File

@@ -15,18 +15,29 @@
*/
package com.android.server.blob;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.app.blob.XmlTags.ATTR_CERTIFICATE;
import static android.app.blob.XmlTags.ATTR_PACKAGE;
import static android.app.blob.XmlTags.ATTR_TYPE;
import static android.app.blob.XmlTags.ATTR_VALUE;
import static android.app.blob.XmlTags.TAG_ALLOWED_PACKAGE;
import static android.app.blob.XmlTags.TAG_ALLOWED_PERMISSION;
import static com.android.server.blob.BlobStoreConfig.TAG;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.permission.PermissionManager;
import android.util.ArraySet;
import android.util.Base64;
import android.util.DebugUtils;
import android.util.Slog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
@@ -53,21 +64,27 @@ class BlobAccessMode {
ACCESS_TYPE_PUBLIC,
ACCESS_TYPE_SAME_SIGNATURE,
ACCESS_TYPE_ALLOWLIST,
ACCESS_TYPE_LOCATION_PERMISSION,
})
@interface AccessType {}
public static final int ACCESS_TYPE_PRIVATE = 1 << 0;
public static final int ACCESS_TYPE_PUBLIC = 1 << 1;
public static final int ACCESS_TYPE_SAME_SIGNATURE = 1 << 2;
public static final int ACCESS_TYPE_ALLOWLIST = 1 << 3;
public static final int ACCESS_TYPE_LOCATION_PERMISSION = 1 << 4;
private int mAccessType = ACCESS_TYPE_PRIVATE;
private final ArraySet<PackageIdentifier> mAllowedPackages = new ArraySet<>();
private final ArraySet<String> mAllowedPermissions = new ArraySet<>();
void allow(BlobAccessMode other) {
if ((other.mAccessType & ACCESS_TYPE_ALLOWLIST) != 0) {
mAllowedPackages.addAll(other.mAllowedPackages);
}
if ((other.mAccessType & ACCESS_TYPE_LOCATION_PERMISSION) != 0) {
mAllowedPermissions.addAll(other.mAllowedPermissions);
}
mAccessType |= other.mAccessType;
}
@@ -84,6 +101,11 @@ class BlobAccessMode {
mAllowedPackages.add(PackageIdentifier.create(packageName, certificate));
}
void allowPackagesWithLocationPermission(@NonNull String permissionName) {
mAccessType |= ACCESS_TYPE_LOCATION_PERMISSION;
mAllowedPermissions.add(permissionName);
}
boolean isPublicAccessAllowed() {
return (mAccessType & ACCESS_TYPE_PUBLIC) != 0;
}
@@ -99,8 +121,15 @@ class BlobAccessMode {
return mAllowedPackages.contains(PackageIdentifier.create(packageName, certificate));
}
boolean isAccessAllowedForCaller(Context context,
@NonNull String callingPackage, @NonNull String committerPackage) {
boolean arePackagesWithLocationPermissionAllowed(@NonNull String permissionName) {
if ((mAccessType & ACCESS_TYPE_LOCATION_PERMISSION) == 0) {
return false;
}
return mAllowedPermissions.contains(permissionName);
}
boolean isAccessAllowedForCaller(Context context, @NonNull String callingPackage,
@NonNull String committerPackage, int callingUid, @Nullable String attributionTag) {
if ((mAccessType & ACCESS_TYPE_PUBLIC) != 0) {
return true;
}
@@ -124,9 +153,37 @@ class BlobAccessMode {
}
}
if ((mAccessType & ACCESS_TYPE_LOCATION_PERMISSION) != 0) {
final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
for (int i = 0; i < mAllowedPermissions.size(); ++i) {
final String permission = mAllowedPermissions.valueAt(i);
if (PermissionManager.checkPackageNamePermission(permission, callingPackage,
UserHandle.getUserId(callingUid)) != PackageManager.PERMISSION_GRANTED) {
continue;
}
// TODO: Add appropriate message
if (appOpsManager.noteOpNoThrow(getAppOp(permission), callingUid, callingPackage,
attributionTag, null /* message */) == AppOpsManager.MODE_ALLOWED) {
return true;
}
}
}
return false;
}
private static String getAppOp(String permission) {
switch (permission) {
case ACCESS_FINE_LOCATION:
return AppOpsManager.OPSTR_FINE_LOCATION;
case ACCESS_COARSE_LOCATION:
return AppOpsManager.OPSTR_COARSE_LOCATION;
default:
Slog.w(TAG, "Unknown permission found: " + permission);
return null;
}
}
int getAccessType() {
return mAccessType;
}
@@ -148,6 +205,16 @@ class BlobAccessMode {
}
fout.decreaseIndent();
}
fout.print("Allowed permissions:");
if (mAllowedPermissions.isEmpty()) {
fout.println(" (Empty)");
} else {
fout.increaseIndent();
for (int i = 0, count = mAllowedPermissions.size(); i < count; ++i) {
fout.println(mAllowedPermissions.valueAt(i).toString());
}
fout.decreaseIndent();
}
}
void writeToXml(@NonNull XmlSerializer out) throws IOException {
@@ -159,6 +226,12 @@ class BlobAccessMode {
XmlUtils.writeByteArrayAttribute(out, ATTR_CERTIFICATE, packageIdentifier.certificate);
out.endTag(null, TAG_ALLOWED_PACKAGE);
}
for (int i = 0, count = mAllowedPermissions.size(); i < count; ++i) {
out.startTag(null, TAG_ALLOWED_PERMISSION);
final String permission = mAllowedPermissions.valueAt(i);
XmlUtils.writeStringAttribute(out, ATTR_VALUE, permission);
out.endTag(null, TAG_ALLOWED_PERMISSION);
}
}
@NonNull
@@ -176,6 +249,10 @@ class BlobAccessMode {
final byte[] certificate = XmlUtils.readByteArrayAttribute(in, ATTR_CERTIFICATE);
blobAccessMode.allowPackageAccess(packageName, certificate);
}
if (TAG_ALLOWED_PERMISSION.equals(in.getName())) {
final String permission = XmlUtils.readStringAttribute(in, ATTR_VALUE);
blobAccessMode.allowPackagesWithLocationPermission(permission);
}
}
return blobAccessMode;
}

View File

@@ -229,7 +229,8 @@ class BlobMetadata {
return getBlobFile().length();
}
boolean isAccessAllowedForCaller(@NonNull String callingPackage, int callingUid) {
boolean isAccessAllowedForCaller(@NonNull String callingPackage, int callingUid,
@Nullable String attributionTag) {
// Don't allow the blob to be accessed after it's expiry time has passed.
if (getBlobHandle().isExpired()) {
return false;
@@ -254,7 +255,7 @@ class BlobMetadata {
// Check if the caller is allowed access as per the access mode specified
// by the committer.
if (committer.blobAccessMode.isAccessAllowedForCaller(mContext,
callingPackage, committer.packageName)) {
callingPackage, committer.packageName, callingUid, attributionTag)) {
return true;
}
}

View File

@@ -402,12 +402,12 @@ public class BlobStoreManagerService extends SystemService {
}
private ParcelFileDescriptor openBlobInternal(BlobHandle blobHandle, int callingUid,
String callingPackage) throws IOException {
String callingPackage, String attributionTag) throws IOException {
synchronized (mBlobsLock) {
final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
.get(blobHandle);
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
callingPackage, callingUid)) {
callingPackage, callingUid, attributionTag)) {
if (blobMetadata == null) {
FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
INVALID_BLOB_ID, INVALID_BLOB_SIZE,
@@ -455,7 +455,7 @@ public class BlobStoreManagerService extends SystemService {
private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId,
CharSequence description, long leaseExpiryTimeMillis,
int callingUid, String callingPackage) {
int callingUid, String callingPackage, String attributionTag) {
synchronized (mBlobsLock) {
final int leasesCount = getLeasedBlobsCountLocked(callingUid, callingPackage);
if (leasesCount >= getMaxLeasedBlobs()) {
@@ -468,7 +468,7 @@ public class BlobStoreManagerService extends SystemService {
final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
.get(blobHandle);
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
callingPackage, callingUid)) {
callingPackage, callingUid, attributionTag)) {
if (blobMetadata == null) {
FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
INVALID_BLOB_ID, INVALID_BLOB_SIZE,
@@ -527,13 +527,13 @@ public class BlobStoreManagerService extends SystemService {
}
private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid,
String callingPackage) {
String callingPackage, String attributionTag) {
synchronized (mBlobsLock) {
final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
getUserBlobsLocked(UserHandle.getUserId(callingUid));
final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
callingPackage, callingUid)) {
callingPackage, callingUid, attributionTag)) {
throw new SecurityException("Caller not allowed to access " + blobHandle
+ "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
}
@@ -634,12 +634,12 @@ public class BlobStoreManagerService extends SystemService {
}
private LeaseInfo getLeaseInfoInternal(BlobHandle blobHandle,
int callingUid, @NonNull String callingPackage) {
int callingUid, @NonNull String callingPackage, String attributionTag) {
synchronized (mBlobsLock) {
final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
.get(blobHandle);
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
callingPackage, callingUid)) {
callingPackage, callingUid, attributionTag)) {
throw new SecurityException("Caller not allowed to access " + blobHandle
+ "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
}
@@ -1465,7 +1465,7 @@ public class BlobStoreManagerService extends SystemService {
@Override
public ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle,
@NonNull String packageName) {
@NonNull String packageName, @Nullable String attributionTag) {
Objects.requireNonNull(blobHandle, "blobHandle must not be null");
blobHandle.assertIsValid();
Objects.requireNonNull(packageName, "packageName must not be null");
@@ -1480,7 +1480,7 @@ public class BlobStoreManagerService extends SystemService {
}
try {
return openBlobInternal(blobHandle, callingUid, packageName);
return openBlobInternal(blobHandle, callingUid, packageName, attributionTag);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
@@ -1489,7 +1489,8 @@ public class BlobStoreManagerService extends SystemService {
@Override
public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId,
@Nullable CharSequence description,
@CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName) {
@CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName,
@Nullable String attributionTag) {
Objects.requireNonNull(blobHandle, "blobHandle must not be null");
blobHandle.assertIsValid();
Preconditions.checkArgument(
@@ -1513,7 +1514,7 @@ public class BlobStoreManagerService extends SystemService {
try {
acquireLeaseInternal(blobHandle, descriptionResId, description,
leaseExpiryTimeMillis, callingUid, packageName);
leaseExpiryTimeMillis, callingUid, packageName, attributionTag);
} catch (Resources.NotFoundException e) {
throw new IllegalArgumentException(e);
} catch (LimitExceededException e) {
@@ -1522,7 +1523,8 @@ public class BlobStoreManagerService extends SystemService {
}
@Override
public void releaseLease(@NonNull BlobHandle blobHandle, @NonNull String packageName) {
public void releaseLease(@NonNull BlobHandle blobHandle, @NonNull String packageName,
@Nullable String attributionTag) {
Objects.requireNonNull(blobHandle, "blobHandle must not be null");
blobHandle.assertIsValid();
Objects.requireNonNull(packageName, "packageName must not be null");
@@ -1536,7 +1538,7 @@ public class BlobStoreManagerService extends SystemService {
+ "callingUid=" + callingUid + ", callingPackage=" + packageName);
}
releaseLeaseInternal(blobHandle, callingUid, packageName);
releaseLeaseInternal(blobHandle, callingUid, packageName, attributionTag);
}
@Override
@@ -1606,7 +1608,8 @@ public class BlobStoreManagerService extends SystemService {
@Override
@Nullable
public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle, @NonNull String packageName) {
public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle, @NonNull String packageName,
@Nullable String attributionTag) {
Objects.requireNonNull(blobHandle, "blobHandle must not be null");
blobHandle.assertIsValid();
Objects.requireNonNull(packageName, "packageName must not be null");
@@ -1620,7 +1623,7 @@ public class BlobStoreManagerService extends SystemService {
+ "callingUid=" + callingUid + ", callingPackage=" + packageName);
}
return getLeaseInfoInternal(blobHandle, callingUid, packageName);
return getLeaseInfoInternal(blobHandle, callingUid, packageName, attributionTag);
}
@Override

View File

@@ -15,6 +15,8 @@
*/
package com.android.server.blob;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.app.blob.BlobStoreManager.COMMIT_RESULT_ERROR;
import static android.app.blob.XmlTags.ATTR_CREATION_TIME_MS;
import static android.app.blob.XmlTags.ATTR_ID;
@@ -365,6 +367,21 @@ class BlobStoreSession extends IBlobStoreSession.Stub {
}
}
@Override
public void allowPackagesWithLocationPermission(@NonNull String permissionName) {
assertCallerIsOwner();
Preconditions.checkArgument(ACCESS_FINE_LOCATION.equals(permissionName)
|| ACCESS_COARSE_LOCATION.equals(permissionName),
"permissionName is unknown: " + permissionName);
synchronized (mSessionLock) {
if (mState != STATE_OPENED) {
throw new IllegalStateException("Not allowed to change access type in state: "
+ stateToString(mState));
}
mBlobAccessMode.allowPackagesWithLocationPermission(permissionName);
}
}
@Override
public boolean isPackageAccessAllowed(@NonNull String packageName,
@NonNull byte[] certificate) {
@@ -405,6 +422,21 @@ class BlobStoreSession extends IBlobStoreSession.Stub {
}
}
@Override
public boolean arePackagesWithLocationPermissionAllowed(@NonNull String permissionName) {
assertCallerIsOwner();
Preconditions.checkArgument(ACCESS_FINE_LOCATION.equals(permissionName)
|| ACCESS_COARSE_LOCATION.equals(permissionName),
"permissionName is unknown: " + permissionName);
synchronized (mSessionLock) {
if (mState != STATE_OPENED) {
throw new IllegalStateException("Not allowed to change access type in state: "
+ stateToString(mState));
}
return mBlobAccessMode.arePackagesWithLocationPermissionAllowed(permissionName);
}
}
@Override
public void close() {
closeSession(STATE_CLOSED, false /* sendCallback */);

View File

@@ -7881,8 +7881,10 @@ package android.app.blob {
public static class BlobStoreManager.Session implements java.io.Closeable {
method public void abandon() throws java.io.IOException;
method public void allowPackageAccess(@NonNull String, @NonNull byte[]) throws java.io.IOException;
method public void allowPackagesWithLocationPermission(@NonNull String) throws java.io.IOException;
method public void allowPublicAccess() throws java.io.IOException;
method public void allowSameSignatureAccess() throws java.io.IOException;
method public boolean arePackagesWithLocationPermissionAllowed(@NonNull String) throws java.io.IOException;
method public void close() throws java.io.IOException;
method public void commit(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws java.io.IOException;
method public long getSize() throws java.io.IOException;