Loading services/core/java/com/android/server/VcnManagementService.java +73 −0 Original line number Diff line number Diff line Loading @@ -25,13 +25,22 @@ import android.net.NetworkProvider; import android.net.NetworkRequest; import android.net.vcn.IVcnManagementService; import android.net.vcn.VcnConfig; import android.os.Binder; import android.os.HandlerThread; import android.os.Looper; import android.os.ParcelUuid; import android.os.Process; import android.os.UserHandle; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import java.util.ArrayList; import java.util.List; /** * VcnManagementService manages Virtual Carrier Network profiles and lifecycles. * Loading Loading @@ -130,6 +139,18 @@ public class VcnManagementService extends IVcnManagementService.Stub { } return mHandlerThread.getLooper(); } /** * Retrieves the caller's UID * * <p>This call MUST be made before calling {@link Binder#clearCallingIdentity}, otherwise * this will not work properly. * * @return */ public int getBinderCallingUid() { return Binder.getCallingUid(); } } /** Notifies the VcnManagementService that external dependencies can be set up. */ Loading @@ -140,6 +161,50 @@ public class VcnManagementService extends IVcnManagementService.Stub { .registerNetworkProvider(mNetworkProvider); } private void enforcePrimaryUser() { final int uid = mDeps.getBinderCallingUid(); if (uid == Process.SYSTEM_UID) { throw new IllegalStateException( "Calling identity was System Server. Was Binder calling identity cleared?"); } if (!UserHandle.getUserHandleForUid(uid).isSystem()) { throw new SecurityException( "VcnManagementService can only be used by callers running as the primary user"); } } private void enforceCallingUserAndCarrierPrivilege(ParcelUuid subscriptionGroup) { // 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(); // TODO (b/172619301): Check based on events propagated from CarrierPrivilegesTracker final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class); final List<SubscriptionInfo> subscriptionInfos = new ArrayList<>(); Binder.withCleanCallingIdentity( () -> { subscriptionInfos.addAll(subMgr.getSubscriptionsInGroup(subscriptionGroup)); }); final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class); for (SubscriptionInfo info : subscriptionInfos) { // Check subscription is active first; much cheaper/faster check, and an app (currently) // cannot be carrier privileged for inactive subscriptions. if (subMgr.isValidSlotIndex(info.getSimSlotIndex()) && telMgr.hasCarrierPrivileges(info.getSubscriptionId())) { // TODO (b/173717728): Allow configuration for inactive, but manageable // subscriptions. // TODO (b/173718661): Check for whole subscription groups at a time. return; } } throw new SecurityException( "Carrier privilege required for subscription group to set VCN Config"); } /** * Sets a VCN config for a given subscription group. * Loading @@ -150,6 +215,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); requireNonNull(config, "config was null"); enforceCallingUserAndCarrierPrivilege(subscriptionGroup); // TODO: Clear Binder calling identity // TODO: Store VCN configuration, trigger startup as necessary } Loading @@ -162,6 +231,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); enforceCallingUserAndCarrierPrivilege(subscriptionGroup); // TODO: Clear Binder calling identity // TODO: Clear VCN configuration, trigger teardown as necessary } Loading tests/vcn/java/com/android/server/VcnManagementServiceTest.java +130 −5 Original line number Diff line number Diff line Loading @@ -16,14 +16,23 @@ package com.android.server; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.content.Context; import android.net.ConnectivityManager; import android.net.vcn.VcnConfig; import android.os.ParcelUuid; import android.os.Process; import android.os.UserHandle; import android.os.test.TestLooper; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; Loading @@ -31,27 +40,73 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Collections; import java.util.UUID; /** Tests for {@link VcnManagementService}. */ @RunWith(AndroidJUnit4.class) @SmallTest public class VcnManagementServiceTest { private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0)); private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO = new SubscriptionInfo( 1 /* id */, "" /* iccId */, 0 /* simSlotIndex */, "Carrier" /* displayName */, "Carrier" /* carrierName */, 0 /* nameSource */, 255 /* iconTint */, "12345" /* number */, 0 /* roaming */, null /* icon */, "0" /* mcc */, "0" /* mnc */, "0" /* countryIso */, false /* isEmbedded */, null /* nativeAccessRules */, null /* cardString */, false /* isOpportunistic */, TEST_UUID_1.toString() /* groupUUID */, 0 /* carrierId */, 0 /* profileClass */); private final Context mMockContext = mock(Context.class); private final VcnManagementService.Dependencies mMockDeps = mock(VcnManagementService.Dependencies.class); private final TestLooper mTestLooper = new TestLooper(); private final ConnectivityManager mConnMgr = mock(ConnectivityManager.class); private final TelephonyManager mTelMgr = mock(TelephonyManager.class); private final SubscriptionManager mSubMgr = mock(SubscriptionManager.class); private final VcnManagementService mVcnMgmtSvc; public VcnManagementServiceTest() { doReturn(Context.CONNECTIVITY_SERVICE) .when(mMockContext) .getSystemServiceName(ConnectivityManager.class); doReturn(mConnMgr).when(mMockContext).getSystemService(Context.CONNECTIVITY_SERVICE); public VcnManagementServiceTest() throws Exception { setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class); setupSystemService( mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class); doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper(); doReturn(Process.FIRST_APPLICATION_UID).when(mMockDeps).getBinderCallingUid(); setupMockedCarrierPrivilege(true); mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps); } private void setupSystemService(Object service, String name, Class<?> serviceClass) { doReturn(name).when(mMockContext).getSystemServiceName(serviceClass); doReturn(service).when(mMockContext).getSystemService(name); } private void setupMockedCarrierPrivilege(boolean isPrivileged) { doReturn(Collections.singletonList(TEST_SUBSCRIPTION_INFO)) .when(mSubMgr) .getSubscriptionsInGroup(any()); doReturn(isPrivileged) .when(mTelMgr) .hasCarrierPrivileges(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId())); } @Test public void testSystemReady() throws Exception { mVcnMgmtSvc.systemReady(); Loading @@ -59,4 +114,74 @@ public class VcnManagementServiceTest { verify(mConnMgr) .registerNetworkProvider(any(VcnManagementService.VcnNetworkProvider.class)); } @Test public void testSetVcnConfigRequiresNonSystemServer() throws Exception { doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid(); try { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, new VcnConfig.Builder().build()); fail("Expected IllegalStateException exception for system server"); } catch (IllegalStateException expected) { } } @Test public void testSetVcnConfigRequiresSystemUser() throws Exception { doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID)) .when(mMockDeps) .getBinderCallingUid(); try { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, new VcnConfig.Builder().build()); fail("Expected security exception for non system user"); } catch (SecurityException expected) { } } @Test public void testSetVcnConfigRequiresCarrierPrivileges() throws Exception { setupMockedCarrierPrivilege(false); try { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, new VcnConfig.Builder().build()); fail("Expected security exception for missing carrier privileges"); } catch (SecurityException expected) { } } @Test public void testClearVcnConfigRequiresNonSystemServer() throws Exception { doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid(); try { mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1); fail("Expected IllegalStateException exception for system server"); } catch (IllegalStateException expected) { } } @Test public void testClearVcnConfigRequiresSystemUser() throws Exception { doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID)) .when(mMockDeps) .getBinderCallingUid(); try { mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1); fail("Expected security exception for non system user"); } catch (SecurityException expected) { } } @Test public void testClearVcnConfigRequiresCarrierPrivileges() throws Exception { setupMockedCarrierPrivilege(false); try { mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1); fail("Expected security exception for missing carrier privileges"); } catch (SecurityException expected) { } } } Loading
services/core/java/com/android/server/VcnManagementService.java +73 −0 Original line number Diff line number Diff line Loading @@ -25,13 +25,22 @@ import android.net.NetworkProvider; import android.net.NetworkRequest; import android.net.vcn.IVcnManagementService; import android.net.vcn.VcnConfig; import android.os.Binder; import android.os.HandlerThread; import android.os.Looper; import android.os.ParcelUuid; import android.os.Process; import android.os.UserHandle; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import java.util.ArrayList; import java.util.List; /** * VcnManagementService manages Virtual Carrier Network profiles and lifecycles. * Loading Loading @@ -130,6 +139,18 @@ public class VcnManagementService extends IVcnManagementService.Stub { } return mHandlerThread.getLooper(); } /** * Retrieves the caller's UID * * <p>This call MUST be made before calling {@link Binder#clearCallingIdentity}, otherwise * this will not work properly. * * @return */ public int getBinderCallingUid() { return Binder.getCallingUid(); } } /** Notifies the VcnManagementService that external dependencies can be set up. */ Loading @@ -140,6 +161,50 @@ public class VcnManagementService extends IVcnManagementService.Stub { .registerNetworkProvider(mNetworkProvider); } private void enforcePrimaryUser() { final int uid = mDeps.getBinderCallingUid(); if (uid == Process.SYSTEM_UID) { throw new IllegalStateException( "Calling identity was System Server. Was Binder calling identity cleared?"); } if (!UserHandle.getUserHandleForUid(uid).isSystem()) { throw new SecurityException( "VcnManagementService can only be used by callers running as the primary user"); } } private void enforceCallingUserAndCarrierPrivilege(ParcelUuid subscriptionGroup) { // 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(); // TODO (b/172619301): Check based on events propagated from CarrierPrivilegesTracker final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class); final List<SubscriptionInfo> subscriptionInfos = new ArrayList<>(); Binder.withCleanCallingIdentity( () -> { subscriptionInfos.addAll(subMgr.getSubscriptionsInGroup(subscriptionGroup)); }); final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class); for (SubscriptionInfo info : subscriptionInfos) { // Check subscription is active first; much cheaper/faster check, and an app (currently) // cannot be carrier privileged for inactive subscriptions. if (subMgr.isValidSlotIndex(info.getSimSlotIndex()) && telMgr.hasCarrierPrivileges(info.getSubscriptionId())) { // TODO (b/173717728): Allow configuration for inactive, but manageable // subscriptions. // TODO (b/173718661): Check for whole subscription groups at a time. return; } } throw new SecurityException( "Carrier privilege required for subscription group to set VCN Config"); } /** * Sets a VCN config for a given subscription group. * Loading @@ -150,6 +215,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); requireNonNull(config, "config was null"); enforceCallingUserAndCarrierPrivilege(subscriptionGroup); // TODO: Clear Binder calling identity // TODO: Store VCN configuration, trigger startup as necessary } Loading @@ -162,6 +231,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) { requireNonNull(subscriptionGroup, "subscriptionGroup was null"); enforceCallingUserAndCarrierPrivilege(subscriptionGroup); // TODO: Clear Binder calling identity // TODO: Clear VCN configuration, trigger teardown as necessary } Loading
tests/vcn/java/com/android/server/VcnManagementServiceTest.java +130 −5 Original line number Diff line number Diff line Loading @@ -16,14 +16,23 @@ package com.android.server; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.content.Context; import android.net.ConnectivityManager; import android.net.vcn.VcnConfig; import android.os.ParcelUuid; import android.os.Process; import android.os.UserHandle; import android.os.test.TestLooper; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; Loading @@ -31,27 +40,73 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Collections; import java.util.UUID; /** Tests for {@link VcnManagementService}. */ @RunWith(AndroidJUnit4.class) @SmallTest public class VcnManagementServiceTest { private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0)); private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO = new SubscriptionInfo( 1 /* id */, "" /* iccId */, 0 /* simSlotIndex */, "Carrier" /* displayName */, "Carrier" /* carrierName */, 0 /* nameSource */, 255 /* iconTint */, "12345" /* number */, 0 /* roaming */, null /* icon */, "0" /* mcc */, "0" /* mnc */, "0" /* countryIso */, false /* isEmbedded */, null /* nativeAccessRules */, null /* cardString */, false /* isOpportunistic */, TEST_UUID_1.toString() /* groupUUID */, 0 /* carrierId */, 0 /* profileClass */); private final Context mMockContext = mock(Context.class); private final VcnManagementService.Dependencies mMockDeps = mock(VcnManagementService.Dependencies.class); private final TestLooper mTestLooper = new TestLooper(); private final ConnectivityManager mConnMgr = mock(ConnectivityManager.class); private final TelephonyManager mTelMgr = mock(TelephonyManager.class); private final SubscriptionManager mSubMgr = mock(SubscriptionManager.class); private final VcnManagementService mVcnMgmtSvc; public VcnManagementServiceTest() { doReturn(Context.CONNECTIVITY_SERVICE) .when(mMockContext) .getSystemServiceName(ConnectivityManager.class); doReturn(mConnMgr).when(mMockContext).getSystemService(Context.CONNECTIVITY_SERVICE); public VcnManagementServiceTest() throws Exception { setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class); setupSystemService( mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class); doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper(); doReturn(Process.FIRST_APPLICATION_UID).when(mMockDeps).getBinderCallingUid(); setupMockedCarrierPrivilege(true); mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps); } private void setupSystemService(Object service, String name, Class<?> serviceClass) { doReturn(name).when(mMockContext).getSystemServiceName(serviceClass); doReturn(service).when(mMockContext).getSystemService(name); } private void setupMockedCarrierPrivilege(boolean isPrivileged) { doReturn(Collections.singletonList(TEST_SUBSCRIPTION_INFO)) .when(mSubMgr) .getSubscriptionsInGroup(any()); doReturn(isPrivileged) .when(mTelMgr) .hasCarrierPrivileges(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId())); } @Test public void testSystemReady() throws Exception { mVcnMgmtSvc.systemReady(); Loading @@ -59,4 +114,74 @@ public class VcnManagementServiceTest { verify(mConnMgr) .registerNetworkProvider(any(VcnManagementService.VcnNetworkProvider.class)); } @Test public void testSetVcnConfigRequiresNonSystemServer() throws Exception { doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid(); try { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, new VcnConfig.Builder().build()); fail("Expected IllegalStateException exception for system server"); } catch (IllegalStateException expected) { } } @Test public void testSetVcnConfigRequiresSystemUser() throws Exception { doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID)) .when(mMockDeps) .getBinderCallingUid(); try { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, new VcnConfig.Builder().build()); fail("Expected security exception for non system user"); } catch (SecurityException expected) { } } @Test public void testSetVcnConfigRequiresCarrierPrivileges() throws Exception { setupMockedCarrierPrivilege(false); try { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, new VcnConfig.Builder().build()); fail("Expected security exception for missing carrier privileges"); } catch (SecurityException expected) { } } @Test public void testClearVcnConfigRequiresNonSystemServer() throws Exception { doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid(); try { mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1); fail("Expected IllegalStateException exception for system server"); } catch (IllegalStateException expected) { } } @Test public void testClearVcnConfigRequiresSystemUser() throws Exception { doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID)) .when(mMockDeps) .getBinderCallingUid(); try { mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1); fail("Expected security exception for non system user"); } catch (SecurityException expected) { } } @Test public void testClearVcnConfigRequiresCarrierPrivileges() throws Exception { setupMockedCarrierPrivilege(false); try { mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1); fail("Expected security exception for missing carrier privileges"); } catch (SecurityException expected) { } } }