From 724d8972e48803ba52b776a0f9b1af2836d71cbe Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Tue, 26 Mar 2024 16:59:11 -0700 Subject: [PATCH] Add a flag to disable the background firewall chain The BACKGROUND firewall chain has been recently added. We want to be able to turn it off in case of contingencies with a device-config flag flip. The flag is assumed to be boot-stable and its value is read at boot. When the flag is off, we ignore any operation on this firewall chain. Additionally, this chain is always considered disabled on versions upto UPSIDE_DOWN_CAKE. Note: All firewall chains are in disabled state at boot by default. Test: atest FrameworksNetTests Test: Manually, using adb shell device_config commands. Bug: 331484830 Change-Id: I9ccc483e02e27a584da6714931466a9f2ec95ca9 --- .../android/server/ConnectivityService.java | 24 +++ .../connectivity/ConnectivityFlags.java | 2 + .../server/ConnectivityServiceTest.java | 28 +++- .../CSFirewallChainTest.kt | 157 ++++++++++++++++++ .../server/connectivityservice/base/CSTest.kt | 1 + 5 files changed, 206 insertions(+), 6 deletions(-) create mode 100644 tests/unit/java/com/android/server/connectivityservice/CSFirewallChainTest.kt diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 123ad8f865..a15a2bf412 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -478,6 +478,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private volatile boolean mLockdownEnabled; private final boolean mRequestRestrictedWifiEnabled; + private final boolean mBackgroundFirewallChainEnabled; /** * Stale copy of uid blocked reasons provided by NPMS. As long as they are accessed only in @@ -1798,6 +1799,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext); mRequestRestrictedWifiEnabled = mDeps.isAtLeastU() && mDeps.isFeatureEnabled(context, REQUEST_RESTRICTED_WIFI); + mBackgroundFirewallChainEnabled = mDeps.isAtLeastV() && mDeps.isFeatureNotChickenedOut( + context, ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN); mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator( mContext, mTelephonyManager, mRequestRestrictedWifiEnabled, this::handleUidCarrierPrivilegesLost, mHandler); @@ -4148,6 +4151,9 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(); pw.println("Multicast routing supported: " + (mMulticastRoutingCoordinatorService != null)); + + pw.println(); + pw.println("Background firewall chain enabled: " + mBackgroundFirewallChainEnabled); } private void dumpNetworks(IndentingPrintWriter pw) { @@ -13517,6 +13523,12 @@ public class ConnectivityService extends IConnectivityManager.Stub public void setUidFirewallRule(final int chain, final int uid, final int rule) { enforceNetworkStackOrSettingsPermission(); + if (chain == FIREWALL_CHAIN_BACKGROUND && !mBackgroundFirewallChainEnabled) { + Log.i(TAG, "Ignoring operation setUidFirewallRule on the background chain because the" + + " feature is disabled."); + return; + } + // There are only two type of firewall rule: FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY int firewallRule = getFirewallRuleType(chain, rule); @@ -13589,6 +13601,12 @@ public class ConnectivityService extends IConnectivityManager.Stub public void setFirewallChainEnabled(final int chain, final boolean enable) { enforceNetworkStackOrSettingsPermission(); + if (chain == FIREWALL_CHAIN_BACKGROUND && !mBackgroundFirewallChainEnabled) { + Log.i(TAG, "Ignoring operation setFirewallChainEnabled on the background chain because" + + " the feature is disabled."); + return; + } + try { mBpfNetMaps.setChildChain(chain, enable); } catch (ServiceSpecificException e) { @@ -13615,6 +13633,12 @@ public class ConnectivityService extends IConnectivityManager.Stub public void replaceFirewallChain(final int chain, final int[] uids) { enforceNetworkStackOrSettingsPermission(); + if (chain == FIREWALL_CHAIN_BACKGROUND && !mBackgroundFirewallChainEnabled) { + Log.i(TAG, "Ignoring operation replaceFirewallChain on the background chain because" + + " the feature is disabled."); + return; + } + mBpfNetMaps.replaceUidChain(chain, uids); } diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java index a55c683bc0..176307d729 100644 --- a/service/src/com/android/server/connectivity/ConnectivityFlags.java +++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java @@ -42,6 +42,8 @@ public final class ConnectivityFlags { public static final String INGRESS_TO_VPN_ADDRESS_FILTERING = "ingress_to_vpn_address_filtering"; + public static final String BACKGROUND_FIREWALL_CHAIN = "background_firewall_chain"; + private boolean mNoRematchAllRequestsOnRegister; /** diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index 1f8a743390..ce495335b5 100755 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -172,6 +172,7 @@ import static com.android.server.ConnectivityService.makeNflogPrefix; import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType; import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister; import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister; +import static com.android.server.connectivity.ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN; import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING; import static com.android.testutils.Cleanup.testAndCleanup; import static com.android.testutils.ConcurrentUtils.await; @@ -391,6 +392,7 @@ import com.android.internal.net.VpnConfig; import com.android.internal.util.WakeupMessage; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; +import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.ArrayTrackRecord; import com.android.net.module.util.BaseNetdUnsolicitedEventListener; import com.android.net.module.util.CollectionUtils; @@ -2169,6 +2171,8 @@ public class ConnectivityServiceTest { return true; case INGRESS_TO_VPN_ADDRESS_FILTERING: return true; + case BACKGROUND_FIREWALL_CHAIN: + return true; default: return super.isFeatureNotChickenedOut(context, name); } @@ -10486,7 +10490,10 @@ public class ConnectivityServiceTest { doTestSetUidFirewallRule(FIREWALL_CHAIN_POWERSAVE, FIREWALL_RULE_DENY); doTestSetUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, FIREWALL_RULE_DENY); doTestSetUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, FIREWALL_RULE_DENY); - doTestSetUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, FIREWALL_RULE_DENY); + if (SdkLevel.isAtLeastV()) { + // FIREWALL_CHAIN_BACKGROUND is only available on V+. + doTestSetUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, FIREWALL_RULE_DENY); + } doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_1, FIREWALL_RULE_ALLOW); doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_2, FIREWALL_RULE_ALLOW); doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_3, FIREWALL_RULE_ALLOW); @@ -10494,16 +10501,19 @@ public class ConnectivityServiceTest { @Test @IgnoreUpTo(SC_V2) public void testSetFirewallChainEnabled() throws Exception { - final List firewallChains = Arrays.asList( + final List firewallChains = new ArrayList<>(Arrays.asList( FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_POWERSAVE, FIREWALL_CHAIN_RESTRICTED, FIREWALL_CHAIN_LOW_POWER_STANDBY, - FIREWALL_CHAIN_BACKGROUND, FIREWALL_CHAIN_OEM_DENY_1, FIREWALL_CHAIN_OEM_DENY_2, - FIREWALL_CHAIN_OEM_DENY_3); + FIREWALL_CHAIN_OEM_DENY_3)); + if (SdkLevel.isAtLeastV()) { + // FIREWALL_CHAIN_BACKGROUND is only available on V+. + firewallChains.add(FIREWALL_CHAIN_BACKGROUND); + } for (final int chain: firewallChains) { mCm.setFirewallChainEnabled(chain, true /* enabled */); verify(mBpfNetMaps).setChildChain(chain, true /* enable */); @@ -10550,7 +10560,10 @@ public class ConnectivityServiceTest { doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_POWERSAVE, allowlist); doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_RESTRICTED, allowlist); doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_LOW_POWER_STANDBY, allowlist); - doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_BACKGROUND, allowlist); + if (SdkLevel.isAtLeastV()) { + // FIREWALL_CHAIN_BACKGROUND is only available on V+. + doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_BACKGROUND, allowlist); + } doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_STANDBY, denylist); doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_OEM_DENY_1, denylist); @@ -10572,7 +10585,10 @@ public class ConnectivityServiceTest { doTestReplaceFirewallChain(FIREWALL_CHAIN_POWERSAVE); doTestReplaceFirewallChain(FIREWALL_CHAIN_RESTRICTED); doTestReplaceFirewallChain(FIREWALL_CHAIN_LOW_POWER_STANDBY); - doTestReplaceFirewallChain(FIREWALL_CHAIN_BACKGROUND); + if (SdkLevel.isAtLeastV()) { + // FIREWALL_CHAIN_BACKGROUND is only available on V+. + doTestReplaceFirewallChain(FIREWALL_CHAIN_BACKGROUND); + } doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_1); doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_2); doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_3); diff --git a/tests/unit/java/com/android/server/connectivityservice/CSFirewallChainTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSFirewallChainTest.kt new file mode 100644 index 0000000000..16de4dadea --- /dev/null +++ b/tests/unit/java/com/android/server/connectivityservice/CSFirewallChainTest.kt @@ -0,0 +1,157 @@ +/* + * 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.ConnectivityManager +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.server.connectivity.ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mockito.any +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +@RunWith(DevSdkIgnoreRunner::class) +@SmallTest +@IgnoreUpTo(Build.VERSION_CODES.S_V2) +class CSFirewallChainTest : CSTest() { + @get:Rule + val ignoreRule = DevSdkIgnoreRule() + + // Tests for setFirewallChainEnabled on FIREWALL_CHAIN_BACKGROUND + @Test + @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, false)]) + fun setFirewallChainEnabled_backgroundChainDisabled() { + verifySetFirewallChainEnabledOnBackgroundDoesNothing() + } + + @Test + @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)]) + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun setFirewallChainEnabled_backgroundChainEnabled_afterU() { + cm.setFirewallChainEnabled(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, true) + verify(bpfNetMaps).setChildChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, true) + + clearInvocations(bpfNetMaps) + + cm.setFirewallChainEnabled(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, false) + verify(bpfNetMaps).setChildChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, false) + } + + @Test + @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)]) + @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun setFirewallChainEnabled_backgroundChainEnabled_uptoU() { + verifySetFirewallChainEnabledOnBackgroundDoesNothing() + } + + private fun verifySetFirewallChainEnabledOnBackgroundDoesNothing() { + cm.setFirewallChainEnabled(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, true) + verify(bpfNetMaps, never()).setChildChain(anyInt(), anyBoolean()) + + cm.setFirewallChainEnabled(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, false) + verify(bpfNetMaps, never()).setChildChain(anyInt(), anyBoolean()) + } + + // Tests for replaceFirewallChain on FIREWALL_CHAIN_BACKGROUND + @Test + @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, false)]) + fun replaceFirewallChain_backgroundChainDisabled() { + verifyReplaceFirewallChainOnBackgroundDoesNothing() + } + + @Test + @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)]) + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun replaceFirewallChain_backgroundChainEnabled_afterU() { + val uids = intArrayOf(53, 42, 79) + cm.replaceFirewallChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uids) + verify(bpfNetMaps).replaceUidChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uids) + } + + @Test + @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)]) + @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun replaceFirewallChain_backgroundChainEnabled_uptoU() { + verifyReplaceFirewallChainOnBackgroundDoesNothing() + } + + private fun verifyReplaceFirewallChainOnBackgroundDoesNothing() { + val uids = intArrayOf(53, 42, 79) + cm.replaceFirewallChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uids) + verify(bpfNetMaps, never()).replaceUidChain(anyInt(), any(IntArray::class.java)) + } + + // Tests for setUidFirewallRule on FIREWALL_CHAIN_BACKGROUND + @Test + @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, false)]) + fun setUidFirewallRule_backgroundChainDisabled() { + verifySetUidFirewallRuleOnBackgroundDoesNothing() + } + + @Test + @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)]) + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun setUidFirewallRule_backgroundChainEnabled_afterU() { + val uid = 2345 + + cm.setUidFirewallRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid, + ConnectivityManager.FIREWALL_RULE_DEFAULT) + verify(bpfNetMaps).setUidRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid, + ConnectivityManager.FIREWALL_RULE_DENY) + + clearInvocations(bpfNetMaps) + + cm.setUidFirewallRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid, + ConnectivityManager.FIREWALL_RULE_DENY) + verify(bpfNetMaps).setUidRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid, + ConnectivityManager.FIREWALL_RULE_DENY) + + clearInvocations(bpfNetMaps) + + cm.setUidFirewallRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid, + ConnectivityManager.FIREWALL_RULE_ALLOW) + verify(bpfNetMaps).setUidRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid, + ConnectivityManager.FIREWALL_RULE_ALLOW) + } + + @Test + @FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)]) + @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun setUidFirewallRule_backgroundChainEnabled_uptoU() { + verifySetUidFirewallRuleOnBackgroundDoesNothing() + } + + private fun verifySetUidFirewallRuleOnBackgroundDoesNothing() { + val uid = 2345 + + listOf(ConnectivityManager.FIREWALL_RULE_DEFAULT, ConnectivityManager.FIREWALL_RULE_ALLOW, + ConnectivityManager.FIREWALL_RULE_DENY).forEach { rule -> + cm.setUidFirewallRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid, rule) + verify(bpfNetMaps, never()).setUidRule(anyInt(), anyInt(), anyInt()) + } + } +} 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 5c4617b1bd..824ea23618 100644 --- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt +++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt @@ -154,6 +154,7 @@ open class CSTest { it[ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS] = true it[ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK] = true it[ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING] = true + it[ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN] = true } fun enableFeature(f: String) = enabledFeatures.set(f, true) fun disableFeature(f: String) = enabledFeatures.set(f, false)