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

Commit 7f8fddc3 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Fix slice listener permissions"

parents fad69a64 3a1d2e97
Loading
Loading
Loading
Loading
+22 −6
Original line number Diff line number Diff line
@@ -147,6 +147,14 @@ public abstract class SliceProvider extends ContentProvider {
     * @hide
     */
    public static final String EXTRA_OVERRIDE_PKG = "override_pkg";
    /**
     * @hide
     */
    public static final String EXTRA_OVERRIDE_UID = "override_uid";
    /**
     * @hide
     */
    public static final String EXTRA_OVERRIDE_PID = "override_pid";

    private static final boolean DEBUG = false;

@@ -302,13 +310,20 @@ public abstract class SliceProvider extends ContentProvider {
            List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);

            String callingPackage = getCallingPackage();
            int callingUid = Binder.getCallingUid();
            int callingPid = Binder.getCallingPid();
            if (extras.containsKey(EXTRA_OVERRIDE_PKG)) {
                if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                    throw new SecurityException("Only the system can override calling pkg");
                }
                // This is safe because we would grant SYSTEM_UID access to all slices
                // and want to allow it to bind slices as if it were a less privileged app
                // to check their permission levels.
                callingPackage = extras.getString(EXTRA_OVERRIDE_PKG);
                callingUid = extras.getInt(EXTRA_OVERRIDE_UID);
                callingPid = extras.getInt(EXTRA_OVERRIDE_PID);
            }
            Slice s = handleBindSlice(uri, supportedSpecs, callingPackage);
            Slice s = handleBindSlice(uri, supportedSpecs, callingPackage, callingUid, callingPid);
            Bundle b = new Bundle();
            b.putParcelable(EXTRA_SLICE, s);
            return b;
