diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index cb3140487f..5405ad37ff 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -4716,19 +4716,19 @@ public class ConnectivityManager { /** * Returns the {@code uid} of the owner of a network connection. * - * @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and - * {@code IPPROTO_UDP} currently supported. + * @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and {@code + * IPPROTO_UDP} currently supported. * @param local The local {@link InetSocketAddress} of a connection. * @param remote The remote {@link InetSocketAddress} of a connection. - * * @return {@code uid} if the connection is found and the app has permission to observe it - * (e.g., if it is associated with the calling VPN app's tunnel) or - * {@link android.os.Process#INVALID_UID} if the connection is not found. - * Throws {@link SecurityException} if the caller is not the active VPN for the current user. - * Throws {@link IllegalArgumentException} if an unsupported protocol is requested. + * (e.g., if it is associated with the calling VPN app's VpnService tunnel) or {@link + * android.os.Process#INVALID_UID} if the connection is not found. + * @throws {@link SecurityException} if the caller is not the active VpnService for the current + * user. + * @throws {@link IllegalArgumentException} if an unsupported protocol is requested. */ - public int getConnectionOwnerUid(int protocol, @NonNull InetSocketAddress local, - @NonNull InetSocketAddress remote) { + public int getConnectionOwnerUid( + int protocol, @NonNull InetSocketAddress local, @NonNull InetSocketAddress remote) { ConnectionInfo connectionInfo = new ConnectionInfo(protocol, local, remote); try { return mService.getConnectionOwnerUid(connectionInfo); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 1ce9d9157a..5f032fc90d 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -7528,6 +7528,13 @@ public class ConnectivityService extends IConnectivityManager.Stub */ public int getConnectionOwnerUid(ConnectionInfo connectionInfo) { final Vpn vpn = enforceActiveVpnOrNetworkStackPermission(); + + // Only VpnService based VPNs should be able to get this information. + if (vpn != null && vpn.getActiveAppVpnType() != VpnManager.TYPE_VPN_SERVICE) { + throw new SecurityException( + "getConnectionOwnerUid() not allowed for non-VpnService VPNs"); + } + if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) { throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol); } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 6fb4612245..8ed497b78b 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -78,6 +78,7 @@ import static android.net.NetworkPolicyManager.RULE_NONE; import static android.net.NetworkPolicyManager.RULE_REJECT_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.system.OsConstants.IPPROTO_TCP; import static com.android.server.ConnectivityServiceTestUtilsKt.transportToLegacyType; import static com.android.testutils.ConcurrentUtilsKt.await; @@ -138,6 +139,7 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.location.LocationManager; import android.net.CaptivePortalData; +import android.net.ConnectionInfo; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.PacketKeepalive; @@ -153,6 +155,7 @@ import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.InetAddresses; import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.IpSecManager; @@ -176,6 +179,7 @@ import android.net.RouteInfo; import android.net.SocketKeepalive; import android.net.UidRange; import android.net.Uri; +import android.net.VpnManager; import android.net.metrics.IpConnectivityLog; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; @@ -272,6 +276,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; +import java.util.function.Supplier; import kotlin.reflect.KClass; @@ -445,15 +450,21 @@ public class ConnectivityServiceTest { return mPackageManager; } + private int checkMockedPermission(String permission, Supplier ifAbsent) { + final Integer granted = mMockedPermissions.get(permission); + return granted != null ? granted : ifAbsent.get(); + } + @Override public int checkPermission(String permission, int pid, int uid) { - final Integer granted = mMockedPermissions.get(permission); - if (granted == null) { - // All non-mocked permissions should be held by the test or unnecessary: check as - // normal to make sure the code does not rely on unexpected permissions. - return super.checkPermission(permission, pid, uid); - } - return granted; + return checkMockedPermission( + permission, () -> super.checkPermission(permission, pid, uid)); + } + + @Override + public int checkCallingOrSelfPermission(String permission) { + return checkMockedPermission( + permission, () -> super.checkCallingOrSelfPermission(permission)); } @Override @@ -1002,6 +1013,7 @@ public class ConnectivityServiceTest { // Careful ! This is different from mNetworkAgent, because MockNetworkAgent does // not inherit from NetworkAgent. private TestNetworkAgentWrapper mMockNetworkAgent; + private int mVpnType = VpnManager.TYPE_VPN_SERVICE; private VpnInfo mVpnInfo; @@ -1022,6 +1034,10 @@ public class ConnectivityServiceTest { updateCapabilities(null /* defaultNetwork */); } + public void setVpnType(int vpnType) { + mVpnType = vpnType; + } + @Override public int getNetId() { if (mMockNetworkAgent == null) { @@ -1040,6 +1056,11 @@ public class ConnectivityServiceTest { return mConnected; // Similar trickery } + @Override + public int getActiveAppVpnType() { + return mVpnType; + } + private void connect(boolean isAlwaysMetered) { mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); mConnected = true; @@ -6429,6 +6450,90 @@ public class ConnectivityServiceTest { assertEquals(Process.INVALID_UID, newNc.getOwnerUid()); } + private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType) + throws Exception { + final Set vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER)); + establishVpn(new LinkProperties(), vpnOwnerUid, vpnRange); + mMockVpn.setVpnType(vpnType); + + final VpnInfo vpnInfo = new VpnInfo(); + vpnInfo.ownerUid = vpnOwnerUid; + mMockVpn.setVpnInfo(vpnInfo); + } + + private void setupConnectionOwnerUidAsVpnApp(int vpnOwnerUid, @VpnManager.VpnType int vpnType) + throws Exception { + setupConnectionOwnerUid(vpnOwnerUid, vpnType); + + // Test as VPN app + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + mServiceContext.setPermission( + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_DENIED); + } + + private ConnectionInfo getTestConnectionInfo() throws Exception { + return new ConnectionInfo( + IPPROTO_TCP, + new InetSocketAddress(InetAddresses.parseNumericAddress("1.2.3.4"), 1234), + new InetSocketAddress(InetAddresses.parseNumericAddress("2.3.4.5"), 2345)); + } + + @Test + public void testGetConnectionOwnerUidPlatformVpn() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_PLATFORM); + + try { + mService.getConnectionOwnerUid(getTestConnectionInfo()); + fail("Expected SecurityException for non-VpnService app"); + } catch (SecurityException expected) { + } + } + + @Test + public void testGetConnectionOwnerUidVpnServiceWrongUser() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUidAsVpnApp(myUid + 1, VpnManager.TYPE_VPN_SERVICE); + + try { + mService.getConnectionOwnerUid(getTestConnectionInfo()); + fail("Expected SecurityException for non-VpnService app"); + } catch (SecurityException expected) { + } + } + + @Test + public void testGetConnectionOwnerUidVpnServiceDoesNotThrow() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_SERVICE); + + // TODO: Test the returned UID + mService.getConnectionOwnerUid(getTestConnectionInfo()); + } + + @Test + public void testGetConnectionOwnerUidVpnServiceNetworkStackDoesNotThrow() throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE); + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + + // TODO: Test the returned UID + mService.getConnectionOwnerUid(getTestConnectionInfo()); + } + + @Test + public void testGetConnectionOwnerUidVpnServiceMainlineNetworkStackDoesNotThrow() + throws Exception { + final int myUid = Process.myUid(); + setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE); + mServiceContext.setPermission( + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED); + + // TODO: Test the returned UID + mService.getConnectionOwnerUid(getTestConnectionInfo()); + } + private TestNetworkAgentWrapper establishVpn( LinkProperties lp, int ownerUid, Set vpnRange) throws Exception { final TestNetworkAgentWrapper diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index eb78529e87..ac1c51837e 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -656,8 +656,12 @@ public class VpnTest { } private Vpn createVpnAndSetupUidChecks(int... grantedOps) throws Exception { - final Vpn vpn = createVpn(primaryUser.id); - setMockedUsers(primaryUser); + return createVpnAndSetupUidChecks(primaryUser, grantedOps); + } + + private Vpn createVpnAndSetupUidChecks(UserInfo user, int... grantedOps) throws Exception { + final Vpn vpn = createVpn(user.id); + setMockedUsers(user); when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt())) .thenReturn(Process.myUid()); @@ -725,6 +729,19 @@ public class VpnTest { } } + @Test + public void testProvisionVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + @Test public void testDeleteVpnProfile() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(); @@ -735,6 +752,19 @@ public class VpnTest { .delete(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), eq(Process.SYSTEM_UID)); } + @Test + public void testDeleteVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + @Test public void testGetVpnProfilePrivileged() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks(); @@ -819,6 +849,32 @@ public class VpnTest { eq(TEST_VPN_PKG)); } + @Test + public void testStartVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + + @Test + public void testStopVpnProfileRestrictedUser() throws Exception { + final Vpn vpn = + createVpnAndSetupUidChecks( + restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + try { + vpn.stopVpnProfile(TEST_VPN_PKG); + fail("Expected SecurityException due to restricted user"); + } catch (SecurityException expected) { + } + } + @Test public void testSetPackageAuthorizationVpnService() throws Exception { final Vpn vpn = createVpnAndSetupUidChecks();