Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 635cfd70 authored by Lorenzo Colitti's avatar Lorenzo Colitti Committed by android-build-merger
Browse files

Merge changes I4f181789,Ia8a0c99b am: 6efdc4a6

am: b5a7f346

Change-Id: Ib59e211d4329f885108de9ea0a74669ffb144e17
parents af6255e2 b5a7f346
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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
+27 −0
Original line number Diff line number Diff line
@@ -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.
 *
@@ -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;
    }
@@ -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;
    }
}
+107 −6
Original line number Diff line number Diff line
@@ -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;
@@ -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;

@@ -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();
@@ -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);

@@ -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.
@@ -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) {
@@ -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()) {
@@ -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;
@@ -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();
@@ -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);
@@ -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);
        }
    }

+195 −17
Original line number Diff line number Diff line
@@ -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;
@@ -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.
@@ -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) {
@@ -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
@@ -151,6 +166,7 @@ public class PermissionMonitor {
            if (uid < 0) {
                continue;
            }
            mAllApps.add(UserHandle.getAppId(uid));

            boolean isNetwork = hasNetworkPermission(app);
            boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
@@ -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;
    }
@@ -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);
@@ -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));
    }

    /**
@@ -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) {
@@ -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.
@@ -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);
+1 −6
Original line number Diff line number Diff line
@@ -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