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

Unverified Commit 49397cc9 authored by Kevin F. Haggerty's avatar Kevin F. Haggerty
Browse files

Merge tag 'android-security-12.1.0_r5' into staging/lineage-19.1_android-security-12.1.0_r5

Android security 12.1.0 release 5

* tag 'android-security-12.1.0_r5':
  Always show all approved apps
  Update checkKeyIntent
  Fix allowlist token issues
  Enforce hard limits on hosts per package and widgets per host.
  enforce limits for VisualVoicemailSmsFilterSettings properties

Change-Id: I7d6785add41c1f83b9f62d0e5acd29d3138d3367
parents 56a3588d 48a270f3
Loading
Loading
Loading
Loading
+37 −11
Original line number Diff line number Diff line
@@ -2519,8 +2519,11 @@ public class Notification implements Parcelable
        if (mAllowlistToken == null) {
            mAllowlistToken = processAllowlistToken;
        }
        // Propagate this token to all pending intents that are unmarshalled from the parcel.
        // Propagate this token to all pending intents that are unmarshalled from the parcel,
        // or keep the one we're already propagating, if that's the case.
        if (!parcel.hasClassCookie(PendingIntent.class)) {
            parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
        }

        when = parcel.readLong();
        creationTime = parcel.readLong();
@@ -2976,10 +2979,25 @@ public class Notification implements Parcelable
                }
            });
        }
        try {
            boolean mustClearCookie = false;
            if (!parcel.hasClassCookie(Notification.class)) {
                // This is the "root" notification, and not an "inner" notification (including
                // publicVersion or anything else that might be embedded in extras). So we want
                // to use its token for every inner notification (might be null).
                parcel.setClassCookie(Notification.class, mAllowlistToken);
                mustClearCookie = true;
            }
            try {
                // IMPORTANT: Add marshaling code in writeToParcelImpl as we
                // want to intercept all pending events written to the parcel.
                writeToParcelImpl(parcel, flags);
            } finally {
                if (mustClearCookie) {
                    parcel.removeClassCookie(Notification.class, mAllowlistToken);
                }
            }

            synchronized (this) {
                // Must be written last!
                parcel.writeArraySet(allPendingIntents);
@@ -2994,7 +3012,10 @@ public class Notification implements Parcelable
    private void writeToParcelImpl(Parcel parcel, int flags) {
        parcel.writeInt(1);

        parcel.writeStrongBinder(mAllowlistToken);
        // Always use the same token as the root notification (might be null).
        IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class);
        parcel.writeStrongBinder(rootNotificationToken);

        parcel.writeLong(when);
        parcel.writeLong(creationTime);
        if (mSmallIcon == null && icon != 0) {
@@ -3350,16 +3371,21 @@ public class Notification implements Parcelable
     * Sets the token used for background operations for the pending intents associated with this
     * notification.
     *
     * This token is automatically set during deserialization for you, you usually won't need to
     * call this unless you want to change the existing token, if any.
     * Note: Should <em>only</em> be invoked by NotificationManagerService, since this is normally
     * populated by unparceling (and also used there). Any other usage is suspect.
     *
     * @hide
     */
    public void clearAllowlistToken() {
        mAllowlistToken = null;
    public void overrideAllowlistToken(IBinder token) {
        mAllowlistToken = token;
        if (publicVersion != null) {
            publicVersion.clearAllowlistToken();
            publicVersion.overrideAllowlistToken(token);
        }
    }

    /** @hide */
    public IBinder getAllowlistToken() {
        return mAllowlistToken;
    }

    /**
+22 −0
Original line number Diff line number Diff line
@@ -651,6 +651,28 @@ public final class Parcel {
        return mClassCookies != null ? mClassCookies.get(clz) : null;
    }

    /** @hide */
    public void removeClassCookie(Class clz, Object expectedCookie) {
        if (mClassCookies != null) {
            Object removedCookie = mClassCookies.remove(clz);
            if (removedCookie != expectedCookie) {
                Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz
                        + ") but instead removed " + removedCookie);
            }
        } else {
            Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz
                    + ") but no cookies were present");
        }
    }

    /**
     * Whether {@link #setClassCookie} has been called with the specified {@code clz}.
     * @hide
     */
    public boolean hasClassCookie(Class clz) {
        return mClassCookies != null && mClassCookies.containsKey(clz);
    }

    /** @hide */
    public final void adoptClassCookies(Parcel from) {
        mClassCookies = from.mClassCookies;
+49 −0
Original line number Diff line number Diff line
@@ -16,15 +16,20 @@

package android.os;

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

import static org.junit.Assert.assertEquals;

import android.platform.test.annotations.Presubmit;
import android.util.Log;

import androidx.test.runner.AndroidJUnit4;

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

import java.util.ArrayList;

@Presubmit
@RunWith(AndroidJUnit4.class)
public class ParcelTest {
@@ -110,4 +115,48 @@ public class ParcelTest {
            assertEquals(string, p.readString16());
        }
    }

    @Test
    public void testClassCookies() {
        Parcel p = Parcel.obtain();
        assertThat(p.hasClassCookie(ParcelTest.class)).isFalse();

        p.setClassCookie(ParcelTest.class, "string_cookie");
        assertThat(p.hasClassCookie(ParcelTest.class)).isTrue();
        assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo("string_cookie");

        p.removeClassCookie(ParcelTest.class, "string_cookie");
        assertThat(p.hasClassCookie(ParcelTest.class)).isFalse();
        assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo(null);

        p.setClassCookie(ParcelTest.class, "to_be_discarded_cookie");
        p.recycle();
        assertThat(p.getClassCookie(ParcelTest.class)).isNull();
    }

    @Test
    public void testClassCookies_removeUnexpected() {
        Parcel p = Parcel.obtain();

        assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "not_present"));

        p.setClassCookie(ParcelTest.class, "value");

        assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "different"));
        assertThat(p.getClassCookie(ParcelTest.class)).isNull(); // still removed

        p.recycle();
    }

    private static void assertLogsWtf(Runnable test) {
        ArrayList<Log.TerribleFailure> wtfs = new ArrayList<>();
        Log.TerribleFailureHandler oldHandler = Log.setWtfHandler(
                (tag, what, system) -> wtfs.add(what));
        try {
            test.run();
        } finally {
            Log.setWtfHandler(oldHandler);
        }
        assertThat(wtfs).hasSize(1);
    }
}
+23 −9
Original line number Diff line number Diff line
@@ -138,11 +138,13 @@ public class ServiceListing {
        }

        final PackageManager pmWrapper = mContext.getPackageManager();
        // Add requesting apps, with full validation
        List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
                new Intent(mIntentAction), flags, user);
        for (ResolveInfo resolveInfo : installedServices) {
            ServiceInfo info = resolveInfo.serviceInfo;

            if (!mEnabledServices.contains(info.getComponentName())) {
                if (!mPermission.equals(info.permission)) {
                    Slog.w(mTag, "Skipping " + mNoun + " service "
                            + info.packageName + "/" + info.name
@@ -155,6 +157,18 @@ public class ServiceListing {
                }
                mServices.add(info);
            }
        }

        // Add all apps with access, in case prior approval was granted without full validation
        for (ComponentName cn : mEnabledServices) {
            List<ResolveInfo> enabledServices = pmWrapper.queryIntentServicesAsUser(
                    new Intent().setComponent(cn), flags, user);
            for (ResolveInfo resolveInfo : enabledServices) {
                ServiceInfo info = resolveInfo.serviceInfo;
                mServices.add(info);
            }
        }

        for (Callback callback : mCallbacks) {
            callback.onServicesReloaded(mServices);
        }
+65 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -29,6 +30,7 @@ import static org.mockito.Mockito.when;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -42,6 +44,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;

@@ -72,19 +75,26 @@ public class ServiceListingTest {
                .build();
    }

    private ArgumentMatcher<Intent> filterEquals(Intent intent) {
        return (test) -> {
            return intent.filterEquals(test);
        };
    }

    @Test
    public void testValidator() {
        ServiceInfo s1 = new ServiceInfo();
        s1.permission = "testPermission";
        s1.packageName = "pkg";
        s1.name = "Service1";
        ServiceInfo s2 = new ServiceInfo();
        s2.permission = "testPermission";
        s2.packageName = "pkg2";
        s2.name = "service2";
        ResolveInfo r1 = new ResolveInfo();
        r1.serviceInfo = s1;
        ResolveInfo r2 = new ResolveInfo();
        r2.serviceInfo = s2;

        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(
                ImmutableList.of(r1, r2));

@@ -118,9 +128,11 @@ public class ServiceListingTest {
        ServiceInfo s1 = new ServiceInfo();
        s1.permission = "testPermission";
        s1.packageName = "pkg";
        s1.name = "Service1";
        ServiceInfo s2 = new ServiceInfo();
        s2.permission = "testPermission";
        s2.packageName = "pkg2";
        s2.name = "service2";
        ResolveInfo r1 = new ResolveInfo();
        r1.serviceInfo = s1;
        ResolveInfo r2 = new ResolveInfo();
@@ -193,4 +205,56 @@ public class ServiceListingTest {
        assertThat(Settings.Secure.getString(RuntimeEnvironment.application.getContentResolver(),
                TEST_SETTING)).contains(testComponent2.flattenToString());
    }

    @Test
    public void testHasPermissionWithoutMeetingCurrentRegs() {
        ServiceInfo s1 = new ServiceInfo();
        s1.permission = "testPermission";
        s1.packageName = "pkg";
        s1.name = "Service1";
        ServiceInfo s2 = new ServiceInfo();
        s2.permission = "testPermission";
        s2.packageName = "pkg2";
        s2.name = "service2";
        ResolveInfo r1 = new ResolveInfo();
        r1.serviceInfo = s1;
        ResolveInfo r2 = new ResolveInfo();
        r2.serviceInfo = s2;

        ComponentName approvedComponent = new ComponentName(s2.packageName, s2.name);

        Settings.Secure.putString(
                mContext.getContentResolver(), TEST_SETTING, approvedComponent.flattenToString());

        when(mPm.queryIntentServicesAsUser(argThat(
                filterEquals(new Intent(TEST_INTENT))), anyInt(), anyInt()))
                .thenReturn(ImmutableList.of(r1));
        when(mPm.queryIntentServicesAsUser(argThat(
                filterEquals(new Intent().setComponent(approvedComponent))),
                anyInt(), anyInt()))
                .thenReturn(ImmutableList.of(r2));

        mServiceListing = new ServiceListing.Builder(mContext)
                .setTag("testTag")
                .setSetting(TEST_SETTING)
                .setNoun("testNoun")
                .setIntentAction(TEST_INTENT)
                .setValidator(info -> {
                    if (info.packageName.equals("pkg")) {
                        return true;
                    }
                    return false;
                })
                .setPermission("testPermission")
                .build();
        ServiceListing.Callback callback = mock(ServiceListing.Callback.class);
        mServiceListing.addCallback(callback);
        mServiceListing.reload();

        verify(mPm, times(2)).queryIntentServicesAsUser(any(), anyInt(), anyInt());
        ArgumentCaptor<List<ServiceInfo>> captor = ArgumentCaptor.forClass(List.class);
        verify(callback, times(1)).onServicesReloaded(captor.capture());

        assertThat(captor.getValue()).containsExactlyElementsIn(ImmutableList.of(s2, s1));
    }
}
Loading