Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e0237fa5 authored by Sudheer Shanka's avatar Sudheer Shanka
Browse files

Add API to allow apps with location permission to access data blobs.

This is a new access mode that apps can use while committing data
blobs to specify that only apps with location permission can the
data blobs.

Bug: 158705914
CTS-Coverage-Bug: 158705914
Test: atest --test-mapping apex/blobstore
Change-Id: If69a2ea317719315f782e71a993cec361fef027f
parent 5db986d7
Loading
Loading
Loading
Loading
+66 −5
Original line number Diff line number Diff line
@@ -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.
+6 −4
Original line number Diff line number Diff line
@@ -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);
}
 No newline at end of file
+2 −0
Original line number Diff line number Diff line
@@ -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();
+4 −0
Original line number Diff line number Diff line
@@ -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";
}
+79 −2
Original line number Diff line number Diff line
@@ -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;
    }
Loading