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

Commit c3736bc1 authored by Robin Lee's avatar Robin Lee
Browse files

Use Vpn rules (not firewall) for always-on VPN

Firewall rules don't work on 464xlat because they were created under
an assumption that there's only one address for the server and it's
ipv4, which doesn't go so well when we're on an ipv6-only network.

Bug: 33159037
Test: runtest -x net/java/com/android/server/connectivity/VpnTest.java
Change-Id: Id331526367fe13838874961da194b07bd50d4c97
parent b8c2a2b8
Loading
Loading
Loading
Loading
+3 −11
Original line number Original line Diff line number Diff line
@@ -3702,17 +3702,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
            existing.shutdown();
            existing.shutdown();
        }
        }


        try {
        if (tracker != null) {
        if (tracker != null) {
                mNetd.setFirewallEnabled(true);
                mNetd.setFirewallInterfaceRule("lo", true);
            mLockdownTracker = tracker;
            mLockdownTracker = tracker;
            mLockdownTracker.init();
            mLockdownTracker.init();
            } else {
                mNetd.setFirewallEnabled(false);
            }
        } catch (RemoteException e) {
            // ignored; NMS lives inside system_server
        }
        }
    }
    }


+1 −2
Original line number Original line Diff line number Diff line
@@ -95,7 +95,6 @@ import com.android.internal.util.HexDump;
import com.android.internal.util.Preconditions;
import com.android.internal.util.Preconditions;
import com.android.server.NativeDaemonConnector.Command;
import com.android.server.NativeDaemonConnector.Command;
import com.android.server.NativeDaemonConnector.SensitiveArg;
import com.android.server.NativeDaemonConnector.SensitiveArg;
import com.android.server.net.LockdownVpnTracker;
import com.google.android.collect.Maps;
import com.google.android.collect.Maps;


import java.io.BufferedReader;
import java.io.BufferedReader;
@@ -628,7 +627,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
                }
                }
            }
            }


            setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
            setFirewallEnabled(mFirewallEnabled);


            syncFirewallChainLocked(FIREWALL_CHAIN_NONE, mUidFirewallRules, "");
            syncFirewallChainLocked(FIREWALL_CHAIN_NONE, mUidFirewallRules, "");
            syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, mUidFirewallStandbyRules, "standby ");
            syncFirewallChainLocked(FIREWALL_CHAIN_STANDBY, mUidFirewallStandbyRules, "standby ");
