diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 98c8368168..ee58c70b9e 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -1902,6 +1902,18 @@ public class ConnectivityService extends IConnectivityManager.Stub InetDiagMessage.destroyLiveTcpSocketsByLocalAddress(address, interfaceId); } + /** + * Call {@link InetDiagMessage#destroyLiveTcpSocketsLackingPermission(int, int)} + * + * @param netId network ID + * @param permission network permission + */ + public void destroyLiveTcpSocketsLackingPermission( + int netId, int permission) + throws SocketException, InterruptedIOException, ErrnoException { + InetDiagMessage.destroyLiveTcpSocketsLackingPermission(netId, permission); + } + /** * Schedule the evaluation timeout. * @@ -5820,8 +5832,14 @@ public class ConnectivityService extends IConnectivityManager.Stub // Delayed teardown. if (nai.isCreated() && !nai.isDestroyed()) { + destroyLiveTcpSocketsLackingPermission( + nai, getNetworkPermission(nai.networkCapabilities), INetd.PERMISSION_SYSTEM); try { mNetd.networkSetPermissionForNetwork(nai.network.netId, INetd.PERMISSION_SYSTEM); + // Call again in case sockets were opened after the first time : see + // updateNetworkPermissions + destroyLiveTcpSocketsLackingPermission(nai, + getNetworkPermission(nai.networkCapabilities), INetd.PERMISSION_SYSTEM); } catch (RemoteException e) { Log.d(TAG, "Error marking network restricted during teardown: ", e); } @@ -10615,8 +10633,14 @@ public class ConnectivityService extends IConnectivityManager.Stub final int oldPermission = getNetworkPermission(nai.networkCapabilities); final int newPermission = getNetworkPermission(newNc); if (oldPermission != newPermission && nai.isCreated() && !nai.isVPN()) { + destroyLiveTcpSocketsLackingPermission(nai, oldPermission, newPermission); try { mNetd.networkSetPermissionForNetwork(nai.network.getNetId(), newPermission); + // Destroy sockets again in case any were opened after + // destroySocketsLackingPermission is called above and before the permissions is + // changed. These sockets won't be able to send any RST packets because they are now + // no longer routed, but at least the apps will get errors. + destroyLiveTcpSocketsLackingPermission(nai, oldPermission, newPermission); } catch (RemoteException | ServiceSpecificException e) { loge("Exception in networkSetPermissionForNetwork: " + e); } @@ -15567,6 +15591,32 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("Exception destroy TCP sockets on local address: ", e); } } + } + private void destroyLiveTcpSocketsLackingPermission( + NetworkAgentInfo nai, int oldPermission, int newPermission) { + if (!mDeps.flagConnectivityServiceDestroySocket()) { + return; + } + // Virtual network doesn't have permission set. + if (!isPermissionMoreRestrictive(oldPermission, newPermission) || nai.isVPN()) { + return; + } + try { + mDeps.destroyLiveTcpSocketsLackingPermission( + nai.network.netId, newPermission); + } catch (SocketException | InterruptedIOException | ErrnoException e) { + loge("Exception destroy TCP sockets lacking permission: " + e); + } + } + + private boolean isPermissionMoreRestrictive(int oldPermission, int newPermission) { + return switch (newPermission) { + case INetd.PERMISSION_NONE -> false; + case INetd.PERMISSION_NETWORK -> oldPermission == INetd.PERMISSION_NONE; + case INetd.PERMISSION_SYSTEM -> oldPermission == INetd.PERMISSION_NONE + || oldPermission == INetd.PERMISSION_NETWORK; + default -> throw new IllegalArgumentException("Invalid permission"); + }; } } diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt index ee8c521055..aeeced6579 100644 --- a/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt +++ b/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt @@ -24,6 +24,7 @@ import android.net.ConnectivityManager.BLOCKED_REASON_NONE import android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND import android.net.ConnectivityManager.FIREWALL_RULE_ALLOW import android.net.ConnectivityManager.FIREWALL_RULE_DENY +import android.net.INetd import android.net.LinkAddress import android.net.LinkProperties import android.net.NetworkCapabilities @@ -54,7 +55,9 @@ import com.android.testutils.DevSdkIgnoreRule import com.android.testutils.DevSdkIgnoreRunner import com.android.testutils.TestableNetworkAgent.Event.OnNetworkDestroyed import com.android.testutils.TestableNetworkCallback +import com.android.testutils.TestableNetworkCallback.Event.CapabilitiesChanged import com.android.testutils.TestableNetworkCallback.Event.LinkPropertiesChanged +import com.android.testutils.TestableNetworkCallback.Event.Lost import java.net.Inet6Address import java.net.InetAddress import junit.framework.Assert.assertFalse @@ -66,6 +69,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mockito.any import org.mockito.Mockito.anyInt +import org.mockito.Mockito.doNothing import org.mockito.Mockito.doReturn import org.mockito.Mockito.inOrder import org.mockito.Mockito.never @@ -512,8 +516,8 @@ class CSDestroySocketTest : CSTest() { @Test fun testDestroySocketForRemovedIpAddressFromSingleNetwork() { - val addressV6_1 = InetAddress.getByName("2100::1234") - val addressV6_2 = InetAddress.getByName("2100:0207::4321:1234") + val addressV6_1 = InetAddress.getByName("2001:DB8:0100::1111") + val addressV6_2 = InetAddress.getByName("2001:DB8:0200::2222") // add a test network. val (agent1, callback1) = prepareNetworkAgent( "wlan2", @@ -523,7 +527,7 @@ class CSDestroySocketTest : CSTest() { // IP address changed val linkProperties2 = makeLp("wlan2") - linkProperties2.addLinkAddress(LinkAddress(addressV6_2, 64)) + linkProperties2.addLinkAddress(addressV6_2.toLinkAddress()) agent1.sendLinkProperties(linkProperties2) callback1.eventuallyExpect { it.network == agent1.network && !it.lp.addresses.contains(addressV6_1) @@ -549,10 +553,10 @@ class CSDestroySocketTest : CSTest() { @Test fun testDestroySocketForRemovedIpAddressFromMultipleNonVpnNetworks() { - val addressV6_1 = InetAddress.getByName("2100::1234") - val addressV6_2 = InetAddress.getByName("2100:0207::4321:1234") + val addressV6_1 = InetAddress.getByName("2001:DB8:0100::1111") + val addressV6_2 = InetAddress.getByName("2001:DB8:0200::2222") val addressV6LinkLocal = InetAddress.getByName("FE80::1234") - val addressV4_1 = InetAddress.getByName("203.0.113.10") + val addressV4_1 = InetAddress.getByName("192.0.2.10") // add 4 test networks(non VPN). 3 have a same IP address, and 1 network has different one. val (cellAgent, cellCallback) = prepareNetworkAgent( @@ -654,11 +658,11 @@ class CSDestroySocketTest : CSTest() { @Test fun testDestroySocketForRemovedIpAddressFromMultipleVpnNetworks() { - val addressV6_1 = InetAddress.getByName("2100::1234") - val addressV6_2 = InetAddress.getByName("2100:0207::4321:1234") - val addressV6_3 = InetAddress.getByName("2234:0207::1234:4321") + val addressV6_1 = InetAddress.getByName("2001:DB8:0100::1111") + val addressV6_2 = InetAddress.getByName("2001:DB8:0200::2222") + val addressV6_3 = InetAddress.getByName("2001:DB8:0333::3333") val addressV6LinkLocal = InetAddress.getByName("FE80::1234") - val addressV4_1 = InetAddress.getByName("203.0.113.10") + val addressV4_1 = InetAddress.getByName("192.0.2.10") // add 3 VPN test networks and 1 non-VPN network. val (vpnAgent1, vpnCallback1) = prepareNetworkAgent( @@ -705,7 +709,7 @@ class CSDestroySocketTest : CSTest() { // Change IP addresses network#2 val linkProperties2 = makeLp("ipsec2") - linkProperties2.addLinkAddress(LinkAddress(addressV6_3, 64)) + linkProperties2.addLinkAddress(addressV6_3.toLinkAddress()) vpnAgent2.sendLinkProperties(linkProperties2) vpnCallback2.eventuallyExpect { it.network == vpnAgent2.network && it.lp.interfaceName == "ipsec2" && it.lp.addresses.contains(addressV6_3) @@ -810,4 +814,58 @@ class CSDestroySocketTest : CSTest() { null /* uidRanges */ ) } + + @Test + fun testDestroySocketsLackingPermissionForPermissionUpdate() { + val inOrder = inOrder(destroySocketsWrapper) + val addressV6_1 = InetAddress.getByName("2001:DB8:0100::1111") + val addressV4_1 = InetAddress.getByName("192.0.2.10") + + doNothing().`when`(netd).networkSetPermissionForNetwork(anyInt(), anyInt()) + + val ncRestricted = + cellNc().removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + val ncNotRestricted = + cellNc().addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + + val (agent, callback) = prepareNetworkAgent( + "rmnet1", + arrayListOf(addressV6_1.toLinkAddress(), addressV4_1.toLinkAddress()), + ncRestricted + ) + + agent.sendNetworkCapabilities(ncNotRestricted) + callback.eventuallyExpect { + it.network == agent.network && + it.caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + } + inOrder.verify(destroySocketsWrapper, never()) + .destroyLiveTcpSocketsLackingPermission(anyInt(), anyInt()) + agent.sendNetworkCapabilities(ncRestricted) + callback.eventuallyExpect() + inOrder.verify(destroySocketsWrapper, times(2)) + .destroyLiveTcpSocketsLackingPermission( + agent.network.netId, + INetd.PERMISSION_SYSTEM + ) + } + + @Test + fun testDestroySocketsLackingPermissionForDelayedTeardown() { + val addressV6_1 = InetAddress.getByName("2001:DB8:0100::1111") + doNothing().`when`(netd).networkSetPermissionForNetwork(anyInt(), anyInt()) + val (agent, callback) = prepareNetworkAgent( + "rmnet1", + arrayListOf(addressV6_1.toLinkAddress()), + makeNc(TRANSPORT_CELLULAR) + ) + agent.sendTeardownDelayMs(1) + agent.disconnect() + agent.eventuallyExpect() + + verify(destroySocketsWrapper, times(2)).destroyLiveTcpSocketsLackingPermission( + agent.network.netId, + INetd.PERMISSION_SYSTEM + ) + } } diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt index 990ef47392..a285dd1103 100644 --- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt +++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt @@ -320,6 +320,7 @@ class CSAgentWrapper( fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc) fun sendNetworkCapabilities(nc: NetworkCapabilities) = agent.sendNetworkCapabilities(nc) fun sendLinkProperties(lp: LinkProperties) = agent.sendLinkProperties(lp) + fun sendTeardownDelayMs(delayMs: Int) = agent.setTeardownDelayMillis(delayMs) fun connectWithCaptivePortal(redirectUrl: String) { setCaptivePortal(redirectUrl) diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt index 6fa420638d..a8f0b58f2b 100644 --- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt +++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt @@ -304,6 +304,10 @@ open class CSTest { address: InetAddress?, interfaceId: Int ) {} + open fun destroyLiveTcpSocketsLackingPermission( + netId: Int, + permission: Int + ) {} } inner class CSDeps : ConnectivityService.Dependencies() { @@ -469,6 +473,17 @@ open class CSTest { interfaceId ) } + + override fun destroyLiveTcpSocketsLackingPermission( + netId: Int, + permission: Int + ) { + destroySocketsWrapper.destroyLiveTcpSocketsLackingPermission( + netId, + permission + ) + } + var netlinkMessageUpdate = Consumer {_ -> } override fun makeAddressUpdateMonitor( h: Handler,