OSDN Git Service

Merge "Fix PermissionMonitor issues" into qt-dev am: 97baed2635
[android-x86/frameworks-base.git] / services / core / java / com / android / server / connectivity / PermissionMonitor.java
index e471c7d..56f4959 100644 (file)
@@ -19,38 +19,53 @@ package com.android.server.connectivity;
 import static android.Manifest.permission.CHANGE_NETWORK_STATE;
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.INTERNET;
 import static android.Manifest.permission.NETWORK_STACK;
-import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
-import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.os.Process.INVALID_UID;
+import static android.os.Process.SYSTEM_UID;
 
-import android.content.BroadcastReceiver;
+import android.annotation.NonNull;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
-import android.net.Uri;
-import android.os.INetworkManagementService;
+import android.net.INetd;
+import android.net.UidRange;
+import android.os.Build;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.text.TextUtils;
+import android.system.OsConstants;
+import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map.Entry;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
+
 /**
  * A utility class to inform Netd of UID permisisons.
  * Does a mass update at boot and then monitors for app install/remove.
@@ -60,46 +75,75 @@ import java.util.Set;
 public class PermissionMonitor {
     private static final String TAG = "PermissionMonitor";
     private static final boolean DBG = true;
-    private static final Boolean SYSTEM = Boolean.TRUE;
-    private static final Boolean NETWORK = Boolean.FALSE;
+    protected static final Boolean SYSTEM = Boolean.TRUE;
+    protected static final Boolean NETWORK = Boolean.FALSE;
+    private static final int VERSION_Q = Build.VERSION_CODES.Q;
 
-    private final Context mContext;
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
-    private final INetworkManagementService mNetd;
-    private final BroadcastReceiver mIntentReceiver;
+    private final INetd mNetd;
 
     // Values are User IDs.
+    @GuardedBy("this")
     private final Set<Integer> mUsers = new HashSet<>();
 
-    // Keys are App IDs. Values are true for SYSTEM permission and false for NETWORK permission.
+    // Keys are app uids. Values are true for SYSTEM permission and false for NETWORK permission.
+    @GuardedBy("this")
     private final Map<Integer, Boolean> mApps = new HashMap<>();
 
-    public PermissionMonitor(Context context, INetworkManagementService netd) {
-        mContext = context;
-        mPackageManager = context.getPackageManager();
-        mUserManager = UserManager.get(context);
-        mNetd = netd;
-        mIntentReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
-                int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-                int appUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-                Uri appData = intent.getData();
-                String appName = appData != null ? appData.getSchemeSpecificPart() : null;
-
-                if (Intent.ACTION_USER_ADDED.equals(action)) {
-                    onUserAdded(user);
-                } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
-                    onUserRemoved(user);
-                } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
-                    onAppAdded(appName, appUid);
-                } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-                    onAppRemoved(appUid);
+    // Keys are active non-bypassable and fully-routed VPN's interface name, Values are uid ranges
+    // for apps under the VPN
+    @GuardedBy("this")
+    private final Map<String, Set<UidRange>> mVpnUidRanges = new HashMap<>();
+
+    // A set of appIds for apps across all users on the device. We track appIds instead of uids
+    // directly to reduce its size and also eliminate the need to update this set when user is
+    // added/removed.
+    @GuardedBy("this")
+    private final Set<Integer> mAllApps = new HashSet<>();
+
+    private class PackageListObserver implements PackageManagerInternal.PackageListObserver {
+
+        private int getPermissionForUid(int uid) {
+            int permission = 0;
+            // Check all the packages for this UID. The UID has the permission if any of the
+            // packages in it has the permission.
+            String[] packages = mPackageManager.getPackagesForUid(uid);
+            if (packages != null && packages.length > 0) {
+                for (String name : packages) {
+                    final PackageInfo app = getPackageInfo(name);
+                    if (app != null && app.requestedPermissions != null) {
+                        permission |= getNetdPermissionMask(app.requestedPermissions,
+                              app.requestedPermissionsFlags);
+                    }
                 }
+            } else {
+                // The last package of this uid is removed from device. Clean the package up.
+                permission = INetd.PERMISSION_UNINSTALLED;
             }
-        };
+            return permission;
+        }
+
+        @Override
+        public void onPackageAdded(String packageName, int uid) {
+            sendPackagePermissionsForUid(uid, getPermissionForUid(uid));
+        }
+
+        @Override
+        public void onPackageChanged(@NonNull String packageName, int uid) {
+            sendPackagePermissionsForUid(uid, getPermissionForUid(uid));
+        }
+
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            sendPackagePermissionsForUid(uid, getPermissionForUid(uid));
+        }
+    }
+
+    public PermissionMonitor(Context context, INetd netd) {
+        mPackageManager = context.getPackageManager();
+        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        mNetd = netd;
     }
 
     // Intended to be called only once at startup, after the system is ready. Installs a broadcast
@@ -107,28 +151,27 @@ public class PermissionMonitor {
     public synchronized void startMonitoring() {
         log("Monitoring");
 
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_USER_ADDED);
-        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
-        mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, intentFilter, null, null);
-
-        intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        intentFilter.addDataScheme("package");
-        mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, intentFilter, null, null);
-
-        List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS);
+        PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+        if (pmi != null) {
+            pmi.getPackageList(new PackageListObserver());
+        } else {
+            loge("failed to get the PackageManagerInternal service");
+        }
+        List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS
+                | MATCH_ANY_USER);
         if (apps == null) {
             loge("No apps");
             return;
         }
 
+        SparseIntArray netdPermsUids = new SparseIntArray();
+
         for (PackageInfo app : apps) {
-            int uid = app.applicationInfo != null ? app.applicationInfo.uid : -1;
+            int uid = app.applicationInfo != null ? app.applicationInfo.uid : INVALID_UID;
             if (uid < 0) {
                 continue;
             }
+            mAllApps.add(UserHandle.getAppId(uid));
 
             boolean isNetwork = hasNetworkPermission(app);
             boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
@@ -141,6 +184,11 @@ public class PermissionMonitor {
                     mApps.put(uid, hasRestrictedPermission);
                 }
             }
+
+            //TODO: unify the management of the permissions into one codepath.
+            int otherNetdPerms = getNetdPermissionMask(app.requestedPermissions,
+                    app.requestedPermissionsFlags);
+            netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms);
         }
 
         List<UserInfo> users = mUserManager.getUsers(true);  // exclude dying users
@@ -150,73 +198,87 @@ public class PermissionMonitor {
             }
         }
 
+        final SparseArray<ArraySet<String>> systemPermission =
+                SystemConfig.getInstance().getSystemPermissions();
+        for (int i = 0; i < systemPermission.size(); i++) {
+            ArraySet<String> perms = systemPermission.valueAt(i);
+            int uid = systemPermission.keyAt(i);
+            int netdPermission = 0;
+            // Get the uids of native services that have UPDATE_DEVICE_STATS or INTERNET permission.
+            if (perms != null) {
+                netdPermission |= perms.contains(UPDATE_DEVICE_STATS)
+                        ? INetd.PERMISSION_UPDATE_DEVICE_STATS : 0;
+                netdPermission |= perms.contains(INTERNET)
+                        ? INetd.PERMISSION_INTERNET : 0;
+            }
+            netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission);
+        }
         log("Users: " + mUsers.size() + ", Apps: " + mApps.size());
         update(mUsers, mApps, true);
+        sendPackagePermissionsToNetd(netdPermsUids);
     }
 
     @VisibleForTesting
-    boolean isPreinstalledSystemApp(PackageInfo app) {
-        int flags = app.applicationInfo != null ? app.applicationInfo.flags : 0;
-        return (flags & (FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP)) != 0;
+    static boolean isVendorApp(@NonNull ApplicationInfo appInfo) {
+        return appInfo.isVendor() || appInfo.isOem() || appInfo.isProduct();
     }
 
     @VisibleForTesting
-    boolean hasPermission(PackageInfo app, String permission) {
-        if (app.requestedPermissions != null) {
-            for (String p : app.requestedPermissions) {
-                if (permission.equals(p)) {
-                    return true;
-                }
-            }
+    protected int getDeviceFirstSdkInt() {
+        return Build.VERSION.FIRST_SDK_INT;
+    }
+
+    @VisibleForTesting
+    boolean hasPermission(@NonNull final PackageInfo app, @NonNull final String permission) {
+        if (app.requestedPermissions == null || app.requestedPermissionsFlags == null) {
+            return false;
         }
-        return false;
+        final int index = ArrayUtils.indexOf(app.requestedPermissions, permission);
+        if (index < 0 || index >= app.requestedPermissionsFlags.length) return false;
+        return (app.requestedPermissionsFlags[index] & REQUESTED_PERMISSION_GRANTED) != 0;
     }
 
-    private boolean hasNetworkPermission(PackageInfo app) {
+    @VisibleForTesting
+    boolean hasNetworkPermission(@NonNull final PackageInfo app) {
         return hasPermission(app, CHANGE_NETWORK_STATE);
     }
 
-    private boolean hasRestrictedNetworkPermission(PackageInfo app) {
-        if (isPreinstalledSystemApp(app)) return true;
-        return hasPermission(app, CONNECTIVITY_INTERNAL)
-                || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
-    }
+    @VisibleForTesting
+    boolean hasRestrictedNetworkPermission(@NonNull final PackageInfo app) {
+        // TODO : remove this check in the future(b/31479477). All apps should just
+        // request the appropriate permission for their use case since android Q.
+        if (app.applicationInfo != null) {
+            // Backward compatibility for b/114245686, on devices that launched before Q daemons
+            // and apps running as the system UID are exempted from this check.
+            if (app.applicationInfo.uid == SYSTEM_UID && getDeviceFirstSdkInt() < VERSION_Q) {
+                return true;
+            }
 
-    private boolean hasUseBackgroundNetworksPermission(PackageInfo app) {
-        // This function defines what it means to hold the permission to use
-        // background networks.
-        return hasPermission(app, CHANGE_NETWORK_STATE)
-                || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS)
-                || hasPermission(app, CONNECTIVITY_INTERNAL)
+            if (app.applicationInfo.targetSdkVersion < VERSION_Q
+                    && isVendorApp(app.applicationInfo)) {
+                return true;
+            }
+        }
+        return hasPermission(app, CONNECTIVITY_INTERNAL)
                 || hasPermission(app, NETWORK_STACK)
-                // TODO : remove this check (b/31479477). Not all preinstalled apps should
-                // have access to background networks, they should just request the appropriate
-                // permission for their use case from the list above.
-                || isPreinstalledSystemApp(app);
+                || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
     }
 
-    public boolean hasUseBackgroundNetworksPermission(int uid) {
-        final String[] names = mPackageManager.getPackagesForUid(uid);
-        if (null == names || names.length == 0) return false;
-        try {
-            // Only using the first package name. There may be multiple names if multiple
-            // apps share the same UID, but in that case they also share permissions so
-            // querying with any of the names will return the same results.
-            int userId = UserHandle.getUserId(uid);
-            final PackageInfo app = mPackageManager.getPackageInfoAsUser(
-                    names[0], GET_PERMISSIONS, userId);
-            return hasUseBackgroundNetworksPermission(app);
-        } catch (NameNotFoundException e) {
-            // App not found.
-            loge("NameNotFoundException " + names[0], e);
-            return false;
-        }
+    /** Returns whether the given uid has using background network permission. */
+    public synchronized boolean hasUseBackgroundNetworksPermission(final int uid) {
+        // Apps with any of the CHANGE_NETWORK_STATE, NETWORK_STACK, CONNECTIVITY_INTERNAL or
+        // CONNECTIVITY_USE_RESTRICTED_NETWORKS permission has the permission to use background
+        // networks. mApps contains the result of checks for both hasNetworkPermission and
+        // hasRestrictedNetworkPermission. If uid is in the mApps list that means uid has one of
+        // permissions at least.
+        return mApps.containsKey(uid);
     }
 
