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

Commit 1198ed06 authored by Sudheer Shanka's avatar Sudheer Shanka Committed by Android (Google) Code Review
Browse files

Merge "Allow certain apps to access blobs across users." into sc-dev

parents 8017f4ed 523643c4
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -26,8 +26,8 @@ import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Base64;
import android.util.IndentingPrintWriter;

import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;

+1 −1
Original line number Diff line number Diff line
@@ -37,9 +37,9 @@ import android.permission.PermissionManager;
import android.util.ArraySet;
import android.util.Base64;
import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
import android.util.Slog;

import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;

import org.xmlpull.v1.XmlPullParser;
+180 −64
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.server.blob;

import static android.Manifest.permission.ACCESS_BLOBS_ACROSS_USERS;
import static android.app.blob.XmlTags.ATTR_COMMIT_TIME_MS;
import static android.app.blob.XmlTags.ATTR_DESCRIPTION;
import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME;
@@ -36,6 +37,7 @@ import static com.android.server.blob.BlobStoreConfig.TAG;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ALLOW_ACCESS_ACROSS_USERS;
import static com.android.server.blob.BlobStoreConfig.hasLeaseWaitTimeElapsed;
import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId;
import static com.android.server.blob.BlobStoreUtils.getPackageResources;
@@ -45,15 +47,18 @@ import android.annotation.Nullable;
import android.app.blob.BlobHandle;
import android.app.blob.LeaseInfo;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.ResourceId;
import android.content.res.Resources;
import android.os.ParcelFileDescriptor;
import android.os.RevocableFileDescriptor;
import android.os.UserHandle;
import android.permission.PermissionManager;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.StatsEvent;
@@ -62,7 +67,6 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import com.android.server.blob.BlobStoreManagerService.DumpArgs;

