From 0b4be79e525a99a73e11cc034dac7a9ee85b77b7 Mon Sep 17 00:00:00 2001 From: Motomu Utsumi Date: Tue, 7 May 2024 15:51:52 +0900 Subject: [PATCH] Add compat flag for ActiveNetworkInfo blocked state Before aosp/3001087, getNetworkActiveInfo did not return network info with blocked state for apps without PERMISSION_INTERNET even if the network is actually blocked. aosp/3001087 updated getNetworkActiveInfo to return correct blocked state on V+. However, this broke some apps that use getNetworkActiveInfo in wrong way and depends on the previous behavior. So this CL introduces the compat flag to keep previous behavior for apps targeting U or older releases. Test: TH Bug: 333340911 Change-Id: I0e0950f2148de0ffdb45495903a5a87e0d4c1c0b --- .../ConnectivityCompatChanges.java | 19 ++++ .../src/com/android/server/BpfNetMaps.java | 19 ++++ .../android/server/ConnectivityService.java | 12 ++- .../com/android/server/BpfNetMapsTest.java | 15 +++ .../server/ConnectivityServiceTest.java | 5 + .../CSActiveNetworkInfoTest.kt | 93 +++++++++++++++++++ 6 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 tests/unit/java/com/android/server/connectivityservice/CSActiveNetworkInfoTest.kt diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java index a80db85005..56537d9baf 100644 --- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java +++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java @@ -99,6 +99,25 @@ public final class ConnectivityCompatChanges { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final long ENABLE_MATCH_LOCAL_NETWORK = 319212206L; + /** + * On Android {@link android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM} or higher releases, when + * apps targeting Android {@link android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM} or higher + * that do not have the {@link android.Manifest.permission#INTERNET} permission call + * {@link android.net.ConnectivityManager#getActiveNetworkInfo()}, the state of the returned + * {@link android.net.NetworkInfo} object will always be + * {@link android.net.NetworkInfo.DetailedState#BLOCKED}. This is because apps without the + * permission cannot access any network. + *

+ * For backwards compatibility, apps running on older releases, or targeting older SDK levels, + * will instead receive objects with the network's current state, + * such as {@link android.net.NetworkInfo.DetailedState#CONNECTED}. + * + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final long NETWORKINFO_WITHOUT_INTERNET_BLOCKED = 333340911L; + private ConnectivityCompatChanges() { } } diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java index c9108c363c..23af0f81ff 100644 --- a/service/src/com/android/server/BpfNetMaps.java +++ b/service/src/com/android/server/BpfNetMaps.java @@ -805,6 +805,25 @@ public class BpfNetMaps { } } + /** + * Get granted permissions for specified uid. If uid is not in the map, this method returns + * {@link android.net.INetd.PERMISSION_INTERNET} since this is a default permission. + * See {@link #setNetPermForUids} + * + * @param uid target uid + * @return granted permissions. + */ + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + public int getNetPermForUid(final int uid) { + try { + final U8 permissions = sUidPermissionMap.getValue(new S32(uid)); + return permissions != null ? permissions.val : PERMISSION_INTERNET; + } catch (ErrnoException e) { + Log.wtf(TAG, "Failed to get permission for uid: " + uid); + return PERMISSION_INTERNET; + } + } + /** * Set Data Saver enabled or disabled * diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 637f794f1a..46bd9bc6b4 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -64,6 +64,7 @@ import static android.net.ConnectivityManager.TYPE_WIFI_P2P; import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.ConnectivityManager.isNetworkTypeValid; import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; +import static android.net.INetd.PERMISSION_INTERNET; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS; import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL; import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_SKIPPED; @@ -101,6 +102,7 @@ import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY; import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_MATCH_LOCAL_NETWORK; import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION; +import static android.net.connectivity.ConnectivityCompatChanges.NETWORKINFO_WITHOUT_INTERNET_BLOCKED; import static android.os.Process.INVALID_UID; import static android.os.Process.VPN_UID; import static android.system.OsConstants.ETH_P_ALL; @@ -2241,7 +2243,8 @@ public class ConnectivityService extends IConnectivityManager.Stub try { final boolean metered = nc == null ? true : nc.isMetered(); if (mDeps.isAtLeastV()) { - return mBpfNetMaps.isUidNetworkingBlocked(uid, metered); + return mBpfNetMaps.isUidNetworkingBlocked(uid, metered) + || (mBpfNetMaps.getNetPermForUid(uid) & PERMISSION_INTERNET) == 0; } else { return mPolicyManager.isUidNetworkingBlocked(uid, metered); } @@ -2322,7 +2325,12 @@ public class ConnectivityService extends IConnectivityManager.Stub final int uid = mDeps.getCallingUid(); final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid); if (nai == null) return null; - final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false); + // Ignore blocked state to keep the backward compatibility if the compat flag is + // disabled and app does not have PERMISSION_INTERNET. + final boolean ignoreBlocked = mDeps.isAtLeastV() + && !mDeps.isChangeEnabled(NETWORKINFO_WITHOUT_INTERNET_BLOCKED, uid) + && (mBpfNetMaps.getNetPermForUid(uid) & PERMISSION_INTERNET) == 0; + final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, ignoreBlocked); maybeLogBlockedNetworkInfo(networkInfo, uid); return networkInfo; } diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java index 62e55d599c..cbc060aabf 100644 --- a/tests/unit/java/com/android/server/BpfNetMapsTest.java +++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java @@ -847,6 +847,21 @@ public final class BpfNetMapsTest { assertNull(mUidPermissionMap.getValue(new S32(uid1))); } + @Test + @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) + public void testGetNetPermFoUid() throws Exception { + mUidPermissionMap.deleteEntry(new S32(TEST_UID)); + assertEquals(PERMISSION_INTERNET, mBpfNetMaps.getNetPermForUid(TEST_UID)); + + mUidPermissionMap.updateEntry(new S32(TEST_UID), new U8((short) PERMISSION_NONE)); + assertEquals(PERMISSION_NONE, mBpfNetMaps.getNetPermForUid(TEST_UID)); + + mUidPermissionMap.updateEntry(new S32(TEST_UID), + new U8((short) (PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS))); + assertEquals(PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS, + mBpfNetMaps.getNetPermForUid(TEST_UID)); + } + @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2) public void testSwapActiveStatsMap() throws Exception { diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index 878c7ff70a..717c5a1ab6 100755 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -87,6 +87,7 @@ import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF; import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; +import static android.net.INetd.PERMISSION_INTERNET; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK; import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP; @@ -151,6 +152,7 @@ import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED; import static android.net.Proxy.PROXY_CHANGE_ACTION; import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.net.connectivity.ConnectivityCompatChanges.NETWORKINFO_WITHOUT_INTERNET_BLOCKED; import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED; import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_REMOVED; import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE; @@ -1945,6 +1947,9 @@ public class ConnectivityServiceTest { setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT); setAlwaysOnNetworks(false); setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); + + mDeps.setChangeIdEnabled(true, NETWORKINFO_WITHOUT_INTERNET_BLOCKED, Process.myUid()); + doReturn(PERMISSION_INTERNET).when(mBpfNetMaps).getNetPermForUid(anyInt()); // Note : Please do not add any new instrumentation here. If you need new instrumentation, // please add it in CSTest and use subclasses of CSTest instead of adding more // tools in ConnectivityServiceTest. diff --git a/tests/unit/java/com/android/server/connectivityservice/CSActiveNetworkInfoTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSActiveNetworkInfoTest.kt new file mode 100644 index 0000000000..1891a78230 --- /dev/null +++ b/tests/unit/java/com/android/server/connectivityservice/CSActiveNetworkInfoTest.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server + +import android.net.INetd.PERMISSION_INTERNET +import android.net.INetd.PERMISSION_NONE +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET +import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED +import android.net.NetworkCapabilities.TRANSPORT_WIFI +import android.net.NetworkInfo.DetailedState.BLOCKED +import android.net.NetworkInfo.DetailedState.CONNECTED +import android.net.connectivity.ConnectivityCompatChanges.NETWORKINFO_WITHOUT_INTERNET_BLOCKED +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mockito.doReturn + +private fun nc() = NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_WIFI) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) + .build() + +@RunWith(DevSdkIgnoreRunner::class) +@SmallTest +@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +class CSActiveNetworkInfoTest : CSTest() { + + fun doTestGetActiveNetworkInfo( + changeEnabled: Boolean, + permissions: Int, + expectBlocked: Boolean + ) { + deps.setChangeIdEnabled(changeEnabled, NETWORKINFO_WITHOUT_INTERNET_BLOCKED) + doReturn(permissions).`when`(bpfNetMaps).getNetPermForUid(anyInt()) + + val agent = Agent(nc = nc()) + agent.connect() + + val networkInfo = cm.activeNetworkInfo + assertNotNull(networkInfo) + if (expectBlocked) { + assertEquals(BLOCKED, networkInfo.detailedState) + } else { + assertEquals(CONNECTED, networkInfo.detailedState) + } + agent.disconnect() + } + + @Test + fun testGetActiveNetworkInfo() { + doReturn(true).`when`(bpfNetMaps).isUidNetworkingBlocked(anyInt(), anyBoolean()) + doTestGetActiveNetworkInfo( + changeEnabled = true, + permissions = PERMISSION_NONE, + expectBlocked = true + ) + doTestGetActiveNetworkInfo( + changeEnabled = false, + permissions = PERMISSION_INTERNET, + expectBlocked = true + ) + // getActiveNetworkInfo does not return NetworkInfo with blocked state if the compat change + // is disabled and the app does not have PERMISSION_INTERNET + doTestGetActiveNetworkInfo( + changeEnabled = false, + permissions = PERMISSION_NONE, + expectBlocked = false + ) + } +}