Add separate user consent for Platform VPNs

This change adds a new VPN user consent flow (using the same text) for
granting the lesser OP_ACTIVATE_PLATFORM_VPN. A new
PlatformVpnConfirmDialog is created as a subclass to preserve all logic,
but ensure the right appop is granted for the relevant dialog.

Intent extras were considered, but are inherently unsafe, since the
caller may add any extras that they would want.

Bug: 144246835
Test: FrameworksNetTests passing
Change-Id: Ia6f36207d43c3748f938430c2780dcf29e5623f3
Merged-In: Ia6f36207d43c3748f938430c2780dcf29e5623f3
This commit is contained in:
Benedict Wong
2019-11-06 00:20:15 -08:00
parent 79ea64f963
commit a73199168b
4 changed files with 75 additions and 15 deletions

View File

@@ -116,7 +116,7 @@ interface IConnectivityManager
boolean prepareVpn(String oldPackage, String newPackage, int userId); boolean prepareVpn(String oldPackage, String newPackage, int userId);
void setVpnPackageAuthorization(String packageName, int userId, boolean authorized); void setVpnPackageAuthorization(String packageName, int userId, int vpnType);
ParcelFileDescriptor establishVpn(in VpnConfig config); ParcelFileDescriptor establishVpn(in VpnConfig config);

View File

@@ -112,6 +112,7 @@ import android.net.SocketKeepalive;
import android.net.TetheringManager; import android.net.TetheringManager;
import android.net.UidRange; import android.net.UidRange;
import android.net.Uri; import android.net.Uri;
import android.net.VpnManager;
import android.net.VpnService; import android.net.VpnService;
import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent; import android.net.metrics.NetworkEvent;
@@ -4293,7 +4294,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
throwIfLockdownEnabled(); throwIfLockdownEnabled();
Vpn vpn = mVpns.get(userId); Vpn vpn = mVpns.get(userId);
if (vpn != null) { if (vpn != null) {
return vpn.prepare(oldPackage, newPackage, false); return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE);
} else { } else {
return false; return false;
} }
@@ -4301,26 +4302,29 @@ public class ConnectivityService extends IConnectivityManager.Stub
} }
/** /**
* Set whether the VPN package has the ability to launch VPNs without user intervention. * Set whether the VPN package has the ability to launch VPNs without user intervention. This
* This method is used by system-privileged apps. * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn}
* VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, * class. If the caller is not {@code userId}, {@link
* {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
* *
* @param packageName The package for which authorization state should change. * @param packageName The package for which authorization state should change.
* @param userId User for whom {@code packageName} is installed. * @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 * @param authorized {@code true} if this app should be able to start a VPN connection without
* explicit user approval, {@code false} if not. * explicit user approval, {@code false} if not.
* * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN
* permissions should be granted. When unauthorizing an app, {@link
* VpnManager.TYPE_VPN_NONE} should be used.
* @hide * @hide
*/ */
@Override @Override
public void setVpnPackageAuthorization(String packageName, int userId, boolean authorized) { public void setVpnPackageAuthorization(
String packageName, int userId, @VpnManager.VpnType int vpnType) {
enforceCrossUserPermission(userId); enforceCrossUserPermission(userId);
synchronized (mVpns) { synchronized (mVpns) {
Vpn vpn = mVpns.get(userId); Vpn vpn = mVpns.get(userId);
if (vpn != null) { if (vpn != null) {
vpn.setPackageAuthorization(packageName, authorized); vpn.setPackageAuthorization(packageName, vpnType);
} }
} }
} }
@@ -7193,7 +7197,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
if (alwaysOnPackage != null) { if (alwaysOnPackage != null) {
setAlwaysOnVpnPackage(userId, null, false, null); setAlwaysOnVpnPackage(userId, null, false, null);
setVpnPackageAuthorization(alwaysOnPackage, userId, false); setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE);
} }
// Turn Always-on VPN off // Turn Always-on VPN off
@@ -7216,7 +7220,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
} else { } else {
// Prevent this app (packagename = vpnConfig.user) from initiating // Prevent this app (packagename = vpnConfig.user) from initiating
// VPN connections in the future without user intervention. // VPN connections in the future without user intervention.
setVpnPackageAuthorization(vpnConfig.user, userId, false); setVpnPackageAuthorization(
vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE);
prepareVpn(null, VpnConfig.LEGACY_VPN, userId); prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
} }

View File

@@ -16,6 +16,7 @@
package android.net; package android.net;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
@@ -24,6 +25,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Intent;
import android.test.mock.MockContext; import android.test.mock.MockContext;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
@@ -78,7 +81,13 @@ public class VpnManagerTest {
when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(false); when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(false);
// Expect intent to be returned, as consent has not already been granted. // Expect intent to be returned, as consent has not already been granted.
assertNotNull(mVpnManager.provisionVpnProfile(profile)); final Intent intent = mVpnManager.provisionVpnProfile(profile);
assertNotNull(intent);
final ComponentName expectedComponentName =
ComponentName.unflattenFromString(
"com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
assertEquals(expectedComponentName, intent.getComponent());
verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
} }

View File

@@ -63,6 +63,7 @@ import android.net.Network;
import android.net.NetworkCapabilities; import android.net.NetworkCapabilities;
import android.net.NetworkInfo.DetailedState; import android.net.NetworkInfo.DetailedState;
import android.net.UidRange; import android.net.UidRange;
import android.net.VpnManager;
import android.net.VpnService; import android.net.VpnService;
import android.os.Build.VERSION_CODES; import android.os.Build.VERSION_CODES;
import android.os.Bundle; import android.os.Bundle;
@@ -471,12 +472,12 @@ public class VpnTest {
order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser)); order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
// When a new VPN package is set the rules should change to cover that package. // When a new VPN package is set the rules should change to cover that package.
vpn.prepare(null, PKGS[0], false /* isPlatformVpn */); vpn.prepare(null, PKGS[0], VpnManager.TYPE_VPN_SERVICE);
order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(entireUser)); order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(entireUser));
order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(exceptPkg0)); order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(exceptPkg0));
// When that VPN package is unset, everything should be undone again in reverse. // When that VPN package is unset, everything should be undone again in reverse.
vpn.prepare(null, VpnConfig.LEGACY_VPN, false /* isPlatformVpn */); vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE);
order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(exceptPkg0)); order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(exceptPkg0));
order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser)); order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
} }
@@ -817,6 +818,51 @@ public class VpnTest {
eq(TEST_VPN_PKG)); eq(TEST_VPN_PKG));
} }
@Test
public void testSetPackageAuthorizationVpnService() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_SERVICE));
verify(mAppOps)
.setMode(
eq(AppOpsManager.OP_ACTIVATE_VPN),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(AppOpsManager.MODE_ALLOWED));
}
@Test
public void testSetPackageAuthorizationPlatformVpn() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_PLATFORM));
verify(mAppOps)
.setMode(
eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(AppOpsManager.MODE_ALLOWED));
}
@Test
public void testSetPackageAuthorizationRevokeAuthorization() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_NONE));
verify(mAppOps)
.setMode(
eq(AppOpsManager.OP_ACTIVATE_VPN),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(AppOpsManager.MODE_IGNORED));
verify(mAppOps)
.setMode(
eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(AppOpsManager.MODE_IGNORED));
}
/** /**
* Mock some methods of vpn object. * Mock some methods of vpn object.
*/ */