blob: a49c0a6e8e0c414158d7751f9543a139663068c8 [file] [log] [blame]
/*
* 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<UserHandle> mUsers = new HashSet<>();
// 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<>();
// 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<>();
// 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<Integer> 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<Integer> 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<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 : 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<String> 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<Integer> 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<UserHandle> users, Map<Integer, Boolean> apps, boolean add) {
List<Integer> network = new ArrayList<>();
List<Integer> system = new ArrayList<>();
for (Entry<Integer, Boolean> app : apps.entrySet()) {
List<Integer> 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<UserHandle> 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<UserHandle> 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<Integer, Boolean> 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<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(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<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<>();
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<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 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<Integer> 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<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));
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<UidRange> 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<Integer> uidsToUpdate = new ArraySet<>(mUidsAllowedOnRestrictedNetworks);
updateUidsAllowedOnRestrictedNetworks(mDeps.getUidsAllowedOnRestrictedNetworks(mContext));
uidsToUpdate.addAll(mUidsAllowedOnRestrictedNetworks);
final Map<Integer, Boolean> updatedUids = new HashMap<>();
final Map<Integer, Boolean> 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<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);
}
}
private static void loge(String s) {
Log.e(TAG, s);
}
private static void loge(String s, Throwable e) {
Log.e(TAG, s, e);
}
}