From 048bc5ea80b0b7b75988df2362945ea7cff59ed7 Mon Sep 17 00:00:00 2001
From: Patrick Rohr <prohr@google.com>
Date: Fri, 23 Oct 2020 13:51:00 +0200
Subject: [PATCH] Add Restricted Mode handling to NetworkPolicyManager

Adds Restricted Mode functionality to NetworkPolicyManager. When this
mode is turned on (via setting), only apps with
android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS will be able to
use the network. For all other apps, the network will be blocked by the
firewall. This is controlled by a new allowlist firewall chain
fw_restricted_mode.

As a first step, this implementation still requires a reboot after the
enabling / disabling the mode to take effect. I will provide the dynamic
configuration in the next CL.

Test: atest CtsHostsideNetworkTests && atest
NetworkPolicyManagerServiceTest
Bug: 170322816
Bug: 157505406
Bug: 170322455
Bug: 175281879

Exempt-From-Owner-Approval: Change already merged on internal gerrit.
Change-Id: I0731fa842c69683953baaf9ec3a9a03454f4c607
Merged-In: I0731fa842c69683953baaf9ec3a9a03454f4c607
---
 .../android/net/NetworkPolicyManager.java     |  13 +-
 core/java/android/provider/Settings.java      |  11 ++
 .../validators/GlobalSettingsValidators.java  |   1 +
 .../android/provider/SettingsBackupTest.java  |   1 +
 .../server/net/NetworkPolicyLogger.java       |   3 +
 .../net/NetworkPolicyManagerService.java      | 139 ++++++++++++++++--
 .../net/NetworkPolicyManagerServiceTest.java  | 109 +++++++++++++-
 7 files changed, 256 insertions(+), 21 deletions(-)

diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index ce16a7835179..36348b365158 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -122,17 +122,26 @@ public class NetworkPolicyManager {
      * @hide
      */
     public static final int RULE_REJECT_ALL = 1 << 6;
+    /**
+     * Reject traffic on all networks for restricted networking mode.
+     */
+    public static final int RULE_REJECT_RESTRICTED_MODE = 1 << 10;
 
     /**
      * Mask used to get the {@code RULE_xxx_METERED} rules
      * @hide
      */
-    public static final int MASK_METERED_NETWORKS = 0b00001111;
+    public static final int MASK_METERED_NETWORKS = 0b000000001111;
     /**
      * Mask used to get the {@code RULE_xxx_ALL} rules
      * @hide
      */
-    public static final int MASK_ALL_NETWORKS     = 0b11110000;
+    public static final int MASK_ALL_NETWORKS     = 0b000011110000;
+    /**
+     * Mask used to get the {@code RULE_xxx_RESTRICTED_MODE} rules
+     * @hide
+     */
+    public static final int MASK_RESTRICTED_MODE_NETWORKS     = 0b111100000000;
 
     /** @hide */
     public static final int FIREWALL_RULE_DEFAULT = 0;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1e14e3a4a701..4086161603a4 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14406,6 +14406,17 @@ public final class Settings {
          */
         public static final String NR_NSA_TRACKING_SCREEN_OFF_MODE =
                 "nr_nsa_tracking_screen_off_mode";
+
+        /**
+         * Used to enable / disable the Restricted Networking Mode in which network access is
+         * restricted to apps holding the CONNECTIVITY_USE_RESTRICTED_NETWORKS permission.
+         *
+         * Values are:
+         * 0: disabled
+         * 1: enabled
+         * @hide
+         */
+        public static final String RESTRICTED_NETWORKING_MODE = "restricted_networking_mode";
     }
 
     /**
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index dd94d2eb8fe0..c559678d4005 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -149,5 +149,6 @@ public class GlobalSettingsValidators {
         VALIDATORS.put(Global.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Global.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.DEVELOPMENT_SETTINGS_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.RESTRICTED_NETWORKING_MODE, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 5ecb17102438..1345c3f071c7 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -420,6 +420,7 @@ public class SettingsBackupTest {
                     Settings.Global.RADIO_WIMAX,
                     Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS,
                     Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT,
+                    Settings.Global.RESTRICTED_NETWORKING_MODE,
                     Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT,
                     Settings.Global.SAFE_BOOT_DISALLOWED,
                     Settings.Global.SELINUX_STATUS,
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 5bd352c8f8e8..676f4218f745 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -78,6 +78,7 @@ public class NetworkPolicyLogger {
     static final int NTWK_BLOCKED_BG_RESTRICT = 5;
     static final int NTWK_ALLOWED_DEFAULT = 6;
     static final int NTWK_ALLOWED_SYSTEM = 7;
+    static final int NTWK_BLOCKED_RESTRICTED_MODE = 8;
 
     private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
     private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
@@ -281,6 +282,8 @@ public class NetworkPolicyLogger {
                 return "blocked when background is restricted";
             case NTWK_ALLOWED_DEFAULT:
                 return "allowed by default";
+            case NTWK_BLOCKED_RESTRICTED_MODE:
+                return "blocked by restricted networking mode";
             default:
                 return String.valueOf(reason);
         }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index a039215487f8..fc94ebaa96b4 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -18,6 +18,7 @@ package com.android.server.net;
 
 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
 import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
 import static android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS;
 import static android.Manifest.permission.NETWORK_SETTINGS;
@@ -44,6 +45,7 @@ import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELI
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
 import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
@@ -57,6 +59,7 @@ import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS;
 import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS;
+import static android.net.NetworkPolicyManager.MASK_RESTRICTED_MODE_NETWORKS;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
@@ -65,12 +68,14 @@ import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
 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.NetworkPolicyManager.RULE_REJECT_RESTRICTED_MODE;
 import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
 import static android.net.NetworkPolicyManager.resolveNetworkId;
 import static android.net.NetworkPolicyManager.uidPoliciesToString;
 import static android.net.NetworkPolicyManager.uidRulesToString;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.NetworkTemplate.MATCH_MOBILE;
 import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
@@ -111,6 +116,7 @@ import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_ALLOWL
 import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT;
 import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_DENYLIST;
 import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_RESTRICTED_MODE;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -445,7 +451,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
     @GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictPower;
     @GuardedBy("mUidRulesFirstLock") volatile boolean mDeviceIdleMode;
     // Store whether user flipped restrict background in battery saver mode
-    @GuardedBy("mUidRulesFirstLock") volatile boolean mRestrictBackgroundChangedInBsm;
+    @GuardedBy("mUidRulesFirstLock")
+    volatile boolean mRestrictBackgroundChangedInBsm;
+    @GuardedBy("mUidRulesFirstLock")
+    volatile boolean mRestrictedNetworkingMode;
 
     private final boolean mSuppressDefaultPolicy;
 
@@ -479,6 +488,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
     final SparseIntArray mUidFirewallDozableRules = new SparseIntArray();
     @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
+    @GuardedBy("mUidRulesFirstLock")
+    final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray();
 
     /** Set of states for the child firewall chains. True if the chain is active. */
     @GuardedBy("mUidRulesFirstLock")
@@ -786,6 +797,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
                     mRestrictPower = mPowerManagerInternal.getLowPowerState(
                             ServiceType.NETWORK_FIREWALL).batterySaverEnabled;
 
+                    mRestrictedNetworkingMode = Settings.Global.getInt(
+                            mContext.getContentResolver(),
+                            Settings.Global.RESTRICTED_NETWORKING_MODE, 0) != 0;
+
                     mSystemReady = true;
 
                     waitForAdminData();
@@ -3501,6 +3516,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
                 fout.print("Restrict background: "); fout.println(mRestrictBackground);
                 fout.print("Restrict power: "); fout.println(mRestrictPower);
                 fout.print("Device idle: "); fout.println(mDeviceIdleMode);
+                fout.print("Restricted networking mode: "); fout.println(mRestrictedNetworkingMode);
                 synchronized (mMeteredIfacesLock) {
                     fout.print("Metered ifaces: ");
                     fout.println(mMeteredIfaces);
@@ -3812,6 +3828,93 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
         }
     }
 
