From 935df08b15d291e9696159526fd067a37a1b66fb Mon Sep 17 00:00:00 2001 From: Kevin Rocard Date: Wed, 3 Apr 2019 12:37:32 -0700 Subject: [PATCH] Refactor security checks to register policies There is no functional changes in this patch The checks were much more complex then needed. The behaviour on empty mix was not explicit, it depended on non trivial behaviour of anyMatch and allMatch and the code flow. There was an early positive return leading to the rest of the permission check to be skipped. Which was correct but prone to bugs if the code was changed. The code in general was hard to change without introducing security bug as well as difficult to review. Change it to a two part section, first the permissions needed are determined explicitly. Then those permissions are checked. Early return is only used for permission denial. Test: atest android.media.cts.AudioPlaybackCaptureTest#testCaptureMediaUsage Bug: 129948989 Change-Id: I6b4177e47025af983ba6c980d58ea308c4c378f1 Signed-off-by: Kevin Rocard --- .../android/server/audio/AudioService.java | 60 ++++++++++++------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 4cde931d2ef5a..20b0deb930e6e 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -6741,40 +6741,58 @@ public class AudioService extends IAudioService.Stub private boolean isPolicyRegisterAllowed(AudioPolicyConfig policyConfig, IMediaProjection projection) { - boolean isLoopbackRenderPolicy = policyConfig.getMixes().stream().allMatch( - mix -> mix.getRouteFlags() == (mix.ROUTE_FLAG_RENDER | mix.ROUTE_FLAG_LOOP_BACK)); + boolean requireValidProjection = false; + boolean requireCaptureAudioOrMediaOutputPerm = false; + boolean requireModifyRouting = false; - if (isLoopbackRenderPolicy) { - boolean allowPrivilegedPlaybackCapture = policyConfig.getMixes().stream().anyMatch( - mix -> mix.getRule().allowPrivilegedPlaybackCapture()); - if (allowPrivilegedPlaybackCapture - && !(hasPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) - || hasPermission(android.Manifest.permission.CAPTURE_MEDIA_OUTPUT))) { - // Opt-out can not be bypassed without a system permission - return false; + if (policyConfig.getMixes().isEmpty()) { + requireModifyRouting |= true; + } + for (AudioMix mix : policyConfig.getMixes()) { + // If mix is requesting a privileged capture + if (mix.getRule().allowPrivilegedPlaybackCapture()) { + // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission + requireCaptureAudioOrMediaOutputPerm |= true; } - if (canProjectAudio(projection)) { - // Policy that do not modify the audio routing only need an audio projection - return true; + // If mix is RENDER|LOOPBACK, then an audio MediaProjection is enough + // otherwise MODIFY_AUDIO_ROUTING permission is required + if (mix.getRouteFlags() == mix.ROUTE_FLAG_LOOP_BACK_RENDER && projection != null) { + requireValidProjection |= true; + } else { + requireModifyRouting |= true; } } - boolean hasPermissionModifyAudioRouting = - (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING)); - if (hasPermissionModifyAudioRouting) { - return true; + if (requireCaptureAudioOrMediaOutputPerm + && !callerHasPermission(android.Manifest.permission.CAPTURE_MEDIA_OUTPUT) + && !callerHasPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)) { + Log.e(TAG, "Privileged audio capture requires CAPTURE_MEDIA_OUTPUT or " + + "CAPTURE_AUDIO_OUTPUT system permission"); + return false; } - return false; + + if (requireValidProjection && !canProjectAudio(projection)) { + return false; + } + + if (requireModifyRouting + && !callerHasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)) { + Log.e(TAG, "Can not capture audio without MODIFY_AUDIO_ROUTING"); + return false; + } + + return true; } - private boolean hasPermission(String permission) { - return PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(permission); + + private boolean callerHasPermission(String permission) { + return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED; } /** @return true if projection is a valid MediaProjection that can project audio. */ private boolean canProjectAudio(IMediaProjection projection) { if (projection == null) { + Log.e(TAG, "MediaProjection is null"); return false; }