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

Commit b03c89de authored by Sam Mortimer's avatar Sam Mortimer Committed by Bruno Martins
Browse files

fw/b: Add support for per app network isolation

* Add support for blocking all network access with
  per uid policy (exposed in wifi/data settings).

* When an app is blocked, two things happen:
  ** Add the uid to a new netd firewall chain fw_isolation
     which blocks all network access.
  ** Generate onLost callbacks for ConnectivityManager requests.
     Move the app network requests to a DetachedNetworks map
     and remove them from the normal ConnectivityService
     machinery to ensure no further callbacks are generated.

* When an app is unblocked, perform the reverse of the steps above.
  This includes reattaching the app network requests and triggering
  onAvailable() callbacks (and others) as though the networks have
  just come back up.

* "Isolation" because the terms blocking and blacklisting
  are used all over the place already for dozing, powersave
  and temporary whitelist rules.  So be distinct to try
  to make the code more readable.

This includes bellow fix:

Author: Oliver Scott <olivercscott@gmail.com>
Date:   Wed Dec 2 13:38:38 2020 -0500

    NetworkPolicyManagerService: Fix network isolation for secondary users

    * NetworkManager setFirewallUidRule checks that the caller is system uid

    * Public service entry points are already protected with
      MANAGE_NETWORK_POLICY permission so simply clear calling identity
      around NetworkPolicyManagerService setUidFirewallRule() call to
      resolve crash for secondary users during settings change.

    Change-Id: Id598264c965aafade8e79b9eeca608711ac49028