@@ -319,7 +334,8 @@ public abstract class SliceProvider extends ContentProvider {
            List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
            Bundle b = new Bundle();
            if (uri != null) {
                Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage());
                Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage(),
                        Binder.getCallingUid(), Binder.getCallingPid());
                b.putParcelable(EXTRA_SLICE, s);
            } else {
                b.putParcelable(EXTRA_SLICE, null);
@@ -401,15 +417,15 @@ public abstract class SliceProvider extends ContentProvider {
    }

    private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs,
            String callingPkg) {
            String callingPkg, int callingUid, int callingPid) {
        // This can be removed once Slice#bindSlice is removed and everyone is using
        // SliceManager#bindSlice.
        String pkg = callingPkg != null ? callingPkg
                : getContext().getPackageManager().getNameForUid(Binder.getCallingUid());
        if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
                : getContext().getPackageManager().getNameForUid(callingUid);
        if (!UserHandle.isSameApp(callingUid, Process.myUid())) {
            try {
                mSliceManager.enforceSlicePermission(sliceUri, pkg,
                        Binder.getCallingPid(), Binder.getCallingUid());
                        callingPid, callingUid);
            } catch (SecurityException e) {
                return createPermissionSlice(getContext(), sliceUri, pkg);
            }
+98 −32
Original line number Diff line number Diff line
@@ -14,12 +14,15 @@

package com.android.server.slice;

import static android.app.slice.SliceManager.PERMISSION_GRANTED;

import android.app.slice.ISliceListener;
import android.app.slice.Slice;
import android.app.slice.SliceProvider;
import android.app.slice.SliceSpec;
import android.content.ContentProviderClient;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
@@ -51,18 +54,16 @@ public class PinnedSliceState {
    @GuardedBy("mLock")
    private final ArraySet<String> mPinnedPkgs = new ArraySet<>();
    @GuardedBy("mLock")
    private final ArrayMap<IBinder, ISliceListener> mListeners = new ArrayMap<>();
    private final ArrayMap<IBinder, ListenerInfo> mListeners = new ArrayMap<>();
    @GuardedBy("mLock")
    private SliceSpec[] mSupportedSpecs = null;
    @GuardedBy("mLock")
    private final ArrayMap<IBinder, String> mPkgMap = new ArrayMap<>();

    private final DeathRecipient mDeathRecipient = this::handleRecheckListeners;
    private boolean mSlicePinned;

    public PinnedSliceState(SliceManagerService service, Uri uri) {
        mService = service;
        mUri = uri;
        mService.getHandler().post(this::handleSendPinned);
        mLock = mService.getLock();
    }

@@ -102,14 +103,27 @@ public class PinnedSliceState {
    }

    public void destroy() {
        mService.getHandler().post(this::handleSendUnpinned);
        setSlicePinned(false);
    }

    public void onChange() {
        mService.getHandler().post(this::handleBind);
    }

    public void addSliceListener(ISliceListener listener, String pkg, SliceSpec[] specs) {
    private void setSlicePinned(boolean pinned) {
        synchronized (mLock) {
            if (mSlicePinned == pinned) return;
            mSlicePinned = pinned;
            if (pinned) {
                mService.getHandler().post(this::handleSendPinned);
            } else {
                mService.getHandler().post(this::handleSendUnpinned);
            }
        }
    }

    public void addSliceListener(ISliceListener listener, String pkg, SliceSpec[] specs,
            boolean hasPermission) {
        synchronized (mLock) {
            if (mListeners.size() == 0) {
                mService.listen(mUri);
@@ -118,26 +132,27 @@ public class PinnedSliceState {
                listener.asBinder().linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
            }
            mListeners.put(listener.asBinder(), listener);
            mPkgMap.put(listener.asBinder(), pkg);
            mListeners.put(listener.asBinder(), new ListenerInfo(listener, pkg, hasPermission,
                    Binder.getCallingUid(), Binder.getCallingPid()));
            mergeSpecs(specs);
            setSlicePinned(hasPermission);
        }
    }

    public boolean removeSliceListener(ISliceListener listener) {
        synchronized (mLock) {
            listener.asBinder().unlinkToDeath(mDeathRecipient, 0);
            mPkgMap.remove(listener.asBinder());
            if (mListeners.containsKey(listener.asBinder()) && mListeners.size() == 1) {
                mService.unlisten(mUri);
            }
            mListeners.remove(listener.asBinder());
        }
        return !isPinned();
        return !hasPinOrListener();
    }

    public void pin(String pkg, SliceSpec[] specs) {
        synchronized (mLock) {
            setSlicePinned(true);
            mPinnedPkgs.add(pkg);
            mergeSpecs(specs);
        }
@@ -147,7 +162,7 @@ public class PinnedSliceState {
        synchronized (mLock) {
            mPinnedPkgs.remove(pkg);
        }
        return !isPinned();
        return !hasPinOrListener();
    }

    public boolean isListening() {
@@ -156,8 +171,32 @@ public class PinnedSliceState {
        }
    }

    public void recheckPackage(String pkg) {
        synchronized (mLock) {
            for (int i = 0; i < mListeners.size(); i++) {
                ListenerInfo info = mListeners.valueAt(i);
                if (!info.hasPermission && Objects.equals(info.pkg, pkg)) {
                    mService.getHandler().post(() -> {
                        // This bind lets the app itself participate in the permission grant.
                        Slice s = doBind(info);
                        if (mService.checkAccess(info.pkg, mUri, info.callingUid, info.callingPid)
                                == PERMISSION_GRANTED) {
                            info.hasPermission = true;
                            setSlicePinned(true);
                            try {
                                info.listener.onSliceUpdated(s);
                            } catch (RemoteException e) {
                                checkSelfRemove();
                            }
                        }
                    });
                }
            }
        }
    }

    @VisibleForTesting
    public boolean isPinned() {
    public boolean hasPinOrListener() {
        synchronized (mLock) {
            return !mPinnedPkgs.isEmpty() || !mListeners.isEmpty();
        }
@@ -171,61 +210,66 @@ public class PinnedSliceState {
        return client;
    }

    private void checkSelfRemove() {
        if (!hasPinOrListener()) {
            // All the listeners died, remove from pinned state.
            mService.unlisten(mUri);
            mService.removePinnedSlice(mUri);
        }
    }

    private void handleRecheckListeners() {
        if (!isPinned()) return;
        if (!hasPinOrListener()) return;
        synchronized (mLock) {
            for (int i = mListeners.size() - 1; i >= 0; i--) {
                ISliceListener l = mListeners.valueAt(i);
                if (!l.asBinder().isBinderAlive()) {
                ListenerInfo l = mListeners.valueAt(i);
                if (!l.listener.asBinder().isBinderAlive()) {
                    mListeners.removeAt(i);
                }
            }
            if (!isPinned()) {
                // All the listeners died, remove from pinned state.
                mService.unlisten(mUri);
                mService.removePinnedSlice(mUri);
            }
            checkSelfRemove();
        }
    }

    private void handleBind() {
        Slice cachedSlice = doBind(null);
        synchronized (mLock) {
            if (!isPinned()) return;
            if (!hasPinOrListener()) return;
            for (int i = mListeners.size() - 1; i >= 0; i--) {
                ISliceListener l = mListeners.valueAt(i);
                ListenerInfo info = mListeners.valueAt(i);
                Slice s = cachedSlice;
                if (s == null || s.hasHint(Slice.HINT_CALLER_NEEDED)) {
                    s = doBind(mPkgMap.get(l));
                if (s == null || s.hasHint(Slice.HINT_CALLER_NEEDED)
                        || !info.hasPermission) {
                    s = doBind(info);
                }
                if (s == null) {
                    mListeners.removeAt(i);
                    continue;
                }
                try {
                    l.onSliceUpdated(s);
                    info.listener.onSliceUpdated(s);
                } catch (RemoteException e) {
                    Log.e(TAG, "Unable to notify slice " + mUri, e);
                    mListeners.removeAt(i);
                    continue;
                }
            }
            if (!isPinned()) {
                // All the listeners died, remove from pinned state.
                mService.unlisten(mUri);
                mService.removePinnedSlice(mUri);
            }
            checkSelfRemove();
        }
    }

    private Slice doBind(String overridePkg) {
    private Slice doBind(ListenerInfo info) {
        try (ContentProviderClient client = getClient()) {
            if (client == null) return null;
            Bundle extras = new Bundle();
            extras.putParcelable(SliceProvider.EXTRA_BIND_URI, mUri);
            extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
                    new ArrayList<>(Arrays.asList(mSupportedSpecs)));
            extras.putString(SliceProvider.EXTRA_OVERRIDE_PKG, overridePkg);
            if (info != null) {
                extras.putString(SliceProvider.EXTRA_OVERRIDE_PKG, info.pkg);
                extras.putInt(SliceProvider.EXTRA_OVERRIDE_UID, info.callingUid);
                extras.putInt(SliceProvider.EXTRA_OVERRIDE_PID, info.callingPid);
            }
            final Bundle res;
            try {
                res = client.call(SliceProvider.METHOD_SLICE, null, extras);
@@ -236,6 +280,10 @@ public class PinnedSliceState {
            if (res == null) return null;
            Bundle.setDefusable(res, true);
            return res.getParcelable(SliceProvider.EXTRA_SLICE);
        } catch (Throwable t) {
            // Calling out of the system process, make sure they don't throw anything at us.
            Log.e(TAG, "Caught throwable while binding " + mUri, t);
            return null;
        }
    }

@@ -264,4 +312,22 @@ public class PinnedSliceState {
            }
        }
    }

    private class ListenerInfo {

        private ISliceListener listener;
        private String pkg;
        private boolean hasPermission;
        private int callingUid;
        private int callingPid;

        public ListenerInfo(ISliceListener listener, String pkg, boolean hasPermission,
                int callingUid, int callingPid) {
            this.listener = listener;
            this.pkg = pkg;
            this.hasPermission = hasPermission;
            this.callingUid = callingUid;
            this.callingPid = callingPid;
        }
    }
}
+47 −31
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.server.slice;
import static android.content.ContentProvider.getUriWithoutUserId;
import static android.content.ContentProvider.getUserIdFromUri;
import static android.content.ContentProvider.maybeAddUserId;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;

import android.Manifest.permission;
import android.app.ActivityManager;
@@ -32,7 +34,6 @@ import android.app.slice.SliceSpec;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
@@ -43,6 +44,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -120,8 +122,10 @@ public class SliceManagerService extends ISliceManager.Stub {
            throws RemoteException {
        verifyCaller(pkg);
        uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
        enforceAccess(pkg, uri);
        getOrCreatePinnedSlice(uri).addSliceListener(listener, pkg, specs);
        enforceCrossUser(pkg, uri);
        getOrCreatePinnedSlice(uri).addSliceListener(listener, pkg, specs,
                checkAccess(pkg, uri, Binder.getCallingUid(), Binder.getCallingUid())
                == PERMISSION_GRANTED);
    }

    @Override
@@ -129,7 +133,6 @@ public class SliceManagerService extends ISliceManager.Stub {
            throws RemoteException {
        verifyCaller(pkg);
        uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
        enforceAccess(pkg, uri);
        if (getPinnedSlice(uri).removeSliceListener(listener)) {
            removePinnedSlice(uri);
        }
@@ -169,7 +172,7 @@ public class SliceManagerService extends ISliceManager.Stub {
    @Override
    public int checkSlicePermission(Uri uri, String pkg, int pid, int uid) throws RemoteException {
        if (mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
                == PackageManager.PERMISSION_GRANTED) {
                == PERMISSION_GRANTED) {
            return SliceManager.PERMISSION_GRANTED;
        }
        if (hasFullSliceAccess(pkg, uid)) {
@@ -201,6 +204,11 @@ public class SliceManagerService extends ISliceManager.Stub {
                Binder.restoreCallingIdentity(ident);
            }
        }
        synchronized (mLock) {
            for (PinnedSliceState p : mPinnedSlicesByUri.values()) {
                p.recheckPackage(pkg);
            }
        }
    }

    ///  ----- internal code -----
@@ -249,17 +257,13 @@ public class SliceManagerService extends ISliceManager.Stub {
        return mHandler;
    }

    private void enforceAccess(String pkg, Uri uri) throws RemoteException {
        int user = Binder.getCallingUserHandle().getIdentifier();
    protected int checkAccess(String pkg, Uri uri, int uid, int pid) {
        int user = UserHandle.getUserId(uid);
        // Check for default launcher/assistant.
        if (!hasFullSliceAccess(pkg, Binder.getCallingUid())) {
            try {
        if (!hasFullSliceAccess(pkg, uid)) {
            // Also allow things with uri access.
                getContext().enforceUriPermission(uri, Binder.getCallingPid(),
                        Binder.getCallingUid(),
                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
                        "Slice binding requires permission to the Uri");
            } catch (SecurityException e) {
            if (getContext().checkUriPermission(uri, pid, uid,
                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PERMISSION_GRANTED) {
                // Last fallback (if the calling app owns the authority, then it can have access).
                long ident = Binder.clearCallingIdentity();
                try {
@@ -267,42 +271,54 @@ public class SliceManagerService extends ISliceManager.Stub {
                    IActivityManager activityManager = ActivityManager.getService();
                    ContentProviderHolder holder = null;
                    String providerName = getUriWithoutUserId(uri).getAuthority();
                    try {
                        try {
                            holder = activityManager.getContentProviderExternal(
                                    providerName, getUserIdFromUri(uri, user), token);
                            if (holder == null || holder.info == null
                                    || !Objects.equals(holder.info.packageName, pkg)) {
                            // No more fallbacks, no access.
                            throw e;
                                return PERMISSION_DENIED;
                            }
                        } finally {
                            if (holder != null && holder.provider != null) {
                                activityManager.removeContentProviderExternal(providerName, token);
                            }
                        }
                    } catch (RemoteException e) {
                        // Can't happen.
                        e.rethrowAsRuntimeException();
                    }
                } finally {
                    // I know, the double finally seems ugly, but seems safest for the identity.
                    Binder.restoreCallingIdentity(ident);
                }
            }
        }
        // Lastly check for any multi-userness. Any return statements above here will break this
        // important check.
        return PERMISSION_GRANTED;
    }

    private void enforceCrossUser(String pkg, Uri uri) {
        int user = Binder.getCallingUserHandle().getIdentifier();
        if (getUserIdFromUri(uri, user) != user) {
            getContext().enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL,
                    "Slice interaction across users requires INTERACT_ACROSS_USERS_FULL");
        }
    }

    private void enforceAccess(String pkg, Uri uri) throws RemoteException {
        if (checkAccess(pkg, uri, Binder.getCallingUid(), Binder.getCallingPid())
                != PERMISSION_GRANTED) {
            throw new SecurityException("Access to slice " + uri + " is required");
        }
        enforceCrossUser(pkg, uri);
    }

    private void enforceFullAccess(String pkg, String name, Uri uri) {
        int user = Binder.getCallingUserHandle().getIdentifier();
        if (!hasFullSliceAccess(pkg, user)) {
            throw new SecurityException(String.format("Call %s requires full slice access", name));
        }
        if (getUserIdFromUri(uri, user) != user) {
            getContext().enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL,
                    "Slice interaction across users requires INTERACT_ACROSS_USERS_FULL");
        }
        enforceCrossUser(pkg, uri);
    }

    private void verifyCaller(String pkg) {
+104 −28

File changed.

Preview size limit exceeded, changes collapsed.