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

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

Merge tag 'android-security-13.0.0_r25' into staging/lineage-20.0_android-security-13.0.0_r25

Android security 13.0.0 release 25

* tag 'android-security-13.0.0_r25':
  Always show all approved apps
  Pass SafeActivityOptions with actual caller for startActivityInTF
  Update checkKeyIntent
  Fix allowlist token issues
  Enforce hard limits on hosts per package and widgets per host.
  enforce limits for VisualVoicemailSmsFilterSettings properties

Conflicts:
	services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java

Change-Id: I2c7030d50f1e770a2fcf4e1c0ca138baf06f35b7
parents 53e2f4b8 feb9e146
Loading
Loading
Loading
Loading
+37 −11
Original line number Diff line number Diff line
@@ -2565,8 +2565,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();
@@ -3026,10 +3029,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);
@@ -3044,7 +3062,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) {
@@ -3400,16 +3421,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
@@ -782,6 +782,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,18 +16,23 @@

package android.os;

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

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

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 {
@@ -239,4 +244,48 @@ public class ParcelTest {
        assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, -1, pB, iB, 0));
        assertThrows(IllegalArgumentException.class, () -> Parcel.compareData(pA, 0, pB, -1, 0));
    }

    @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