Loading core/java/android/app/slice/SliceProvider.java +22 −6 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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); Loading Loading @@ -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); } Loading services/core/java/com/android/server/slice/PinnedSliceState.java +98 −32 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); } Loading Loading @@ -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); Loading @@ -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); } Loading @@ -147,7 +162,7 @@ public class PinnedSliceState { synchronized (mLock) { mPinnedPkgs.remove(pkg); } return !isPinned(); return !hasPinOrListener(); } public boolean isListening() { Loading @@ -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(); } Loading @@ -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); Loading @@ -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; } } Loading Loading @@ -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; } } } services/core/java/com/android/server/slice/SliceManagerService.java +47 −31 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading @@ -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); } Loading Loading @@ -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)) { Loading Loading @@ -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 ----- Loading Loading @@ -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 { Loading @@ -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) { Loading services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java +104 −28 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/app/slice/SliceProvider.java +22 −6 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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); Loading Loading @@ -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); } Loading
services/core/java/com/android/server/slice/PinnedSliceState.java +98 −32 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); } Loading Loading @@ -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); Loading @@ -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); } Loading @@ -147,7 +162,7 @@ public class PinnedSliceState { synchronized (mLock) { mPinnedPkgs.remove(pkg); } return !isPinned(); return !hasPinOrListener(); } public boolean isListening() { Loading @@ -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(); } Loading @@ -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); Loading @@ -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; } } Loading Loading @@ -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; } } }
services/core/java/com/android/server/slice/SliceManagerService.java +47 −31 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading @@ -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); } Loading Loading @@ -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)) { Loading Loading @@ -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 ----- Loading Loading @@ -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 { Loading @@ -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) { Loading
services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java +104 −28 File changed.Preview size limit exceeded, changes collapsed. Show changes