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

Commit 78efa2b7 authored by Danesh M's avatar Danesh M Committed by Bruno Martins
Browse files

fw/b: Squash of app fw restriction commits

Author: Danesh M <daneshm90@gmail.com>
Date:   Mon Feb 29 10:02:34 2016 -0800

    [2/3] NetworkManagement : Add ability to restrict app data/wifi usage

    CYAN-3976
    CRACKLING-834

    Change-Id: Iaa0483d0ad64511184f0f31d93552a93fbab6dd0

----

Author: Uldiniad <olivercscott@gmail.com>
Date:   Wed Oct 31 02:32:03 2018 +0000

    NetworkManagement : Add ability to restrict app vpn usage

    Change-Id: Ia6bd0894f3298fe6fb5cca343cbfe025e3b88ee9

----

Author: Sam Mortimer <sam@mortimer.me.uk>
Date:   Thu Aug 29 17:12:58 2019 -0700

    fw/b: Use common network restrict apps method

    * These are lineage additions (that originated
      from caf).

    * addrestrictappsondata, addrestrictappsonvpn and
      addrestrictappsonwlan all do a similar thing
      (fw/b passes different interface arguments).

    * Consolidate into addrestrictappsoninterface
      (and removerestrictappsoninterface)

    * Requires corresponding system/netd change.

    Change-Id: I1f7cb568dd0415aaec880cf98ae97032ab555bd1

----

Author: Sam Mortimer <sam@mortimer.me.uk>
Date:   Tue Apr 14 17:47:58 2020 -0400

    fw/b: Prevent double interface restriction remove on interface name change

    * When temporarily removing a restriction owing to interface name
      change, update the boolean state array to match.  Otherwise, we
      get out of sync, follow-on double removes can occur and the system
      server will crash.

    * In addition, it was observed that it is possible to receive a network
      callback for a (VPN) network that has both WIFI and VPN transports set (it
      looked transient rather than persisent but difficult to tell).  So make
      the list of use cases in priority of match order, putting VPN first.

    Change-Id: If484b5a715e0a972769c847ea4549fd84afb3ccf

----

Author: Sam Mortimer <sam@mortimer.me.uk>
Date:   Sun May 03 17:18:00 2020 -0400

    fw/b data restrictions: Don't call getNetworkCapabilities() in the callback

    * Docs say that calling getNetworkCapabilities() from within a network
      callback is racy and not to do it.

    * Refactor to make use of onCapabilitiesChanged() to glean capabilities
      instead.

    Change-Id: If9c4cd7c1bd0594697b0ac98903600ecd583e55b

Change-Id: If925f7f794d09664eac37da9478e443bce7cc496
parent 1f766503
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -73,6 +73,12 @@ public class NetworkPolicyManager {
     * @hide
     */
    public static final int POLICY_ALLOW_METERED_BACKGROUND = 0x4;
    /** Reject network usage on cellular network */
    public static final int POLICY_REJECT_CELLULAR = 0x10000;
    /** Reject network usage on virtual private network */
    public static final int POLICY_REJECT_VPN = 0x20000;
    /** Reject network usage on wifi network */
    public static final int POLICY_REJECT_WIFI = 0x8000;

    /*
     * Rules defining whether an uid has access to a network given its type (metered / non-metered).
+5 −0
Original line number Diff line number Diff line
@@ -389,4 +389,9 @@ interface INetworkManagementService
    void setAllowOnlyVpnForUids(boolean enable, in UidRange[] uidRanges);

    boolean isNetworkRestricted(int uid);

    /**
     * Restrict UID from accessing a network interface
     */
    void restrictAppOnInterface(String key, int uid, boolean restrict);
}
+202 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.STATS_PER_UID;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.TrafficStats.UID_TETHERING;
import static android.system.OsConstants.ENETDOWN;

import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;