+47 −15
Original line number Original line Diff line number Diff line
@@ -263,6 +263,30 @@ public class Vpn {
        updateAlwaysOnNotification(detailedState);
        updateAlwaysOnNotification(detailedState);
    }
    }


    /**
     * Chooses whether to force all connections to go though VPN.
     *
     * Used to enable/disable legacy VPN lockdown.
     *
     * This uses the same ip rule mechanism as {@link #setAlwaysOnPackage(String, boolean)};
     * previous settings from calling that function will be replaced and saved with the
     * always-on state.
     *
     * @param lockdown whether to prevent all traffic outside of a VPN.
     */
    public synchronized void setLockdown(boolean lockdown) {
        enforceControlPermissionOrInternalCaller();

        setVpnForcedLocked(lockdown);
        mLockdown = lockdown;

        // Update app lockdown setting if it changed. Legacy VPN lockdown status is controlled by
        // LockdownVpnTracker.isEnabled() which keeps track of its own state.
        if (mAlwaysOn) {
            saveAlwaysOnPackage();
        }
    }

    /**
    /**
     * Configures an always-on VPN connection through a specific application.
     * Configures an always-on VPN connection through a specific application.
     * This connection is automatically granted and persisted after a reboot.
     * This connection is automatically granted and persisted after a reboot.
@@ -376,7 +400,7 @@ public class Vpn {
            mSystemServices.settingsSecurePutStringForUser(Settings.Secure.ALWAYS_ON_VPN_APP,
            mSystemServices.settingsSecurePutStringForUser(Settings.Secure.ALWAYS_ON_VPN_APP,
                    getAlwaysOnPackage(), mUserHandle);
                    getAlwaysOnPackage(), mUserHandle);
            mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
            mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
                    (mLockdown ? 1 : 0), mUserHandle);
                    (mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
        } finally {
        } finally {
            Binder.restoreCallingIdentity(token);
            Binder.restoreCallingIdentity(token);
        }
        }
@@ -557,6 +581,7 @@ public class Vpn {
            mConfig = null;
            mConfig = null;


            updateState(DetailedState.IDLE, "prepare");
            updateState(DetailedState.IDLE, "prepare");
            setVpnForcedLocked(mLockdown);
        } finally {
        } finally {
            Binder.restoreCallingIdentity(token);
            Binder.restoreCallingIdentity(token);
        }
        }
@@ -1003,12 +1028,10 @@ public class Vpn {
                        Log.wtf(TAG, "Failed to add restricted user to owner", e);
                        Log.wtf(TAG, "Failed to add restricted user to owner", e);
                    }
                    }
                }
                }
                if (mAlwaysOn) {
                setVpnForcedLocked(mLockdown);
                setVpnForcedLocked(mLockdown);
            }
            }
        }
        }
    }
    }
    }


    public void onUserRemoved(int userHandle) {
    public void onUserRemoved(int userHandle) {
        // clean up if restricted
        // clean up if restricted
@@ -1022,19 +1045,17 @@ public class Vpn {
                        Log.wtf(TAG, "Failed to remove restricted user to owner", e);
                        Log.wtf(TAG, "Failed to remove restricted user to owner", e);
                    }
                    }
                }
                }
                if (mAlwaysOn) {
                setVpnForcedLocked(mLockdown);
                setVpnForcedLocked(mLockdown);
            }
            }
        }
        }
    }
    }
    }


    /**
    /**
     * Called when the user associated with this VPN has just been stopped.
     * Called when the user associated with this VPN has just been stopped.
     */
     */
    public synchronized void onUserStopped() {
    public synchronized void onUserStopped() {
        // Switch off networking lockdown (if it was enabled)
        // Switch off networking lockdown (if it was enabled)
        setVpnForcedLocked(false);
        setLockdown(false);
        mAlwaysOn = false;
        mAlwaysOn = false;


        unregisterPackageChangeReceiverLocked();
        unregisterPackageChangeReceiverLocked();
@@ -1061,20 +1082,31 @@ public class Vpn {
     */
     */
    @GuardedBy("this")
    @GuardedBy("this")
    private void setVpnForcedLocked(boolean enforce) {
    private void setVpnForcedLocked(boolean enforce) {
        final List<String> exemptedPackages =
                isNullOrLegacyVpn(mPackage) ? null : Collections.singletonList(mPackage);
        setVpnForcedWithExemptionsLocked(enforce, exemptedPackages);
    }

    /**
     * @see #setVpnForcedLocked
     */
    @GuardedBy("this")
    private void setVpnForcedWithExemptionsLocked(boolean enforce,
            @Nullable List<String> exemptedPackages) {
        final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers);
        final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers);

        Set<UidRange> addedRanges = Collections.emptySet();
        if (enforce) {
        if (enforce) {
            final Set<UidRange> addedRanges = createUserAndRestrictedProfilesRanges(mUserHandle,
            addedRanges = createUserAndRestrictedProfilesRanges(mUserHandle,
                    /* allowedApplications */ null,
                    /* allowedApplications */ null,
                    /* disallowedApplications */ Collections.singletonList(mPackage));
                    /* disallowedApplications */ exemptedPackages);


            removedRanges.removeAll(addedRanges);
            removedRanges.removeAll(addedRanges);
            addedRanges.removeAll(mBlockedUsers);
            addedRanges.removeAll(mBlockedUsers);
        }


        setAllowOnlyVpnForUids(false, removedRanges);
        setAllowOnlyVpnForUids(false, removedRanges);
        setAllowOnlyVpnForUids(true, addedRanges);
        setAllowOnlyVpnForUids(true, addedRanges);
        } else {
            setAllowOnlyVpnForUids(false, removedRanges);
        }
    }
    }


    /**
    /**
+2 −65
Original line number Original line Diff line number Diff line
@@ -139,7 +139,6 @@ public class LockdownVpnTracker {
                " " + mAcceptedEgressIface + "->" + egressIface);
                " " + mAcceptedEgressIface + "->" + egressIface);


        if (egressDisconnected || egressChanged) {
        if (egressDisconnected || egressChanged) {
            clearSourceRulesLocked();
            mAcceptedEgressIface = null;
            mAcceptedEgressIface = null;
            mVpn.stopLegacyVpnPrivileged();
            mVpn.stopLegacyVpnPrivileged();
        }
        }
@@ -191,24 +190,6 @@ public class LockdownVpnTracker {
            EventLogTags.writeLockdownVpnConnected(egressType);
            EventLogTags.writeLockdownVpnConnected(egressType);
            showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
            showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);


            try {
                clearSourceRulesLocked();

                mNetService.setFirewallInterfaceRule(iface, true);
                for (LinkAddress addr : sourceAddrs) {
                    setFirewallEgressSourceRule(addr, true);
                }

                mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_ALLOW);
                mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, Os.getuid(), FIREWALL_RULE_ALLOW);

                mErrorCount = 0;
                mAcceptedIface = iface;
                mAcceptedSourceAddr = sourceAddrs;
            } catch (RemoteException e) {
                throw new RuntimeException("Problem setting firewall rules", e);
            }

            final NetworkInfo clone = new NetworkInfo(egressInfo);
            final NetworkInfo clone = new NetworkInfo(egressInfo);
            augmentNetworkInfo(clone);
            augmentNetworkInfo(clone);
            mConnService.sendConnectedBroadcast(clone);
            mConnService.sendConnectedBroadcast(clone);
@@ -225,19 +206,11 @@ public class LockdownVpnTracker {
        Slog.d(TAG, "initLocked()");
        Slog.d(TAG, "initLocked()");


        mVpn.setEnableTeardown(false);
        mVpn.setEnableTeardown(false);
        mVpn.setLockdown(true);


        final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
        final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
        mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
        mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);


        try {
            // TODO: support non-standard port numbers
            mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
            mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
            mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
        } catch (RemoteException e) {
            throw new RuntimeException("Problem setting firewall rules", e);
        }

        handleStateChangedLocked();
        handleStateChangedLocked();
    }
    }


@@ -254,14 +227,7 @@ public class LockdownVpnTracker {
        mErrorCount = 0;
        mErrorCount = 0;


        mVpn.stopLegacyVpnPrivileged();
        mVpn.stopLegacyVpnPrivileged();
        try {
        mVpn.setLockdown(false);
            mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
            mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
            mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
        } catch (RemoteException e) {
            throw new RuntimeException("Problem setting firewall rules", e);
        }
        clearSourceRulesLocked();
        hideNotification();
        hideNotification();


        mContext.unregisterReceiver(mResetReceiver);
        mContext.unregisterReceiver(mResetReceiver);
@@ -278,35 +244,6 @@ public class LockdownVpnTracker {
        }
        }
    }
    }


    private void clearSourceRulesLocked() {
        try {
            if (mAcceptedIface != null) {
                mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
                mAcceptedIface = null;
            }
            if (mAcceptedSourceAddr != null) {
                for (LinkAddress addr : mAcceptedSourceAddr) {
                    setFirewallEgressSourceRule(addr, false);
                }

                mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE, ROOT_UID, FIREWALL_RULE_DEFAULT);
                mNetService.setFirewallUidRule(FIREWALL_CHAIN_NONE,Os.getuid(), FIREWALL_RULE_DEFAULT);

                mAcceptedSourceAddr = null;
            }
        } catch (RemoteException e) {
            throw new RuntimeException("Problem setting firewall rules", e);
        }
    }

    private void setFirewallEgressSourceRule(
            LinkAddress address, boolean allow) throws RemoteException {
        // Our source address based firewall rules must only cover our own source address, not the
        // whole subnet
        final String addrString = address.getAddress().getHostAddress();
        mNetService.setFirewallEgressSourceRule(addrString, allow);
    }

    public void onNetworkInfoChanged() {
    public void onNetworkInfoChanged() {
        synchronized (mStateLock) {
        synchronized (mStateLock) {
            handleStateChangedLocked();
            handleStateChangedLocked();
+57 −0
Original line number Original line Diff line number Diff line
@@ -27,6 +27,7 @@ import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo;
@@ -42,6 +43,8 @@ import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArrayMap;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.ArraySet;


import com.android.internal.net.VpnConfig;

import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Arrays;
import java.util.Map;
import java.util.Map;
@@ -101,8 +104,10 @@ public class VpnTest extends AndroidTestCase {
    @Override
    @Override
    public void setUp() throws Exception {
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        MockitoAnnotations.initMocks(this);

        when(mContext.getPackageManager()).thenReturn(mPackageManager);
        when(mContext.getPackageManager()).thenReturn(mPackageManager);
        setMockedPackages(mPackages);
        setMockedPackages(mPackages);

        when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
        when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
        when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
        when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
        when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
        when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
@@ -257,6 +262,58 @@ public class VpnTest extends AndroidTestCase {
        }));
        }));
    }
    }


    @SmallTest
    public void testLockdownRuleRepeatability() throws Exception {
        final Vpn vpn = createVpn(primaryUser.id);

        // Given legacy lockdown is already enabled,
        vpn.setLockdown(true);
        verify(mNetService, times(1)).setAllowOnlyVpnForUids(
                eq(true), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)}));

        // Enabling legacy lockdown twice should do nothing.
        vpn.setLockdown(true);
        verify(mNetService, times(1)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));

        // And disabling should remove the rules exactly once.
        vpn.setLockdown(false);
        verify(mNetService, times(1)).setAllowOnlyVpnForUids(
                eq(false), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)}));

        // Removing the lockdown again should have no effect.
        vpn.setLockdown(false);
        verify(mNetService, times(2)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));
    }

    @SmallTest
    public void testLockdownRuleReversibility() throws Exception {
        final Vpn vpn = createVpn(primaryUser.id);

        final UidRange[] entireUser = {
            UidRange.createForUser(primaryUser.id)
        };
        final UidRange[] exceptPkg0 = {
            new UidRange(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
            new UidRange(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop)
        };

        final InOrder order = inOrder(mNetService);

        // Given lockdown is enabled with no package (legacy VPN),
        vpn.setLockdown(true);
        order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));

        // When a new VPN package is set the rules should change to cover that package.
        vpn.prepare(null, PKGS[0]);
        order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(entireUser));
        order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(exceptPkg0));

        // When that VPN package is unset, everything should be undone again in reverse.
        vpn.prepare(null, VpnConfig.LEGACY_VPN);
        order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(exceptPkg0));
        order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
    }

    @SmallTest
    @SmallTest
    public void testNotificationShownForAlwaysOnApp() {
    public void testNotificationShownForAlwaysOnApp() {
        final UserHandle userHandle = UserHandle.of(primaryUser.id);
        final UserHandle userHandle = UserHandle.of(primaryUser.id);