diff options
7 files changed, 207 insertions, 37 deletions
diff --git a/core/java/android/service/voice/VoiceInteractionManagerInternal.java b/core/java/android/service/voice/VoiceInteractionManagerInternal.java index f5c959137d9b..c048286545c3 100644 --- a/core/java/android/service/voice/VoiceInteractionManagerInternal.java +++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java @@ -16,9 +16,12 @@ package android.service.voice; +import android.annotation.Nullable; import android.os.Bundle; import android.os.IBinder; +import com.android.internal.annotations.Immutable; + /** * @hide @@ -46,4 +49,38 @@ public abstract class VoiceInteractionManagerInternal { * Returns whether the given package is currently in an active session */ public abstract boolean hasActiveSession(String packageName); + + /** + * Gets the identity of the currently active HotwordDetectionService. + * + * @see HotwordDetectionServiceIdentity + */ + @Nullable + public abstract HotwordDetectionServiceIdentity getHotwordDetectionServiceIdentity(); + + /** + * Provides the uids of the currently active + * {@link android.service.voice.HotwordDetectionService} and its owning package. The + * HotwordDetectionService is an isolated service, so it has a separate uid. + */ + @Immutable + public static class HotwordDetectionServiceIdentity { + private final int mIsolatedUid; + private final int mOwnerUid; + + public HotwordDetectionServiceIdentity(int isolatedUid, int ownerUid) { + mIsolatedUid = isolatedUid; + mOwnerUid = ownerUid; + } + + /** Gets the uid of the currently active isolated process hosting the service. */ + public int getIsolatedUid() { + return mIsolatedUid; + } + + /** Gets the uid of the package that provides the HotwordDetectionService. */ + public int getOwnerUid() { + return mOwnerUid; + } + } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 92b6a0818de8..a391dbc1fa47 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -17,7 +17,9 @@ package com.android.server.pm.permission; import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY; +import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; +import static android.Manifest.permission.RECORD_AUDIO; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_ERRORED; @@ -148,6 +150,7 @@ import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider; import com.android.server.pm.permission.PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.SoftRestrictedPermissionPolicy; @@ -308,6 +311,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { @NonNull private final OnPermissionChangeListeners mOnPermissionChangeListeners; + @Nullable + private HotwordDetectionServiceProvider mHotwordDetectionServiceProvider; + // TODO: Take a look at the methods defined in the callback. // The callback was initially created to support the split between permission // manager and the package manager. However, it's started to be used for other @@ -5200,6 +5206,16 @@ public class PermissionManagerService extends IPermissionManager.Stub { public int[] getGidsForUid(int uid) { return PermissionManagerService.this.getGidsForUid(uid); } + + @Override + public void setHotwordDetectionServiceProvider(HotwordDetectionServiceProvider provider) { + mHotwordDetectionServiceProvider = provider; + } + + @Override + public HotwordDetectionServiceProvider getHotwordDetectionServiceProvider() { + return mHotwordDetectionServiceProvider; + } } /** @@ -5476,10 +5492,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { private final @NonNull Context mContext; private final @NonNull AppOpsManager mAppOpsManager; + private final @NonNull PermissionManagerServiceInternal mPermissionManagerServiceInternal; PermissionCheckerService(@NonNull Context context) { mContext = context; mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + mPermissionManagerServiceInternal = + LocalServices.getService(PermissionManagerServiceInternal.class); } @Override @@ -5492,8 +5511,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { Objects.requireNonNull(attributionSourceState); final AttributionSource attributionSource = new AttributionSource( attributionSourceState); - final int result = checkPermission(mContext, permission, attributionSource, message, - forDataDelivery, startDataDelivery, fromDatasource, attributedOp); + final int result = checkPermission(mContext, mPermissionManagerServiceInternal, + permission, attributionSource, message, forDataDelivery, startDataDelivery, + fromDatasource, attributedOp); // Finish any started op if some step in the attribution chain failed. if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED && result != PermissionChecker.PERMISSION_SOFT_DENIED) { @@ -5582,10 +5602,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @PermissionCheckerManager.PermissionResult - private static int checkPermission(@NonNull Context context, @NonNull String permission, - @NonNull AttributionSource attributionSource, @Nullable String message, - boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource, - int attributedOp) { + private static int checkPermission(@NonNull Context context, + @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, + @NonNull String permission, @NonNull AttributionSource attributionSource, + @Nullable String message, boolean forDataDelivery, boolean startDataDelivery, + boolean fromDatasource, int attributedOp) { PermissionInfo permissionInfo = sPlatformPermissions.get(permission); if (permissionInfo == null) { @@ -5602,22 +5623,25 @@ public class PermissionManagerService extends IPermissionManager.Stub { } if (permissionInfo.isAppOp()) { - return checkAppOpPermission(context, permission, attributionSource, message, - forDataDelivery, fromDatasource); + return checkAppOpPermission(context, permissionManagerServiceInt, permission, + attributionSource, message, forDataDelivery, fromDatasource); } if (permissionInfo.isRuntime()) { - return checkRuntimePermission(context, permission, attributionSource, message, - forDataDelivery, startDataDelivery, fromDatasource, attributedOp); + return checkRuntimePermission(context, permissionManagerServiceInt, permission, + attributionSource, message, forDataDelivery, startDataDelivery, + fromDatasource, attributedOp); } - if (!fromDatasource && !checkPermission(context, permission, attributionSource.getUid(), + if (!fromDatasource && !checkPermission(context, permissionManagerServiceInt, + permission, attributionSource.getUid(), attributionSource.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } if (attributionSource.getNext() != null) { - return checkPermission(context, permission, attributionSource.getNext(), message, - forDataDelivery, startDataDelivery, /*fromDatasource*/ false, attributedOp); + return checkPermission(context, permissionManagerServiceInt, permission, + attributionSource.getNext(), message, forDataDelivery, startDataDelivery, + /*fromDatasource*/ false, attributedOp); } return PermissionChecker.PERMISSION_GRANTED; @@ -5625,6 +5649,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { @PermissionCheckerManager.PermissionResult private static int checkAppOpPermission(@NonNull Context context, + @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean fromDatasource) { final int op = AppOpsManager.permissionToOpCode(permission); @@ -5668,13 +5693,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { return PermissionChecker.PERMISSION_HARD_DENIED; } case AppOpsManager.MODE_DEFAULT: { - if (!skipCurrentChecks && !checkPermission(context, permission, - attributionSource.getUid(), attributionSource - .getRenouncedPermissions())) { + if (!skipCurrentChecks && !checkPermission(context, + permissionManagerServiceInt, permission, attributionSource.getUid(), + attributionSource.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } - if (next != null && !checkPermission(context, permission, - next.getUid(), next.getRenouncedPermissions())) { + if (next != null && !checkPermission(context, permissionManagerServiceInt, + permission, next.getUid(), next.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } } @@ -5689,6 +5714,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { } private static int checkRuntimePermission(@NonNull Context context, + @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, @NonNull String permission, @NonNull AttributionSource attributionSource, @Nullable String message, boolean forDataDelivery, boolean startDataDelivery, boolean fromDatasource, int attributedOp) { @@ -5713,13 +5739,13 @@ public class PermissionManagerService extends IPermissionManager.Stub { } // If we already checked the permission for this one, skip the work - if (!skipCurrentChecks && !checkPermission(context, permission, - current.getUid(), current.getRenouncedPermissions())) { + if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt, + permission, current.getUid(), current.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } - if (next != null && !checkPermission(context, permission, - next.getUid(), next.getRenouncedPermissions())) { + if (next != null && !checkPermission(context, permissionManagerServiceInt, + permission, next.getUid(), next.getRenouncedPermissions())) { return PermissionChecker.PERMISSION_HARD_DENIED; } @@ -5774,10 +5800,26 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } - private static boolean checkPermission(@NonNull Context context, @NonNull String permission, - int uid, @NonNull Set<String> renouncedPermissions) { - final boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1, + private static boolean checkPermission(@NonNull Context context, + @NonNull PermissionManagerServiceInternal permissionManagerServiceInt, + @NonNull String permission, int uid, @NonNull Set<String> renouncedPermissions) { + boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED; + + // Override certain permissions checks for the HotwordDetectionService. This service is + // an isolated service, which ordinarily cannot hold permissions. + // There's probably a cleaner, more generalizable way to do this. For now, this is + // the only use case for this, so simply override here. + if (!permissionGranted + && Process.isIsolated(uid) // simple check which fails-fast for the common case + && (permission.equals(RECORD_AUDIO) + || permission.equals(CAPTURE_AUDIO_HOTWORD))) { + HotwordDetectionServiceProvider hotwordServiceProvider = + permissionManagerServiceInt.getHotwordDetectionServiceProvider(); + permissionGranted = hotwordServiceProvider != null + && uid == hotwordServiceProvider.getUid(); + } + if (permissionGranted && renouncedPermissions.contains(permission) && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS, /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index 895cabc33bab..f4fb8102d922 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -428,4 +428,28 @@ public interface PermissionManagerServiceInternal extends PermissionManagerInter } } } + + /** + * Sets the provider of the currently active HotwordDetectionService. + * + * @see HotwordDetectionServiceProvider + */ + void setHotwordDetectionServiceProvider(@Nullable HotwordDetectionServiceProvider provider); + + /** + * Gets the provider of the currently active HotwordDetectionService. + * + * @see HotwordDetectionServiceProvider + */ + @Nullable + HotwordDetectionServiceProvider getHotwordDetectionServiceProvider(); + + /** + * Provides the uid of the currently active + * {@link android.service.voice.HotwordDetectionService}, which should be granted RECORD_AUDIO + * and CAPTURE_AUDIO_HOTWORD permissions. + */ + interface HotwordDetectionServiceProvider { + int getUid(); + } } diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index 70b5c62a4973..94005b2c8361 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -33,7 +33,10 @@ import android.content.pm.ResolveInfo; import android.location.LocationManagerInternal; import android.net.Uri; import android.os.IBinder; +import android.os.Process; import android.os.UserHandle; +import android.service.voice.VoiceInteractionManagerInternal; +import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -78,6 +81,9 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat @NonNull private final RoleManager mRoleManager; + @NonNull + private final VoiceInteractionManagerInternal mVoiceInteractionManagerInternal; + /** * The locking policy around the location tags is a bit special. Since we want to * avoid grabbing the lock on every op note we are taking the approach where the @@ -101,6 +107,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat public AppOpsPolicy(@NonNull Context context) { mContext = context; mRoleManager = mContext.getSystemService(RoleManager.class); + mVoiceInteractionManagerInternal = LocalServices.getService( + VoiceInteractionManagerInternal.class); final LocationManagerInternal locationManagerInternal = LocalServices.getService( LocationManagerInternal.class); @@ -150,7 +158,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat public int checkOperation(int code, int uid, String packageName, @Nullable String attributionTag, boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) { - return superImpl.apply(code, uid, packageName, attributionTag, raw); + return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag, raw); } @Override @@ -164,8 +172,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl) { - return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), uid, - packageName, attributionTag, shouldCollectAsyncNotedOp, + return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), + resolveUid(code, uid), packageName, attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage); } @@ -190,8 +198,9 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat String, Boolean, Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl) { return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag), - uid, packageName, attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, - message, shouldCollectMessage, attributionFlags, attributionChainId); + resolveUid(code, uid), packageName, attributionTag, startIfModeDefault, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, + attributionChainId); } @Override @@ -404,4 +413,23 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat } return code; } + + private int resolveUid(int code, int uid) { + // The HotwordDetectionService is an isolated service, which ordinarily cannot hold + // permissions. So we allow it to assume the owning package identity for certain + // operations. + // Note: The package name coming from the audio server is already the one for the owning + // package, so we don't need to modify it. + if (Process.isIsolated(uid) // simple check which fails-fast for the common case + && (code == AppOpsManager.OP_RECORD_AUDIO + || code == AppOpsManager.OP_RECORD_AUDIO_HOTWORD)) { + final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = + mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); + if (hotwordDetectionServiceIdentity != null + && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { + uid = hotwordDetectionServiceIdentity.getOwnerUid(); + } + } + return uid; + } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 39cb4588b57a..bac703121c10 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -34,6 +34,7 @@ import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioRecord; import android.media.MediaRecorder; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; @@ -48,6 +49,7 @@ import android.service.voice.HotwordRejectedResult; import android.service.voice.IDspHotwordDetectionCallback; import android.service.voice.IHotwordDetectionService; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; +import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; import android.util.Pair; import android.util.Slog; import android.view.contentcapture.IContentCaptureManager; @@ -56,6 +58,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; +import com.android.server.LocalServices; +import com.android.server.pm.permission.PermissionManagerServiceInternal; import java.io.Closeable; import java.io.IOException; @@ -94,20 +98,24 @@ final class HotwordDetectionConnection { private final AtomicBoolean mUpdateStateFinish = new AtomicBoolean(false); final Object mLock; + final int mVoiceInteractionServiceUid; final ComponentName mDetectionComponentName; final int mUser; final Context mContext; final @NonNull ServiceConnector<IHotwordDetectionService> mRemoteHotwordDetectionService; boolean mBound; + volatile HotwordDetectionServiceIdentity mIdentity; @GuardedBy("mLock") private ParcelFileDescriptor mCurrentAudioSink; - HotwordDetectionConnection(Object lock, Context context, ComponentName serviceName, - int userId, boolean bindInstantServiceAllowed, @Nullable PersistableBundle options, - @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) { + HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid, + ComponentName serviceName, int userId, boolean bindInstantServiceAllowed, + @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, + IHotwordRecognitionStatusCallback callback) { mLock = lock; mContext = context; + mVoiceInteractionServiceUid = voiceInteractionServiceUid; mDetectionComponentName = serviceName; mUser = userId; final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE); @@ -164,7 +172,15 @@ final class HotwordDetectionConnection { public void sendResult(Bundle bundle) throws RemoteException { if (DEBUG) { Slog.d(TAG, "updateState finish"); + Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid()); } + // TODO: Do this earlier than this callback and have the provider point to the + // current state stored in VoiceInteractionManagerServiceImpl. + final int uid = Binder.getCallingUid(); + LocalServices.getService(PermissionManagerServiceInternal.class) + .setHotwordDetectionServiceProvider(() -> uid); + mIdentity = + new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid); future.complete(null); try { if (mUpdateStateFinish.getAndSet(true)) { @@ -235,6 +251,9 @@ final class HotwordDetectionConnection { if (mBound) { mRemoteHotwordDetectionService.unbind(); mBound = false; + LocalServices.getService(PermissionManagerServiceInternal.class) + .setHotwordDetectionServiceProvider(null); + mIdentity = null; } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index f3d80b13f290..a14912e5593d 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -251,6 +251,26 @@ public class VoiceInteractionManagerService extends SystemService { return TextUtils.equals(packageName, session.mSessionComponentName.getPackageName()); } + + @Override + public HotwordDetectionServiceIdentity getHotwordDetectionServiceIdentity() { + // IMPORTANT: This is called when performing permission checks; do not lock! + + // TODO: Have AppOpsPolicy register a listener instead of calling in here everytime. + // Then also remove the `volatile`s that were added with this method. + + VoiceInteractionManagerServiceImpl impl = + VoiceInteractionManagerService.this.mServiceStub.mImpl; + if (impl == null) { + return null; + } + HotwordDetectionConnection hotwordDetectionConnection = + impl.mHotwordDetectionConnection; + if (hotwordDetectionConnection == null) { + return null; + } + return hotwordDetectionConnection.mIdentity; + } } // implementation entry point and binder service @@ -258,7 +278,7 @@ public class VoiceInteractionManagerService extends SystemService { class VoiceInteractionManagerServiceStub extends IVoiceInteractionManagerService.Stub { - VoiceInteractionManagerServiceImpl mImpl; + volatile VoiceInteractionManagerServiceImpl mImpl; private boolean mSafeMode; private int mCurUser; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 2206b0a61b9e..ca30bc51e046 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -98,7 +98,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne final ComponentName mHotwordDetectionComponentName; boolean mBound = false; IVoiceInteractionService mService; - HotwordDetectionConnection mHotwordDetectionConnection; + volatile HotwordDetectionConnection mHotwordDetectionConnection; VoiceInteractionSessionConnection mActiveSession; int mDisabledShowContext; @@ -447,8 +447,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne if (mHotwordDetectionConnection == null) { mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext, - mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false, - options, sharedMemory, callback); + mInfo.getServiceInfo().applicationInfo.uid, mHotwordDetectionComponentName, + mUser, /* bindInstantServiceAllowed= */ false, options, sharedMemory, callback); } else { mHotwordDetectionConnection.updateStateLocked(options, sharedMemory); } |