@@ -52,8 +53,11 @@ import android.net.InterfaceConfiguration;
import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
import android.net.NetworkRequest;
import android.net.NetworkStack;
import android.net.NetworkStats;
import android.net.NetworkUtils;
@@ -248,6 +252,55 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
            new RemoteCallbackList<>();
    private boolean mNetworkActive;

    /* map keys used by netd to keep per app interface restrictions
     * separate for each use case.
     */
    private static final String RESTRICT_USECASE_CELLULAR = "cellular";
    private static final String RESTRICT_USECASE_VPN  = "vpn";
    private static final String RESTRICT_USECASE_WIFI = "wifi";

    // Helper class for managing per uid interface blacklists.
    private static class RestrictIf {
        // Use case string
        public String useCase;
        // Interface name
        public String ifName;
        // NetworkCapabilities transport type used for this blacklist
        public int transport;
        // Active uid blacklist
        public SparseBooleanArray active;
        // Desired uid blacklist changes
        public SparseBooleanArray pending;

        RestrictIf(String useCase, int transport) {
            this.useCase = useCase;
            this.ifName = null;
            this.transport = transport;
            this.active = new SparseBooleanArray();
            this.pending = new SparseBooleanArray();
        }
    }

    @GuardedBy("mQuotaLock")
    private RestrictIf[] mRestrictIf = {
            // Ordered by match preference (in the event we get a callback with
            // multiple transports).
            new RestrictIf(RESTRICT_USECASE_VPN, NetworkCapabilities.TRANSPORT_VPN),
            new RestrictIf(RESTRICT_USECASE_CELLULAR, NetworkCapabilities.TRANSPORT_CELLULAR),
            new RestrictIf(RESTRICT_USECASE_WIFI, NetworkCapabilities.TRANSPORT_WIFI),
    };

    private RestrictIf getUseCaseRestrictIf(String useCase) {
        for (RestrictIf restrictIf : mRestrictIf) {
            if (restrictIf.useCase.equals(useCase)) {
                return restrictIf;
            }
        }
        throw new IllegalStateException("Unknown interface restriction");
    }

    private final HashMap<Network, NetworkCapabilities> mNetworkCapabilitiesMap = new HashMap<>();

    /**
     * Constructs a new NetworkManagementService instance
     *
@@ -293,6 +346,65 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
    }

    public void systemReady() {
        final ConnectivityManager mConnectivityManager =
                mContext.getSystemService(ConnectivityManager.class);

        final NetworkRequest.Builder builder = new NetworkRequest.Builder()
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
        for (RestrictIf restrictIf : mRestrictIf) {
            builder.addTransportType(restrictIf.transport);
        }
        final NetworkRequest request = builder.build();

        final ConnectivityManager.NetworkCallback mNetworkCallback =
                new ConnectivityManager.NetworkCallback() {
            @Override
            public void onCapabilitiesChanged(Network network,
                    NetworkCapabilities networkCapabilities) {
                mNetworkCapabilitiesMap.put(network, networkCapabilities);
            }

            @Override
            public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
                // Callback ordering in Oreo+ is documented to be:
                // onCapabilitiesChanged, onLinkPropertiesChanged
                // At this point, we should always find the network in our
                // local map but guard anyway.
                NetworkCapabilities nc = mNetworkCapabilitiesMap.get(network);
                if (nc == null) {
                    Slog.e(TAG, "onLinkPropertiesChanged: network was not in map: "
                            + "network=" + network + " linkProperties=" + linkProperties);
                    return;
                }
                RestrictIf matchedRestrictIf = null;
                for (RestrictIf restrictIf : mRestrictIf) {
                    if (nc.hasTransport(restrictIf.transport)) {
                        matchedRestrictIf = restrictIf;
                        break;
                    }
                }
                if (matchedRestrictIf == null) {
                    return;
                }
                final String iface = linkProperties.getInterfaceName();
                if (TextUtils.isEmpty(iface)) {
                    return;
                }
                // The post below requires final arguments so
                final RestrictIf finalRestrictIf = matchedRestrictIf;
                // Exit the callback ASAP and move further work onto daemon thread
                mDaemonHandler.post(() ->
                        updateAppOnInterfaceCallback(finalRestrictIf, iface));
            }

            @Override
            public void onLost(Network network) {
                mNetworkCapabilitiesMap.remove(network);
            }
        };

        mConnectivityManager.registerNetworkCallback(request, mNetworkCallback);

        if (DBG) {
            final long start = System.currentTimeMillis();
            prepareNativeDaemon();
@@ -1405,6 +1517,96 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
        return stableRanges;
    }

    private void updateAppOnInterfaceCallback(RestrictIf restrictIf, String newIface) {
        synchronized (mQuotaLock) {
            if (TextUtils.isEmpty(restrictIf.ifName)) {
                restrictIf.ifName = newIface;
            } else if (!restrictIf.ifName.equals(newIface)) { // interface name has changed
                // Prevent new incoming requests colliding with an update in progress
                for (int i = 0; i < restrictIf.active.size(); i++) {
                    final int uid = restrictIf.active.keyAt(i);
                    final boolean restrict = restrictIf.active.valueAt(i);
                    // Only remove/readd if a restriction is currently in place
                    if (!restrict) {
                        continue;
                    }
                    setAppOnInterfaceLocked(restrictIf.useCase, restrictIf.ifName, uid, false);
                    restrictIf.active.setValueAt(i, false);
                    // Use pending list to queue re-add.
                    // (Prefer keeping existing pending status if it exists.)
                    if (restrictIf.pending.indexOfKey(uid) < 0) {
                        restrictIf.pending.put(uid, true);
                    }
                }
                restrictIf.ifName = newIface;
            }
            processPendingAppOnInterfaceLocked(restrictIf);
        }
    }

    @Override
    public void restrictAppOnInterface(String useCase, int uid, boolean restrict) {
        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
        synchronized (mQuotaLock) {
            restrictAppOnInterfaceLocked(getUseCaseRestrictIf(useCase), uid, restrict);
        }
    }

    private void restrictAppOnInterfaceLocked(RestrictIf restrictIf, int uid, boolean restrict) {
        if (TextUtils.isEmpty(restrictIf.ifName)) {
            // We don't have an interface name yet so queue
            // the request for when it comes up
            restrictIf.pending.put(uid, restrict);
            return;
        }

        boolean oldValue = restrictIf.active.get(uid, false);
        if (oldValue == restrict) {
            return;
        }

        if (setAppOnInterfaceLocked(restrictIf.useCase, restrictIf.ifName, uid, restrict)) {
            restrictIf.active.put(uid, restrict);
        } else {
            // Perhaps the interface was down, queue to retry after receipt
            // of the next network callback for this network.
            restrictIf.pending.put(uid, true);
        }
    }

    private boolean setAppOnInterfaceLocked(String useCase, String ifName, int uid,
            boolean restrict) {
        boolean ok = true;
        try {
            if (restrict) {
                mNetdService.bandwidthAddRestrictAppOnInterface(useCase, ifName, uid);
            } else {
                mNetdService.bandwidthRemoveRestrictAppOnInterface(useCase, ifName, uid);
            }
        } catch (RemoteException e) {
            throw new IllegalStateException(e);
        } catch (ServiceSpecificException e) {
            // ENETDOWN is returned when the interface cannot be resolved to an index.
            // (and is only returned by bandwidthAdd... call)
            if (e.errorCode == ENETDOWN) {
                ok = false;
            } else {
                throw new IllegalStateException(e);
            }
        }
        return ok;
    }

    private void processPendingAppOnInterfaceLocked(RestrictIf restrictIf) {
        // Work on a copy of the pending list since failed add requests
        // get put back on.
        SparseBooleanArray pendingList = restrictIf.pending.clone();
        restrictIf.pending = new SparseBooleanArray();
        for (int i = 0; i < pendingList.size(); i++) {
            restrictAppOnInterfaceLocked(restrictIf, pendingList.keyAt(i), pendingList.valueAt(i));
        }
    }

    @Override
    public void setAllowOnlyVpnForUids(boolean add, UidRange[] uidRanges)
            throws ServiceSpecificException {
+14 −0
Original line number Diff line number Diff line
@@ -60,6 +60,9 @@ import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS;
import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.POLICY_REJECT_CELLULAR;
import static android.net.NetworkPolicyManager.POLICY_REJECT_VPN;
import static android.net.NetworkPolicyManager.POLICY_REJECT_WIFI;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
import static android.net.NetworkPolicyManager.RULE_NONE;
@@ -4300,6 +4303,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        final int oldRule = oldUidRules & MASK_METERED_NETWORKS;
        int newRule = RULE_NONE;

        try {
            mNetworkManager.restrictAppOnInterface("cellular", uid,
                    (uidPolicy & POLICY_REJECT_CELLULAR) != 0);
            mNetworkManager.restrictAppOnInterface("vpn", uid,
                    (uidPolicy & POLICY_REJECT_VPN) != 0);
            mNetworkManager.restrictAppOnInterface("wifi", uid,
                    (uidPolicy & POLICY_REJECT_WIFI) != 0);
        } catch (RemoteException e) {
            // ignored; service lives in system_server
        }

        // First step: define the new rule based on user restrictions and foreground state.
        if (isRestrictedByAdmin) {
            newRule = RULE_REJECT_METERED;