-    private int[] toIntArray(List<Integer> list) {
+    private int[] toIntArray(Collection<Integer> list) {
         int[] array = new int[list.size()];
-        for (int i = 0; i < list.size(); i++) {
-            array[i] = list.get(i);
+        int i = 0;
+        for (Integer item : list) {
+            array[i++] = item;
         }
         return array;
     }
@@ -232,18 +294,25 @@ public class PermissionMonitor {
         }
         try {
             if (add) {
-                mNetd.setPermission("NETWORK", toIntArray(network));
-                mNetd.setPermission("SYSTEM", toIntArray(system));
+                mNetd.networkSetPermissionForUser(INetd.PERMISSION_NETWORK, toIntArray(network));
+                mNetd.networkSetPermissionForUser(INetd.PERMISSION_SYSTEM, toIntArray(system));
             } else {
-                mNetd.clearPermission(toIntArray(network));
-                mNetd.clearPermission(toIntArray(system));
+                mNetd.networkClearPermissionForUser(toIntArray(network));
+                mNetd.networkClearPermissionForUser(toIntArray(system));
             }
         } catch (RemoteException e) {
             loge("Exception when updating permissions: " + e);
         }
     }
 
-    private synchronized void onUserAdded(int user) {
+    /**
+     * Called when a user is added. See {link #ACTION_USER_ADDED}.
+     *
+     * @param user The integer userHandle of the added user. See {@link #EXTRA_USER_HANDLE}.
+     *
+     * @hide
+     */
+    public synchronized void onUserAdded(int user) {
         if (user < 0) {
             loge("Invalid user in onUserAdded: " + user);
             return;
@@ -255,7 +324,14 @@ public class PermissionMonitor {
         update(users, mApps, true);
     }
 
-    private synchronized void onUserRemoved(int user) {
+    /**
+     * Called when an user is removed. See {link #ACTION_USER_REMOVED}.
+     *
+     * @param user The integer userHandle of the removed user. See {@link #EXTRA_USER_HANDLE}.
+     *
+     * @hide
+     */
+    public synchronized void onUserRemoved(int user) {
         if (user < 0) {
             loge("Invalid user in onUserRemoved: " + user);
             return;
@@ -267,8 +343,8 @@ public class PermissionMonitor {
         update(users, mApps, false);
     }
 
-
-    private Boolean highestPermissionForUid(Boolean currentPermission, String name) {
+    @VisibleForTesting
+    protected Boolean highestPermissionForUid(Boolean currentPermission, String name) {
         if (currentPermission == SYSTEM) {
             return currentPermission;
         }
@@ -286,33 +362,67 @@ public class PermissionMonitor {
         return currentPermission;
     }
 
-    private synchronized void onAppAdded(String appName, int appUid) {
-        if (TextUtils.isEmpty(appName) || appUid < 0) {
-            loge("Invalid app in onAppAdded: " + appName + " | " + appUid);
-            return;
-        }
-
+    /**
+     * Called when a package is added. See {link #ACTION_PACKAGE_ADDED}.
+     *
+     * @param packageName The name of the new package.
+     * @param uid The uid of the new package.
+     *
+     * @hide
+     */
+    public synchronized void onPackageAdded(String packageName, int uid) {
         // If multiple packages share a UID (cf: android:sharedUserId) and ask for different
         // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
-        final Boolean permission = highestPermissionForUid(mApps.get(appUid), appName);
-        if (permission != mApps.get(appUid)) {
-            mApps.put(appUid, permission);
+        final Boolean permission = highestPermissionForUid(mApps.get(uid), packageName);
+        if (permission != mApps.get(uid)) {
+            mApps.put(uid, permission);
 
             Map<Integer, Boolean> apps = new HashMap<>();
-            apps.put(appUid, permission);
+            apps.put(uid, permission);
             update(mUsers, apps, true);
         }
+
+        // If the newly-installed package falls within some VPN's uid range, update Netd with it.
+        // This needs to happen after the mApps update above, since removeBypassingUids() depends
+        // on mApps to check if the package can bypass VPN.
+        for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+            if (UidRange.containsUid(vpn.getValue(), uid)) {
+                final Set<Integer> changedUids = new HashSet<>();
+                changedUids.add(uid);
+                removeBypassingUids(changedUids, /* vpnAppUid */ -1);
+                updateVpnUids(vpn.getKey(), changedUids, true);
+            }
+        }
+        mAllApps.add(UserHandle.getAppId(uid));
     }
 
-    private synchronized void onAppRemoved(int appUid) {
-        if (appUid < 0) {
-            loge("Invalid app in onAppRemoved: " + appUid);
-            return;
+    /**
+     * Called when a package is removed. See {link #ACTION_PACKAGE_REMOVED}.
+     *
+     * @param uid containing the integer uid previously assigned to the package.
+     *
+     * @hide
+     */
+    public synchronized void onPackageRemoved(int uid) {
+        // If the newly-removed package falls within some VPN's uid range, update Netd with it.
+        // This needs to happen before the mApps update below, since removeBypassingUids() depends
+        // on mApps to check if the package can bypass VPN.
+        for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+            if (UidRange.containsUid(vpn.getValue(), uid)) {
+                final Set<Integer> changedUids = new HashSet<>();
+                changedUids.add(uid);
+                removeBypassingUids(changedUids, /* vpnAppUid */ -1);
+                updateVpnUids(vpn.getKey(), changedUids, false);
+            }
+        }
+        // If the package has been removed from all users on the device, clear it form mAllApps.
+        if (mPackageManager.getNameForUid(uid) == null) {
+            mAllApps.remove(UserHandle.getAppId(uid));
         }
-        Map<Integer, Boolean> apps = new HashMap<>();
 
+        Map<Integer, Boolean> apps = new HashMap<>();
         Boolean permission = null;
-        String[] packages = mPackageManager.getPackagesForUid(appUid);
+        String[] packages = mPackageManager.getPackagesForUid(uid);
         if (packages != null && packages.length > 0) {
             for (String name : packages) {
                 permission = highestPermissionForUid(permission, name);
@@ -324,20 +434,266 @@ public class PermissionMonitor {
                 }
             }
         }
-        if (permission == mApps.get(appUid)) {
+        if (permission == mApps.get(uid)) {
             // The permissions of this UID have not changed. Nothing to do.
             return;
         } else if (permission != null) {
-            mApps.put(appUid, permission);
-            apps.put(appUid, permission);
+            mApps.put(uid, permission);
+            apps.put(uid, permission);
             update(mUsers, apps, true);
         } else {
-            mApps.remove(appUid);
-            apps.put(appUid, NETWORK);  // doesn't matter which permission we pick here
+            mApps.remove(uid);
+            apps.put(uid, NETWORK);  // doesn't matter which permission we pick here
             update(mUsers, apps, false);
         }
     }
 
+    private static int getNetdPermissionMask(String[] requestedPermissions,
+                                             int[] requestedPermissionsFlags) {
+        int permissions = 0;
+        if (requestedPermissions == null || requestedPermissionsFlags == null) return permissions;
+        for (int i = 0; i < requestedPermissions.length; i++) {
+            if (requestedPermissions[i].equals(INTERNET)
+                    && ((requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED) != 0)) {
+                permissions |= INetd.PERMISSION_INTERNET;
+            }
+            if (requestedPermissions[i].equals(UPDATE_DEVICE_STATS)
+                    && ((requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED) != 0)) {
+                permissions |= INetd.PERMISSION_UPDATE_DEVICE_STATS;
+            }
+        }
+        return permissions;
+    }
+
+    private PackageInfo getPackageInfo(String packageName) {
+        try {
+            PackageInfo app = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS
+                    | MATCH_ANY_USER);
+            return app;
+        } catch (NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Called when a new set of UID ranges are added to an active VPN network
+     *
+     * @param iface The active VPN network's interface name
+     * @param rangesToAdd The new UID ranges to be added to the network
+     * @param vpnAppUid The uid of the VPN app
+     */
+    public synchronized void onVpnUidRangesAdded(@NonNull String iface, Set<UidRange> rangesToAdd,
+            int vpnAppUid) {
+        // Calculate the list of new app uids under the VPN due to the new UID ranges and update
+        // Netd about them. Because mAllApps only contains appIds instead of uids, the result might
+        // be an overestimation if an app is not installed on the user on which the VPN is running,
+        // but that's safe.
+        final Set<Integer> changedUids = intersectUids(rangesToAdd, mAllApps);
+        removeBypassingUids(changedUids, vpnAppUid);
+        updateVpnUids(iface, changedUids, true);
+        if (mVpnUidRanges.containsKey(iface)) {
+            mVpnUidRanges.get(iface).addAll(rangesToAdd);
+        } else {
+            mVpnUidRanges.put(iface, new HashSet<UidRange>(rangesToAdd));
+        }
+    }
+
+    /**
+     * Called when a set of UID ranges are removed from an active VPN network
+     *
+     * @param iface The VPN network's interface name
+     * @param rangesToRemove Existing UID ranges to be removed from the VPN network
+     * @param vpnAppUid The uid of the VPN app
+     */
+    public synchronized void onVpnUidRangesRemoved(@NonNull String iface,
+            Set<UidRange> rangesToRemove, int vpnAppUid) {
+        // Calculate the list of app uids that are no longer under the VPN due to the removed UID
+        // ranges and update Netd about them.
+        final Set<Integer> changedUids = intersectUids(rangesToRemove, mAllApps);
+        removeBypassingUids(changedUids, vpnAppUid);
+        updateVpnUids(iface, changedUids, false);
+        Set<UidRange> existingRanges = mVpnUidRanges.getOrDefault(iface, null);
+        if (existingRanges == null) {
+            loge("Attempt to remove unknown vpn uid Range iface = " + iface);
+            return;
+        }
+        existingRanges.removeAll(rangesToRemove);
+        if (existingRanges.size() == 0) {
+            mVpnUidRanges.remove(iface);
+        }
+    }
+
+    /**
+     * Compute the intersection of a set of UidRanges and appIds. Returns a set of uids
+     * that satisfies:
+     *   1. falls into one of the UidRange
+     *   2. matches one of the appIds
+     */
+    private Set<Integer> intersectUids(Set<UidRange> ranges, Set<Integer> appIds) {
+        Set<Integer> result = new HashSet<>();
+        for (UidRange range : ranges) {
+            for (int userId = range.getStartUser(); userId <= range.getEndUser(); userId++) {
+                for (int appId : appIds) {
+                    final int uid = UserHandle.getUid(userId, appId);
+                    if (range.contains(uid)) {
+                        result.add(uid);
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Remove all apps which can elect to bypass the VPN from the list of uids
+     *
+     * An app can elect to bypass the VPN if it hold SYSTEM permission, or if its the active VPN
+     * app itself.
+     *
+     * @param uids The list of uids to operate on
+     * @param vpnAppUid The uid of the VPN app
+     */
+    private void removeBypassingUids(Set<Integer> uids, int vpnAppUid) {
+        uids.remove(vpnAppUid);
+        uids.removeIf(uid -> mApps.getOrDefault(uid, NETWORK) == SYSTEM);
+    }
+
+    /**
+     * Update netd about the list of uids that are under an active VPN connection which they cannot
+     * bypass.
+     *
+     * This is to instruct netd to set up appropriate filtering rules for these uids, such that they
+     * can only receive ingress packets from the VPN's tunnel interface (and loopback).
+     *
+     * @param iface the interface name of the active VPN connection
+     * @param add {@code true} if the uids are to be added to the interface, {@code false} if they
+     *        are to be removed from the interface.
+     */
+    private void updateVpnUids(String iface, Set<Integer> uids, boolean add) {
+        if (uids.size() == 0) {
+            return;
+        }
+        try {
+            if (add) {
+                mNetd.firewallAddUidInterfaceRules(iface, toIntArray(uids));
+            } else {
+                mNetd.firewallRemoveUidInterfaceRules(toIntArray(uids));
+            }
+        } catch (ServiceSpecificException e) {
+            // Silently ignore exception when device does not support eBPF, otherwise just log
+            // the exception and do not crash
+            if (e.errorCode != OsConstants.EOPNOTSUPP) {
+                loge("Exception when updating permissions: ", e);
+            }
+        } catch (RemoteException e) {
+            loge("Exception when updating permissions: ", e);
+        }
+    }
+
+    /**
+     * Called by PackageListObserver when a package is installed/uninstalled. Send the updated
+     * permission information to netd.
+     *
+     * @param uid the app uid of the package installed
+     * @param permissions the permissions the app requested and netd cares about.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    void sendPackagePermissionsForUid(int uid, int permissions) {
+        SparseIntArray netdPermissionsAppIds = new SparseIntArray();
+        netdPermissionsAppIds.put(uid, permissions);
+        sendPackagePermissionsToNetd(netdPermissionsAppIds);
+    }
+
+    /**
+     * Called by packageManagerService to send IPC to netd. Grant or revoke the INTERNET
+     * and/or UPDATE_DEVICE_STATS permission of the uids in array.
+     *
+     * @param netdPermissionsAppIds integer pairs of uids and the permission granted to it. If the
+     * permission is 0, revoke all permissions of that uid.
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    void sendPackagePermissionsToNetd(SparseIntArray netdPermissionsAppIds) {
+        if (mNetd == null) {
+            Log.e(TAG, "Failed to get the netd service");
+            return;
+        }
+        ArrayList<Integer> allPermissionAppIds = new ArrayList<>();
+        ArrayList<Integer> internetPermissionAppIds = new ArrayList<>();
+        ArrayList<Integer> updateStatsPermissionAppIds = new ArrayList<>();
+        ArrayList<Integer> noPermissionAppIds = new ArrayList<>();
+        ArrayList<Integer> uninstalledAppIds = new ArrayList<>();
+        for (int i = 0; i < netdPermissionsAppIds.size(); i++) {
+            int permissions = netdPermissionsAppIds.valueAt(i);
+            switch(permissions) {
+                case (INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS):
+                    allPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
+                case INetd.PERMISSION_INTERNET:
+                    internetPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
+                case INetd.PERMISSION_UPDATE_DEVICE_STATS:
+                    updateStatsPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
+                case INetd.PERMISSION_NONE:
+                    noPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
+                case INetd.PERMISSION_UNINSTALLED:
+                    uninstalledAppIds.add(netdPermissionsAppIds.keyAt(i));
+                default:
+                    Log.e(TAG, "unknown permission type: " + permissions + "for uid: "
+                            + netdPermissionsAppIds.keyAt(i));
+            }
+        }
+        try {
+            // TODO: add a lock inside netd to protect IPC trafficSetNetPermForUids()
+            if (allPermissionAppIds.size() != 0) {
+                mNetd.trafficSetNetPermForUids(
+                        INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS,
+                        ArrayUtils.convertToIntArray(allPermissionAppIds));
+            }
+            if (internetPermissionAppIds.size() != 0) {
+                mNetd.trafficSetNetPermForUids(INetd.PERMISSION_INTERNET,
+                        ArrayUtils.convertToIntArray(internetPermissionAppIds));
+            }
+            if (updateStatsPermissionAppIds.size() != 0) {
+                mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UPDATE_DEVICE_STATS,
+                        ArrayUtils.convertToIntArray(updateStatsPermissionAppIds));
+            }
+            if (noPermissionAppIds.size() != 0) {
+                mNetd.trafficSetNetPermForUids(INetd.PERMISSION_NONE,
+                        ArrayUtils.convertToIntArray(noPermissionAppIds));
+            }
+            if (uninstalledAppIds.size() != 0) {
+                mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UNINSTALLED,
+                        ArrayUtils.convertToIntArray(uninstalledAppIds));
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Pass appId list of special permission failed." + e);
+        }
+    }
+
+    /** Should only be used by unit tests */
+    @VisibleForTesting
+    public Set<UidRange> getVpnUidRanges(String iface) {
+        return mVpnUidRanges.get(iface);
+    }
+
+    /** Dump info to dumpsys */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("Interface filtering rules:");
+        pw.increaseIndent();
+        for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+            pw.println("Interface: " + vpn.getKey());
+            pw.println("UIDs: " + vpn.getValue().toString());
+            pw.println();
+        }
+        pw.decreaseIndent();
+    }
+
     private static void log(String s) {
         if (DBG) {
             Log.d(TAG, s);