Whitelist packages from VPN lockdown.
Bug: 77468593 Test: atest com.android.server.connectivity.VpnTest Change-Id: I9119c139ab07a761ce5dfd1365b70eb905fd32dc
This commit is contained in:
@@ -1014,20 +1014,54 @@ public class ConnectivityManager {
|
||||
* to remove an existing always-on VPN configuration.
|
||||
* @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
|
||||
* {@code false} otherwise.
|
||||
* @param lockdownWhitelist The list of packages that are allowed to access network directly
|
||||
* when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
|
||||
* this method must be called when a package that should be whitelisted is installed or
|
||||
* uninstalled.
|
||||
* @return {@code true} if the package is set as always-on VPN controller;
|
||||
* {@code false} otherwise.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
|
||||
public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
|
||||
boolean lockdownEnabled) {
|
||||
boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist) {
|
||||
try {
|
||||
return mService.setAlwaysOnVpnPackage(userId, vpnPackage, lockdownEnabled);
|
||||
return mService.setAlwaysOnVpnPackage(
|
||||
userId, vpnPackage, lockdownEnabled, lockdownWhitelist);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures an always-on VPN connection through a specific application.
|
||||
* This connection is automatically granted and persisted after a reboot.
|
||||
*
|
||||
* <p>The designated package should declare a {@link VpnService} in its
|
||||
* manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
|
||||
* otherwise the call will fail.
|
||||
*
|
||||
* @param userId The identifier of the user to set an always-on VPN for.
|
||||
* @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
|
||||
* to remove an existing always-on VPN configuration.
|
||||
* @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
|
||||
* {@code false} otherwise.
|
||||
* @return {@code true} if the package is set as always-on VPN controller;
|
||||
* {@code false} otherwise.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
|
||||
public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
|
||||
boolean lockdownEnabled) {
|
||||
try {
|
||||
return mService.setAlwaysOnVpnPackage(
|
||||
userId, vpnPackage, lockdownEnabled, /* whitelist */ null);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package name of the currently set always-on VPN application.
|
||||
* If there is no always-on VPN set, or the VPN is provided by the system instead
|
||||
* of by an app, {@code null} will be returned.
|
||||
@@ -1036,6 +1070,7 @@ public class ConnectivityManager {
|
||||
* or {@code null} if none is set.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
|
||||
public String getAlwaysOnVpnPackageForUser(int userId) {
|
||||
try {
|
||||
return mService.getAlwaysOnVpnPackage(userId);
|
||||
@@ -1044,6 +1079,36 @@ public class ConnectivityManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether always-on VPN is in lockdown mode.
|
||||
*
|
||||
* @hide
|
||||
**/
|
||||
@RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
|
||||
public boolean isVpnLockdownEnabled(int userId) {
|
||||
try {
|
||||
return mService.isVpnLockdownEnabled(userId);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of packages that are allowed to access network when always-on VPN is in
|
||||
* lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
|
||||
*
|
||||
* @hide
|
||||
**/
|
||||
@RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
|
||||
public List<String> getVpnLockdownWhitelist(int userId) {
|
||||
try {
|
||||
return mService.getVpnLockdownWhitelist(userId);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns details about the currently active default data network
|
||||
* for a given uid. This is for internal use only to avoid spying
|
||||
|
||||
@@ -125,8 +125,11 @@ interface IConnectivityManager
|
||||
|
||||
boolean updateLockdownVpn();
|
||||
boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
|
||||
boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown);
|
||||
boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
|
||||
in List<String> lockdownWhitelist);
|
||||
String getAlwaysOnVpnPackage(int userId);
|
||||
boolean isVpnLockdownEnabled(int userId);
|
||||
List<String> getVpnLockdownWhitelist(int userId);
|
||||
|
||||
int checkMobileProvisioning(int suggestedTimeOutMs);
|
||||
|
||||
|
||||
@@ -5670,6 +5670,16 @@ public final class Settings {
|
||||
*/
|
||||
public static final String ALWAYS_ON_VPN_LOCKDOWN = "always_on_vpn_lockdown";
|
||||
|
||||
/**
|
||||
* Comma separated list of packages that are allowed to access the network when VPN is in
|
||||
* lockdown mode but not running.
|
||||
* @see #ALWAYS_ON_VPN_LOCKDOWN
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String ALWAYS_ON_VPN_LOCKDOWN_WHITELIST =
|
||||
"always_on_vpn_lockdown_whitelist";
|
||||
|
||||
/**
|
||||
* Whether applications can be installed for this user via the system's
|
||||
* {@link Intent#ACTION_INSTALL_PACKAGE} mechanism.
|
||||
|
||||
@@ -3231,6 +3231,12 @@
|
||||
android:protectionLevel="signature|privileged" />
|
||||
<uses-permission android:name="android.permission.CONTROL_VPN" />
|
||||
|
||||
<!-- Allows an application to access and modify always-on VPN configuration.
|
||||
<p>Not for use by third-party or privileged applications.
|
||||
@hide -->
|
||||
<permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<!-- Allows an application to capture audio output.
|
||||
<p>Not for use by third-party applications.</p> -->
|
||||
<permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"
|
||||
|
||||
@@ -512,6 +512,7 @@ public class SettingsBackupTest {
|
||||
Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
|
||||
Settings.Secure.ALWAYS_ON_VPN_APP,
|
||||
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
|
||||
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST,
|
||||
Settings.Secure.ANDROID_ID,
|
||||
Settings.Secure.ANR_SHOW_BACKGROUND,
|
||||
Settings.Secure.ASSISTANT,
|
||||
|
||||
@@ -1884,6 +1884,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
"ConnectivityService");
|
||||
}
|
||||
|
||||
private void enforceControlAlwaysOnVpnPermission() {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
|
||||
"ConnectivityService");
|
||||
}
|
||||
|
||||
private void enforceNetworkStackSettingsOrSetup() {
|
||||
enforceAnyPermissionOf(
|
||||
android.Manifest.permission.NETWORK_SETTINGS,
|
||||
@@ -1891,6 +1897,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
android.Manifest.permission.NETWORK_STACK);
|
||||
}
|
||||
|
||||
private void enforceNetworkStackPermission() {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.NETWORK_STACK,
|
||||
"ConnectivityService");
|
||||
}
|
||||
|
||||
private boolean checkNetworkStackPermission() {
|
||||
return PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
|
||||
android.Manifest.permission.NETWORK_STACK);
|
||||
@@ -4147,8 +4159,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown) {
|
||||
enforceConnectivityInternalPermission();
|
||||
public boolean setAlwaysOnVpnPackage(
|
||||
int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) {
|
||||
enforceControlAlwaysOnVpnPermission();
|
||||
enforceCrossUserPermission(userId);
|
||||
|
||||
synchronized (mVpns) {
|
||||
@@ -4162,11 +4175,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
|
||||
return false;
|
||||
}
|
||||
if (!vpn.setAlwaysOnPackage(packageName, lockdown)) {
|
||||
if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) {
|
||||
return false;
|
||||
}
|
||||
if (!startAlwaysOnVpn(userId)) {
|
||||
vpn.setAlwaysOnPackage(null, false);
|
||||
vpn.setAlwaysOnPackage(null, false, null);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -4175,7 +4188,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
|
||||
@Override
|
||||
public String getAlwaysOnVpnPackage(int userId) {
|
||||
enforceConnectivityInternalPermission();
|
||||
enforceControlAlwaysOnVpnPermission();
|
||||
enforceCrossUserPermission(userId);
|
||||
|
||||
synchronized (mVpns) {
|
||||
@@ -4188,6 +4201,36 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVpnLockdownEnabled(int userId) {
|
||||
enforceControlAlwaysOnVpnPermission();
|
||||
enforceCrossUserPermission(userId);
|
||||
|
||||
synchronized (mVpns) {
|
||||
Vpn vpn = mVpns.get(userId);
|
||||
if (vpn == null) {
|
||||
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
|
||||
return false;
|
||||
}
|
||||
return vpn.getLockdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getVpnLockdownWhitelist(int userId) {
|
||||
enforceControlAlwaysOnVpnPermission();
|
||||
enforceCrossUserPermission(userId);
|
||||
|
||||
synchronized (mVpns) {
|
||||
Vpn vpn = mVpns.get(userId);
|
||||
if (vpn == null) {
|
||||
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
|
||||
return null;
|
||||
}
|
||||
return vpn.getLockdownWhitelist();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int checkMobileProvisioning(int suggestedTimeOutMs) {
|
||||
// TODO: Remove? Any reason to trigger a provisioning check?
|
||||
@@ -4417,7 +4460,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
|
||||
Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
|
||||
+ userId);
|
||||
vpn.setAlwaysOnPackage(null, false);
|
||||
vpn.setAlwaysOnPackage(null, false, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6297,7 +6340,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
synchronized (mVpns) {
|
||||
final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
|
||||
if (alwaysOnPackage != null) {
|
||||
setAlwaysOnVpnPackage(userId, null, false);
|
||||
setAlwaysOnVpnPackage(userId, null, false, null);
|
||||
setVpnPackageAuthorization(alwaysOnPackage, userId, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ public class Vpn {
|
||||
.divide(BigInteger.valueOf(100));
|
||||
}
|
||||
// How many routes to evaluate before bailing and declaring this Vpn should provide
|
||||
// the INTERNET capability. This is necessary because computing the adress space is
|
||||
// the INTERNET capability. This is necessary because computing the address space is
|
||||
// O(n²) and this is running in the system service, so a limit is needed to alleviate
|
||||
// the risk of attack.
|
||||
// This is taken as a total of IPv4 + IPV6 routes for simplicity, but the algorithm
|
||||
@@ -193,6 +193,12 @@ public class Vpn {
|
||||
*/
|
||||
private boolean mLockdown = false;
|
||||
|
||||
/**
|
||||
* Set of packages in addition to the VPN app itself that can access the network directly when
|
||||
* VPN is not connected even if {@code mLockdown} is set.
|
||||
*/
|
||||
private @NonNull List<String> mLockdownWhitelist = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* List of UIDs for which networking should be blocked until VPN is ready, during brief periods
|
||||
* when VPN is not running. For example, during system startup or after a crash.
|
||||
@@ -320,9 +326,9 @@ public class Vpn {
|
||||
*
|
||||
* Used to enable/disable legacy VPN lockdown.
|
||||
*
|
||||
* This uses the same ip rule mechanism as {@link #setAlwaysOnPackage(String, boolean)};
|
||||
* previous settings from calling that function will be replaced and saved with the
|
||||
* always-on state.
|
||||
* This uses the same ip rule mechanism as
|
||||
* {@link #setAlwaysOnPackage(String, boolean, List<String>)}; previous settings from calling
|
||||
* that function will be replaced and saved with the always-on state.
|
||||
*
|
||||
* @param lockdown whether to prevent all traffic outside of a VPN.
|
||||
*/
|
||||
@@ -419,12 +425,14 @@ public class Vpn {
|
||||
*
|
||||
* @param packageName the package to designate as always-on VPN supplier.
|
||||
* @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
|
||||
* @param lockdownWhitelist packages to be whitelisted from lockdown.
|
||||
* @return {@code true} if the package has been set as always-on, {@code false} otherwise.
|
||||
*/
|
||||
public synchronized boolean setAlwaysOnPackage(String packageName, boolean lockdown) {
|
||||
public synchronized boolean setAlwaysOnPackage(
|
||||
String packageName, boolean lockdown, List<String> lockdownWhitelist) {
|
||||
enforceControlPermissionOrInternalCaller();
|
||||
|
||||
if (setAlwaysOnPackageInternal(packageName, lockdown)) {
|
||||
if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist)) {
|
||||
saveAlwaysOnPackage();
|
||||
return true;
|
||||
}
|
||||
@@ -439,15 +447,27 @@ public class Vpn {
|
||||
*
|
||||
* @param packageName the package to designate as always-on VPN supplier.
|
||||
* @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
|
||||
* @param lockdownWhitelist packages to be whitelisted from lockdown. This is only used if
|
||||
* {@code lockdown} is {@code true}. Packages must not contain commas.
|
||||
* @return {@code true} if the package has been set as always-on, {@code false} otherwise.
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private boolean setAlwaysOnPackageInternal(String packageName, boolean lockdown) {
|
||||
private boolean setAlwaysOnPackageInternal(
|
||||
String packageName, boolean lockdown, List<String> lockdownWhitelist) {
|
||||
if (VpnConfig.LEGACY_VPN.equals(packageName)) {
|
||||
Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lockdownWhitelist != null) {
|
||||
for (String pkg : lockdownWhitelist) {
|
||||
if (pkg.contains(",")) {
|
||||
Log.w(TAG, "Not setting always-on vpn, invalid whitelisted package: " + pkg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (packageName != null) {
|
||||
// Pre-authorize new always-on VPN package.
|
||||
if (!setPackageAuthorization(packageName, true)) {
|
||||
@@ -460,13 +480,18 @@ public class Vpn {
|
||||
}
|
||||
|
||||
mLockdown = (mAlwaysOn && lockdown);
|
||||
mLockdownWhitelist = (mLockdown && lockdownWhitelist != null)
|
||||
? Collections.unmodifiableList(new ArrayList<>(lockdownWhitelist))
|
||||
: Collections.emptyList();
|
||||
|
||||
if (isCurrentPreparedPackage(packageName)) {
|
||||
updateAlwaysOnNotification(mNetworkInfo.getDetailedState());
|
||||
setVpnForcedLocked(mLockdown);
|
||||
} else {
|
||||
// Prepare this app. The notification will update as a side-effect of updateState().
|
||||
// It also calls setVpnForcedLocked().
|
||||
prepareInternal(packageName);
|
||||
}
|
||||
setVpnForcedLocked(mLockdown);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -478,13 +503,19 @@ public class Vpn {
|
||||
* @return the package name of the VPN controller responsible for always-on VPN,
|
||||
* or {@code null} if none is set or always-on VPN is controlled through
|
||||
* lockdown instead.
|
||||
* @hide
|
||||
*/
|
||||
public synchronized String getAlwaysOnPackage() {
|
||||
enforceControlPermissionOrInternalCaller();
|
||||
return (mAlwaysOn ? mPackage : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an immutable list of packages whitelisted from always-on VPN lockdown.
|
||||
*/
|
||||
public synchronized List<String> getLockdownWhitelist() {
|
||||
return mLockdown ? mLockdownWhitelist : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the always-on package and lockdown config into Settings.Secure
|
||||
*/
|
||||
@@ -496,6 +527,9 @@ public class Vpn {
|
||||
getAlwaysOnPackage(), mUserHandle);
|
||||
mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
|
||||
(mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
|
||||
mSystemServices.settingsSecurePutStringForUser(
|
||||
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST,
|
||||
String.join(",", mLockdownWhitelist), mUserHandle);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
@@ -512,7 +546,11 @@ public class Vpn {
|
||||
Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle);
|
||||
final boolean alwaysOnLockdown = mSystemServices.settingsSecureGetIntForUser(
|
||||
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserHandle) != 0;
|
||||
setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown);
|
||||
final String whitelistString = mSystemServices.settingsSecureGetStringForUser(
|
||||
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, mUserHandle);
|
||||
final List<String> whitelistedPackages = TextUtils.isEmpty(whitelistString)
|
||||
? Collections.emptyList() : Arrays.asList(whitelistString.split(","));
|
||||
setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown, whitelistedPackages);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
@@ -532,7 +570,7 @@ public class Vpn {
|
||||
}
|
||||
// Remove always-on VPN if it's not supported.
|
||||
if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
|
||||
setAlwaysOnPackage(null, false);
|
||||
setAlwaysOnPackage(null, false, null);
|
||||
return false;
|
||||
}
|
||||
// Skip if the service is already established. This isn't bulletproof: it's not bound
|
||||
@@ -1249,9 +1287,10 @@ public class Vpn {
|
||||
}
|
||||
|
||||
/**
|
||||
* Restrict network access from all UIDs affected by this {@link Vpn}, apart from the VPN
|
||||
* service app itself, to only sockets that have had {@code protect()} called on them. All
|
||||
* non-VPN traffic is blocked via a {@code PROHIBIT} response from the kernel.
|
||||
* Restricts network access from all UIDs affected by this {@link Vpn}, apart from the VPN
|
||||
* service app itself and whitelisted packages, to only sockets that have had {@code protect()}
|
||||
* called on them. All non-VPN traffic is blocked via a {@code PROHIBIT} response from the
|
||||
* kernel.
|
||||
*
|
||||
* The exception for the VPN UID isn't technically necessary -- setup should use protected
|
||||
* sockets -- but in practice it saves apps that don't protect their sockets from breaking.
|
||||
@@ -1267,8 +1306,13 @@ public class Vpn {
|
||||
*/
|
||||
@GuardedBy("this")
|
||||
private void setVpnForcedLocked(boolean enforce) {
|
||||
final List<String> exemptedPackages =
|
||||
isNullOrLegacyVpn(mPackage) ? null : Collections.singletonList(mPackage);
|
||||
final List<String> exemptedPackages;
|
||||
if (isNullOrLegacyVpn(mPackage)) {
|
||||
exemptedPackages = null;
|
||||
} else {
|
||||
exemptedPackages = new ArrayList<>(mLockdownWhitelist);
|
||||
exemptedPackages.add(mPackage);
|
||||
}
|
||||
final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers);
|
||||
|
||||
Set<UidRange> addedRanges = Collections.emptySet();
|
||||
|
||||
@@ -16,10 +16,6 @@
|
||||
|
||||
package com.android.server.pm;
|
||||
|
||||
import com.google.android.collect.Sets;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
@@ -42,6 +38,10 @@ import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import com.google.android.collect.Sets;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
@@ -660,6 +660,7 @@ public class UserRestrictionsUtils {
|
||||
|
||||
case android.provider.Settings.Secure.ALWAYS_ON_VPN_APP:
|
||||
case android.provider.Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN:
|
||||
case android.provider.Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST:
|
||||
// Whitelist system uid (ConnectivityService) and root uid to change always-on vpn
|
||||
final int appId = UserHandle.getAppId(callingUid);
|
||||
if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) {
|
||||
|
||||
@@ -246,17 +246,17 @@ public class VpnTest {
|
||||
assertFalse(vpn.getLockdown());
|
||||
|
||||
// Set always-on without lockdown.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
|
||||
assertTrue(vpn.getAlwaysOn());
|
||||
assertFalse(vpn.getLockdown());
|
||||
|
||||
// Set always-on with lockdown.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
|
||||
assertTrue(vpn.getAlwaysOn());
|
||||
assertTrue(vpn.getLockdown());
|
||||
|
||||
// Remove always-on configuration.
|
||||
assertTrue(vpn.setAlwaysOnPackage(null, false));
|
||||
assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
|
||||
assertFalse(vpn.getAlwaysOn());
|
||||
assertFalse(vpn.getLockdown());
|
||||
}
|
||||
@@ -270,11 +270,11 @@ public class VpnTest {
|
||||
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
|
||||
|
||||
// Set always-on without lockdown.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
|
||||
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
|
||||
|
||||
// Set always-on with lockdown.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
|
||||
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
|
||||
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
|
||||
@@ -283,7 +283,7 @@ public class VpnTest {
|
||||
assertUnblocked(vpn, user.start + PKG_UIDS[1]);
|
||||
|
||||
// Switch to another app.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
|
||||
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
|
||||
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
|
||||
@@ -296,6 +296,87 @@ public class VpnTest {
|
||||
assertUnblocked(vpn, user.start + PKG_UIDS[3]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLockdownWhitelist() throws Exception {
|
||||
final Vpn vpn = createVpn(primaryUser.id);
|
||||
final UidRange user = UidRange.createForUser(primaryUser.id);
|
||||
|
||||
// Set always-on with lockdown and whitelist app PKGS[2] from lockdown.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2])));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
|
||||
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
|
||||
new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
|
||||
}));
|
||||
assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
|
||||
assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
|
||||
|
||||
// Change whitelisted app to PKGS[3].
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3])));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
|
||||
new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
|
||||
}));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
|
||||
new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1),
|
||||
new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
|
||||
}));
|
||||
assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2]);
|
||||
assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]);
|
||||
|
||||
// Change the VPN app.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3])));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
|
||||
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
|
||||
new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
|
||||
}));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
|
||||
new UidRange(user.start, user.start + PKG_UIDS[0] - 1),
|
||||
new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1)
|
||||
}));
|
||||
assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
|
||||
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
|
||||
|
||||
// Remove the whitelist.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
|
||||
new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
|
||||
new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
|
||||
}));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
|
||||
new UidRange(user.start + PKG_UIDS[0] + 1, user.stop),
|
||||
}));
|
||||
assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2],
|
||||
user.start + PKG_UIDS[3]);
|
||||
assertUnblocked(vpn, user.start + PKG_UIDS[0]);
|
||||
|
||||
// Add the whitelist.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1])));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
|
||||
new UidRange(user.start + PKG_UIDS[0] + 1, user.stop)
|
||||
}));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
|
||||
new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
|
||||
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
|
||||
}));
|
||||
assertBlocked(vpn, user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
|
||||
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
|
||||
|
||||
// Try whitelisting a package with a comma, should be rejected.
|
||||
assertFalse(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList("a.b,c.d")));
|
||||
|
||||
// Pass a non-existent packages in the whitelist, they (and only they) should be ignored.
|
||||
// Whitelisted package should change from PGKS[1] to PKGS[2].
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true,
|
||||
Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{
|
||||
new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
|
||||
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
|
||||
}));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[]{
|
||||
new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[2] - 1),
|
||||
new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLockdownAddingAProfile() throws Exception {
|
||||
final Vpn vpn = createVpn(primaryUser.id);
|
||||
@@ -310,7 +391,7 @@ public class VpnTest {
|
||||
final UidRange profile = UidRange.createForUser(tempProfile.id);
|
||||
|
||||
// Set lockdown.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
|
||||
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
|
||||
new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
|
||||
new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
|
||||
@@ -436,7 +517,7 @@ public class VpnTest {
|
||||
.cancelAsUser(anyString(), anyInt(), eq(userHandle));
|
||||
|
||||
// Start showing a notification for disconnected once always-on.
|
||||
vpn.setAlwaysOnPackage(PKGS[0], false);
|
||||
vpn.setAlwaysOnPackage(PKGS[0], false, null);
|
||||
order.verify(mNotificationManager)
|
||||
.notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
|
||||
|
||||
@@ -450,7 +531,7 @@ public class VpnTest {
|
||||
.notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
|
||||
|
||||
// Notification should be cleared after unsetting always-on package.
|
||||
vpn.setAlwaysOnPackage(null, false);
|
||||
vpn.setAlwaysOnPackage(null, false, null);
|
||||
order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
|
||||
}
|
||||
|
||||
@@ -583,7 +664,9 @@ public class VpnTest {
|
||||
doAnswer(invocation -> {
|
||||
final String appName = (String) invocation.getArguments()[0];
|
||||
final int userId = (int) invocation.getArguments()[1];
|
||||
return UserHandle.getUid(userId, packages.get(appName));
|
||||
Integer appId = packages.get(appName);
|
||||
if (appId == null) throw new PackageManager.NameNotFoundException(appName);
|
||||
return UserHandle.getUid(userId, appId);
|
||||
}).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt());
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user