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
This commit is contained in:
Motomu Utsumi 2024-05-07 15:51:52 +09:00
commit 0b4be79e52
6 changed files with 161 additions and 2 deletions

View file

@ -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.
* <p>
* 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() {
}
}

View file

@ -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
*

View file

@ -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;
}

View file

@ -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 {

View file

@ -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.

View file

@ -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
)
}
}