From a04932bafbbf7d99efd18276152cc2c9c9b2073e Mon Sep 17 00:00:00 2001 From: Danesh M Date: Mon, 29 Feb 2016 10:02:34 -0800 Subject: [PATCH] fw/b: Squash of app fw restriction commits Author: Danesh M 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 Date: Wed Oct 31 02:32:03 2018 +0000 NetworkManagement : Add ability to restrict app vpn usage Change-Id: Ia6bd0894f3298fe6fb5cca343cbfe025e3b88ee9 ---- Author: Sam Mortimer 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 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 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 --- .../android/net/NetworkPolicyManager.java | 6 + .../android/os/INetworkManagementService.aidl | 5 + .../server/NetworkManagementService.java | 202 ++++++++++++++++++ .../net/NetworkPolicyManagerService.java | 14 ++ 4 files changed, 227 insertions(+) diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 1922b6df2e7f3..a3ba180ce46e7 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -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). diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 0cce19222d277..8e6fc6e153ceb 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -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); } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 0ddfa1c16a0a6..f40f64d06b15f 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -36,6 +36,7 @@ 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.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 @@ private static class IdleTimerParams { 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 mNetworkCapabilitiesMap = new HashMap<>(); + /** * Constructs a new NetworkManagementService instance * @@ -293,6 +346,65 @@ public static NetworkManagementService create(Context context) throws Interrupte } 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 @@ private static UidRangeParcel[] toStableParcels(UidRange[] ranges) { 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 { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index fa0a3174d265c..385613ebb6c4c 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -60,6 +60,9 @@ 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; @@ -4322,6 +4325,17 @@ private void updateRulesForDataUsageRestrictionsULInner(int uid) { 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;