Loading core/java/android/net/vcn/VcnManager.java +10 −5 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -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 */ Loading services/core/java/com/android/server/VcnManagementService.java +127 −30 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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(() -> { Loading Loading @@ -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} Loading Loading @@ -504,6 +541,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { final Map<ParcelUuid, Set<Integer>> currSubGrpMappings = getSubGroupToSubIdMappings(mLastSnapshot); if (!currSubGrpMappings.equals(oldSubGrpMappings)) { garbageCollectAndWriteVcnConfigsLocked(); notifyAllPolicyListenersLocked(); } } Loading Loading @@ -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. * Loading @@ -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); Loading @@ -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. */ Loading @@ -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); } } Loading tests/vcn/java/com/android/server/VcnManagementServiceTest.java +90 −17 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 = Loading Loading @@ -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 Loading Loading @@ -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(); } Loading Loading @@ -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(); Loading Loading @@ -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(); Loading Loading @@ -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) { } Loading @@ -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 */); Loading Loading @@ -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) { } Loading @@ -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 Loading Loading
core/java/android/net/vcn/VcnManager.java +10 −5 Original line number Diff line number Diff line Loading @@ -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. */ Loading @@ -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 */ Loading
services/core/java/com/android/server/VcnManagementService.java +127 −30 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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(() -> { Loading Loading @@ -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} Loading Loading @@ -504,6 +541,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { final Map<ParcelUuid, Set<Integer>> currSubGrpMappings = getSubGroupToSubIdMappings(mLastSnapshot); if (!currSubGrpMappings.equals(oldSubGrpMappings)) { garbageCollectAndWriteVcnConfigsLocked(); notifyAllPolicyListenersLocked(); } } Loading Loading @@ -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. * Loading @@ -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); Loading @@ -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. */ Loading @@ -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); } } Loading
tests/vcn/java/com/android/server/VcnManagementServiceTest.java +90 −17 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 = Loading Loading @@ -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 Loading Loading @@ -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(); } Loading Loading @@ -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(); Loading Loading @@ -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(); Loading Loading @@ -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) { } Loading @@ -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 */); Loading Loading @@ -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) { } Loading @@ -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 Loading