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

Commit d1937863 authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Add a new hidden Icon API for loading

The new API checks (for URI based Icons) that the Uri can be accessed by
the passed callingUid, regardless of the app that is calling
loadDrawable. This allows SystemUI to load drawables on behalf of other
apps safely.

Fixes: 301110522
Test: atest IconTest
Change-Id: Iae392277aaf9560536a0b1cf231fa98b577a32d7
parent d346a1f1
Loading
Loading
Loading
Loading
+157 −1
Original line number Diff line number Diff line
@@ -18,13 +18,20 @@ package android.graphics.drawable;

import static com.google.common.truth.Truth.assertThat;

import android.app.IUriGrantsManager;
import android.content.ContentProvider;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
import android.graphics.Region;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.test.AndroidTestCase;
import android.util.Log;

@@ -34,6 +41,7 @@ import com.android.frameworks.coretests.R;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
@@ -457,6 +465,81 @@ public class IconTest extends AndroidTestCase {
        assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
    }

    @SmallTest
    public void testLoadSafeDrawable_loadSuccessful() throws FileNotFoundException {
        int uid = 12345;
        String packageName = "test_pkg";

        final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                .getBitmap();
        final File dir = getContext().getExternalFilesDir(null);
        final File file1 = new File(dir, "file1-original.png");
        bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1));

        final Icon im1 = Icon.createWithFilePath(file1.toString());

        TestableIUriGrantsManager ugm =
                new TestableIUriGrantsManager(/* rejectCheckRequests */ false);

        Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant(
                getContext(), ugm, uid, packageName);
        assertThat(loadedDrawable).isNotNull();

        assertThat(ugm.mRequests.size()).isEqualTo(1);
        TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0);
        assertThat(r.mCallingUid).isEqualTo(uid);
        assertThat(r.mPackageName).isEqualTo(packageName);
        assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri()));
        assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri()));

        final Bitmap test1 = Bitmap.createBitmap(loadedDrawable.getIntrinsicWidth(),
                loadedDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        loadedDrawable.setBounds(0, 0, loadedDrawable.getIntrinsicWidth(),
                loadedDrawable.getIntrinsicHeight());
        loadedDrawable.draw(new Canvas(test1));

        bit1.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(new File(dir, "bitmap1-original.png")));
        test1.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(new File(dir, "bitmap1-test.png")));
        if (!equalBitmaps(bit1, test1)) {
            findBitmapDifferences(bit1, test1);
            fail("bitmap1 differs, check " + dir);
        }
    }

    @SmallTest
    public void testLoadSafeDrawable_grantRejected_nullDrawable() throws FileNotFoundException {
        int uid = 12345;
        String packageName = "test_pkg";

        final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                .getBitmap();
        final File dir = getContext().getExternalFilesDir(null);
        final File file1 = new File(dir, "file1-original.png");
        bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1));

        final Icon im1 = Icon.createWithFilePath(file1.toString());

        TestableIUriGrantsManager ugm =
                new TestableIUriGrantsManager(/* rejectCheckRequests */ true);

        Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant(
                getContext(), ugm, uid, packageName);

        assertThat(ugm.mRequests.size()).isEqualTo(1);
        TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0);
        assertThat(r.mCallingUid).isEqualTo(uid);
        assertThat(r.mPackageName).isEqualTo(packageName);
        assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri()));
        assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri()));

        assertThat(loadedDrawable).isNull();
    }


    // ======== utils ========

    static final char[] GRADIENT = " .:;+=xX$#".toCharArray();
@@ -541,4 +624,77 @@ public class IconTest extends AndroidTestCase {
        }
        L(sb.toString());
    }

    private static class TestableIUriGrantsManager extends IUriGrantsManager.Stub {

        final ArrayList<CheckRequest> mRequests = new ArrayList<>();
        final boolean mRejectCheckRequests;

        TestableIUriGrantsManager(boolean rejectCheckRequests) {
            this.mRejectCheckRequests = rejectCheckRequests;
        }

        @Override
        public void takePersistableUriPermission(Uri uri, int i, String s, int i1)
                throws RemoteException {

        }

        @Override
        public void releasePersistableUriPermission(Uri uri, int i, String s, int i1)
                throws RemoteException {

        }

        @Override
        public void grantUriPermissionFromOwner(IBinder iBinder, int i, String s, Uri uri, int i1,
                int i2, int i3) throws RemoteException {

        }

        @Override
        public ParceledListSlice getGrantedUriPermissions(String s, int i) throws RemoteException {
            return null;
        }

        @Override
        public void clearGrantedUriPermissions(String s, int i) throws RemoteException {

        }

        @Override
        public ParceledListSlice getUriPermissions(String s, boolean b, boolean b1)
                throws RemoteException {
            return null;
        }

        @Override
        public int checkGrantUriPermission_ignoreNonSystem(
                int uid, String packageName, Uri uri, int mode, int userId)
                throws RemoteException {
            CheckRequest r = new CheckRequest(uid, packageName, uri, mode, userId);
            mRequests.add(r);
            if (mRejectCheckRequests) {
                throw new SecurityException();
            } else {
                return uid;
            }
        }

        static class CheckRequest {
            final int mCallingUid;
            final String mPackageName;
            final Uri mUri;
            final int mMode;
            final int mUserId;

            CheckRequest(int callingUid, String packageName, Uri uri, int mode, int userId) {
                this.mCallingUid = callingUid;
                this.mPackageName = packageName;
                this.mUri = uri;
                this.mMode = mode;
                this.mUserId = userId;
            }
        }
    }
}
 No newline at end of file
+46 −0
Original line number Diff line number Diff line
@@ -24,9 +24,12 @@ import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.IUriGrantsManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
@@ -44,10 +47,13 @@ import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.RequiresPermission;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
@@ -530,6 +536,46 @@ public final class Icon implements Parcelable {
        return loadDrawable(context);
    }

    /**
     * Load a drawable, but in the case of URI types, it will check if the passed uid has a grant
     * to load the resource. The check will be performed using the permissions of the passed uid,
     * and not those of the caller.
     * <p>
     * This should be called for {@link Icon} objects that come from a not trusted source and may
     * contain a URI.
     *
     * After the check, if passed, {@link #loadDrawable} will be called. If failed, this will
     * return {@code null}.
     *
     * @see #loadDrawable
     *
     * @hide
     */
    @Nullable
    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
    public Drawable loadDrawableCheckingUriGrant(
            Context context,
            IUriGrantsManager iugm,
            int callingUid,
            String packageName
    ) {
        if (getType() == TYPE_URI || getType() == TYPE_URI_ADAPTIVE_BITMAP) {
            try {
                iugm.checkGrantUriPermission_ignoreNonSystem(
                        callingUid,
                        packageName,
                        ContentProvider.getUriWithoutUserId(getUri()),
                        Intent.FLAG_GRANT_READ_URI_PERMISSION,
                        ContentProvider.getUserIdFromUri(getUri())
                );
            } catch (SecurityException | RemoteException e) {
                Log.e(TAG, "Failed to get URI permission for: " + getUri(), e);
                return null;
            }
        }
        return loadDrawable(context);
    }

    /** @hide */
    public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10);