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

Commit fa8e17db authored by Jean-Michel Trivi's avatar Jean-Michel Trivi
Browse files

RingtonePlayer: URI userId must match that of caller

Prevent one user from playing media from another user
by checking that the userId of the content matches
the current userId.

Bug: 400434060
Test: atest com.android.systemui.ringtone.RingtonePlayerTest
Flag: android.media.audio.ringtoneUserUriCheck
Change-Id: Ib9a9a8f5049b53ab8bbff5e324988dbc6c7c7c25
parent 6fa5bdd1
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.media;

import android.annotation.Nullable;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -126,6 +127,8 @@ public class RingtonePlayer implements CoreStartable {
                Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
                        + Binder.getCallingUid() + ")");
            }
            enforceUriUserId(uri);

            Client client;
            synchronized (mClients) {
                client = mClients.get(token);
@@ -207,6 +210,7 @@ public class RingtonePlayer implements CoreStartable {

        @Override
        public String getTitle(Uri uri) {
            enforceUriUserId(uri);
            final UserHandle user = Binder.getCallingUserHandle();
            return Ringtone.getTitle(getContextForUser(user), uri,
                    false /*followSettingsUri*/, false /*allowRemote*/);
@@ -214,6 +218,7 @@ public class RingtonePlayer implements CoreStartable {

        @Override
        public ParcelFileDescriptor openRingtone(Uri uri) {
            enforceUriUserId(uri);
            final UserHandle user = Binder.getCallingUserHandle();
            final ContentResolver resolver = getContextForUser(user).getContentResolver();

@@ -241,6 +246,28 @@ public class RingtonePlayer implements CoreStartable {
        }
    };

    /**
     * Must be called from the Binder calling thread.
     * Ensures caller is from the same userId as the content they're trying to access.
     * @param uri the URI to check
     * @throws SecurityException when in a non-system call and userId in uri differs from the
     *                           caller's userId
     */
    private void enforceUriUserId(Uri uri) throws SecurityException {
        final int uriUserId = ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId());
        // for a non-system call, verify the URI to play belongs to the same user as the caller
        if (UserHandle.isApp(Binder.getCallingUid()) && (UserHandle.myUserId() != uriUserId)) {
            final String errorMessage = "Illegal access to uri=" + uri
                    + " content associated with user=" + uriUserId
                    + ", current userID: " + UserHandle.myUserId();
            if (android.media.audio.Flags.ringtoneUserUriCheck()) {
                throw new SecurityException(errorMessage);
            } else {
                Log.e(TAG, errorMessage, new Exception());
            }
        }
    }

    private Context getContextForUser(UserHandle user) {
        try {
            return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
+72 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.ringtone;

import static org.junit.Assert.assertThrows;

import android.media.AudioAttributes;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Binder;
import android.os.UserHandle;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class RingtonePlayerTest extends SysuiTestCase {

    private AudioManager mAudioManager;

    private static final String TAG = "RingtonePlayerTest";

    @Before
    public void setup() throws Exception {
        mAudioManager = getContext().getSystemService(AudioManager.class);
    }

    @Test
    public void testRingtonePlayerUriUserCheck() {
        android.media.IRingtonePlayer irp = mAudioManager.getRingtonePlayer();
        final AudioAttributes aa = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build();
        // get a UserId that doesn't belong to mine
        final int otherUserId = UserHandle.myUserId() == 0 ? 10 : 0;
        // build a URI that I shouldn't have access to
        final Uri uri = new Uri.Builder()
                .scheme("content").authority(otherUserId + "@media")
                .appendPath("external").appendPath("downloads")
                .appendPath("bogusPathThatDoesNotMatter.mp3")
                .build();
        if (android.media.audio.Flags.ringtoneUserUriCheck()) {
            assertThrows(SecurityException.class, () ->
                    irp.play(new Binder(), uri, aa, 1.0f /*volume*/, false /*looping*/)
            );

            assertThrows(SecurityException.class, () ->
                    irp.getTitle(uri));
        }
    }

}
+2 −0
Original line number Diff line number Diff line
@@ -4995,6 +4995,8 @@ public class AudioService extends IAudioService.Stub
        pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
                + disablePrescaleAbsoluteVolume());
        pw.println("\tcom.android.media.audio.setStreamVolumeOrder - EOL");
        pw.println("\tandroid.media.audio.ringtoneUserUriCheck:"
                + android.media.audio.Flags.ringtoneUserUriCheck());
        pw.println("\tandroid.media.audio.roForegroundAudioControl:"
                + roForegroundAudioControl());
        pw.println("\tandroid.media.audio.scoManagedByAudio:"