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

Commit 3b3dd942 authored by Robin Lee's avatar Robin Lee
Browse files

Support cross-user VPN calls (with permission)

Settings and SystemUI need to act on other users than USER_OWNER.

This is gated by INTERACT_ACROSS_USERS_FULL in addition to the existing
CONTROL_VPN checks, so the number of processes able to interfere with
other profiles' VPNs should be quite small.

Bug: 20692490
Bug: 20747154
Bug: 20872408
Change-Id: I6e5d7220f73435bec350719e7b4715935caf4e19
parent 0125d76f
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -106,13 +106,13 @@ interface IConnectivityManager

    ProxyInfo getDefaultProxy();

    boolean prepareVpn(String oldPackage, String newPackage);
    boolean prepareVpn(String oldPackage, String newPackage, int userId);

    void setVpnPackageAuthorization(boolean authorized);
    void setVpnPackageAuthorization(String packageName, int userId, boolean authorized);

    ParcelFileDescriptor establishVpn(in VpnConfig config);

    VpnConfig getVpnConfig();
    VpnConfig getVpnConfig(int userId);

    void startLegacyVpn(in VpnProfile profile);

+5 −4
Original line number Diff line number Diff line
@@ -156,7 +156,7 @@ public class VpnService extends Service {
     */
    public static Intent prepare(Context context) {
        try {
            if (getService().prepareVpn(context.getPackageName(), null)) {
            if (getService().prepareVpn(context.getPackageName(), null, UserHandle.myUserId())) {
                return null;
            }
        } catch (RemoteException e) {
@@ -182,10 +182,11 @@ public class VpnService extends Service {
        String packageName = context.getPackageName();
        try {
            // Only prepare if we're not already prepared.
            if (!cm.prepareVpn(packageName, null)) {
                cm.prepareVpn(null, packageName);
            int userId = UserHandle.myUserId();
            if (!cm.prepareVpn(packageName, null, userId)) {
                cm.prepareVpn(null, packageName, userId);
            }
            cm.setVpnPackageAuthorization(true);
            cm.setVpnPackageAuthorization(packageName, userId, true);
        } catch (RemoteException e) {
            // ignore
        }
+4 −3
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.net.IConnectivityManager;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.text.Html;
import android.text.Html.ImageGetter;
import android.util.Log;
@@ -50,7 +51,7 @@ public class ConfirmDialog extends AlertActivity
            mService = IConnectivityManager.Stub.asInterface(
                    ServiceManager.getService(Context.CONNECTIVITY_SERVICE));

            if (mService.prepareVpn(mPackage, null)) {
            if (mService.prepareVpn(mPackage, null, UserHandle.myUserId())) {
                setResult(RESULT_OK);
                finish();
                return;
@@ -94,10 +95,10 @@ public class ConfirmDialog extends AlertActivity
    @Override
    public void onClick(DialogInterface dialog, int which) {
        try {
            if (mService.prepareVpn(null, mPackage)) {
            if (mService.prepareVpn(null, mPackage, UserHandle.myUserId())) {
                // Authorize this app to initiate VPN connections in the future without user
                // intervention.
                mService.setVpnPackageAuthorization(true);
                mService.setVpnPackageAuthorization(mPackage, UserHandle.myUserId(), true);
                setResult(RESULT_OK);
            }
        } catch (Exception e) {
+5 −3
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
@@ -63,7 +64,7 @@ public class ManageDialog extends AlertActivity implements
            mService = IConnectivityManager.Stub.asInterface(
                    ServiceManager.getService(Context.CONNECTIVITY_SERVICE));

            mConfig = mService.getVpnConfig();
            mConfig = mService.getVpnConfig(UserHandle.myUserId());

            // mConfig can be null if we are a restricted user, in that case don't show this dialog
            if (mConfig == null) {
@@ -120,10 +121,11 @@ public class ManageDialog extends AlertActivity implements
            if (which == DialogInterface.BUTTON_POSITIVE) {
                mConfig.configureIntent.send();
            } else if (which == DialogInterface.BUTTON_NEUTRAL) {
                final int myUserId = UserHandle.myUserId();
                if (mConfig.legacy) {
                    mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
                    mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, myUserId);
                } else {
                    mService.prepareVpn(mConfig.user, VpnConfig.LEGACY_VPN);
                    mService.prepareVpn(mConfig.user, VpnConfig.LEGACY_VPN, myUserId);
                }
            }
        } catch (Exception e) {
+58 −21
Original line number Diff line number Diff line
@@ -1406,6 +1406,22 @@ public class ConnectivityService extends IConnectivityManager.Stub
        }
    };

    /**
     * Require that the caller is either in the same user or has appropriate permission to interact
     * across users.
     *
     * @param userId Target user for whatever operation the current IPC is supposed to perform.
     */
    private void enforceCrossUserPermission(int userId) {
        if (userId == UserHandle.getCallingUserId()) {
            // Not a cross-user call.
            return;
        }
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
                "ConnectivityService");
    }

    private void enforceInternetPermission() {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.INTERNET,
@@ -2941,29 +2957,48 @@ public class ConnectivityService extends IConnectivityManager.Stub

    /**
     * Prepare for a VPN application.
     * Permissions are checked in Vpn class.
     * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
     * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
     *
     * @param oldPackage Package name of the application which currently controls VPN, which will
     *                   be replaced. If there is no such application, this should should either be
     *                   {@code null} or {@link VpnConfig.LEGACY_VPN}.
     * @param newPackage Package name of the application which should gain control of VPN, or
     *                   {@code null} to disable.
     * @param userId User for whom to prepare the new VPN.
     *
     * @hide
     */
    @Override
    public boolean prepareVpn(String oldPackage, String newPackage) {
    public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
            int userId) {
        enforceCrossUserPermission(userId);
        throwIfLockdownEnabled();
        int user = UserHandle.getUserId(Binder.getCallingUid());

        synchronized(mVpns) {
            return mVpns.get(user).prepare(oldPackage, newPackage);
            return mVpns.get(userId).prepare(oldPackage, newPackage);
        }
    }

    /**
     * Set whether the current VPN package has the ability to launch VPNs without
     * user intervention. This method is used by system-privileged apps.
     * Permissions are checked in Vpn class.
     * Set whether the VPN package has the ability to launch VPNs without user intervention.
     * This method is used by system-privileged apps.
     * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
     * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
     *
     * @param packageName The package for which authorization state should change.
     * @param userId User for whom {@code packageName} is installed.
     * @param authorized {@code true} if this app should be able to start a VPN connection without
     *                   explicit user approval, {@code false} if not.
     *
     * @hide
     */
    @Override
    public void setVpnPackageAuthorization(boolean authorized) {
        int user = UserHandle.getUserId(Binder.getCallingUid());
    public void setVpnPackageAuthorization(String packageName, int userId, boolean authorized) {
        enforceCrossUserPermission(userId);

        synchronized(mVpns) {
            mVpns.get(user).setPackageAuthorization(authorized);
            mVpns.get(userId).setPackageAuthorization(packageName, authorized);
        }
    }

@@ -3065,16 +3100,16 @@ public class ConnectivityService extends IConnectivityManager.Stub
    }

    /**
     * Returns the information of the ongoing VPN. This method is used by VpnDialogs and
     * not available in ConnectivityManager.
     * Returns the information of the ongoing VPN for {@code userId}. This method is used by
     * VpnDialogs and not available in ConnectivityManager.
     * Permissions are checked in Vpn class.
     * @hide
     */
    @Override
    public VpnConfig getVpnConfig() {
        int user = UserHandle.getUserId(Binder.getCallingUid());
    public VpnConfig getVpnConfig(int userId) {
        enforceCrossUserPermission(userId);
        synchronized(mVpns) {
            return mVpns.get(user).getVpnConfig();
            return mVpns.get(userId).getVpnConfig();
        }
    }

@@ -4556,6 +4591,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
    @Override
    public void factoryReset() {
        enforceConnectivityInternalPermission();
        final int userId = UserHandle.getCallingUserId();

        // Turn airplane mode off
        setAirplaneMode(false);

@@ -4565,16 +4602,16 @@ public class ConnectivityService extends IConnectivityManager.Stub
        }

        // Turn VPN off
        VpnConfig vpnConfig = getVpnConfig();
        VpnConfig vpnConfig = getVpnConfig(userId);
        if (vpnConfig != null) {
            if (vpnConfig.legacy) {
                prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
                prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
            } else {
                // Prevent this app from initiating VPN connections in the future without
                // user intervention.
                setVpnPackageAuthorization(false);
                // Prevent this app (packagename = vpnConfig.user) from initiating VPN connections
                // in the future without user intervention.
                setVpnPackageAuthorization(vpnConfig.user, userId, false);

                prepareVpn(vpnConfig.user, VpnConfig.LEGACY_VPN);
                prepareVpn(vpnConfig.user, VpnConfig.LEGACY_VPN, userId);
            }
        }
    }
Loading