/* * Copyright (C) 2014 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.connectivity; import static android.Manifest.permission.CHANGE_NETWORK_STATE; 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.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.net.ConnectivitySettingsManager.UIDS_ALLOWED_ON_RESTRICTED_NETWORKS; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; import static com.android.net.module.util.CollectionUtils.toIntArray; import android.annotation.NonNull; import android.content.BroadcastReceiver; 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.database.ContentObserver; import android.net.ConnectivitySettingsManager; import android.net.INetd; import android.net.UidRange; import android.net.Uri; import android.os.Build; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.SystemConfigManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; 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.IndentingPrintWriter; import com.android.net.module.util.CollectionUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; 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. * * @hide */ public class PermissionMonitor { private static final String TAG = "PermissionMonitor"; private static final boolean DBG = true; 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 PackageManager mPackageManager; private final UserManager mUserManager; private final SystemConfigManager mSystemConfigManager; private final INetd mNetd; private final Dependencies mDeps; private final Context mContext; @GuardedBy("this") private final Set mUsers = new HashSet<>(); // Keys are app uids. Values are true for SYSTEM permission and false for NETWORK permission. @GuardedBy("this") private final Map mApps = new HashMap<>(); // 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> 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 mAllApps = new HashSet<>(); // A set of uids which are allowed to use restricted networks. The packages of these uids can't // hold the CONNECTIVITY_USE_RESTRICTED_NETWORKS permission because they can't be // signature|privileged apps. However, these apps should still be able to use restricted // networks under certain conditions (e.g. government app using emergency services). So grant // netd system permission to these uids which is listed in UIDS_ALLOWED_ON_RESTRICTED_NETWORKS. @GuardedBy("this") private final Set mUidsAllowedOnRestrictedNetworks = new ArraySet<>(); private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); final Uri packageData = intent.getData(); final String packageName = packageData != null ? packageData.getSchemeSpecificPart() : null; onPackageAdded(packageName, uid); } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); final Uri packageData = intent.getData(); final String packageName = packageData != null ? packageData.getSchemeSpecificPart() : null; onPackageRemoved(packageName, uid); } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { final String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); onExternalApplicationsAvailable(pkgList); } else { Log.wtf(TAG, "received unexpected intent: " + action); } } }; /** * Dependencies of PermissionMonitor, for injection in tests. */ @VisibleForTesting public static class Dependencies { /** * Get device first sdk version. */ public int getDeviceFirstSdkInt() { return Build.VERSION.DEVICE_INITIAL_SDK_INT; } /** * Get uids allowed to use restricted networks via ConnectivitySettingsManager. */ public Set getUidsAllowedOnRestrictedNetworks(@NonNull Context context) { return ConnectivitySettingsManager.getUidsAllowedOnRestrictedNetworks(context); } /** * Register ContentObserver for given Uri. */ public void registerContentObserver(@NonNull Context context, @NonNull Uri uri, boolean notifyForDescendants, @NonNull ContentObserver observer) { context.getContentResolver().registerContentObserver( uri, notifyForDescendants, observer); } } public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd) { this(context, netd, new Dependencies()); } @VisibleForTesting PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd, @NonNull final Dependencies deps) { mPackageManager = context.getPackageManager(); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mSystemConfigManager = context.getSystemService(SystemConfigManager.class); mNetd = netd; mDeps = deps; mContext = context; } // Intended to be called only once at startup, after the system is ready. Installs a broadcast // receiver to monitor ongoing UID changes, so this shouldn't/needn't be called again. public synchronized void startMonitoring() { log("Monitoring"); final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); userAllContext.registerReceiver( mIntentReceiver, intentFilter, null /* broadcastPermission */, null /* scheduler */); final IntentFilter externalIntentFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); userAllContext.registerReceiver( mIntentReceiver, externalIntentFilter, null /* broadcastPermission */, null /* scheduler */); // Register UIDS_ALLOWED_ON_RESTRICTED_NETWORKS setting observer mDeps.registerContentObserver( userAllContext, Settings.Global.getUriFor(UIDS_ALLOWED_ON_RESTRICTED_NETWORKS), false /* notifyForDescendants */, new ContentObserver(null) { @Override public void onChange(boolean selfChange) { onSettingChanged(); } }); // Read UIDS_ALLOWED_ON_RESTRICTED_NETWORKS setting and update // mUidsAllowedOnRestrictedNetworks. updateUidsAllowedOnRestrictedNetworks(mDeps.getUidsAllowedOnRestrictedNetworks(mContext)); List 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 : INVALID_UID; if (uid < 0) { continue; } mAllApps.add(UserHandle.getAppId(uid)); boolean isNetwork = hasNetworkPermission(app); boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app); if (isNetwork || hasRestrictedPermission) { Boolean permission = mApps.get(UserHandle.getAppId(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). if (permission == null || permission == NETWORK) { mApps.put(UserHandle.getAppId(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); } mUsers.addAll(mUserManager.getUserHandles(true /* excludeDying */)); final SparseArray netdPermToSystemPerm = new SparseArray<>(); netdPermToSystemPerm.put(INetd.PERMISSION_INTERNET, INTERNET); netdPermToSystemPerm.put(INetd.PERMISSION_UPDATE_DEVICE_STATS, UPDATE_DEVICE_STATS); for (int i = 0; i < netdPermToSystemPerm.size(); i++) { final int netdPermission = netdPermToSystemPerm.keyAt(i); final String systemPermission = netdPermToSystemPerm.valueAt(i); final int[] hasPermissionUids = mSystemConfigManager.getSystemPermissionUids(systemPermission); for (int j = 0; j < hasPermissionUids.length; j++) { final int uid = hasPermissionUids[j]; netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission); } } log("Users: " + mUsers.size() + ", Apps: " + mApps.size()); update(mUsers, mApps, true); sendPackagePermissionsToNetd(netdPermsUids); } @VisibleForTesting synchronized void updateUidsAllowedOnRestrictedNetworks(final Set uids) { mUidsAllowedOnRestrictedNetworks.clear(); // This is necessary for the app id to match in isUidAllowedOnRestrictedNetworks, and will // grant the permission to all uids associated with the app ID. This is safe even if the app // is only installed on some users because the uid cannot match some other app – this uid is // in effect not installed and can't be run. // TODO (b/192431153): Change appIds back to uids. for (int uid : uids) { mUidsAllowedOnRestrictedNetworks.add(UserHandle.getAppId(uid)); } } @VisibleForTesting static boolean isVendorApp(@NonNull ApplicationInfo appInfo) { return appInfo.isVendor() || appInfo.isOem() || appInfo.isProduct(); } @VisibleForTesting boolean isCarryoverPackage(final ApplicationInfo appInfo) { if (appInfo == null) return false; return (appInfo.targetSdkVersion < VERSION_Q && isVendorApp(appInfo)) // Backward compatibility for b/114245686, on devices that launched before Q daemons // and apps running as the system UID are exempted from this check. || (appInfo.uid == SYSTEM_UID && mDeps.getDeviceFirstSdkInt() < VERSION_Q); } @VisibleForTesting synchronized boolean isUidAllowedOnRestrictedNetworks(final ApplicationInfo appInfo) { if (appInfo == null) return false; // Check whether package's uid is in allowed on restricted networks uid list. If so, this // uid can have netd system permission. return mUidsAllowedOnRestrictedNetworks.contains(UserHandle.getAppId(appInfo.uid)); } @VisibleForTesting boolean hasPermission(@NonNull final PackageInfo app, @NonNull final String permission) { if (app.requestedPermissions == null || app.requestedPermissionsFlags == null) { return false; } final int index = CollectionUtils.indexOf(app.requestedPermissions, permission); if (index < 0 || index >= app.requestedPermissionsFlags.length) return false; return (app.requestedPermissionsFlags[index] & REQUESTED_PERMISSION_GRANTED) != 0; } @VisibleForTesting boolean hasNetworkPermission(@NonNull final PackageInfo app) { return hasPermission(app, CHANGE_NETWORK_STATE); } @VisibleForTesting boolean hasRestrictedNetworkPermission(@NonNull final PackageInfo app) { // TODO : remove carryover package check in the future(b/31479477). All apps should just // request the appropriate permission for their use case since android Q. return isCarryoverPackage(app.applicationInfo) || isUidAllowedOnRestrictedNetworks(app.applicationInfo) || hasPermission(app, PERMISSION_MAINLINE_NETWORK_STACK) || hasPermission(app, NETWORK_STACK) || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS); } /** 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(UserHandle.getAppId(uid)); } /** * Returns whether the given uid has permission to use restricted networks. */ public synchronized boolean hasRestrictedNetworksPermission(int uid) { return Boolean.TRUE.equals(mApps.get(UserHandle.getAppId(uid))); } private void update(Set users, Map apps, boolean add) { List network = new ArrayList<>(); List system = new ArrayList<>(); for (Entry app : apps.entrySet()) { List list = app.getValue() ? system : network; for (UserHandle user : users) { if (user == null) continue; list.add(user.getUid(app.getKey())); } } try { if (add) { mNetd.networkSetPermissionForUser(INetd.PERMISSION_NETWORK, toIntArray(network)); mNetd.networkSetPermissionForUser(INetd.PERMISSION_SYSTEM, toIntArray(system)); } else { mNetd.networkClearPermissionForUser(toIntArray(network)); mNetd.networkClearPermissionForUser(toIntArray(system)); } } catch (RemoteException e) { loge("Exception when updating permissions: " + e); } } /** * 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(@NonNull UserHandle user) { mUsers.add(user); Set users = new HashSet<>(); users.add(user); update(users, mApps, true); } /** * 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(@NonNull UserHandle user) { mUsers.remove(user); Set users = new HashSet<>(); users.add(user); update(users, mApps, false); } /** * Compare the current network permission and the given package's permission to find out highest * permission for the uid. * * @param currentPermission Current uid network permission * @param name The package has same uid that need compare its permission to update uid network * permission. */ @VisibleForTesting protected Boolean highestPermissionForUid(Boolean currentPermission, String name) { if (currentPermission == SYSTEM) { return currentPermission; } try { final PackageInfo app = mPackageManager.getPackageInfo(name, GET_PERMISSIONS | MATCH_ANY_USER); final boolean isNetwork = hasNetworkPermission(app); final boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app); if (isNetwork || hasRestrictedPermission) { currentPermission = hasRestrictedPermission; } } catch (NameNotFoundException e) { // App not found. loge("NameNotFoundException " + name); } return currentPermission; } private int getPermissionForUid(final int uid) { int permission = INetd.PERMISSION_NONE; // Check all the packages for this UID. The UID has the permission if any of the // packages in it has the permission. final 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; } /** * Called when a package is added. * * @param packageName The name of the new package. * @param uid The uid of the new package. * * @hide */ public synchronized void onPackageAdded(@NonNull final String packageName, final int uid) { // TODO: Netd is using appId for checking traffic permission. Correct the methods that are // using appId instead of uid actually sendPackagePermissionsForUid(UserHandle.getAppId(uid), getPermissionForUid(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 int appId = UserHandle.getAppId(uid); final Boolean permission = highestPermissionForUid(mApps.get(appId), packageName); if (permission != mApps.get(appId)) { mApps.put(appId, permission); Map apps = new HashMap<>(); apps.put(appId, 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> vpn : mVpnUidRanges.entrySet()) { if (UidRange.containsUid(vpn.getValue(), uid)) { final Set changedUids = new HashSet<>(); changedUids.add(uid); removeBypassingUids(changedUids, /* vpnAppUid */ -1); updateVpnUids(vpn.getKey(), changedUids, true); } } mAllApps.add(appId); } private Boolean highestUidNetworkPermission(int uid) { Boolean permission = null; final String[] packages = mPackageManager.getPackagesForUid(uid); if (!CollectionUtils.isEmpty(packages)) { for (String name : packages) { // If multiple packages have the same UID, give the UID all permissions that // any package in that UID has. permission = highestPermissionForUid(permission, name); if (permission == SYSTEM) { break; } } } return permission; } /** * Called when a package is removed. * * @param packageName The name of the removed package or null. * @param uid containing the integer uid previously assigned to the package. * * @hide */ public synchronized void onPackageRemoved(@NonNull final String packageName, final int uid) { // TODO: Netd is using appId for checking traffic permission. Correct the methods that are // using appId instead of uid actually sendPackagePermissionsForUid(UserHandle.getAppId(uid), getPermissionForUid(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> vpn : mVpnUidRanges.entrySet()) { if (UidRange.containsUid(vpn.getValue(), uid)) { final Set 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 apps = new HashMap<>(); final Boolean permission = highestUidNetworkPermission(uid); if (permission == SYSTEM) { // An app with this UID still has the SYSTEM permission. // Therefore, this UID must already have the SYSTEM permission. // Nothing to do. return; } final int appId = UserHandle.getAppId(uid); if (permission == mApps.get(appId)) { // The permissions of this UID have not changed. Nothing to do. return; } else if (permission != null) { mApps.put(appId, permission); apps.put(appId, permission); update(mUsers, apps, true); } else { mApps.remove(appId); apps.put(appId, 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 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 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(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 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 changedUids = intersectUids(rangesToRemove, mAllApps); removeBypassingUids(changedUids, vpnAppUid); updateVpnUids(iface, changedUids, false); Set 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 intersectUids(Set ranges, Set appIds) { Set result = new HashSet<>(); for (UidRange range : ranges) { for (int userId = range.getStartUser(); userId <= range.getEndUser(); userId++) { for (int appId : appIds) { final UserHandle handle = UserHandle.of(userId); if (handle == null) continue; final int uid = handle.getUid(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 uids, int vpnAppUid) { uids.remove(vpnAppUid); uids.removeIf(uid -> mApps.getOrDefault(UserHandle.getAppId(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 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 allPermissionAppIds = new ArrayList<>(); ArrayList internetPermissionAppIds = new ArrayList<>(); ArrayList updateStatsPermissionAppIds = new ArrayList<>(); ArrayList noPermissionAppIds = new ArrayList<>(); ArrayList 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)); break; 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, toIntArray(allPermissionAppIds)); } if (internetPermissionAppIds.size() != 0) { mNetd.trafficSetNetPermForUids(INetd.PERMISSION_INTERNET, toIntArray(internetPermissionAppIds)); } if (updateStatsPermissionAppIds.size() != 0) { mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UPDATE_DEVICE_STATS, toIntArray(updateStatsPermissionAppIds)); } if (noPermissionAppIds.size() != 0) { mNetd.trafficSetNetPermForUids(INetd.PERMISSION_NONE, toIntArray(noPermissionAppIds)); } if (uninstalledAppIds.size() != 0) { mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UNINSTALLED, toIntArray(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 getVpnUidRanges(String iface) { return mVpnUidRanges.get(iface); } private synchronized void onSettingChanged() { // Step1. Update uids allowed to use restricted networks and compute the set of uids to // update. final Set uidsToUpdate = new ArraySet<>(mUidsAllowedOnRestrictedNetworks); updateUidsAllowedOnRestrictedNetworks(mDeps.getUidsAllowedOnRestrictedNetworks(mContext)); uidsToUpdate.addAll(mUidsAllowedOnRestrictedNetworks); final Map updatedUids = new HashMap<>(); final Map removedUids = new HashMap<>(); // Step2. For each uid to update, find out its new permission. for (Integer uid : uidsToUpdate) { final Boolean permission = highestUidNetworkPermission(uid); final int appId = UserHandle.getAppId(uid); if (null == permission) { removedUids.put(appId, NETWORK); // Doesn't matter which permission is set here. mApps.remove(appId); } else { updatedUids.put(appId, permission); mApps.put(appId, permission); } } // Step3. Update or revoke permission for uids with netd. update(mUsers, updatedUids, true /* add */); update(mUsers, removedUids, false /* add */); } private synchronized void onExternalApplicationsAvailable(String[] pkgList) { if (CollectionUtils.isEmpty(pkgList)) { Log.e(TAG, "No available external application."); return; } for (String app : pkgList) { final PackageInfo info = getPackageInfo(app); if (info == null || info.applicationInfo == null) continue; final int appId = info.applicationInfo.uid; onPackageAdded(app, appId); // Use onPackageAdded to add package one by one. } } /** Dump info to dumpsys */ public void dump(IndentingPrintWriter pw) { pw.println("Interface filtering rules:"); pw.increaseIndent(); for (Map.Entry> 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); } } private static void loge(String s) { Log.e(TAG, s); } private static void loge(String s, Throwable e) { Log.e(TAG, s, e); } }