diff options
| author | 2015-05-12 18:14:58 +0100 | |
|---|---|---|
| committer | 2015-05-18 23:35:31 +0100 | |
| commit | 3b3dd942ec6a0beaccd1cef0723d72786435d8f3 (patch) | |
| tree | 6bf3cc9895290fa3ec9a509fb87d30afb649697e | |
| parent | 0125d76fa37d26a29f06371ff349546e21cd3f4d (diff) | |
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
6 files changed, 82 insertions, 39 deletions
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 77200a57e498..c1b4a1fb53bc 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/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index a0e65eba5673..2bb48b33f385 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -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 } diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java index ea8b2ec7bb42..48e05823f625 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java @@ -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) { diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java index cc8500ae34a8..76b234694790 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java @@ -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) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 16b3dcfa8b85..5c0e3287d61d 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); } } } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index aeecdf3cab02..e1ec8a636e72 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -298,13 +298,15 @@ public class Vpn { } /** - * Set whether the current package has the ability to launch VPNs without user intervention. + * Set whether a package has the ability to launch VPNs without user intervention. */ - public void setPackageAuthorization(boolean authorized) { + public void setPackageAuthorization(String packageName, boolean authorized) { // Check if the caller is authorized. enforceControlPermission(); - if (mPackage == null || VpnConfig.LEGACY_VPN.equals(mPackage)) { + int uid = getAppUid(packageName, mUserHandle); + if (uid == -1 || VpnConfig.LEGACY_VPN.equals(packageName)) { + // Authorization for nonexistent packages (or fake ones) can't be updated. return; } @@ -312,10 +314,10 @@ public class Vpn { try { AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); - appOps.setMode(AppOpsManager.OP_ACTIVATE_VPN, mOwnerUID, mPackage, + appOps.setMode(AppOpsManager.OP_ACTIVATE_VPN, uid, packageName, authorized ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); } catch (Exception e) { - Log.wtf(TAG, "Failed to set app ops for package " + mPackage, e); + Log.wtf(TAG, "Failed to set app ops for package " + packageName + ", uid " + uid, e); } finally { Binder.restoreCallingIdentity(token); } |