From 1e71fc8356c7ae0c9d2cf8fa3ee4fe2bb20f4423 Mon Sep 17 00:00:00 2001 From: Robin Lee Date: Tue, 12 May 2015 18:14:58 +0100 Subject: [PATCH] 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 --- .../android/net/IConnectivityManager.aidl | 6 +- .../android/server/ConnectivityService.java | 79 ++++++++++++++----- 2 files changed, 61 insertions(+), 24 deletions(-) diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 77200a57e4..c1b4a1fb53 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -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); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 16b3dcfa8b..5c0e3287d6 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -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); } } }