Enforce restricted user, getConnectionOwnerUid checks

This CL adds checks to ensure restricted users cannot change or
start/stop platform VPNs. In addition, this also adds checks to the
ConnectivityManager#getConnectionOwnerUid() to ensure that only
VpnService based VPNs can identify connections

Bug: 148040659
Test: FrameworksNetTests run
Change-Id: Id47ada5766036bfc84f3ba47f66f2d2683af916d
This commit is contained in:
Benedict Wong
2020-01-20 22:14:59 -08:00
parent 02d8578fc3
commit 0bd4bba07a
4 changed files with 186 additions and 18 deletions

View File

@@ -4716,19 +4716,19 @@ public class ConnectivityManager {
/**
* Returns the {@code uid} of the owner of a network connection.
*
* @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and
* {@code IPPROTO_UDP} currently supported.
* @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and {@code
* IPPROTO_UDP} currently supported.
* @param local The local {@link InetSocketAddress} of a connection.
* @param remote The remote {@link InetSocketAddress} of a connection.
*
* @return {@code uid} if the connection is found and the app has permission to observe it
* (e.g., if it is associated with the calling VPN app's tunnel) or
* {@link android.os.Process#INVALID_UID} if the connection is not found.
* Throws {@link SecurityException} if the caller is not the active VPN for the current user.
* Throws {@link IllegalArgumentException} if an unsupported protocol is requested.
* (e.g., if it is associated with the calling VPN app's VpnService tunnel) or {@link
* android.os.Process#INVALID_UID} if the connection is not found.
* @throws {@link SecurityException} if the caller is not the active VpnService for the current
* user.
* @throws {@link IllegalArgumentException} if an unsupported protocol is requested.
*/
public int getConnectionOwnerUid(int protocol, @NonNull InetSocketAddress local,
@NonNull InetSocketAddress remote) {
public int getConnectionOwnerUid(
int protocol, @NonNull InetSocketAddress local, @NonNull InetSocketAddress remote) {
ConnectionInfo connectionInfo = new ConnectionInfo(protocol, local, remote);
try {
return mService.getConnectionOwnerUid(connectionInfo);

View File

@@ -7486,6 +7486,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
public int getConnectionOwnerUid(ConnectionInfo connectionInfo) {
final Vpn vpn = enforceActiveVpnOrNetworkStackPermission();
// Only VpnService based VPNs should be able to get this information.
if (vpn != null && vpn.getActiveAppVpnType() != VpnManager.TYPE_VPN_SERVICE) {
throw new SecurityException(
"getConnectionOwnerUid() not allowed for non-VpnService VPNs");
}
if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) {
throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol);
}

View File

@@ -78,6 +78,7 @@ import static android.net.NetworkPolicyManager.RULE_NONE;
import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import static android.net.RouteInfo.RTN_UNREACHABLE;
import static android.system.OsConstants.IPPROTO_TCP;
import static com.android.server.ConnectivityServiceTestUtilsKt.transportToLegacyType;
import static com.android.testutils.ConcurrentUtilsKt.await;
@@ -138,6 +139,7 @@ import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.location.LocationManager;
import android.net.CaptivePortalData;
import android.net.ConnectionInfo;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager.PacketKeepalive;
@@ -153,6 +155,7 @@ import android.net.INetworkMonitorCallbacks;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.InetAddresses;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
import android.net.IpSecManager;
@@ -176,6 +179,7 @@ import android.net.RouteInfo;
import android.net.SocketKeepalive;
import android.net.UidRange;
import android.net.Uri;
import android.net.VpnManager;
import android.net.metrics.IpConnectivityLog;
import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
@@ -272,6 +276,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.function.Supplier;
import kotlin.reflect.KClass;
@@ -445,15 +450,21 @@ public class ConnectivityServiceTest {
return mPackageManager;
}
private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
final Integer granted = mMockedPermissions.get(permission);
return granted != null ? granted : ifAbsent.get();
}
@Override
public int checkPermission(String permission, int pid, int uid) {
final Integer granted = mMockedPermissions.get(permission);
if (granted == null) {
// All non-mocked permissions should be held by the test or unnecessary: check as
// normal to make sure the code does not rely on unexpected permissions.
return super.checkPermission(permission, pid, uid);
}
return granted;
return checkMockedPermission(
permission, () -> super.checkPermission(permission, pid, uid));
}
@Override
public int checkCallingOrSelfPermission(String permission) {
return checkMockedPermission(
permission, () -> super.checkCallingOrSelfPermission(permission));
}
@Override
@@ -994,6 +1005,7 @@ public class ConnectivityServiceTest {
// Careful ! This is different from mNetworkAgent, because MockNetworkAgent does
// not inherit from NetworkAgent.
private TestNetworkAgentWrapper mMockNetworkAgent;
private int mVpnType = VpnManager.TYPE_VPN_SERVICE;
private VpnInfo mVpnInfo;
@@ -1014,6 +1026,10 @@ public class ConnectivityServiceTest {
updateCapabilities(null /* defaultNetwork */);
}
public void setVpnType(int vpnType) {
mVpnType = vpnType;
}
@Override
public int getNetId() {
if (mMockNetworkAgent == null) {
@@ -1032,6 +1048,11 @@ public class ConnectivityServiceTest {
return mConnected; // Similar trickery
}
@Override
public int getActiveAppVpnType() {
return mVpnType;
}
private void connect(boolean isAlwaysMetered) {
mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
mConnected = true;
@@ -6382,6 +6403,90 @@ public class ConnectivityServiceTest {
assertEquals(Process.INVALID_UID, newNc.getOwnerUid());
}
private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
throws Exception {
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
establishVpn(new LinkProperties(), vpnOwnerUid, vpnRange);
mMockVpn.setVpnType(vpnType);
final VpnInfo vpnInfo = new VpnInfo();
vpnInfo.ownerUid = vpnOwnerUid;
mMockVpn.setVpnInfo(vpnInfo);
}
private void setupConnectionOwnerUidAsVpnApp(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
throws Exception {
setupConnectionOwnerUid(vpnOwnerUid, vpnType);
// Test as VPN app
mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
mServiceContext.setPermission(
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_DENIED);
}
private ConnectionInfo getTestConnectionInfo() throws Exception {
return new ConnectionInfo(
IPPROTO_TCP,
new InetSocketAddress(InetAddresses.parseNumericAddress("1.2.3.4"), 1234),
new InetSocketAddress(InetAddresses.parseNumericAddress("2.3.4.5"), 2345));
}
@Test
public void testGetConnectionOwnerUidPlatformVpn() throws Exception {
final int myUid = Process.myUid();
setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_PLATFORM);
try {
mService.getConnectionOwnerUid(getTestConnectionInfo());
fail("Expected SecurityException for non-VpnService app");
} catch (SecurityException expected) {
}
}
@Test
public void testGetConnectionOwnerUidVpnServiceWrongUser() throws Exception {
final int myUid = Process.myUid();
setupConnectionOwnerUidAsVpnApp(myUid + 1, VpnManager.TYPE_VPN_SERVICE);
try {
mService.getConnectionOwnerUid(getTestConnectionInfo());
fail("Expected SecurityException for non-VpnService app");
} catch (SecurityException expected) {
}
}
@Test
public void testGetConnectionOwnerUidVpnServiceDoesNotThrow() throws Exception {
final int myUid = Process.myUid();
setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_SERVICE);
// TODO: Test the returned UID
mService.getConnectionOwnerUid(getTestConnectionInfo());
}
@Test
public void testGetConnectionOwnerUidVpnServiceNetworkStackDoesNotThrow() throws Exception {
final int myUid = Process.myUid();
setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE);
mServiceContext.setPermission(
android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
// TODO: Test the returned UID
mService.getConnectionOwnerUid(getTestConnectionInfo());
}
@Test
public void testGetConnectionOwnerUidVpnServiceMainlineNetworkStackDoesNotThrow()
throws Exception {
final int myUid = Process.myUid();
setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE);
mServiceContext.setPermission(
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED);
// TODO: Test the returned UID
mService.getConnectionOwnerUid(getTestConnectionInfo());
}
private TestNetworkAgentWrapper establishVpn(
LinkProperties lp, int ownerUid, Set<UidRange> vpnRange) throws Exception {
final TestNetworkAgentWrapper

View File

@@ -656,8 +656,12 @@ public class VpnTest {
}
private Vpn createVpnAndSetupUidChecks(int... grantedOps) throws Exception {
final Vpn vpn = createVpn(primaryUser.id);
setMockedUsers(primaryUser);
return createVpnAndSetupUidChecks(primaryUser, grantedOps);
}
private Vpn createVpnAndSetupUidChecks(UserInfo user, int... grantedOps) throws Exception {
final Vpn vpn = createVpn(user.id);
setMockedUsers(user);
when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
.thenReturn(Process.myUid());
@@ -725,6 +729,19 @@ public class VpnTest {
}
}
@Test
public void testProvisionVpnProfileRestrictedUser() throws Exception {
final Vpn vpn =
createVpnAndSetupUidChecks(
restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN);
try {
vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore);
fail("Expected SecurityException due to restricted user");
} catch (SecurityException expected) {
}
}
@Test
public void testDeleteVpnProfile() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
@@ -735,6 +752,19 @@ public class VpnTest {
.delete(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), eq(Process.SYSTEM_UID));
}
@Test
public void testDeleteVpnProfileRestrictedUser() throws Exception {
final Vpn vpn =
createVpnAndSetupUidChecks(
restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN);
try {
vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore);
fail("Expected SecurityException due to restricted user");
} catch (SecurityException expected) {
}
}
@Test
public void testGetVpnProfilePrivileged() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
@@ -819,6 +849,32 @@ public class VpnTest {
eq(TEST_VPN_PKG));
}
@Test
public void testStartVpnProfileRestrictedUser() throws Exception {
final Vpn vpn =
createVpnAndSetupUidChecks(
restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN);
try {
vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
fail("Expected SecurityException due to restricted user");
} catch (SecurityException expected) {
}
}
@Test
public void testStopVpnProfileRestrictedUser() throws Exception {
final Vpn vpn =
createVpnAndSetupUidChecks(
restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN);
try {
vpn.stopVpnProfile(TEST_VPN_PKG);
fail("Expected SecurityException due to restricted user");
} catch (SecurityException expected) {
}
}
@Test
public void testSetPackageAuthorizationVpnService() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();