@@ -85,7 +89,6 @@ class BlobMetadata {

    private final long mBlobId;
    private final BlobHandle mBlobHandle;
    private final int mUserId;

    @GuardedBy("mMetadataLock")
    private final ArraySet<Committer> mCommitters = new ArraySet<>();
@@ -94,24 +97,23 @@ class BlobMetadata {
    private final ArraySet<Leasee> mLeasees = new ArraySet<>();

    /**
     * Contains packageName -> {RevocableFileDescriptors}.
     * Contains Accessor -> {RevocableFileDescriptors}.
     *
     * Keep track of RevocableFileDescriptors given to clients which are not yet revoked/closed so
     * that when clients access is revoked or the blob gets deleted, we can be sure that clients
     * do not have any reference to the blob and the space occupied by the blob can be freed.
     */
    @GuardedBy("mRevocableFds")
    private final ArrayMap<String, ArraySet<RevocableFileDescriptor>> mRevocableFds =
    private final ArrayMap<Accessor, ArraySet<RevocableFileDescriptor>> mRevocableFds =
            new ArrayMap<>();

    // Do not access this directly, instead use #getBlobFile().
    private File mBlobFile;

    BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId) {
    BlobMetadata(Context context, long blobId, BlobHandle blobHandle) {
        mContext = context;
        this.mBlobId = blobId;
        this.mBlobHandle = blobHandle;
        this.mUserId = userId;
    }

    long getBlobId() {
@@ -122,10 +124,6 @@ class BlobMetadata {
        return mBlobHandle;
    }

    int getUserId() {
        return mUserId;
    }

    void addOrReplaceCommitter(@NonNull Committer committer) {
        synchronized (mMetadataLock) {
            // We need to override the committer data, so first remove any existing
@@ -155,13 +153,24 @@ class BlobMetadata {
        }
    }

    void removeCommittersFromUnknownPkgs(SparseArray<String> knownPackages) {
    void removeCommittersFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages) {
        synchronized (mMetadataLock) {
            mCommitters.removeIf(committer ->
                    !committer.packageName.equals(knownPackages.get(committer.uid)));
            mCommitters.removeIf(committer -> {
                final int userId = UserHandle.getUserId(committer.uid);
                final SparseArray<String> userPackages = knownPackages.get(userId);
                if (userPackages == null) {
                    return true;
                }
                return !committer.packageName.equals(userPackages.get(committer.uid));
            });
        }
    }

    void addCommittersAndLeasees(BlobMetadata blobMetadata) {
        mCommitters.addAll(blobMetadata.mCommitters);
        mLeasees.addAll(blobMetadata.mLeasees);
    }

    @Nullable
    Committer getExistingCommitter(@NonNull String packageName, int uid) {
        synchronized (mCommitters) {
@@ -201,10 +210,16 @@ class BlobMetadata {
        }
    }

    void removeLeaseesFromUnknownPkgs(SparseArray<String> knownPackages) {
    void removeLeaseesFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages) {
        synchronized (mMetadataLock) {
            mLeasees.removeIf(leasee ->
                    !leasee.packageName.equals(knownPackages.get(leasee.uid)));
            mLeasees.removeIf(leasee -> {
                final int userId = UserHandle.getUserId(leasee.uid);
                final SparseArray<String> userPackages = knownPackages.get(userId);
                if (userPackages == null) {
                    return true;
                }
                return !leasee.packageName.equals(userPackages.get(leasee.uid));
            });
        }
    }

@@ -214,6 +229,25 @@ class BlobMetadata {
        }
    }

    void removeDataForUser(int userId) {
        synchronized (mMetadataLock) {
            mCommitters.removeIf(committer -> (userId == UserHandle.getUserId(committer.uid)));
            mLeasees.removeIf(leasee -> (userId == UserHandle.getUserId(leasee.uid)));
            mRevocableFds.entrySet().removeIf(entry -> {
                final Accessor accessor = entry.getKey();
                final ArraySet<RevocableFileDescriptor> rFds = entry.getValue();
                if (userId != UserHandle.getUserId(accessor.uid)) {
                    return false;
                }
                for (int i = 0, fdCount = rFds.size(); i < fdCount; ++i) {
                    rFds.valueAt(i).revoke();
                }
                rFds.clear();
                return true;
            });
        }
    }

    boolean hasValidLeases() {
        synchronized (mMetadataLock) {
            for (int i = 0, size = mLeasees.size(); i < size; ++i) {
@@ -244,8 +278,12 @@ class BlobMetadata {
                }
            }

            final int callingUserId = UserHandle.getUserId(callingUid);
            for (int i = 0, size = mCommitters.size(); i < size; ++i) {
                final Committer committer = mCommitters.valueAt(i);
                if (callingUserId != UserHandle.getUserId(committer.uid)) {
                    continue;
                }

                // Check if the caller is the same package that committed the blob.
                if (committer.equals(callingPackage, callingUid)) {
@@ -259,38 +297,105 @@ class BlobMetadata {
                    return true;
                }
            }

            final boolean canCallerAccessBlobsAcrossUsers = checkCallerCanAccessBlobsAcrossUsers(
                    callingPackage, callingUserId);
            if (!canCallerAccessBlobsAcrossUsers) {
                return false;
            }
            for (int i = 0, size = mCommitters.size(); i < size; ++i) {
                final Committer committer = mCommitters.valueAt(i);
                final int committerUserId = UserHandle.getUserId(committer.uid);
                if (callingUserId == committerUserId) {
                    continue;
                }
                if (!checkCallerCanAccessBlobsAcrossUsers(callingPackage, committerUserId)) {
                    continue;
                }

                // Check if the caller is allowed access as per the access mode specified
                // by the committer.
                if (committer.blobAccessMode.isAccessAllowedForCaller(mContext,
                        callingPackage, committer.packageName, callingUid, attributionTag)) {
                    return true;
                }
            }

        }
        return false;
    }

    private static boolean checkCallerCanAccessBlobsAcrossUsers(
            String callingPackage, int callingUserId) {
        return PermissionManager.checkPackageNamePermission(ACCESS_BLOBS_ACROSS_USERS,
                callingPackage, callingUserId) == PackageManager.PERMISSION_GRANTED;
    }

    boolean hasACommitterOrLeaseeInUser(int userId) {
        return hasACommitterInUser(userId) || hasALeaseeInUser(userId);
    }

    boolean hasACommitterInUser(int userId) {
        synchronized (mMetadataLock) {
            for (int i = 0, size = mCommitters.size(); i < size; ++i) {
                final Committer committer = mCommitters.valueAt(i);
                if (userId == UserHandle.getUserId(committer.uid)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean hasALeaseeInUser(int userId) {
        synchronized (mMetadataLock) {
            for (int i = 0, size = mLeasees.size(); i < size; ++i) {
                final Leasee leasee = mLeasees.valueAt(i);
                if (userId == UserHandle.getUserId(leasee.uid)) {
                    return true;
                }
            }
        }
        return false;
    }

    boolean isACommitter(@NonNull String packageName, int uid) {
        synchronized (mMetadataLock) {
            return isAnAccessor(mCommitters, packageName, uid);
            return isAnAccessor(mCommitters, packageName, uid, UserHandle.getUserId(uid));
        }
    }

    boolean isALeasee(@Nullable String packageName, int uid) {
        synchronized (mMetadataLock) {
            final Leasee leasee = getAccessor(mLeasees, packageName, uid);
            final Leasee leasee = getAccessor(mLeasees, packageName, uid,
                    UserHandle.getUserId(uid));
            return leasee != null && leasee.isStillValid();
        }
    }

    private boolean isALeaseeInUser(@Nullable String packageName, int uid, int userId) {
        synchronized (mMetadataLock) {
            final Leasee leasee = getAccessor(mLeasees, packageName, uid, userId);
            return leasee != null && leasee.isStillValid();
        }
    }

    private static <T extends Accessor> boolean isAnAccessor(@NonNull ArraySet<T> accessors,
            @Nullable String packageName, int uid) {
            @Nullable String packageName, int uid, int userId) {
        // Check if the package is an accessor of the data blob.
        return getAccessor(accessors, packageName, uid) != null;
        return getAccessor(accessors, packageName, uid, userId) != null;
    }

    private static <T extends Accessor> T getAccessor(@NonNull ArraySet<T> accessors,
            @Nullable String packageName, int uid) {
            @Nullable String packageName, int uid, int userId) {
        // Check if the package is an accessor of the data blob.
        for (int i = 0, size = accessors.size(); i < size; ++i) {
            final Accessor accessor = accessors.valueAt(i);
            if (packageName != null && uid != INVALID_UID
                    && accessor.equals(packageName, uid)) {
                return (T) accessor;
            } else if (packageName != null && accessor.packageName.equals(packageName)) {
            } else if (packageName != null && accessor.packageName.equals(packageName)
                    && userId == UserHandle.getUserId(accessor.uid)) {
                return (T) accessor;
            } else if (uid != INVALID_UID && accessor.uid == uid) {
                return (T) accessor;
@@ -299,23 +404,29 @@ class BlobMetadata {
        return null;
    }

    boolean isALeasee(@NonNull String packageName) {
        return isALeasee(packageName, INVALID_UID);
    boolean shouldAttributeToLeasee(@NonNull String packageName, int userId,
            boolean callerHasStatsPermission) {
        if (!isALeaseeInUser(packageName, INVALID_UID, userId)) {
            return false;
        }

    boolean isALeasee(int uid) {
        return isALeasee(null, uid);
        if (!callerHasStatsPermission || !hasOtherLeasees(packageName, INVALID_UID, userId)) {
            return true;
        }

    boolean hasOtherLeasees(@NonNull String packageName) {
        return hasOtherLeasees(packageName, INVALID_UID);
        return false;
    }

    boolean hasOtherLeasees(int uid) {
        return hasOtherLeasees(null, uid);
    boolean shouldAttributeToLeasee(int uid, boolean callerHasStatsPermission) {
        final int userId = UserHandle.getUserId(uid);
        if (!isALeaseeInUser(null, uid, userId)) {
            return false;
        }
        if (!callerHasStatsPermission || !hasOtherLeasees(null, uid, userId)) {
            return true;
        }
        return false;
    }

    private boolean hasOtherLeasees(@Nullable String packageName, int uid) {
    private boolean hasOtherLeasees(@Nullable String packageName, int uid, int userId) {
        synchronized (mMetadataLock) {
            for (int i = 0, size = mLeasees.size(); i < size; ++i) {
                final Leasee leasee = mLeasees.valueAt(i);
@@ -326,7 +437,8 @@ class BlobMetadata {
                if (packageName != null && uid != INVALID_UID
                        && !leasee.equals(packageName, uid)) {
                    return true;
                } else if (packageName != null && !leasee.packageName.equals(packageName)) {
                } else if (packageName != null && (!leasee.packageName.equals(packageName)
                        || userId != UserHandle.getUserId(leasee.uid))) {
                    return true;
                } else if (uid != INVALID_UID && leasee.uid != uid) {
                    return true;
@@ -371,7 +483,7 @@ class BlobMetadata {
        return mBlobFile;
    }

    ParcelFileDescriptor openForRead(String callingPackage) throws IOException {
    ParcelFileDescriptor openForRead(String callingPackage, int callingUid) throws IOException {
        // TODO: Add limit on opened fds
        FileDescriptor fd;
        try {
@@ -381,7 +493,7 @@ class BlobMetadata {
        }
        try {
            if (BlobStoreConfig.shouldUseRevocableFdForReads()) {
                return createRevocableFd(fd, callingPackage);
                return createRevocableFd(fd, callingPackage, callingUid);
            } else {
                return new ParcelFileDescriptor(fd);
            }
@@ -393,26 +505,28 @@ class BlobMetadata {

    @NonNull
    private ParcelFileDescriptor createRevocableFd(FileDescriptor fd,
            String callingPackage) throws IOException {
            String callingPackage, int callingUid) throws IOException {
        final RevocableFileDescriptor revocableFd =
                new RevocableFileDescriptor(mContext, fd);
        final Accessor accessor;
        synchronized (mRevocableFds) {
            ArraySet<RevocableFileDescriptor> revocableFdsForPkg =
                    mRevocableFds.get(callingPackage);
            if (revocableFdsForPkg == null) {
                revocableFdsForPkg = new ArraySet<>();
                mRevocableFds.put(callingPackage, revocableFdsForPkg);
            accessor = new Accessor(callingPackage, callingUid);
            ArraySet<RevocableFileDescriptor> revocableFdsForAccessor =
                    mRevocableFds.get(accessor);
            if (revocableFdsForAccessor == null) {
                revocableFdsForAccessor = new ArraySet<>();
                mRevocableFds.put(accessor, revocableFdsForAccessor);
            }
            revocableFdsForPkg.add(revocableFd);
            revocableFdsForAccessor.add(revocableFd);
        }
        revocableFd.addOnCloseListener((e) -> {
            synchronized (mRevocableFds) {
                final ArraySet<RevocableFileDescriptor> revocableFdsForPkg =
                        mRevocableFds.get(callingPackage);
                if (revocableFdsForPkg != null) {
                    revocableFdsForPkg.remove(revocableFd);
                    if (revocableFdsForPkg.isEmpty()) {
                        mRevocableFds.remove(callingPackage);
                final ArraySet<RevocableFileDescriptor> revocableFdsForAccessor =
                        mRevocableFds.get(accessor);
                if (revocableFdsForAccessor != null) {
                    revocableFdsForAccessor.remove(revocableFd);
                    if (revocableFdsForAccessor.isEmpty()) {
                        mRevocableFds.remove(accessor);
                    }
                }
            }
@@ -421,22 +535,23 @@ class BlobMetadata {
    }

    void destroy() {
        revokeAllFds();
        revokeAndClearAllFds();
        getBlobFile().delete();
    }

    private void revokeAllFds() {
    private void revokeAndClearAllFds() {
        synchronized (mRevocableFds) {
            for (int i = 0, pkgCount = mRevocableFds.size(); i < pkgCount; ++i) {
                final ArraySet<RevocableFileDescriptor> packageFds =
            for (int i = 0, accessorCount = mRevocableFds.size(); i < accessorCount; ++i) {
                final ArraySet<RevocableFileDescriptor> rFds =
                        mRevocableFds.valueAt(i);
                if (packageFds == null) {
                if (rFds == null) {
                    continue;
                }
                for (int j = 0, fdCount = packageFds.size(); j < fdCount; ++j) {
                    packageFds.valueAt(j).revoke();
                for (int j = 0, fdCount = rFds.size(); j < fdCount; ++j) {
                    rFds.valueAt(j).revoke();
                }
            }
            mRevocableFds.clear();
        }
    }

@@ -547,10 +662,10 @@ class BlobMetadata {
                fout.println("<empty>");
            } else {
                for (int i = 0, count = mRevocableFds.size(); i < count; ++i) {
                    final String packageName = mRevocableFds.keyAt(i);
                    final ArraySet<RevocableFileDescriptor> packageFds =
                    final Accessor accessor = mRevocableFds.keyAt(i);
                    final ArraySet<RevocableFileDescriptor> rFds =
                            mRevocableFds.valueAt(i);
                    fout.println(packageName + "#" + packageFds.size());
                    fout.println(accessor + ": #" + rFds.size());
                }
            }
            fout.decreaseIndent();
@@ -560,7 +675,6 @@ class BlobMetadata {
    void writeToXml(XmlSerializer out) throws IOException {
        synchronized (mMetadataLock) {
            XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId);
            XmlUtils.writeIntAttribute(out, ATTR_USER_ID, mUserId);

            out.startTag(null, TAG_BLOB_HANDLE);
            mBlobHandle.writeToXml(out);
@@ -584,7 +698,9 @@ class BlobMetadata {
    static BlobMetadata createFromXml(XmlPullParser in, int version, Context context)
            throws XmlPullParserException, IOException {
        final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID);
        final int userId = XmlUtils.readIntAttribute(in, ATTR_USER_ID);
        if (version < XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) {
            XmlUtils.readIntAttribute(in, ATTR_USER_ID);
        }

        BlobHandle blobHandle = null;
        final ArraySet<Committer> committers = new ArraySet<>();
@@ -608,7 +724,7 @@ class BlobMetadata {
            return null;
        }

        final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle, userId);
        final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle);
        blobMetadata.setCommitters(committers);
        blobMetadata.setLeasees(leasees);
        return blobMetadata;
+3 −3
Original line number Diff line number Diff line
@@ -27,12 +27,11 @@ import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.text.TextUtils;
import android.util.DataUnit;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;

import com.android.internal.util.IndentingPrintWriter;

import java.io.File;
import java.util.concurrent.TimeUnit;

@@ -47,8 +46,9 @@ class BlobStoreConfig {
    public static final int XML_VERSION_ADD_DESC_RES_NAME = 3;
    public static final int XML_VERSION_ADD_COMMIT_TIME = 4;
    public static final int XML_VERSION_ADD_SESSION_CREATION_TIME = 5;
    public static final int XML_VERSION_ALLOW_ACCESS_ACROSS_USERS = 6;

    public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_SESSION_CREATION_TIME;
    public static final int XML_VERSION_CURRENT = XML_VERSION_ALLOW_ACCESS_ACROSS_USERS;

    public static final long INVALID_BLOB_ID = 0;
    public static final long INVALID_BLOB_SIZE = 0;
Loading