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

Commit 935df08b authored by Kevin Rocard's avatar Kevin Rocard
Browse files

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: default avatarKevin Rocard <krocard@google.com>
parent 9637326e
Loading
Loading
Loading
Loading
+40 −22
Original line number Diff line number Diff line
@@ -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));

        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;
        boolean requireValidProjection = false;
        boolean requireCaptureAudioOrMediaOutputPerm = false;
        boolean requireModifyRouting = 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;
        }

        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;
        }
    private boolean hasPermission(String permission) {
        return PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(permission);

        return true;
    }

    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;
        }