Loading core/java/android/net/NetworkCapabilities.java +5 −0 Original line number Diff line number Diff line Loading @@ -822,6 +822,11 @@ public final class NetworkCapabilities implements Parcelable { mEstablishingVpnAppUid = uid; } /** @hide */ public int getEstablishingVpnAppUid() { return mEstablishingVpnAppUid; } /** * Value indicating that link bandwidth is unspecified. * @hide Loading core/java/android/net/UidRange.java +27 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ import static android.os.UserHandle.PER_USER_RANGE; import android.os.Parcel; import android.os.Parcelable; import java.util.Collection; /** * An inclusive range of UIDs. * Loading @@ -42,10 +44,16 @@ public final class UidRange implements Parcelable { return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1); } /** Returns the smallest user Id which is contained in this UidRange */ public int getStartUser() { return start / PER_USER_RANGE; } /** Returns the largest user Id which is contained in this UidRange */ public int getEndUser() { return stop / PER_USER_RANGE; } public boolean contains(int uid) { return start <= uid && uid <= stop; } Loading Loading @@ -117,4 +125,23 @@ public final class UidRange implements Parcelable { return new UidRange[size]; } }; /** * Returns whether any of the UidRange in the collection contains the specified uid * * @param ranges The collection of UidRange to check * @param uid the uid in question * @return {@code true} if the uid is contained within the ranges, {@code false} otherwise * * @see UidRange#contains(int) */ public static boolean containsUid(Collection<UidRange> ranges, int uid) { if (ranges == null) return false; for (UidRange range : ranges) { if (range.contains(uid)) { return true; } } return false; } } services/core/java/com/android/server/ConnectivityService.java +107 −6 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import static android.system.OsConstants.IPPROTO_UDP; import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.BroadcastOptions; import android.app.NotificationManager; Loading Loading @@ -276,7 +277,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private Tethering mTethering; private final PermissionMonitor mPermissionMonitor; @VisibleForTesting protected final PermissionMonitor mPermissionMonitor; private KeyStore mKeyStore; Loading Loading @@ -829,13 +831,13 @@ public class ConnectivityService extends IConnectivityManager.Stub public ConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager) { this(context, netManager, statsService, policyManager, getDnsResolver(), new IpConnectivityLog()); getDnsResolver(), new IpConnectivityLog(), NetdService.getInstance()); } @VisibleForTesting protected ConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager, IDnsResolver dnsresolver, IpConnectivityLog logger) { IDnsResolver dnsresolver, IpConnectivityLog logger, INetd netd) { if (DBG) log("ConnectivityService starting up"); mSystemProperties = getSystemProperties(); Loading Loading @@ -875,7 +877,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mDnsResolver = checkNotNull(dnsresolver, "missing IDnsResolver"); mProxyTracker = makeProxyTracker(); mNetd = NetdService.getInstance(); mNetd = netd; mKeyStore = KeyStore.getInstance(); mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); Loading Loading @@ -961,7 +963,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mTethering = makeTethering(); mPermissionMonitor = new PermissionMonitor(mContext, mNMS, mNetd); mPermissionMonitor = new PermissionMonitor(mContext, mNetd); // Set up the listener for user state for creating user VPNs. // Should run on mHandler to avoid any races. Loading Loading @@ -2441,6 +2443,13 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println("NetworkStackClient logs:"); pw.increaseIndent(); NetworkStackClient.getInstance().dump(pw); pw.decreaseIndent(); pw.println(); pw.println("Permission Monitor:"); pw.increaseIndent(); mPermissionMonitor.dump(pw); pw.decreaseIndent(); } private void dumpNetworks(IndentingPrintWriter pw) { Loading Loading @@ -5458,6 +5467,11 @@ public class ConnectivityService extends IConnectivityManager.Stub networkAgent.clatd.fixupLinkProperties(oldLp, newLp); updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities); // update filtering rules, need to happen after the interface update so netd knows about the // new interface (the interface name -> index map becomes initialized) updateVpnFiltering(newLp, oldLp, networkAgent); updateMtu(newLp, oldLp); // TODO - figure out what to do for clat // for (LinkProperties lp : newLp.getStackedLinks()) { Loading Loading @@ -5623,6 +5637,37 @@ public class ConnectivityService extends IConnectivityManager.Stub } } private void updateVpnFiltering(LinkProperties newLp, LinkProperties oldLp, NetworkAgentInfo nai) { final String oldIface = oldLp != null ? oldLp.getInterfaceName() : null; final String newIface = newLp != null ? newLp.getInterfaceName() : null; final boolean wasFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, oldLp); final boolean needsFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, newLp); if (!wasFiltering && !needsFiltering) { // Nothing to do. return; } if (Objects.equals(oldIface, newIface) && (wasFiltering == needsFiltering)) { // Nothing changed. return; } final Set<UidRange> ranges = nai.networkCapabilities.getUids(); final int vpnAppUid = nai.networkCapabilities.getEstablishingVpnAppUid(); // TODO: this create a window of opportunity for apps to receive traffic between the time // when the old rules are removed and the time when new rules are added. To fix this, // make eBPF support two whitelisted interfaces so here new rules can be added before the // old rules are being removed. if (wasFiltering) { mPermissionMonitor.onVpnUidRangesRemoved(oldIface, ranges, vpnAppUid); } if (needsFiltering) { mPermissionMonitor.onVpnUidRangesAdded(newIface, ranges, vpnAppUid); } } private int getNetworkPermission(NetworkCapabilities nc) { if (!nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) { return INetd.PERMISSION_SYSTEM; Loading Loading @@ -5765,6 +5810,34 @@ public class ConnectivityService extends IConnectivityManager.Stub } } /** * Returns whether VPN isolation (ingress interface filtering) should be applied on the given * network. * * Ingress interface filtering enforces that all apps under the given network can only receive * packets from the network's interface (and loopback). This is important for VPNs because * apps that cannot bypass a fully-routed VPN shouldn't be able to receive packets from any * non-VPN interfaces. * * As a result, this method should return true iff * 1. the network is an app VPN (not legacy VPN) * 2. the VPN does not allow bypass * 3. the VPN is fully-routed * 4. the VPN interface is non-null * * @See INetd#firewallAddUidInterfaceRules * @See INetd#firewallRemoveUidInterfaceRules */ private boolean requiresVpnIsolation(@NonNull NetworkAgentInfo nai, NetworkCapabilities nc, LinkProperties lp) { if (nc == null || lp == null) return false; return nai.isVPN() && !nai.networkMisc.allowBypass && nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID && lp.getInterfaceName() != null && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute()); } private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc, NetworkCapabilities newNc) { Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUids(); Loading @@ -5777,6 +5850,12 @@ public class ConnectivityService extends IConnectivityManager.Stub newRanges.removeAll(prevRangesCopy); try { // When updating the VPN uid routing rules, add the new range first then remove the old // range. If old range were removed first, there would be a window between the old // range being removed and the new range being added, during which UIDs contained // in both ranges are not subject to any VPN routing rules. Adding new range before // removing old range works because, unlike the filtering rules below, it's possible to // add duplicate UID routing rules. if (!newRanges.isEmpty()) { final UidRange[] addedRangesArray = new UidRange[newRanges.size()]; newRanges.toArray(addedRangesArray); Loading @@ -5787,9 +5866,31 @@ public class ConnectivityService extends IConnectivityManager.Stub prevRanges.toArray(removedRangesArray); mNMS.removeVpnUidRanges(nai.network.netId, removedRangesArray); } final boolean wasFiltering = requiresVpnIsolation(nai, prevNc, nai.linkProperties); final boolean shouldFilter = requiresVpnIsolation(nai, newNc, nai.linkProperties); final String iface = nai.linkProperties.getInterfaceName(); // For VPN uid interface filtering, old ranges need to be removed before new ranges can // be added, due to the range being expanded and stored as invidiual UIDs. For example // the UIDs might be updated from [0, 99999] to ([0, 10012], [10014, 99999]) which means // prevRanges = [0, 99999] while newRanges = [0, 10012], [10014, 99999]. If prevRanges // were added first and then newRanges got removed later, there would be only one uid // 10013 left. A consequence of removing old ranges before adding new ranges is that // there is now a window of opportunity when the UIDs are not subject to any filtering. // Note that this is in contrast with the (more robust) update of VPN routing rules // above, where the addition of new ranges happens before the removal of old ranges. // TODO Fix this window by computing an accurate diff on Set<UidRange>, so the old range // to be removed will never overlap with the new range to be added. if (wasFiltering && !prevRanges.isEmpty()) { mPermissionMonitor.onVpnUidRangesRemoved(iface, prevRanges, prevNc.getEstablishingVpnAppUid()); } if (shouldFilter && !newRanges.isEmpty()) { mPermissionMonitor.onVpnUidRangesAdded(iface, newRanges, newNc.getEstablishingVpnAppUid()); } } catch (Exception e) { // Never crash! loge("Exception in updateUids: " + e); loge("Exception in updateUids: ", e); } } Loading services/core/java/com/android/server/connectivity/PermissionMonitor.java +195 −17 Original line number Diff line number Diff line Loading @@ -37,22 +37,27 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.net.INetd; import android.net.UidRange; import android.os.Build; import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; import android.os.UserManager; 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; Loading @@ -60,6 +65,7 @@ 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. Loading @@ -73,18 +79,29 @@ public class PermissionMonitor { 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 mNMS; 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<>(); // 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) { Loading Loading @@ -118,12 +135,10 @@ public class PermissionMonitor { } } public PermissionMonitor(Context context, INetworkManagementService nms, INetd netdService) { mContext = context; public PermissionMonitor(Context context, INetd netd) { mPackageManager = context.getPackageManager(); mUserManager = UserManager.get(context); mNMS = nms; mNetd = netdService; 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 Loading Loading @@ -151,6 +166,7 @@ public class PermissionMonitor { if (uid < 0) { continue; } mAllApps.add(UserHandle.getAppId(uid)); boolean isNetwork = hasNetworkPermission(app); boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app); Loading Loading @@ -270,10 +286,11 @@ public class PermissionMonitor { } } 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; } Loading @@ -289,11 +306,11 @@ public class PermissionMonitor { } try { if (add) { mNMS.setPermission("NETWORK", toIntArray(network)); mNMS.setPermission("SYSTEM", toIntArray(system)); mNetd.networkSetPermissionForUser(INetd.PERMISSION_NETWORK, toIntArray(network)); mNetd.networkSetPermissionForUser(INetd.PERMISSION_SYSTEM, toIntArray(system)); } else { mNMS.clearPermission(toIntArray(network)); mNMS.clearPermission(toIntArray(system)); mNetd.networkClearPermissionForUser(toIntArray(network)); mNetd.networkClearPermissionForUser(toIntArray(system)); } } catch (RemoteException e) { loge("Exception when updating permissions: " + e); Loading Loading @@ -376,6 +393,19 @@ public class PermissionMonitor { 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)); } /** Loading @@ -386,8 +416,23 @@ public class PermissionMonitor { * @hide */ public synchronized void onPackageRemoved(int uid) { Map<Integer, Boolean> apps = new HashMap<>(); // 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<>(); Boolean permission = null; String[] packages = mPackageManager.getPackagesForUid(uid); if (packages != null && packages.length > 0) { Loading Loading @@ -442,6 +487,121 @@ public class PermissionMonitor { } } /** * 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. Loading Loading @@ -528,6 +688,24 @@ public class PermissionMonitor { } } /** 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); Loading services/core/java/com/android/server/connectivity/Vpn.java +1 −6 Original line number Diff line number Diff line Loading @@ -1604,12 +1604,7 @@ public class Vpn { if (mNetworkInfo.isConnected()) { return !appliesToUid(uid); } else { for (UidRange uidRange : mBlockedUsers) { if (uidRange.contains(uid)) { return true; } } return false; return UidRange.containsUid(mBlockedUsers, uid); } } Loading Loading
core/java/android/net/NetworkCapabilities.java +5 −0 Original line number Diff line number Diff line Loading @@ -822,6 +822,11 @@ public final class NetworkCapabilities implements Parcelable { mEstablishingVpnAppUid = uid; } /** @hide */ public int getEstablishingVpnAppUid() { return mEstablishingVpnAppUid; } /** * Value indicating that link bandwidth is unspecified. * @hide Loading
core/java/android/net/UidRange.java +27 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,8 @@ import static android.os.UserHandle.PER_USER_RANGE; import android.os.Parcel; import android.os.Parcelable; import java.util.Collection; /** * An inclusive range of UIDs. * Loading @@ -42,10 +44,16 @@ public final class UidRange implements Parcelable { return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1); } /** Returns the smallest user Id which is contained in this UidRange */ public int getStartUser() { return start / PER_USER_RANGE; } /** Returns the largest user Id which is contained in this UidRange */ public int getEndUser() { return stop / PER_USER_RANGE; } public boolean contains(int uid) { return start <= uid && uid <= stop; } Loading Loading @@ -117,4 +125,23 @@ public final class UidRange implements Parcelable { return new UidRange[size]; } }; /** * Returns whether any of the UidRange in the collection contains the specified uid * * @param ranges The collection of UidRange to check * @param uid the uid in question * @return {@code true} if the uid is contained within the ranges, {@code false} otherwise * * @see UidRange#contains(int) */ public static boolean containsUid(Collection<UidRange> ranges, int uid) { if (ranges == null) return false; for (UidRange range : ranges) { if (range.contains(uid)) { return true; } } return false; } }
services/core/java/com/android/server/ConnectivityService.java +107 −6 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import static android.system.OsConstants.IPPROTO_UDP; import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.BroadcastOptions; import android.app.NotificationManager; Loading Loading @@ -276,7 +277,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private Tethering mTethering; private final PermissionMonitor mPermissionMonitor; @VisibleForTesting protected final PermissionMonitor mPermissionMonitor; private KeyStore mKeyStore; Loading Loading @@ -829,13 +831,13 @@ public class ConnectivityService extends IConnectivityManager.Stub public ConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager) { this(context, netManager, statsService, policyManager, getDnsResolver(), new IpConnectivityLog()); getDnsResolver(), new IpConnectivityLog(), NetdService.getInstance()); } @VisibleForTesting protected ConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager, IDnsResolver dnsresolver, IpConnectivityLog logger) { IDnsResolver dnsresolver, IpConnectivityLog logger, INetd netd) { if (DBG) log("ConnectivityService starting up"); mSystemProperties = getSystemProperties(); Loading Loading @@ -875,7 +877,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mDnsResolver = checkNotNull(dnsresolver, "missing IDnsResolver"); mProxyTracker = makeProxyTracker(); mNetd = NetdService.getInstance(); mNetd = netd; mKeyStore = KeyStore.getInstance(); mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); Loading Loading @@ -961,7 +963,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mTethering = makeTethering(); mPermissionMonitor = new PermissionMonitor(mContext, mNMS, mNetd); mPermissionMonitor = new PermissionMonitor(mContext, mNetd); // Set up the listener for user state for creating user VPNs. // Should run on mHandler to avoid any races. Loading Loading @@ -2441,6 +2443,13 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println("NetworkStackClient logs:"); pw.increaseIndent(); NetworkStackClient.getInstance().dump(pw); pw.decreaseIndent(); pw.println(); pw.println("Permission Monitor:"); pw.increaseIndent(); mPermissionMonitor.dump(pw); pw.decreaseIndent(); } private void dumpNetworks(IndentingPrintWriter pw) { Loading Loading @@ -5458,6 +5467,11 @@ public class ConnectivityService extends IConnectivityManager.Stub networkAgent.clatd.fixupLinkProperties(oldLp, newLp); updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities); // update filtering rules, need to happen after the interface update so netd knows about the // new interface (the interface name -> index map becomes initialized) updateVpnFiltering(newLp, oldLp, networkAgent); updateMtu(newLp, oldLp); // TODO - figure out what to do for clat // for (LinkProperties lp : newLp.getStackedLinks()) { Loading Loading @@ -5623,6 +5637,37 @@ public class ConnectivityService extends IConnectivityManager.Stub } } private void updateVpnFiltering(LinkProperties newLp, LinkProperties oldLp, NetworkAgentInfo nai) { final String oldIface = oldLp != null ? oldLp.getInterfaceName() : null; final String newIface = newLp != null ? newLp.getInterfaceName() : null; final boolean wasFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, oldLp); final boolean needsFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, newLp); if (!wasFiltering && !needsFiltering) { // Nothing to do. return; } if (Objects.equals(oldIface, newIface) && (wasFiltering == needsFiltering)) { // Nothing changed. return; } final Set<UidRange> ranges = nai.networkCapabilities.getUids(); final int vpnAppUid = nai.networkCapabilities.getEstablishingVpnAppUid(); // TODO: this create a window of opportunity for apps to receive traffic between the time // when the old rules are removed and the time when new rules are added. To fix this, // make eBPF support two whitelisted interfaces so here new rules can be added before the // old rules are being removed. if (wasFiltering) { mPermissionMonitor.onVpnUidRangesRemoved(oldIface, ranges, vpnAppUid); } if (needsFiltering) { mPermissionMonitor.onVpnUidRangesAdded(newIface, ranges, vpnAppUid); } } private int getNetworkPermission(NetworkCapabilities nc) { if (!nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) { return INetd.PERMISSION_SYSTEM; Loading Loading @@ -5765,6 +5810,34 @@ public class ConnectivityService extends IConnectivityManager.Stub } } /** * Returns whether VPN isolation (ingress interface filtering) should be applied on the given * network. * * Ingress interface filtering enforces that all apps under the given network can only receive * packets from the network's interface (and loopback). This is important for VPNs because * apps that cannot bypass a fully-routed VPN shouldn't be able to receive packets from any * non-VPN interfaces. * * As a result, this method should return true iff * 1. the network is an app VPN (not legacy VPN) * 2. the VPN does not allow bypass * 3. the VPN is fully-routed * 4. the VPN interface is non-null * * @See INetd#firewallAddUidInterfaceRules * @See INetd#firewallRemoveUidInterfaceRules */ private boolean requiresVpnIsolation(@NonNull NetworkAgentInfo nai, NetworkCapabilities nc, LinkProperties lp) { if (nc == null || lp == null) return false; return nai.isVPN() && !nai.networkMisc.allowBypass && nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID && lp.getInterfaceName() != null && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute()); } private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc, NetworkCapabilities newNc) { Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUids(); Loading @@ -5777,6 +5850,12 @@ public class ConnectivityService extends IConnectivityManager.Stub newRanges.removeAll(prevRangesCopy); try { // When updating the VPN uid routing rules, add the new range first then remove the old // range. If old range were removed first, there would be a window between the old // range being removed and the new range being added, during which UIDs contained // in both ranges are not subject to any VPN routing rules. Adding new range before // removing old range works because, unlike the filtering rules below, it's possible to // add duplicate UID routing rules. if (!newRanges.isEmpty()) { final UidRange[] addedRangesArray = new UidRange[newRanges.size()]; newRanges.toArray(addedRangesArray); Loading @@ -5787,9 +5866,31 @@ public class ConnectivityService extends IConnectivityManager.Stub prevRanges.toArray(removedRangesArray); mNMS.removeVpnUidRanges(nai.network.netId, removedRangesArray); } final boolean wasFiltering = requiresVpnIsolation(nai, prevNc, nai.linkProperties); final boolean shouldFilter = requiresVpnIsolation(nai, newNc, nai.linkProperties); final String iface = nai.linkProperties.getInterfaceName(); // For VPN uid interface filtering, old ranges need to be removed before new ranges can // be added, due to the range being expanded and stored as invidiual UIDs. For example // the UIDs might be updated from [0, 99999] to ([0, 10012], [10014, 99999]) which means // prevRanges = [0, 99999] while newRanges = [0, 10012], [10014, 99999]. If prevRanges // were added first and then newRanges got removed later, there would be only one uid // 10013 left. A consequence of removing old ranges before adding new ranges is that // there is now a window of opportunity when the UIDs are not subject to any filtering. // Note that this is in contrast with the (more robust) update of VPN routing rules // above, where the addition of new ranges happens before the removal of old ranges. // TODO Fix this window by computing an accurate diff on Set<UidRange>, so the old range // to be removed will never overlap with the new range to be added. if (wasFiltering && !prevRanges.isEmpty()) { mPermissionMonitor.onVpnUidRangesRemoved(iface, prevRanges, prevNc.getEstablishingVpnAppUid()); } if (shouldFilter && !newRanges.isEmpty()) { mPermissionMonitor.onVpnUidRangesAdded(iface, newRanges, newNc.getEstablishingVpnAppUid()); } } catch (Exception e) { // Never crash! loge("Exception in updateUids: " + e); loge("Exception in updateUids: ", e); } } Loading
services/core/java/com/android/server/connectivity/PermissionMonitor.java +195 −17 Original line number Diff line number Diff line Loading @@ -37,22 +37,27 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.net.INetd; import android.net.UidRange; import android.os.Build; import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; import android.os.UserManager; 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; Loading @@ -60,6 +65,7 @@ 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. Loading @@ -73,18 +79,29 @@ public class PermissionMonitor { 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 mNMS; 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<>(); // 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) { Loading Loading @@ -118,12 +135,10 @@ public class PermissionMonitor { } } public PermissionMonitor(Context context, INetworkManagementService nms, INetd netdService) { mContext = context; public PermissionMonitor(Context context, INetd netd) { mPackageManager = context.getPackageManager(); mUserManager = UserManager.get(context); mNMS = nms; mNetd = netdService; 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 Loading Loading @@ -151,6 +166,7 @@ public class PermissionMonitor { if (uid < 0) { continue; } mAllApps.add(UserHandle.getAppId(uid)); boolean isNetwork = hasNetworkPermission(app); boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app); Loading Loading @@ -270,10 +286,11 @@ public class PermissionMonitor { } } 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; } Loading @@ -289,11 +306,11 @@ public class PermissionMonitor { } try { if (add) { mNMS.setPermission("NETWORK", toIntArray(network)); mNMS.setPermission("SYSTEM", toIntArray(system)); mNetd.networkSetPermissionForUser(INetd.PERMISSION_NETWORK, toIntArray(network)); mNetd.networkSetPermissionForUser(INetd.PERMISSION_SYSTEM, toIntArray(system)); } else { mNMS.clearPermission(toIntArray(network)); mNMS.clearPermission(toIntArray(system)); mNetd.networkClearPermissionForUser(toIntArray(network)); mNetd.networkClearPermissionForUser(toIntArray(system)); } } catch (RemoteException e) { loge("Exception when updating permissions: " + e); Loading Loading @@ -376,6 +393,19 @@ public class PermissionMonitor { 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)); } /** Loading @@ -386,8 +416,23 @@ public class PermissionMonitor { * @hide */ public synchronized void onPackageRemoved(int uid) { Map<Integer, Boolean> apps = new HashMap<>(); // 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<>(); Boolean permission = null; String[] packages = mPackageManager.getPackagesForUid(uid); if (packages != null && packages.length > 0) { Loading Loading @@ -442,6 +487,121 @@ public class PermissionMonitor { } } /** * 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. Loading Loading @@ -528,6 +688,24 @@ public class PermissionMonitor { } } /** 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); Loading
services/core/java/com/android/server/connectivity/Vpn.java +1 −6 Original line number Diff line number Diff line Loading @@ -1604,12 +1604,7 @@ public class Vpn { if (mNetworkInfo.isConnected()) { return !appliesToUid(uid); } else { for (UidRange uidRange : mBlockedUsers) { if (uidRange.contains(uid)) { return true; } } return false; return UidRange.containsUid(mBlockedUsers, uid); } } Loading