Change-Id: Id36308bdb8279879ac456b94704007a392b71b0e
parent 78efa2b7
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -79,6 +79,8 @@ public class NetworkPolicyManager {
    public static final int POLICY_REJECT_VPN = 0x20000;
    /** Reject network usage on wifi network */
    public static final int POLICY_REJECT_WIFI = 0x8000;
    /** Reject network usage on all networks */
    public static final int POLICY_REJECT_ALL = 0x40000;

    /*
     * Rules defining whether an uid has access to a network given its type (metered / non-metered).
@@ -128,7 +130,11 @@ public class NetworkPolicyManager {
     * @hide
     */
    public static final int RULE_REJECT_ALL = 1 << 6;

    /**
     * Reject traffic on all networks at all times
     * @hide
     */
    public static final int RULE_REJECT_ISOLATED = 1 << 7;
    /**
     * Mask used to get the {@code RULE_xxx_METERED} rules
     * @hide
@@ -150,6 +156,8 @@ public class NetworkPolicyManager {
    public static final String FIREWALL_CHAIN_NAME_STANDBY = "standby";
    /** @hide */
    public static final String FIREWALL_CHAIN_NAME_POWERSAVE = "powersave";
    /** @hide */
    public static final String FIREWALL_CHAIN_NAME_ISOLATED = "isolated";

    private static final boolean ALLOW_PLATFORM_APP_POLICY = true;

+177 −0
Original line number Diff line number Diff line
@@ -175,6 +175,7 @@ import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.Xml;

@@ -667,6 +668,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
    final Map<IBinder, ConnectivityDiagnosticsCallbackInfo> mConnectivityDiagnosticsCallbacks =
            new HashMap<>();

    // Tracking for network isolated uids
    //
    @GuardedBy("mIsolatedUids")
    private final SparseBooleanArray mIsolatedUids = new SparseBooleanArray();
    // Network requests that are still active but will not receive any callbacks
    // owing to the calling uid beging network isolated.  Only accessed on
    // event handler thread so no locking necessary.
    private final HashMap<NetworkRequest, NetworkRequestInfo> mDetachedRequests = new HashMap<>();

    /**
     * Implements support for the legacy "one network per network type" model.
     *
@@ -1337,6 +1347,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
     */
    private boolean isNetworkWithLinkPropertiesBlocked(LinkProperties lp, int uid,
            boolean ignoreBlocked) {
        // Network isolation should be checked first since it overrides ignore blocked status.
        if (isUidIsolated(uid)) {
            return true;
        }
        // Networks aren't blocked when ignoring blocked status
        if (ignoreBlocked) {
            return false;
@@ -1505,6 +1519,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
    @Override
    public NetworkInfo[] getAllNetworkInfo() {
        enforceAccessPermission();
        if (isUidIsolated(Binder.getCallingUid())) {
            // If a uid is network isolated, do not provide any visibility.
            return new NetworkInfo[0];
        }

        final ArrayList<NetworkInfo> result = Lists.newArrayList();
        for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
                networkType++) {
@@ -1530,6 +1549,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
    @Override
    public Network[] getAllNetworks() {
        enforceAccessPermission();
        if (isUidIsolated(Binder.getCallingUid())) {
            // If a uid is network isolated, do not provide any visibility.
            return new Network[0];
        }
        synchronized (mNetworkForNetId) {
            final Network[] result = new Network[mNetworkForNetId.size()];
            for (int i = 0; i < mNetworkForNetId.size(); i++) {
@@ -2001,6 +2024,18 @@ public class ConnectivityService extends IConnectivityManager.Stub
        final int oldRules = mUidRules.get(uid, RULE_NONE);
        if (oldRules == newRules) return;

        final boolean wasIsolated =
                mPolicyManagerInternal.isNetworkingIsolatedByUidRules(oldRules);
        final boolean isIsolated =
                mPolicyManagerInternal.isNetworkingIsolatedByUidRules(newRules);
        if (isIsolated != wasIsolated) {
            if (isIsolated) {
                setUidIsolated(uid);
            } else {
                clearUidIsolated(uid);
            }
        }

        maybeNotifyNetworkBlockedForNewUidRules(uid, newRules);

        if (newRules == RULE_NONE) {
@@ -2010,6 +2045,110 @@ public class ConnectivityService extends IConnectivityManager.Stub
        }
    }

    private void setUidIsolated(int uid) {
        synchronized (mIsolatedUids) {
            mIsolatedUids.put(uid, true);
        }
        detachNetworkRequestsForUid(uid);
    }

    private void clearUidIsolated(int uid) {
        synchronized (mIsolatedUids) {
            mIsolatedUids.delete(uid);
        }
        reattachNetworkRequestsForUid(uid);
    }

    private boolean isUidIsolated(int uid) {
        synchronized (mIsolatedUids) {
            return mIsolatedUids.get(uid);
        }
    }

    private void addDetachedRequest(NetworkRequestInfo nri) {
        mDetachedRequests.put(nri.request, nri);
    }

    // App has died or callback is being unregistered, clean up.
    // Match callback by PendingIntent.
    private boolean removeDetachedNetworkRequests(PendingIntent pendingIntent, int uid) {
        // Use same logic as findExistingNetworkRequestInfo()
        Intent intent = pendingIntent.getIntent();
        for (NetworkRequestInfo nri : mDetachedRequests.values()) {
            PendingIntent existingPendingIntent = nri.mPendingIntent;
            if (existingPendingIntent != null &&
                    existingPendingIntent.getIntent().filterEquals(intent)) {
                return removeDetachedNetworkRequests(nri, uid);
            }
        }
        return false;
    }

    // App has died or callback is being unregistered, clean up.
    // Match callback by NetworkRequest.
    private boolean removeDetachedNetworkRequests(NetworkRequest request, int uid) {
        return removeDetachedNetworkRequests(mDetachedRequests.get(request), uid);
    }

    private boolean removeDetachedNetworkRequests(NetworkRequestInfo nri, int uid) {
        // binderDied() runs as system uid so allow clean up
        if (nri != null && (nri.mUid == uid || uid == Process.SYSTEM_UID)) {
            nri.unlinkDeathRecipient();
            mDetachedRequests.remove(nri.request);
            return true;
        }
        return false;
    }

    private void detachNetworkRequestsForUid(int uid) {
        // Collect network requests for this uid
        final List<NetworkRequestInfo> nriList = new ArrayList<>();
        for (NetworkRequestInfo nri : mNetworkRequests.values()) {
            if (nri != null && nri.mUid == uid) {
                nriList.add(nri);
            }
        }
        for (NetworkRequestInfo nri : nriList) {
            if (VDBG || DDBG) {
                Log.d(TAG, "detaching request " + nri);
            }

            // Trigger onLost callbacks for matching networks
            for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
                if (nai.isSatisfyingRequest(nri.request.requestId)) {
                    if (VDBG || DDBG) {
                        Log.d(TAG, "sending onLost to " + nri + " for " + nai);
                    }
                    callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_LOST, 0);
                }
            }
            // Remove the request from normal ConnectivityService machinery.
            // This prevents receiving any further callbacks.
            handleRemoveNetworkRequest(nri);
            // Go through nri constructor to reinitialize things cleaned up
            // by handleRemoveNetworkRequest().   eg updating per uid
            // reference counts and setting binder linkToDeath.
            mDetachedRequests.put(nri.request, new NetworkRequestInfo(nri));
        }
    }

    private void reattachNetworkRequestsForUid(int uid) {
        // Collect network requests for this uid
        final List<NetworkRequestInfo> nriList = new ArrayList<>();
        for (NetworkRequestInfo nri : mDetachedRequests.values()) {
            if (nri != null && nri.mUid == uid) {
                nriList.add(nri);
            }
        }
        for (NetworkRequestInfo nri : nriList) {
            if (VDBG || DDBG) {
                Log.d(TAG, "reattaching request " + nri);
            }
            mDetachedRequests.remove(nri.request);
            handleRegisterNetworkRequest(nri);
        }
    }

    void handleRestrictBackgroundChanged(boolean restrictBackground) {
        if (mRestrictBackground == restrictBackground) return;

@@ -3447,12 +3586,19 @@ public class ConnectivityService extends IConnectivityManager.Stub
                    + nri.request + " because their intents matched.");
            handleReleaseNetworkRequest(existingRequest.request, getCallingUid(),
                    /* callOnUnavailable */ false);
        } else {
            // Remove anything on the detached list
            removeDetachedNetworkRequests(nri.mPendingIntent, Binder.getCallingUid());
        }
        handleRegisterNetworkRequest(nri);
    }

    private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
        ensureRunningOnConnectivityServiceThread();
        if (isUidIsolated(nri.mUid)) {
            addDetachedRequest(nri);
            return;
        }
        mNetworkRequests.put(nri.request, nri);
        mNetworkRequestInfoLogs.log("REGISTER " + nri);
        if (nri.request.isListen()) {
@@ -3471,6 +3617,10 @@ public class ConnectivityService extends IConnectivityManager.Stub

    private void handleReleaseNetworkRequestWithIntent(PendingIntent pendingIntent,
            int callingUid) {
        if (removeDetachedNetworkRequests(pendingIntent, callingUid)) {
            // Was already released during detach so nothing further to do.
            return;
        }
        NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent);
        if (nri != null) {
            handleReleaseNetworkRequest(nri.request, callingUid, /* callOnUnavailable */ false);
@@ -3561,6 +3711,14 @@ public class ConnectivityService extends IConnectivityManager.Stub

    private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid,
            boolean callOnUnavailable) {
        if (removeDetachedNetworkRequests(request, callingUid)) {
            // Was already released during detach so nothing further to do.
            if (VDBG || DDBG) {
                Log.d(TAG, "removed detached request = " + request + " uid = " + callingUid);
            }
            return;
        }

        final NetworkRequestInfo nri =
                getNriForAppRequest(request, callingUid, "release NetworkRequest");
        if (nri == null) {
@@ -5410,6 +5568,25 @@ public class ConnectivityService extends IConnectivityManager.Stub
            this(r, null);
        }

        // Recreate new from a previously detached request.
        NetworkRequestInfo(NetworkRequestInfo nri) {
            request = nri.request;
            messenger = nri.messenger;
            mBinder = nri.mBinder;
            mPendingIntent = nri.mPendingIntent;
            mPid = nri.mPid;
            mUid = nri.mUid;
            enforceRequestCountLimit();

            if (mBinder != null) {
                try {
                    mBinder.linkToDeath(this, 0);
                } catch (RemoteException e) {
                    binderDied();
                }
            }
        }

        private void enforceRequestCountLimit() {
            synchronized (mUidToNetworkRequestCount) {
                int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1;
+31 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
import static android.Manifest.permission.SHUTDOWN;
import static android.net.INetd.FIREWALL_BLACKLIST;
import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
import static android.net.INetd.FIREWALL_CHAIN_ISOLATED;
import static android.net.INetd.FIREWALL_CHAIN_NONE;
import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
@@ -29,6 +30,7 @@ import static android.net.INetd.FIREWALL_RULE_ALLOW;
import static android.net.INetd.FIREWALL_RULE_DENY;
import static android.net.INetd.FIREWALL_WHITELIST;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_ISOLATED;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
@@ -219,6 +221,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
     */
    @GuardedBy("mRulesLock")
    private SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
    /**
     * Set of UIDs that are to be blocked/allowed by firewall controller.  This set of Ids matches
     * unconditionally at all times.
     */
    @GuardedBy("mRulesLock")
    private SparseIntArray mUidFirewallIsolatedRules = new SparseIntArray();
    /** Set of states for the child firewall chains. True if the chain is active. */
    @GuardedBy("mRulesLock")
    final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@@ -714,9 +722,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
            syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, "standby ");
            syncFirewallChainLocked(FIREWALL_CHAIN_DOZABLE, "dozable ");
            syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave ");
            syncFirewallChainLocked(FIREWALL_CHAIN_ISOLATED, "isolated ");

            final int[] chains =
                    {FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE};
                    {FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE,
                    FIREWALL_CHAIN_ISOLATED};
            for (int chain : chains) {
                if (getFirewallChainState(chain)) {
                    setFirewallChainEnabled(chain, true);
@@ -1908,6 +1918,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
                return FIREWALL_CHAIN_NAME_DOZABLE;
            case FIREWALL_CHAIN_POWERSAVE:
                return FIREWALL_CHAIN_NAME_POWERSAVE;
            case FIREWALL_CHAIN_ISOLATED:
                return FIREWALL_CHAIN_NAME_ISOLATED;
            default:
                throw new IllegalArgumentException("Bad child chain: " + chain);
        }
@@ -1921,6 +1933,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
                return FIREWALL_WHITELIST;
            case FIREWALL_CHAIN_POWERSAVE:
                return FIREWALL_WHITELIST;
            case FIREWALL_CHAIN_ISOLATED:
                return FIREWALL_BLACKLIST;
            default:
                return isFirewallEnabled() ? FIREWALL_WHITELIST : FIREWALL_BLACKLIST;
        }
@@ -1965,6 +1979,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
                    case FIREWALL_CHAIN_POWERSAVE:
                        mNetdService.firewallReplaceUidChain("fw_powersave", true, uids);
                        break;
                    case FIREWALL_CHAIN_ISOLATED:
                        mNetdService.firewallReplaceUidChain("fw_isolated", false, uids);
                        break;
                    case FIREWALL_CHAIN_NONE:
                    default:
                        Slog.d(TAG, "setFirewallUidRules() called on invalid chain: " + chain);
@@ -2049,6 +2066,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
                return mUidFirewallDozableRules;
            case FIREWALL_CHAIN_POWERSAVE:
                return mUidFirewallPowerSaveRules;
            case FIREWALL_CHAIN_ISOLATED:
                return mUidFirewallIsolatedRules;
            case FIREWALL_CHAIN_NONE:
                return mUidFirewallRules;
            default:
@@ -2134,6 +2153,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
            pw.println("UID firewall powersave chain enabled: " +
                    getFirewallChainState(FIREWALL_CHAIN_POWERSAVE));
            dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_POWERSAVE, mUidFirewallPowerSaveRules);

            pw.println("UID firewall isolated chain enabled: " +
                    getFirewallChainState(FIREWALL_CHAIN_ISOLATED));
            dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_ISOLATED, mUidFirewallIsolatedRules);
        }

        synchronized (mIdleTimerLock) {
@@ -2312,6 +2335,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub {

    private boolean isNetworkRestrictedInternal(int uid) {
        synchronized (mRulesLock) {
            if (getFirewallChainState(FIREWALL_CHAIN_ISOLATED)
                    && mUidFirewallIsolatedRules.get(uid) == FIREWALL_RULE_DENY) {
                if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of isolated mode");
                return true;
            }
            if (getFirewallChainState(FIREWALL_CHAIN_STANDBY)
                    && mUidFirewallStandbyRules.get(uid) == FIREWALL_RULE_DENY) {
                if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of app standby mode");
@@ -2397,7 +2425,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub {
                final int[] chains = {
                        FIREWALL_CHAIN_DOZABLE,
                        FIREWALL_CHAIN_STANDBY,
                        FIREWALL_CHAIN_POWERSAVE
                        FIREWALL_CHAIN_POWERSAVE,
                        FIREWALL_CHAIN_ISOLATED
                };
                for (int chain : chains) {
                    setFirewallChainState(chain, false);
+7 −0
Original line number Diff line number Diff line
@@ -16,11 +16,13 @@
package com.android.server.net;

import static android.net.INetd.FIREWALL_CHAIN_DOZABLE;
import static android.net.INetd.FIREWALL_CHAIN_ISOLATED;
import static android.net.INetd.FIREWALL_CHAIN_POWERSAVE;
import static android.net.INetd.FIREWALL_CHAIN_STANDBY;
import static android.net.INetd.FIREWALL_RULE_ALLOW;
import static android.net.INetd.FIREWALL_RULE_DENY;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_ISOLATED;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
@@ -76,6 +78,7 @@ public class NetworkPolicyLogger {
    static final int NTWK_BLOCKED_BG_RESTRICT = 5;
    static final int NTWK_ALLOWED_DEFAULT = 6;
    static final int NTWK_ALLOWED_SYSTEM = 7;
    static final int NTWK_BLOCKED_ISOLATED = 8;

    private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
    private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
@@ -279,6 +282,8 @@ public class NetworkPolicyLogger {
                return "blocked when background is restricted";
            case NTWK_ALLOWED_DEFAULT:
                return "allowed by default";
            case NTWK_BLOCKED_ISOLATED:
                return "blocked by isolation";
            default:
                return String.valueOf(reason);
        }
@@ -339,6 +344,8 @@ public class NetworkPolicyLogger {
                return FIREWALL_CHAIN_NAME_STANDBY;
            case FIREWALL_CHAIN_POWERSAVE:
                return FIREWALL_CHAIN_NAME_POWERSAVE;
            case FIREWALL_CHAIN_ISOLATED:
                return FIREWALL_CHAIN_NAME_ISOLATED;
            default:
                return String.valueOf(chain);
        }
+8 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.net;

import static com.android.server.net.NetworkPolicyManagerService.isNetworkingIsolatedByUidRulesInternal;
import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal;

import android.annotation.NonNull;
@@ -43,6 +44,13 @@ public abstract class NetworkPolicyManagerInternal {
     */
    public abstract boolean isUidRestrictedOnMeteredNetworks(int uid);

    /**
     * @return true if the uid rules provided mean that network access should be blocked.
     */
    public static boolean isNetworkingIsolatedByUidRules(int uidRules) {
        return isNetworkingIsolatedByUidRulesInternal(uidRules);
    };

    /**
     * @return true if networking is blocked on the given interface for the given uid according
     * to current networking policies.
Loading