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

Commit cfe28327 authored by Benedict Wong's avatar Benedict Wong Committed by Automerger Merge Worker
Browse files

Merge "Allow provisioning package to retrieve subGrp, clear it's own config"...

Merge "Allow provisioning package to retrieve subGrp, clear it's own config" am: 1428215b am: 6b6f825b am: 4a6b279b am: 0ae3528d

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2056512



Change-Id: I8a42f19ac6a94adeed04cedfb09f3eec1a78242d
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 8246d5b7 0ae3528d
Loading
Loading
Loading
Loading
+10 −5
Original line number Diff line number Diff line
@@ -172,11 +172,11 @@ public class VcnManager {
     *
     * <p>An app that has carrier privileges for any of the subscriptions in the given group may
     * clear a VCN configuration. This API is ONLY permitted for callers running as the primary
     * user. Any active VCN will be torn down.
     * user. Any active VCN associated with this configuration will be torn down.
     *
     * @param subscriptionGroup the subscription group that the configuration should be applied to
     * @throws SecurityException if the caller does not have carrier privileges, or is not running
     *     as the primary user
     * @throws SecurityException if the caller does not have carrier privileges, is not the owner of
     *     the associated configuration, or is not running as the primary user
     * @throws IOException if the configuration failed to be cleared from disk. This may occur due
     *     to temporary disk errors, or more permanent conditions such as a full disk.
     */
@@ -196,8 +196,13 @@ public class VcnManager {
    /**
     * Retrieves the list of Subscription Groups for which a VCN Configuration has been set.
     *
     * <p>The returned list will include only subscription groups for which the carrier app is
     * privileged, and which have an associated {@link VcnConfig}.
     * <p>The returned list will include only subscription groups for which an associated {@link
     * VcnConfig} exists, and the app is either:
     *
     * <ul>
     *   <li>Carrier privileged for that subscription group, or
     *   <li>Is the provisioning package of the config
     * </ul>
     *
     * @throws SecurityException if the caller is not running as the primary user
     */
+127 −30
Original line number Diff line number Diff line
@@ -87,6 +87,7 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -172,7 +173,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {
    @NonNull private final VcnNetworkProvider mNetworkProvider;
    @NonNull private final TelephonySubscriptionTrackerCallback mTelephonySubscriptionTrackerCb;
    @NonNull private final TelephonySubscriptionTracker mTelephonySubscriptionTracker;
    @NonNull private final BroadcastReceiver mPkgChangeReceiver;
    @NonNull private final BroadcastReceiver mVcnBroadcastReceiver;

    @NonNull
    private final TrackingNetworkCallback mTrackingNetworkCallback = new TrackingNetworkCallback();
@@ -217,28 +218,17 @@ public class VcnManagementService extends IVcnManagementService.Stub {

        mConfigDiskRwHelper = mDeps.newPersistableBundleLockingReadWriteHelper(VCN_CONFIG_FILE);

        mPkgChangeReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                final String action = intent.getAction();

                if (Intent.ACTION_PACKAGE_ADDED.equals(action)
                        || Intent.ACTION_PACKAGE_REPLACED.equals(action)
                        || Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
                    mTelephonySubscriptionTracker.handleSubscriptionsChanged();
                } else {
                    Log.wtf(TAG, "received unexpected intent: " + action);
                }
            }
        };
        mVcnBroadcastReceiver = new VcnBroadcastReceiver();

        final IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        intentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
        intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
        intentFilter.addDataScheme("package");
        mContext.registerReceiver(
                mPkgChangeReceiver, intentFilter, null /* broadcastPermission */, mHandler);
                mVcnBroadcastReceiver, intentFilter, null /* broadcastPermission */, mHandler);

        // Run on handler to ensure I/O does not block system server startup
        mHandler.post(() -> {
@@ -443,6 +433,53 @@ public class VcnManagementService extends IVcnManagementService.Stub {
        return Objects.equals(subGrp, snapshot.getActiveDataSubscriptionGroup());
    }

    private class VcnBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();

            switch (action) {
                case Intent.ACTION_PACKAGE_ADDED: // Fallthrough
                case Intent.ACTION_PACKAGE_REPLACED: // Fallthrough
                case Intent.ACTION_PACKAGE_REMOVED:
                    // Reevaluate subscriptions
                    mTelephonySubscriptionTracker.handleSubscriptionsChanged();

                    break;
                case Intent.ACTION_PACKAGE_FULLY_REMOVED:
                case Intent.ACTION_PACKAGE_DATA_CLEARED:
                    final String pkgName = intent.getData().getSchemeSpecificPart();

                    if (pkgName == null || pkgName.isEmpty()) {
                        logWtf("Package name was empty or null for intent with action" + action);
                        return;
                    }

                    // Clear configs for the packages that had data cleared, or removed.
                    synchronized (mLock) {
                        final List<ParcelUuid> toRemove = new ArrayList<>();
                        for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
                            if (pkgName.equals(entry.getValue().getProvisioningPackageName())) {
                                toRemove.add(entry.getKey());
                            }
                        }

                        for (ParcelUuid subGrp : toRemove) {
                            stopAndClearVcnConfigInternalLocked(subGrp);
                        }

                        if (!toRemove.isEmpty()) {
                            writeConfigsToDiskLocked();
                        }
                    }

                    break;
                default:
                    Slog.wtf(TAG, "received unexpected intent: " + action);
            }
        }
    }

    private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback {
        /**
         * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker}
@@ -504,6 +541,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {
                final Map<ParcelUuid, Set<Integer>> currSubGrpMappings =
                        getSubGroupToSubIdMappings(mLastSnapshot);
                if (!currSubGrpMappings.equals(oldSubGrpMappings)) {
                    garbageCollectAndWriteVcnConfigsLocked();
                    notifyAllPolicyListenersLocked();
                }
            }
@@ -645,6 +683,39 @@ public class VcnManagementService extends IVcnManagementService.Stub {
        });
    }

    private void enforceCarrierPrivilegeOrProvisioningPackage(
            @NonNull ParcelUuid subscriptionGroup, @NonNull String pkg) {
        // Only apps running in the primary (system) user are allowed to configure the VCN. This is
        // in line with Telephony's behavior with regards to binding to a Carrier App provided
        // CarrierConfigService.
        enforcePrimaryUser();

        if (isProvisioningPackageForConfig(subscriptionGroup, pkg)) {
            return;
        }

        // Must NOT be called from cleared binder identity, since this checks user calling identity
        enforceCallingUserAndCarrierPrivilege(subscriptionGroup, pkg);
    }

    private boolean isProvisioningPackageForConfig(
            @NonNull ParcelUuid subscriptionGroup, @NonNull String pkg) {
        // Try-finally to return early if matching owned subscription found.
        final long identity = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                final VcnConfig config = mConfigs.get(subscriptionGroup);
                if (config != null && pkg.equals(config.getProvisioningPackageName())) {
                    return true;
                }
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }

        return false;
    }

    /**
     * Clears the VcnManagementService for a given subscription group.
     *
@@ -658,10 +729,17 @@ public class VcnManagementService extends IVcnManagementService.Stub {

        mContext.getSystemService(AppOpsManager.class)
                .checkPackage(mDeps.getBinderCallingUid(), opPkgName);
        enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
        enforceCarrierPrivilegeOrProvisioningPackage(subscriptionGroup, opPkgName);

        Binder.withCleanCallingIdentity(() -> {
            synchronized (mLock) {
                stopAndClearVcnConfigInternalLocked(subscriptionGroup);
                writeConfigsToDiskLocked();
            }
        });
    }

    private void stopAndClearVcnConfigInternalLocked(@NonNull ParcelUuid subscriptionGroup) {
        mConfigs.remove(subscriptionGroup);
        final boolean vcnExists = mVcns.containsKey(subscriptionGroup);

@@ -673,16 +751,34 @@ public class VcnManagementService extends IVcnManagementService.Stub {
            notifyAllPermissionedStatusCallbacksLocked(
                    subscriptionGroup, VCN_STATUS_CODE_NOT_CONFIGURED);
        }
    }

    private void garbageCollectAndWriteVcnConfigsLocked() {
        final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class);

        boolean shouldWrite = false;

        final Iterator<ParcelUuid> configsIterator = mConfigs.keySet().iterator();
        while (configsIterator.hasNext()) {
            final ParcelUuid subGrp = configsIterator.next();

            final List<SubscriptionInfo> subscriptions = subMgr.getSubscriptionsInGroup(subGrp);
            if (subscriptions == null || subscriptions.isEmpty()) {
                // Trim subGrps with no more subscriptions; must have moved to another subGrp
                configsIterator.remove();
                shouldWrite = true;
            }
        }

        if (shouldWrite) {
            writeConfigsToDiskLocked();
        }
        });
    }

    /**
     * Retrieves the list of subscription groups with configured VcnConfigs
     *
     * <p>Limited to subscription groups for which the caller is carrier privileged.
     * <p>Limited to subscription groups for which the caller had configured.
     *
     * <p>Implements the IVcnManagementService Binder interface.
     */
@@ -698,7 +794,8 @@ public class VcnManagementService extends IVcnManagementService.Stub {
        final List<ParcelUuid> result = new ArrayList<>();
        synchronized (mLock) {
            for (ParcelUuid subGrp : mConfigs.keySet()) {
                if (mLastSnapshot.packageHasPermissionsForSubscriptionGroup(subGrp, opPkgName)) {
                if (mLastSnapshot.packageHasPermissionsForSubscriptionGroup(subGrp, opPkgName)
                        || isProvisioningPackageForConfig(subGrp, opPkgName)) {
                    result.add(subGrp);
                }
            }
+90 −17
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.TelephonyNetworkSpecifier;
import android.net.Uri;
import android.net.vcn.IVcnStatusCallback;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
import android.net.vcn.VcnConfig;
@@ -114,18 +115,24 @@ import java.util.UUID;
public class VcnManagementServiceTest {
    private static final String TEST_PACKAGE_NAME =
            VcnManagementServiceTest.class.getPackage().getName();
    private static final String TEST_PACKAGE_NAME_2 = "TEST_PKG_2";
    private static final String TEST_CB_PACKAGE_NAME =
            VcnManagementServiceTest.class.getPackage().getName() + ".callback";
    private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0));
    private static final ParcelUuid TEST_UUID_2 = new ParcelUuid(new UUID(1, 1));
    private static final ParcelUuid TEST_UUID_3 = new ParcelUuid(new UUID(2, 2));
    private static final VcnConfig TEST_VCN_CONFIG;
    private static final VcnConfig TEST_VCN_CONFIG_PKG_2;
    private static final int TEST_UID = Process.FIRST_APPLICATION_UID;

    static {
        final Context mockConfigContext = mock(Context.class);
        doReturn(TEST_PACKAGE_NAME).when(mockConfigContext).getOpPackageName();

        doReturn(TEST_PACKAGE_NAME).when(mockConfigContext).getOpPackageName();
        TEST_VCN_CONFIG = VcnConfigTest.buildTestConfig(mockConfigContext);

        doReturn(TEST_PACKAGE_NAME_2).when(mockConfigContext).getOpPackageName();
        TEST_VCN_CONFIG_PKG_2 = VcnConfigTest.buildTestConfig(mockConfigContext);
    }

    private static final Map<ParcelUuid, VcnConfig> TEST_VCN_CONFIG_MAP =
@@ -246,18 +253,24 @@ public class VcnManagementServiceTest {
                        eq(android.Manifest.permission.NETWORK_FACTORY), any());
    }


    private void setupMockedCarrierPrivilege(boolean isPrivileged) {
        setupMockedCarrierPrivilege(isPrivileged, TEST_PACKAGE_NAME);
    }

    private void setupMockedCarrierPrivilege(boolean isPrivileged, String pkg) {
        doReturn(Collections.singletonList(TEST_SUBSCRIPTION_INFO))
                .when(mSubMgr)
                .getSubscriptionsInGroup(any());
        doReturn(mTelMgr)
                .when(mTelMgr)
                .createForSubscriptionId(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId()));
        doReturn(isPrivileged
        doReturn(
                        isPrivileged
                                ? CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
                                : CARRIER_PRIVILEGE_STATUS_NO_ACCESS)
                .when(mTelMgr)
                .checkCarrierPrivilegesForPackage(eq(TEST_PACKAGE_NAME));
                .checkCarrierPrivilegesForPackage(eq(pkg));
    }

    @Test
@@ -414,7 +427,13 @@ public class VcnManagementServiceTest {
    private BroadcastReceiver getPackageChangeReceiver() {
        final ArgumentCaptor<BroadcastReceiver> captor =
                ArgumentCaptor.forClass(BroadcastReceiver.class);
        verify(mMockContext).registerReceiver(captor.capture(), any(), any(), any());
        verify(mMockContext).registerReceiver(captor.capture(), argThat(filter -> {
            return filter.hasAction(Intent.ACTION_PACKAGE_ADDED)
                    && filter.hasAction(Intent.ACTION_PACKAGE_REPLACED)
                    && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED)
                    && filter.hasAction(Intent.ACTION_PACKAGE_DATA_CLEARED)
                    && filter.hasAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
        }), any(), any());
        return captor.getValue();
    }

@@ -538,6 +557,44 @@ public class VcnManagementServiceTest {
        verify(mSubscriptionTracker).handleSubscriptionsChanged();
    }

    @Test
    public void testPackageChangeListener_packageDataCleared() throws Exception {
        triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_1, Collections.singleton(TEST_UUID_1));
        final Vcn vcn = mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1);

        final BroadcastReceiver receiver = getPackageChangeReceiver();
        assertEquals(TEST_VCN_CONFIG_MAP, mVcnMgmtSvc.getConfigs());

        final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
        intent.setData(Uri.parse("package:" + TEST_PACKAGE_NAME));
        intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(TEST_UID));

        receiver.onReceive(mMockContext, intent);
        mTestLooper.dispatchAll();
        verify(vcn).teardownAsynchronously();
        assertTrue(mVcnMgmtSvc.getConfigs().isEmpty());
        verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
    }

    @Test
    public void testPackageChangeListener_packageFullyRemoved() throws Exception {
        triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_1, Collections.singleton(TEST_UUID_1));
        final Vcn vcn = mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1);

        final BroadcastReceiver receiver = getPackageChangeReceiver();
        assertEquals(TEST_VCN_CONFIG_MAP, mVcnMgmtSvc.getConfigs());

        final Intent intent = new Intent(Intent.ACTION_PACKAGE_FULLY_REMOVED);
        intent.setData(Uri.parse("package:" + TEST_PACKAGE_NAME));
        intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(TEST_UID));

        receiver.onReceive(mMockContext, intent);
        mTestLooper.dispatchAll();
        verify(vcn).teardownAsynchronously();
        assertTrue(mVcnMgmtSvc.getConfigs().isEmpty());
        verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
    }

    @Test
    public void testSetVcnConfigRequiresNonSystemServer() throws Exception {
        doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
@@ -578,7 +635,7 @@ public class VcnManagementServiceTest {
    @Test
    public void testSetVcnConfigMismatchedPackages() throws Exception {
        try {
            mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, "IncorrectPackage");
            mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME_2);
            fail("Expected exception due to mismatched packages in config and method call");
        } catch (IllegalArgumentException expected) {
            verify(mMockPolicyListener, never()).onPolicyChanged();
@@ -678,11 +735,12 @@ public class VcnManagementServiceTest {
    }

    @Test
    public void testClearVcnConfigRequiresCarrierPrivileges() throws Exception {
    public void testClearVcnConfigRequiresCarrierPrivilegesOrProvisioningPackage()
            throws Exception {
        setupMockedCarrierPrivilege(false);

        try {
            mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
            mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME_2);
            fail("Expected security exception for missing carrier privileges");
        } catch (SecurityException expected) {
        }
@@ -691,19 +749,31 @@ public class VcnManagementServiceTest {
    @Test
    public void testClearVcnConfigMismatchedPackages() throws Exception {
        try {
            mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, "IncorrectPackage");
            mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME_2);
            fail("Expected security exception due to mismatched packages");
        } catch (SecurityException expected) {
        }
    }

    @Test
    public void testClearVcnConfig() throws Exception {
    public void testClearVcnConfig_callerIsProvisioningPackage() throws Exception {
        // Lose carrier privileges to test that provisioning package is sufficient.
        setupMockedCarrierPrivilege(false);

        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
        assertTrue(mVcnMgmtSvc.getConfigs().isEmpty());
        verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
    }

    @Test
    public void testClearVcnConfig_callerIsCarrierPrivileged() throws Exception {
        setupMockedCarrierPrivilege(true, TEST_PACKAGE_NAME_2);

        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME_2);
        assertTrue(mVcnMgmtSvc.getConfigs().isEmpty());
        verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
    }

    @Test
    public void testClearVcnConfigNotifiesStatusCallback() throws Exception {
        setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */);
@@ -755,11 +825,12 @@ public class VcnManagementServiceTest {

    @Test
    public void testGetConfiguredSubscriptionGroupsMismatchedPackages() throws Exception {
        final String badPackage = "IncorrectPackage";
        doThrow(new SecurityException()).when(mAppOpsMgr).checkPackage(TEST_UID, badPackage);
        doThrow(new SecurityException())
                .when(mAppOpsMgr)
                .checkPackage(TEST_UID, TEST_PACKAGE_NAME_2);

        try {
            mVcnMgmtSvc.getConfiguredSubscriptionGroups(badPackage);
            mVcnMgmtSvc.getConfiguredSubscriptionGroups(TEST_PACKAGE_NAME_2);
            fail("Expected security exception due to mismatched packages");
        } catch (SecurityException expected) {
        }
@@ -767,14 +838,16 @@ public class VcnManagementServiceTest {

    @Test
    public void testGetConfiguredSubscriptionGroups() throws Exception {
        setupMockedCarrierPrivilege(true, TEST_PACKAGE_NAME_2);
        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
        mVcnMgmtSvc.setVcnConfig(TEST_UUID_3, TEST_VCN_CONFIG_PKG_2, TEST_PACKAGE_NAME_2);

        // Assert that if both UUID 1 and 2 are provisioned, the caller only gets ones that they are
        // privileged for.
        // Assert that if UUIDs 1, 2 and 3 are provisioned, the caller only gets ones that they are
        // privileged for, or are the provisioning package of.
        triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_1, Collections.singleton(TEST_UUID_1));
        final List<ParcelUuid> subGrps =
                mVcnMgmtSvc.getConfiguredSubscriptionGroups(TEST_PACKAGE_NAME);
        assertEquals(Collections.singletonList(TEST_UUID_1), subGrps);
        assertEquals(Arrays.asList(new ParcelUuid[] {TEST_UUID_1, TEST_UUID_2}), subGrps);
    }

    @Test