1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.connectivity; 18 19 import static android.content.pm.UserInfo.FLAG_ADMIN; 20 import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; 21 import static android.content.pm.UserInfo.FLAG_PRIMARY; 22 import static android.content.pm.UserInfo.FLAG_RESTRICTED; 23 import static org.mockito.AdditionalMatchers.*; 24 import static org.mockito.Mockito.*; 25 26 import android.annotation.UserIdInt; 27 import android.app.AppOpsManager; 28 import android.app.NotificationManager; 29 import android.content.Context; 30 import android.content.pm.ApplicationInfo; 31 import android.content.pm.PackageManager; 32 import android.content.pm.ResolveInfo; 33 import android.content.pm.ServiceInfo; 34 import android.content.pm.UserInfo; 35 import android.net.NetworkInfo.DetailedState; 36 import android.net.UidRange; 37 import android.net.VpnService; 38 import android.os.Build.VERSION_CODES; 39 import android.os.Bundle; 40 import android.os.INetworkManagementService; 41 import android.os.Looper; 42 import android.os.UserHandle; 43 import android.os.UserManager; 44 import android.test.AndroidTestCase; 45 import android.test.suitebuilder.annotation.SmallTest; 46 import android.util.ArrayMap; 47 import android.util.ArraySet; 48 49 import com.android.internal.net.VpnConfig; 50 51 import org.mockito.Answers; 52 import org.mockito.InOrder; 53 import org.mockito.Mock; 54 import org.mockito.MockitoAnnotations; 55 56 import java.util.ArrayList; 57 import java.util.Arrays; 58 import java.util.Collections; 59 import java.util.Map; 60 import java.util.Set; 61 62 /** 63 * Tests for {@link Vpn}. 64 * 65 * Build, install and run with: 66 * runtest --path java/com/android/server/connectivity/VpnTest.java 67 */ 68 public class VpnTest extends AndroidTestCase { 69 private static final String TAG = "VpnTest"; 70 71 // Mock users 72 static final UserInfo primaryUser = new UserInfo(27, "Primary", FLAG_ADMIN | FLAG_PRIMARY); 73 static final UserInfo secondaryUser = new UserInfo(15, "Secondary", FLAG_ADMIN); 74 static final UserInfo restrictedProfileA = new UserInfo(40, "RestrictedA", FLAG_RESTRICTED); 75 static final UserInfo restrictedProfileB = new UserInfo(42, "RestrictedB", FLAG_RESTRICTED); 76 static final UserInfo managedProfileA = new UserInfo(45, "ManagedA", FLAG_MANAGED_PROFILE); 77 static { 78 restrictedProfileA.restrictedProfileParentId = primaryUser.id; 79 restrictedProfileB.restrictedProfileParentId = secondaryUser.id; 80 managedProfileA.profileGroupId = primaryUser.id; 81 } 82 83 /** 84 * Names and UIDs for some fake packages. Important points: 85 * - UID is ordered increasing. 86 * - One pair of packages have consecutive UIDs. 87 */ 88 static final String[] PKGS = {"com.example", "org.example", "net.example", "web.vpn"}; 89 static final int[] PKG_UIDS = {66, 77, 78, 400}; 90 91 // Mock packages 92 static final Map<String, Integer> mPackages = new ArrayMap<>(); 93 static { 94 for (int i = 0; i < PKGS.length; i++) { 95 mPackages.put(PKGS[i], PKG_UIDS[i]); 96 } 97 } 98 99 @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; 100 @Mock private UserManager mUserManager; 101 @Mock private PackageManager mPackageManager; 102 @Mock private INetworkManagementService mNetService; 103 @Mock private AppOpsManager mAppOps; 104 @Mock private NotificationManager mNotificationManager; 105 @Mock private Vpn.SystemServices mSystemServices; 106 107 @Override 108 public void setUp() throws Exception { 109 MockitoAnnotations.initMocks(this); 110 111 when(mContext.getPackageManager()).thenReturn(mPackageManager); 112 setMockedPackages(mPackages); 113 114 when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName()); 115 when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); 116 when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps); 117 when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE))) 118 .thenReturn(mNotificationManager); 119 120 // Used by {@link Notification.Builder} 121 ApplicationInfo applicationInfo = new ApplicationInfo(); 122 applicationInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT; 123 when(mContext.getApplicationInfo()).thenReturn(applicationInfo); 124 125 doNothing().when(mNetService).registerObserver(any()); 126 } 127 128 @SmallTest 129 public void testRestrictedProfilesAreAddedToVpn() { 130 setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB); 131 132 final Vpn vpn = createVpn(primaryUser.id); 133 final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, 134 null, null); 135 136 assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] { 137 UidRange.createForUser(primaryUser.id), 138 UidRange.createForUser(restrictedProfileA.id) 139 })), ranges); 140 } 141 142 @SmallTest 143 public void testManagedProfilesAreNotAddedToVpn() { 144 setMockedUsers(primaryUser, managedProfileA); 145 146 final Vpn vpn = createVpn(primaryUser.id); 147 final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, 148 null, null); 149 150 assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] { 151 UidRange.createForUser(primaryUser.id) 152 })), ranges); 153 } 154 155 @SmallTest 156 public void testAddUserToVpnOnlyAddsOneUser() { 157 setMockedUsers(primaryUser, restrictedProfileA, managedProfileA); 158 159 final Vpn vpn = createVpn(primaryUser.id); 160 final Set<UidRange> ranges = new ArraySet<>(); 161 vpn.addUserToRanges(ranges, primaryUser.id, null, null); 162 163 assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] { 164 UidRange.createForUser(primaryUser.id) 165 })), ranges); 166 } 167 168 @SmallTest 169 public void testUidWhiteAndBlacklist() throws Exception { 170 final Vpn vpn = createVpn(primaryUser.id); 171 final UidRange user = UidRange.createForUser(primaryUser.id); 172 final String[] packages = {PKGS[0], PKGS[1], PKGS[2]}; 173 174 // Whitelist 175 final Set<UidRange> allow = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, 176 Arrays.asList(packages), null); 177 assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] { 178 new UidRange(user.start + PKG_UIDS[0], user.start + PKG_UIDS[0]), 179 new UidRange(user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]) 180 })), allow); 181 182 // Blacklist 183 final Set<UidRange> disallow = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, 184 null, Arrays.asList(packages)); 185 assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] { 186 new UidRange(user.start, user.start + PKG_UIDS[0] - 1), 187 new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1), 188 /* Empty range between UIDS[1] and UIDS[2], should be excluded, */ 189 new UidRange(user.start + PKG_UIDS[2] + 1, user.stop) 190 })), disallow); 191 } 192 193 @SmallTest 194 public void testLockdownChangingPackage() throws Exception { 195 final Vpn vpn = createVpn(primaryUser.id); 196 final UidRange user = UidRange.createForUser(primaryUser.id); 197 198 // Default state. 199 assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); 200 201 // Set always-on without lockdown. 202 assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false)); 203 assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); 204 205 // Set always-on with lockdown. 206 assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true)); 207 verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { 208 new UidRange(user.start, user.start + PKG_UIDS[1] - 1), 209 new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) 210 })); 211 assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]); 212 assertUnblocked(vpn, user.start + PKG_UIDS[1]); 213 214 // Switch to another app. 215 assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true)); 216 verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { 217 new UidRange(user.start, user.start + PKG_UIDS[1] - 1), 218 new UidRange(user.start + PKG_UIDS[1] + 1, user.stop) 219 })); 220 verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { 221 new UidRange(user.start, user.start + PKG_UIDS[3] - 1), 222 new UidRange(user.start + PKG_UIDS[3] + 1, user.stop) 223 })); 224 assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]); 225 assertUnblocked(vpn, user.start + PKG_UIDS[3]); 226 } 227 228 @SmallTest 229 public void testLockdownAddingAProfile() throws Exception { 230 final Vpn vpn = createVpn(primaryUser.id); 231 setMockedUsers(primaryUser); 232 233 // Make a copy of the restricted profile, as we're going to mark it deleted halfway through. 234 final UserInfo tempProfile = new UserInfo(restrictedProfileA.id, restrictedProfileA.name, 235 restrictedProfileA.flags); 236 tempProfile.restrictedProfileParentId = primaryUser.id; 237 238 final UidRange user = UidRange.createForUser(primaryUser.id); 239 final UidRange profile = UidRange.createForUser(tempProfile.id); 240 241 // Set lockdown. 242 assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true)); 243 verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { 244 new UidRange(user.start, user.start + PKG_UIDS[3] - 1), 245 new UidRange(user.start + PKG_UIDS[3] + 1, user.stop) 246 })); 247 248 // Verify restricted user isn't affected at first. 249 assertUnblocked(vpn, profile.start + PKG_UIDS[0]); 250 251 // Add the restricted user. 252 setMockedUsers(primaryUser, tempProfile); 253 vpn.onUserAdded(tempProfile.id); 254 verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] { 255 new UidRange(profile.start, profile.start + PKG_UIDS[3] - 1), 256 new UidRange(profile.start + PKG_UIDS[3] + 1, profile.stop) 257 })); 258 259 // Remove the restricted user. 260 tempProfile.partial = true; 261 vpn.onUserRemoved(tempProfile.id); 262 verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] { 263 new UidRange(profile.start, profile.start + PKG_UIDS[3] - 1), 264 new UidRange(profile.start + PKG_UIDS[3] + 1, profile.stop) 265 })); 266 } 267 268 @SmallTest 269 public void testLockdownRuleRepeatability() throws Exception { 270 final Vpn vpn = createVpn(primaryUser.id); 271 272 // Given legacy lockdown is already enabled, 273 vpn.setLockdown(true); 274 verify(mNetService, times(1)).setAllowOnlyVpnForUids( 275 eq(true), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)})); 276 277 // Enabling legacy lockdown twice should do nothing. 278 vpn.setLockdown(true); 279 verify(mNetService, times(1)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class)); 280 281 // And disabling should remove the rules exactly once. 282 vpn.setLockdown(false); 283 verify(mNetService, times(1)).setAllowOnlyVpnForUids( 284 eq(false), aryEq(new UidRange[] {UidRange.createForUser(primaryUser.id)})); 285 286 // Removing the lockdown again should have no effect. 287 vpn.setLockdown(false); 288 verify(mNetService, times(2)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class)); 289 } 290 291 @SmallTest 292 public void testLockdownRuleReversibility() throws Exception { 293 final Vpn vpn = createVpn(primaryUser.id); 294 295 final UidRange[] entireUser = { 296 UidRange.createForUser(primaryUser.id) 297 }; 298 final UidRange[] exceptPkg0 = { 299 new UidRange(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1), 300 new UidRange(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop) 301 }; 302 303 final InOrder order = inOrder(mNetService); 304 305 // Given lockdown is enabled with no package (legacy VPN), 306 vpn.setLockdown(true); 307 order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser)); 308 309 // When a new VPN package is set the rules should change to cover that package. 310 vpn.prepare(null, PKGS[0]); 311 order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(entireUser)); 312 order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(exceptPkg0)); 313 314 // When that VPN package is unset, everything should be undone again in reverse. 315 vpn.prepare(null, VpnConfig.LEGACY_VPN); 316 order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(exceptPkg0)); 317 order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser)); 318 } 319 320 @SmallTest 321 public void testIsAlwaysOnPackageSupported() throws Exception { 322 final Vpn vpn = createVpn(primaryUser.id); 323 324 ApplicationInfo appInfo = new ApplicationInfo(); 325 when(mPackageManager.getApplicationInfoAsUser(eq(PKGS[0]), anyInt(), eq(primaryUser.id))) 326 .thenReturn(appInfo); 327 328 ServiceInfo svcInfo = new ServiceInfo(); 329 ResolveInfo resInfo = new ResolveInfo(); 330 resInfo.serviceInfo = svcInfo; 331 when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA), 332 eq(primaryUser.id))) 333 .thenReturn(Collections.singletonList(resInfo)); 334 335 // null package name should return false 336 assertFalse(vpn.isAlwaysOnPackageSupported(null)); 337 338 // Pre-N apps are not supported 339 appInfo.targetSdkVersion = VERSION_CODES.M; 340 assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0])); 341 342 // N+ apps are supported by default 343 appInfo.targetSdkVersion = VERSION_CODES.N; 344 assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0])); 345 346 // Apps that opt out explicitly are not supported 347 appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT; 348 Bundle metaData = new Bundle(); 349 metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false); 350 svcInfo.metaData = metaData; 351 assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0])); 352 } 353 354 @SmallTest 355 public void testNotificationShownForAlwaysOnApp() { 356 final UserHandle userHandle = UserHandle.of(primaryUser.id); 357 final Vpn vpn = createVpn(primaryUser.id); 358 setMockedUsers(primaryUser); 359 360 final InOrder order = inOrder(mNotificationManager); 361 362 // Don't show a notification for regular disconnected states. 363 vpn.updateState(DetailedState.DISCONNECTED, TAG); 364 order.verify(mNotificationManager, atLeastOnce()) 365 .cancelAsUser(anyString(), anyInt(), eq(userHandle)); 366 367 // Start showing a notification for disconnected once always-on. 368 vpn.setAlwaysOnPackage(PKGS[0], false); 369 order.verify(mNotificationManager) 370 .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle)); 371 372 // Stop showing the notification once connected. 373 vpn.updateState(DetailedState.CONNECTED, TAG); 374 order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle)); 375 376 // Show the notification if we disconnect again. 377 vpn.updateState(DetailedState.DISCONNECTED, TAG); 378 order.verify(mNotificationManager) 379 .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle)); 380 381 // Notification should be cleared after unsetting always-on package. 382 vpn.setAlwaysOnPackage(null, false); 383 order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle)); 384 } 385 386 /** 387 * Mock some methods of vpn object. 388 */ 389 private Vpn createVpn(@UserIdInt int userId) { 390 return new Vpn(Looper.myLooper(), mContext, mNetService, userId, mSystemServices); 391 } 392 393 private static void assertBlocked(Vpn vpn, int... uids) { 394 for (int uid : uids) { 395 assertTrue("Uid " + uid + " should be blocked", vpn.isBlockingUid(uid)); 396 } 397 } 398 399 private static void assertUnblocked(Vpn vpn, int... uids) { 400 for (int uid : uids) { 401 assertFalse("Uid " + uid + " should not be blocked", vpn.isBlockingUid(uid)); 402 } 403 } 404 405 /** 406 * Populate {@link #mUserManager} with a list of fake users. 407 */ 408 private void setMockedUsers(UserInfo... users) { 409 final Map<Integer, UserInfo> userMap = new ArrayMap<>(); 410 for (UserInfo user : users) { 411 userMap.put(user.id, user); 412 } 413 414 /** 415 * @see UserManagerService#getUsers(boolean) 416 */ 417 doAnswer(invocation -> { 418 final boolean excludeDying = (boolean) invocation.getArguments()[0]; 419 final ArrayList<UserInfo> result = new ArrayList<>(users.length); 420 for (UserInfo ui : users) { 421 if (!excludeDying || (ui.isEnabled() && !ui.partial)) { 422 result.add(ui); 423 } 424 } 425 return result; 426 }).when(mUserManager).getUsers(anyBoolean()); 427 428 doAnswer(invocation -> { 429 final int id = (int) invocation.getArguments()[0]; 430 return userMap.get(id); 431 }).when(mUserManager).getUserInfo(anyInt()); 432 433 doAnswer(invocation -> { 434 final int id = (int) invocation.getArguments()[0]; 435 return (userMap.get(id).flags & UserInfo.FLAG_ADMIN) != 0; 436 }).when(mUserManager).canHaveRestrictedProfile(anyInt()); 437 } 438 439 /** 440 * Populate {@link #mPackageManager} with a fake packageName-to-UID mapping. 441 */ 442 private void setMockedPackages(final Map<String, Integer> packages) { 443 try { 444 doAnswer(invocation -> { 445 final String appName = (String) invocation.getArguments()[0]; 446 final int userId = (int) invocation.getArguments()[1]; 447 return UserHandle.getUid(userId, packages.get(appName)); 448 }).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt()); 449 } catch (Exception e) { 450 } 451 } 452 } 453