Block incoming non-VPN packets to apps under fully-routed VPN
When a fully-routed VPN is running, we want to prevent normal apps
under the VPN from receiving packets originating from any local non-VPN
interfaces. This is achieved by using eBPF to create a per-UID input
interface whitelist and populate the whitelist such that all
non-bypassable apps under a VPN can only receive packets from the VPN's
TUN interface (and loopback implicitly)
This is the framework part of the change that build the whitelist.
The whitelist needs to be updated in the following cases:
* When a VPN is connected and disconnected
This will cover the change to allowBypass bit, since that can't be
changed without reconnecting.
* When a VPN's NetworkCapabilites is changed (whitelist/blacklist app changes)
* When a new app is installed
* When an existing app is removed
* When a VPN becomes fully-routed or is no longer fully-routed
New user/profile creation will automatically result in a whitelist app change
transition so it doesn't need to be handled specially here.
Due to the limitation of the kernel IPSec interacting with eBPF (sk_buf->ifindex
does not point to the virtual tunnel interface for kernel IPSec), the whitelist
will only apply to app VPNs but not legacy VPN connections, to prevent breaking
connectivity with kernel IPSec entirely.
Test: atest PermissionMonitorTest
Test: atest android.net.RouteInfoTest
Test: atest com.android.server.ConnectivityServiceTest
Test: atest HostsideVpnTests
Bug: 114231106
Merged-In: I5af81bc80dadd086261ba4b1eb706cc873bb7cfa
Change-Id: I5af81bc80dadd086261ba4b1eb706cc873bb7cfa
(cherry picked from commit 65968ea16bf49f678d4a43c220e1d67393170459)
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server;
|
||||
|
||||
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
||||
import static android.content.pm.PackageManager.MATCH_ANY_USER;
|
||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||
import static android.net.ConnectivityManager.NETID_UNSET;
|
||||
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
|
||||
@@ -60,11 +62,13 @@ import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
|
||||
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 com.android.internal.util.TestUtils.waitForIdleHandler;
|
||||
import static com.android.internal.util.TestUtils.waitForIdleLooper;
|
||||
import static com.android.internal.util.TestUtils.waitForIdleSerialExecutor;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
@@ -72,12 +76,14 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
@@ -97,6 +103,10 @@ import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
@@ -151,6 +161,7 @@ import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.system.Os;
|
||||
import android.test.mock.MockContentResolver;
|
||||
@@ -186,6 +197,7 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
@@ -194,6 +206,7 @@ import org.mockito.stubbing.Answer;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
@@ -261,6 +274,8 @@ public class ConnectivityServiceTest {
|
||||
@Mock IDnsResolver mMockDnsResolver;
|
||||
@Mock INetd mMockNetd;
|
||||
@Mock NetworkStackClient mNetworkStack;
|
||||
@Mock PackageManager mPackageManager;
|
||||
@Mock UserManager mUserManager;
|
||||
|
||||
private ArgumentCaptor<String[]> mStringArrayCaptor = ArgumentCaptor.forClass(String[].class);
|
||||
|
||||
@@ -331,6 +346,7 @@ public class ConnectivityServiceTest {
|
||||
if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
|
||||
if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
|
||||
if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack;
|
||||
if (Context.USER_SERVICE.equals(name)) return mUserManager;
|
||||
return super.getSystemService(name);
|
||||
}
|
||||
|
||||
@@ -343,6 +359,11 @@ public class ConnectivityServiceTest {
|
||||
public Resources getResources() {
|
||||
return mResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackageManager getPackageManager() {
|
||||
return mPackageManager;
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForIdle(int timeoutMsAsInt) {
|
||||
@@ -1057,7 +1078,7 @@ public class ConnectivityServiceTest {
|
||||
public WrappedConnectivityService(Context context, INetworkManagementService netManager,
|
||||
INetworkStatsService statsService, INetworkPolicyManager policyManager,
|
||||
IpConnectivityLog log, INetd netd, IDnsResolver dnsResolver) {
|
||||
super(context, netManager, statsService, policyManager, dnsResolver, log);
|
||||
super(context, netManager, statsService, policyManager, dnsResolver, log, netd);
|
||||
mNetd = netd;
|
||||
mLingerDelayMs = TEST_LINGER_DELAY_MS;
|
||||
}
|
||||
@@ -1196,6 +1217,11 @@ public class ConnectivityServiceTest {
|
||||
fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms");
|
||||
}
|
||||
|
||||
private static final int VPN_USER = 0;
|
||||
private static final int APP1_UID = UserHandle.getUid(VPN_USER, 10100);
|
||||
private static final int APP2_UID = UserHandle.getUid(VPN_USER, 10101);
|
||||
private static final int VPN_UID = UserHandle.getUid(VPN_USER, 10043);
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mContext = InstrumentationRegistry.getContext();
|
||||
@@ -1203,6 +1229,17 @@ public class ConnectivityServiceTest {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mMetricsService.defaultNetworkMetrics()).thenReturn(mDefaultNetworkMetrics);
|
||||
|
||||
when(mUserManager.getUsers(eq(true))).thenReturn(
|
||||
Arrays.asList(new UserInfo[] {
|
||||
new UserInfo(VPN_USER, "", 0),
|
||||
}));
|
||||
when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
|
||||
Arrays.asList(new PackageInfo[] {
|
||||
buildPackageInfo(/* SYSTEM */ false, APP1_UID),
|
||||
buildPackageInfo(/* SYSTEM */ false, APP2_UID),
|
||||
buildPackageInfo(/* SYSTEM */ false, VPN_UID)
|
||||
}));
|
||||
|
||||
// InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
|
||||
// http://b/25897652 .
|
||||
if (Looper.myLooper() == null) {
|
||||
@@ -6129,4 +6166,165 @@ public class ConnectivityServiceTest {
|
||||
assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
|
||||
assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullyRoutedVpnResultsInInterfaceFilteringRules() throws Exception {
|
||||
LinkProperties lp = new LinkProperties();
|
||||
lp.setInterfaceName("tun0");
|
||||
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
|
||||
// The uid range needs to cover the test app so the network is visible to it.
|
||||
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
|
||||
final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
|
||||
|
||||
// Connected VPN should have interface rules set up. There are two expected invocations,
|
||||
// one during VPN uid update, one during VPN LinkProperties update
|
||||
ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
|
||||
verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
|
||||
assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
|
||||
assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
|
||||
assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange));
|
||||
|
||||
vpnNetworkAgent.disconnect();
|
||||
waitForIdle();
|
||||
|
||||
// Disconnected VPN should have interface rules removed
|
||||
verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
|
||||
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
|
||||
assertNull(mService.mPermissionMonitor.getVpnUidRanges("tun0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception {
|
||||
LinkProperties lp = new LinkProperties();
|
||||
lp.setInterfaceName("tun0");
|
||||
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
|
||||
// The uid range needs to cover the test app so the network is visible to it.
|
||||
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
|
||||
final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, Process.SYSTEM_UID, vpnRange);
|
||||
|
||||
// Legacy VPN should not have interface rules set up
|
||||
verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
|
||||
}
|
||||
|
||||
public void testLocalIpv4OnlyVpnDoesNotResultInInterfaceFilteringRule()
|
||||
throws Exception {
|
||||
LinkProperties lp = new LinkProperties();
|
||||
lp.setInterfaceName("tun0");
|
||||
lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun0"));
|
||||
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
|
||||
// The uid range needs to cover the test app so the network is visible to it.
|
||||
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
|
||||
final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, Process.SYSTEM_UID, vpnRange);
|
||||
|
||||
// IPv6 unreachable route should not be misinterpreted as a default route
|
||||
verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVpnHandoverChangesInterfaceFilteringRule() throws Exception {
|
||||
LinkProperties lp = new LinkProperties();
|
||||
lp.setInterfaceName("tun0");
|
||||
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
|
||||
// The uid range needs to cover the test app so the network is visible to it.
|
||||
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
|
||||
final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
|
||||
|
||||
// Connected VPN should have interface rules set up. There are two expected invocations,
|
||||
// one during VPN uid update, one during VPN LinkProperties update
|
||||
ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
|
||||
verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
|
||||
assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
|
||||
assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
|
||||
|
||||
reset(mMockNetd);
|
||||
InOrder inOrder = inOrder(mMockNetd);
|
||||
lp.setInterfaceName("tun1");
|
||||
vpnNetworkAgent.sendLinkProperties(lp);
|
||||
waitForIdle();
|
||||
// VPN handover (switch to a new interface) should result in rules being updated (old rules
|
||||
// removed first, then new rules added)
|
||||
inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
|
||||
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
|
||||
inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
|
||||
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
|
||||
|
||||
reset(mMockNetd);
|
||||
lp = new LinkProperties();
|
||||
lp.setInterfaceName("tun1");
|
||||
lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1"));
|
||||
vpnNetworkAgent.sendLinkProperties(lp);
|
||||
waitForIdle();
|
||||
// VPN not routing everything should no longer have interface filtering rules
|
||||
verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
|
||||
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
|
||||
|
||||
reset(mMockNetd);
|
||||
lp = new LinkProperties();
|
||||
lp.setInterfaceName("tun1");
|
||||
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
|
||||
vpnNetworkAgent.sendLinkProperties(lp);
|
||||
waitForIdle();
|
||||
// Back to routing all IPv6 traffic should have filtering rules
|
||||
verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
|
||||
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUidUpdateChangesInterfaceFilteringRule() throws Exception {
|
||||
LinkProperties lp = new LinkProperties();
|
||||
lp.setInterfaceName("tun0");
|
||||
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
|
||||
// The uid range needs to cover the test app so the network is visible to it.
|
||||
final UidRange vpnRange = UidRange.createForUser(VPN_USER);
|
||||
final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID,
|
||||
Collections.singleton(vpnRange));
|
||||
|
||||
reset(mMockNetd);
|
||||
InOrder inOrder = inOrder(mMockNetd);
|
||||
|
||||
// Update to new range which is old range minus APP1, i.e. only APP2
|
||||
final Set<UidRange> newRanges = new HashSet<>(Arrays.asList(
|
||||
new UidRange(vpnRange.start, APP1_UID - 1),
|
||||
new UidRange(APP1_UID + 1, vpnRange.stop)));
|
||||
vpnNetworkAgent.setUids(newRanges);
|
||||
waitForIdle();
|
||||
|
||||
ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
|
||||
// Verify old rules are removed before new rules are added
|
||||
inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
|
||||
assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
|
||||
inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
|
||||
assertContainsExactly(uidCaptor.getValue(), APP2_UID);
|
||||
}
|
||||
|
||||
|
||||
private MockNetworkAgent establishVpn(LinkProperties lp, int establishingUid,
|
||||
Set<UidRange> vpnRange) {
|
||||
final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN, lp);
|
||||
vpnNetworkAgent.getNetworkCapabilities().setEstablishingVpnAppUid(establishingUid);
|
||||
mMockVpn.setNetworkAgent(vpnNetworkAgent);
|
||||
mMockVpn.connect();
|
||||
mMockVpn.setUids(vpnRange);
|
||||
vpnNetworkAgent.connect(true);
|
||||
waitForIdle();
|
||||
return vpnNetworkAgent;
|
||||
}
|
||||
|
||||
private void assertContainsExactly(int[] actual, int... expected) {
|
||||
int[] sortedActual = Arrays.copyOf(actual, actual.length);
|
||||
int[] sortedExpected = Arrays.copyOf(expected, expected.length);
|
||||
Arrays.sort(sortedActual);
|
||||
Arrays.sort(sortedExpected);
|
||||
assertArrayEquals(sortedExpected, sortedActual);
|
||||
}
|
||||
|
||||
private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) {
|
||||
final PackageInfo packageInfo = new PackageInfo();
|
||||
packageInfo.requestedPermissions = new String[0];
|
||||
packageInfo.applicationInfo = new ApplicationInfo();
|
||||
packageInfo.applicationInfo.privateFlags = 0;
|
||||
packageInfo.applicationInfo.uid = UserHandle.getUid(UserHandle.USER_SYSTEM,
|
||||
UserHandle.getAppId(uid));
|
||||
return packageInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRODUCT;
|
||||
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_VENDOR;
|
||||
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
|
||||
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
||||
import static android.content.pm.PackageManager.MATCH_ANY_USER;
|
||||
import static android.os.Process.SYSTEM_UID;
|
||||
|
||||
import static com.android.server.connectivity.PermissionMonitor.NETWORK;
|
||||
@@ -36,13 +37,16 @@ import static com.android.server.connectivity.PermissionMonitor.SYSTEM;
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.AdditionalMatchers.aryEq;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -53,10 +57,12 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageList;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.net.INetd;
|
||||
import android.net.UidRange;
|
||||
import android.os.Build;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
@@ -73,7 +79,12 @@ import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
@@ -84,10 +95,12 @@ public class PermissionMonitorTest {
|
||||
private static final int MOCK_UID2 = 10086;
|
||||
private static final int SYSTEM_UID1 = 1000;
|
||||
private static final int SYSTEM_UID2 = 1008;
|
||||
private static final int VPN_UID = 10002;
|
||||
private static final String MOCK_PACKAGE1 = "appName1";
|
||||
private static final String MOCK_PACKAGE2 = "appName2";
|
||||
private static final String SYSTEM_PACKAGE1 = "sysName1";
|
||||
private static final String SYSTEM_PACKAGE2 = "sysName2";
|
||||
private static final String VPN_PACKAGE = "vpnApp";
|
||||
private static final String PARTITION_SYSTEM = "system";
|
||||
private static final String PARTITION_OEM = "oem";
|
||||
private static final String PARTITION_PRODUCT = "product";
|
||||
@@ -97,9 +110,9 @@ public class PermissionMonitorTest {
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Mock private PackageManager mPackageManager;
|
||||
@Mock private INetworkManagementService mNMS;
|
||||
@Mock private INetd mNetdService;
|
||||
@Mock private PackageManagerInternal mMockPmi;
|
||||
@Mock private UserManager mUserManager;
|
||||
|
||||
private PackageManagerInternal.PackageListObserver mObserver;
|
||||
private PermissionMonitor mPermissionMonitor;
|
||||
@@ -108,7 +121,14 @@ public class PermissionMonitorTest {
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mContext.getPackageManager()).thenReturn(mPackageManager);
|
||||
mPermissionMonitor = spy(new PermissionMonitor(mContext, mNMS, mNetdService));
|
||||
when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
|
||||
when(mUserManager.getUsers(eq(true))).thenReturn(
|
||||
Arrays.asList(new UserInfo[] {
|
||||
new UserInfo(MOCK_USER1, "", 0),
|
||||
new UserInfo(MOCK_USER2, "", 0),
|
||||
}));
|
||||
|
||||
mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService));
|
||||
|
||||
LocalServices.removeServiceForTest(PackageManagerInternal.class);
|
||||
LocalServices.addService(PackageManagerInternal.class, mMockPmi);
|
||||
@@ -134,7 +154,7 @@ public class PermissionMonitorTest {
|
||||
return mPermissionMonitor.hasUseBackgroundNetworksPermission(uid);
|
||||
}
|
||||
|
||||
private PackageInfo packageInfoWithPermissions(String[] permissions, String partition) {
|
||||
private static PackageInfo packageInfoWithPermissions(String[] permissions, String partition) {
|
||||
int[] requestedPermissionsFlags = new int[permissions.length];
|
||||
for (int i = 0; i < permissions.length; i++) {
|
||||
requestedPermissionsFlags[i] = REQUESTED_PERMISSION_GRANTED;
|
||||
@@ -143,7 +163,7 @@ public class PermissionMonitorTest {
|
||||
requestedPermissionsFlags);
|
||||
}
|
||||
|
||||
private PackageInfo packageInfoWithPermissions(String[] permissions, String partition,
|
||||
private static PackageInfo packageInfoWithPermissions(String[] permissions, String partition,
|
||||
int[] requestedPermissionsFlags) {
|
||||
final PackageInfo packageInfo = new PackageInfo();
|
||||
packageInfo.requestedPermissions = permissions;
|
||||
@@ -165,6 +185,18 @@ public class PermissionMonitorTest {
|
||||
return packageInfo;
|
||||
}
|
||||
|
||||
private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) {
|
||||
final PackageInfo pkgInfo;
|
||||
if (hasSystemPermission) {
|
||||
pkgInfo = packageInfoWithPermissions(new String[] {CHANGE_NETWORK_STATE, NETWORK_STACK},
|
||||
PARTITION_SYSTEM);
|
||||
} else {
|
||||
pkgInfo = packageInfoWithPermissions(new String[] {}, "");
|
||||
}
|
||||
pkgInfo.applicationInfo.uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
|
||||
return pkgInfo;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasPermission() {
|
||||
PackageInfo app = packageInfoWithPermissions(new String[] {}, PARTITION_SYSTEM);
|
||||
@@ -245,14 +277,14 @@ public class PermissionMonitorTest {
|
||||
assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_WIFI_STATE));
|
||||
}
|
||||
|
||||
private class NMSMonitor {
|
||||
private class NetdMonitor {
|
||||
private final HashMap<Integer, Boolean> mApps = new HashMap<>();
|
||||
|
||||
NMSMonitor(INetworkManagementService mockNMS) throws Exception {
|
||||
NetdMonitor(INetd mockNetd) throws Exception {
|
||||
// Add hook to verify and track result of setPermission.
|
||||
doAnswer((InvocationOnMock invocation) -> {
|
||||
final Object[] args = invocation.getArguments();
|
||||
final Boolean isSystem = args[0].equals("SYSTEM");
|
||||
final Boolean isSystem = args[0].equals(INetd.PERMISSION_SYSTEM);
|
||||
for (final int uid : (int[]) args[1]) {
|
||||
// TODO: Currently, permission monitor will send duplicate commands for each uid
|
||||
// corresponding to each user. Need to fix that and uncomment below test.
|
||||
@@ -262,7 +294,7 @@ public class PermissionMonitorTest {
|
||||
mApps.put(uid, isSystem);
|
||||
}
|
||||
return null;
|
||||
}).when(mockNMS).setPermission(anyString(), any(int[].class));
|
||||
}).when(mockNetd).networkSetPermissionForUser(anyInt(), any(int[].class));
|
||||
|
||||
// Add hook to verify and track result of clearPermission.
|
||||
doAnswer((InvocationOnMock invocation) -> {
|
||||
@@ -276,7 +308,7 @@ public class PermissionMonitorTest {
|
||||
mApps.remove(uid);
|
||||
}
|
||||
return null;
|
||||
}).when(mockNMS).clearPermission(any(int[].class));
|
||||
}).when(mockNetd).networkClearPermissionForUser(any(int[].class));
|
||||
}
|
||||
|
||||
public void expectPermission(Boolean permission, int[] users, int[] apps) {
|
||||
@@ -307,7 +339,7 @@ public class PermissionMonitorTest {
|
||||
|
||||
@Test
|
||||
public void testUserAndPackageAddRemove() throws Exception {
|
||||
final NMSMonitor mNMSMonitor = new NMSMonitor(mNMS);
|
||||
final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService);
|
||||
|
||||
// MOCK_UID1: MOCK_PACKAGE1 only has network permission.
|
||||
// SYSTEM_UID: SYSTEM_PACKAGE1 has system permission.
|
||||
@@ -323,48 +355,123 @@ public class PermissionMonitorTest {
|
||||
// Add SYSTEM_PACKAGE2, expect only have network permission.
|
||||
mPermissionMonitor.onUserAdded(MOCK_USER1);
|
||||
addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID);
|
||||
mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
|
||||
mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
|
||||
|
||||
// Add SYSTEM_PACKAGE1, expect permission escalate.
|
||||
addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID);
|
||||
mNMSMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
|
||||
mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
|
||||
|
||||
mPermissionMonitor.onUserAdded(MOCK_USER2);
|
||||
mNMSMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
|
||||
mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{SYSTEM_UID});
|
||||
|
||||
addPackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
|
||||
mNMSMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
|
||||
mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{SYSTEM_UID});
|
||||
mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
|
||||
mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{MOCK_UID1});
|
||||
|
||||
// Remove MOCK_UID1, expect no permission left for all user.
|
||||
mPermissionMonitor.onPackageRemoved(MOCK_UID1);
|
||||
removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_UID1);
|
||||
mNMSMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1});
|
||||
mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1});
|
||||
|
||||
// Remove SYSTEM_PACKAGE1, expect permission downgrade.
|
||||
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2});
|
||||
removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, SYSTEM_UID);
|
||||
mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
|
||||
mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{SYSTEM_UID});
|
||||
|
||||
mPermissionMonitor.onUserRemoved(MOCK_USER1);
|
||||
mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID});
|
||||
mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID});
|
||||
|
||||
// Remove all packages, expect no permission left.
|
||||
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{});
|
||||
removePackageForUsers(new int[]{MOCK_USER2}, SYSTEM_UID);
|
||||
mNMSMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
|
||||
mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{SYSTEM_UID, MOCK_UID1});
|
||||
|
||||
// Remove last user, expect no redundant clearPermission is invoked.
|
||||
mPermissionMonitor.onUserRemoved(MOCK_USER2);
|
||||
mNMSMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
|
||||
mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
|
||||
new int[]{SYSTEM_UID, MOCK_UID1});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
|
||||
when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
|
||||
Arrays.asList(new PackageInfo[] {
|
||||
buildPackageInfo(/* SYSTEM */ true, SYSTEM_UID1, MOCK_USER1),
|
||||
buildPackageInfo(/* SYSTEM */ false, MOCK_UID1, MOCK_USER1),
|
||||
buildPackageInfo(/* SYSTEM */ false, MOCK_UID2, MOCK_USER1),
|
||||
buildPackageInfo(/* SYSTEM */ false, VPN_UID, MOCK_USER1)
|
||||
}));
|
||||
when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS))).thenReturn(
|
||||
buildPackageInfo(false, MOCK_UID1, MOCK_USER1));
|
||||
mPermissionMonitor.startMonitoring();
|
||||
// Every app on user 0 except MOCK_UID2 are under VPN.
|
||||
final Set<UidRange> vpnRange1 = new HashSet<>(Arrays.asList(new UidRange[] {
|
||||
new UidRange(0, MOCK_UID2 - 1),
|
||||
new UidRange(MOCK_UID2 + 1, UserHandle.PER_USER_RANGE - 1)}));
|
||||
final Set<UidRange> vpnRange2 = Collections.singleton(new UidRange(MOCK_UID2, MOCK_UID2));
|
||||
|
||||
// When VPN is connected, expect a rule to be set up for user app MOCK_UID1
|
||||
mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange1, VPN_UID);
|
||||
verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
|
||||
aryEq(new int[] {MOCK_UID1}));
|
||||
|
||||
reset(mNetdService);
|
||||
|
||||
// When MOCK_UID1 package is uninstalled and reinstalled, expect Netd to be updated
|
||||
mPermissionMonitor.onPackageRemoved(UserHandle.getUid(MOCK_USER1, MOCK_UID1));
|
||||
verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
|
||||
mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, UserHandle.getUid(MOCK_USER1, MOCK_UID1));
|
||||
verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
|
||||
aryEq(new int[] {MOCK_UID1}));
|
||||
|
||||
reset(mNetdService);
|
||||
|
||||
// During VPN uid update (vpnRange1 -> vpnRange2), ConnectivityService first deletes the
|
||||
// old UID rules then adds the new ones. Expect netd to be updated
|
||||
mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange1, VPN_UID);
|
||||
verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
|
||||
mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange2, VPN_UID);
|
||||
verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
|
||||
aryEq(new int[] {MOCK_UID2}));
|
||||
|
||||
reset(mNetdService);
|
||||
|
||||
// When VPN is disconnected, expect rules to be torn down
|
||||
mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange2, VPN_UID);
|
||||
verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID2}));
|
||||
assertNull(mPermissionMonitor.getVpnUidRanges("tun0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception {
|
||||
when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
|
||||
Arrays.asList(new PackageInfo[] {
|
||||
buildPackageInfo(true, SYSTEM_UID1, MOCK_USER1),
|
||||
buildPackageInfo(false, VPN_UID, MOCK_USER1)
|
||||
}));
|
||||
when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS))).thenReturn(
|
||||
buildPackageInfo(false, MOCK_UID1, MOCK_USER1));
|
||||
|
||||
mPermissionMonitor.startMonitoring();
|
||||
final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(MOCK_USER1));
|
||||
mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange, VPN_UID);
|
||||
|
||||
// Newly-installed package should have uid rules added
|
||||
mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, UserHandle.getUid(MOCK_USER1, MOCK_UID1));
|
||||
verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
|
||||
aryEq(new int[] {MOCK_UID1}));
|
||||
|
||||
// Removed package should have its uid rules removed
|
||||
mPermissionMonitor.onPackageRemoved(UserHandle.getUid(MOCK_USER1, MOCK_UID1));
|
||||
verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
|
||||
}
|
||||
|
||||
|
||||
// Normal package add/remove operations will trigger multiple intent for uids corresponding to
|
||||
// each user. To simulate generic package operations, the onPackageAdded/Removed will need to be
|
||||
// called multiple times with the uid corresponding to each user.
|
||||
|
||||
Reference in New Issue
Block a user