+    /**
+     * updates restricted mode state / access for all apps
+     * Called on initialization and when restricted mode is enabled / disabled.
+     */
+    @VisibleForTesting
+    @GuardedBy("mUidRulesFirstLock")
+    void updateRestrictedModeAllowlistUL() {
+        mUidFirewallRestrictedModeRules.clear();
+        forEachUid("updateRestrictedModeAllowlist", uid -> {
+            final int oldUidRule = mUidRules.get(uid);
+            final int newUidRule = getNewRestrictedModeUidRule(uid, oldUidRule);
+            final boolean hasUidRuleChanged = oldUidRule != newUidRule;
+            final int newFirewallRule = getRestrictedModeFirewallRule(newUidRule);
+
+            // setUidFirewallRulesUL will allowlist all uids that are passed to it, so only add
+            // non-default rules.
+            if (newFirewallRule != FIREWALL_RULE_DEFAULT) {
+                mUidFirewallRestrictedModeRules.append(uid, newFirewallRule);
+            }
+
+            if (hasUidRuleChanged) {
+                mUidRules.put(uid, newUidRule);
+                mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget();
+            }
+        });
+        if (mRestrictedNetworkingMode) {
+            // firewall rules only need to be set when this mode is being enabled.
+            setUidFirewallRulesUL(FIREWALL_CHAIN_RESTRICTED, mUidFirewallRestrictedModeRules);
+        }
+        enableFirewallChainUL(FIREWALL_CHAIN_RESTRICTED, mRestrictedNetworkingMode);
+    }
+
+    // updates restricted mode state / access for a single app / uid.
+    @VisibleForTesting
+    @GuardedBy("mUidRulesFirstLock")
+    void updateRestrictedModeForUidUL(int uid) {
+        final int oldUidRule = mUidRules.get(uid);
+        final int newUidRule = getNewRestrictedModeUidRule(uid, oldUidRule);
+        final boolean hasUidRuleChanged = oldUidRule != newUidRule;
+
+        if (hasUidRuleChanged) {
+            mUidRules.put(uid, newUidRule);
+            mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget();
+        }
+
+        // if restricted networking mode is on, and the app has an access exemption, the uid rule
+        // will not change, but the firewall rule will have to be updated.
+        if (mRestrictedNetworkingMode) {
+            // Note: setUidFirewallRule also updates mUidFirewallRestrictedModeRules.
+            // In this case, default firewall rules can also be added.
+            setUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, uid,
+                    getRestrictedModeFirewallRule(newUidRule));
+        }
+    }
+
+    private int getNewRestrictedModeUidRule(int uid, int oldUidRule) {
+        int newRule = oldUidRule;
+        newRule &= ~MASK_RESTRICTED_MODE_NETWORKS;
+        if (mRestrictedNetworkingMode && !hasRestrictedModeAccess(uid)) {
+            newRule |= RULE_REJECT_RESTRICTED_MODE;
+        }
+        return newRule;
+    }
+
+    private static int getRestrictedModeFirewallRule(int uidRule) {
+        if ((uidRule & RULE_REJECT_RESTRICTED_MODE) != 0) {
+            // rejected in restricted mode, this is the default behavior.
+            return FIREWALL_RULE_DEFAULT;
+        } else {
+            return FIREWALL_RULE_ALLOW;
+        }
+    }
+
+    private boolean hasRestrictedModeAccess(int uid) {
+        try {
+            // TODO: this needs to be kept in sync with
+            // PermissionMonitor#hasRestrictedNetworkPermission
+            return mIPm.checkUidPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, uid)
+                    == PERMISSION_GRANTED
+                    || mIPm.checkUidPermission(NETWORK_STACK, uid) == PERMISSION_GRANTED
+                    || mIPm.checkUidPermission(PERMISSION_MAINLINE_NETWORK_STACK, uid)
+                    == PERMISSION_GRANTED;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
     @GuardedBy("mUidRulesFirstLock")
     void updateRulesForPowerSaveUL() {
         Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForPowerSaveUL");
@@ -4033,6 +4136,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
             updateRulesForAppIdleUL();
             updateRulesForRestrictPowerUL();
             updateRulesForRestrictBackgroundUL();
+            updateRestrictedModeAllowlistUL();
 
             // If the set of restricted networks may have changed, re-evaluate those.
             if (restrictedNetworksChanged) {
@@ -4249,6 +4353,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
         mPowerSaveWhitelistAppIds.delete(uid);
         mPowerSaveTempWhitelistAppIds.delete(uid);
         mAppIdleTempWhitelistAppIds.delete(uid);
+        mUidFirewallRestrictedModeRules.delete(uid);
 
         // ...then update iptables asynchronously.
         mHandler.obtainMessage(MSG_RESET_FIREWALL_RULES_BY_UID, uid, 0).sendToTarget();
@@ -4274,6 +4379,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
         updateRuleForAppIdleUL(uid);
         updateRuleForRestrictPowerUL(uid);
 
+        // If the uid has the necessary permissions, then it should be added to the restricted mode
+        // firewall allowlist.
+        updateRestrictedModeForUidUL(uid);
+
         // Update internal state for power-related modes.
         updateRulesForPowerRestrictionsUL(uid);
 
@@ -4999,6 +5108,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
                 mUidFirewallStandbyRules.put(uid, rule);
             } else if (chain == FIREWALL_CHAIN_POWERSAVE) {
                 mUidFirewallPowerSaveRules.put(uid, rule);
+            } else if (chain == FIREWALL_CHAIN_RESTRICTED) {
+                mUidFirewallRestrictedModeRules.put(uid, rule);
             }
 
             try {
@@ -5044,6 +5155,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
             mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
             mNetworkManager
                     .setFirewallUidRule(FIREWALL_CHAIN_POWERSAVE, uid, FIREWALL_RULE_DEFAULT);
+            mNetworkManager
+                    .setFirewallUidRule(FIREWALL_CHAIN_RESTRICTED, uid, FIREWALL_RULE_DEFAULT);
             mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
             mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
         } catch (IllegalStateException e) {
@@ -5230,26 +5343,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
         // Networks are never blocked for system components
         if (isSystem(uid)) {
             reason = NTWK_ALLOWED_SYSTEM;
-        }
-        else if (hasRule(uidRules, RULE_REJECT_ALL)) {
+        } else if (hasRule(uidRules, RULE_REJECT_RESTRICTED_MODE)) {
+            reason = NTWK_BLOCKED_RESTRICTED_MODE;
+        } else if (hasRule(uidRules, RULE_REJECT_ALL)) {
             reason = NTWK_BLOCKED_POWER;
-        }
-        else if (!isNetworkMetered) {
+        } else if (!isNetworkMetered) {
             reason = NTWK_ALLOWED_NON_METERED;
-        }
-        else if (hasRule(uidRules, RULE_REJECT_METERED)) {
+        } else if (hasRule(uidRules, RULE_REJECT_METERED)) {
             reason = NTWK_BLOCKED_DENYLIST;
-        }
-        else if (hasRule(uidRules, RULE_ALLOW_METERED)) {
+        } else if (hasRule(uidRules, RULE_ALLOW_METERED)) {
             reason = NTWK_ALLOWED_ALLOWLIST;
-        }
-        else if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
+        } else if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
             reason = NTWK_ALLOWED_TMP_ALLOWLIST;
-        }
-        else if (isBackgroundRestricted) {
+        } else if (isBackgroundRestricted) {
             reason = NTWK_BLOCKED_BG_RESTRICT;
-        }
-        else {
+        } else {
             reason = NTWK_ALLOWED_DEFAULT;
         }
 
@@ -5262,6 +5370,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
             case NTWK_ALLOWED_SYSTEM:
                 blocked = false;
                 break;
+            case NTWK_BLOCKED_RESTRICTED_MODE:
             case NTWK_BLOCKED_POWER:
             case NTWK_BLOCKED_DENYLIST:
             case NTWK_BLOCKED_BG_RESTRICT:
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index fec0273383e7..4db7ce2e6ef5 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -16,13 +16,18 @@
 
 package com.android.server.net;
 
+import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.NETWORK_STACK;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.INetd.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.SNOOZE_NEVER;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
@@ -34,6 +39,7 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
 import static android.net.NetworkPolicyManager.uidPoliciesToString;
 import static android.net.NetworkPolicyManager.uidRulesToString;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.TAG_ALL;
@@ -74,6 +80,7 @@ import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
@@ -97,6 +104,7 @@ import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
+import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
@@ -123,6 +131,7 @@ import android.os.RemoteException;
 import android.os.SimpleClock;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
@@ -131,6 +140,7 @@ import android.telephony.SubscriptionPlan;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.DataUnit;
 import android.util.Log;
 import android.util.Pair;
@@ -187,6 +197,7 @@ import java.util.Calendar;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.TimeZone;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
@@ -240,6 +251,7 @@ public class NetworkPolicyManagerServiceTest {
     private @Mock SubscriptionManager mSubscriptionManager;
     private @Mock CarrierConfigManager mCarrierConfigManager;
     private @Mock TelephonyManager mTelephonyManager;
+    private @Mock UserManager mUserManager;
 
     private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor =
             ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
@@ -351,6 +363,8 @@ public class NetworkPolicyManagerServiceTest {
                         return mNotifManager;
                     case Context.CONNECTIVITY_SERVICE:
                         return mConnectivityManager;
+                    case Context.USER_SERVICE:
+                        return mUserManager;
                     default:
                         return super.getSystemService(name);
                 }
@@ -407,11 +421,14 @@ public class NetworkPolicyManagerServiceTest {
         when(mPackageManager.getPackagesForUid(UID_B)).thenReturn(new String[] {PKG_NAME_B});
         when(mPackageManager.getPackagesForUid(UID_C)).thenReturn(new String[] {PKG_NAME_C});
         when(mPackageManager.getApplicationInfo(eq(PKG_NAME_A), anyInt()))
-                .thenReturn(buildApplicationInfo(PKG_NAME_A));
+                .thenReturn(buildApplicationInfo(PKG_NAME_A, UID_A));
         when(mPackageManager.getApplicationInfo(eq(PKG_NAME_B), anyInt()))
-                .thenReturn(buildApplicationInfo(PKG_NAME_B));
+                .thenReturn(buildApplicationInfo(PKG_NAME_B, UID_B));
         when(mPackageManager.getApplicationInfo(eq(PKG_NAME_C), anyInt()))
-                .thenReturn(buildApplicationInfo(PKG_NAME_C));
+                .thenReturn(buildApplicationInfo(PKG_NAME_C, UID_C));
+        when(mPackageManager.getInstalledApplications(anyInt())).thenReturn(
+                buildInstalledApplicationInfoList());
+        when(mUserManager.getUsers()).thenReturn(buildUserInfoList());
         when(mNetworkManager.isBandwidthControlEnabled()).thenReturn(true);
         when(mNetworkManager.setDataSaverModeEnabled(anyBoolean())).thenReturn(true);
         doNothing().when(mConnectivityManager)
@@ -1874,6 +1891,66 @@ public class NetworkPolicyManagerServiceTest {
         }
     }
 
+    private void enableRestrictedMode(boolean enable) throws Exception {
+        mService.mRestrictedNetworkingMode = enable;
+        mService.updateRestrictedModeAllowlistUL();
+        verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_RESTRICTED,
+                enable);
+    }
+
+    @Test
+    public void testUpdateRestrictedModeAllowlist() throws Exception {
+        // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+        clearInvocations(mNetworkManager);
+        expectHasUseRestrictedNetworksPermission(UID_A, true);
+        expectHasUseRestrictedNetworksPermission(UID_B, false);
+
+        Map<Integer, Integer> firewallUidRules = new ArrayMap<>();
+        doAnswer(arg -> {
+            int[] uids = arg.getArgument(1);
+            int[] rules = arg.getArgument(2);
+            assertTrue(uids.length == rules.length);
+
+            for (int i = 0; i < uids.length; ++i) {
+                firewallUidRules.put(uids[i], rules[i]);
+            }
+            return null;
+        }).when(mNetworkManager).setFirewallUidRules(eq(FIREWALL_CHAIN_RESTRICTED),
+                any(int[].class), any(int[].class));
+
+        enableRestrictedMode(true);
+        assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_A).intValue());
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+        assertTrue(mService.isUidNetworkingBlocked(UID_B, false));
+
+        enableRestrictedMode(false);
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+        assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
+    }
+
+    @Test
+    public void testUpdateRestrictedModeForUid() throws Exception {
+        // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+        clearInvocations(mNetworkManager);
+        expectHasUseRestrictedNetworksPermission(UID_A, true);
+        expectHasUseRestrictedNetworksPermission(UID_B, false);
+        enableRestrictedMode(true);
+
+        // UID_D and UID_E are not part of installed applications list, so it won't have any
+        // firewall rules set yet
+        expectHasUseRestrictedNetworksPermission(UID_D, false);
+        mService.updateRestrictedModeForUidUL(UID_D);
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_RESTRICTED, UID_D,
+                FIREWALL_RULE_DEFAULT);
+        assertTrue(mService.isUidNetworkingBlocked(UID_D, false));
+
+        expectHasUseRestrictedNetworksPermission(UID_E, true);
+        mService.updateRestrictedModeForUidUL(UID_E);
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_RESTRICTED, UID_E,
+                FIREWALL_RULE_ALLOW);
+        assertFalse(mService.isUidNetworkingBlocked(UID_E, false));
+    }
+
     private String formatBlockedStateError(int uid, int rule, boolean metered,
             boolean backgroundRestricted) {
         return String.format(
@@ -1888,12 +1965,27 @@ public class NetworkPolicyManagerServiceTest {
                 .build();
     }
 
-    private ApplicationInfo buildApplicationInfo(String label) {
+    private ApplicationInfo buildApplicationInfo(String label, int uid) {
         final ApplicationInfo ai = new ApplicationInfo();
         ai.nonLocalizedLabel = label;
+        ai.uid = uid;
         return ai;
     }
 
+    private List<ApplicationInfo> buildInstalledApplicationInfoList() {
+        final List<ApplicationInfo> installedApps = new ArrayList<>();
+        installedApps.add(buildApplicationInfo(PKG_NAME_A, UID_A));
+        installedApps.add(buildApplicationInfo(PKG_NAME_B, UID_B));
+        installedApps.add(buildApplicationInfo(PKG_NAME_C, UID_C));
+        return installedApps;
+    }
+
+    private List<UserInfo> buildUserInfoList() {
+        final List<UserInfo> users = new ArrayList<>();
+        users.add(new UserInfo(USER_ID, "user1", 0));
+        return users;
+    }
+
     private NetworkInfo buildNetworkInfo() {
         final NetworkInfo ni = new NetworkInfo(ConnectivityManager.TYPE_MOBILE,
                 TelephonyManager.NETWORK_TYPE_LTE, null, null);
@@ -1967,6 +2059,15 @@ public class NetworkPolicyManagerServiceTest {
                 hasIt ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED);
     }
 
+    private void expectHasUseRestrictedNetworksPermission(int uid, boolean hasIt) throws Exception {
+        when(mIpm.checkUidPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, uid)).thenReturn(
+                hasIt ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED);
+        when(mIpm.checkUidPermission(NETWORK_STACK, uid)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+        when(mIpm.checkUidPermission(PERMISSION_MAINLINE_NETWORK_STACK, uid)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+    }
+
     private void expectNetworkState(boolean roaming) throws Exception {
         when(mCarrierConfigManager.getConfigForSubId(eq(TEST_SUB_ID)))
                 .thenReturn(mCarrierConfig);
-- 
GitLab