Allow getting/setting app exclusion list for specific vpn profile
Provide a way for the system Setting app or other similar system apps to set the app exclusion list for the specific VPN from VpnManager. The list affects only VPN provisioned from VpnManager to keep the design consistence for app using VpnService. The list is stored in the keystore for its persistence. Bug: 192078259 Test: atest FrameworksNetTests CtsNetTestCases Change-Id: I157823866e1899b40aa36ea2c0bc4a80370108be
This commit is contained in:
committed by
Chiachang Wang
parent
823599b4a8
commit
d19b3c03d2
@@ -42,6 +42,8 @@ interface IVpnManager {
|
||||
String startVpnProfile(String packageName);
|
||||
void stopVpnProfile(String packageName);
|
||||
VpnProfileState getProvisionedVpnProfileState(String packageName);
|
||||
boolean setAppExclusionList(int userId, String vpnPackage, in List<String> excludedApps);
|
||||
List<String> getAppExclusionList(int userId, String vpnPackage);
|
||||
|
||||
/** Always-on VPN APIs */
|
||||
boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
|
||||
|
||||
@@ -594,6 +594,63 @@ public class VpnManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the application exclusion list for the specified VPN profile.
|
||||
*
|
||||
* <p>If an app in the set of excluded apps is not installed for the given user, it will be
|
||||
* skipped in the list of app exclusions. If apps are installed or removed, any active VPN will
|
||||
* have its UID set updated automatically. If the caller is not {@code userId},
|
||||
* {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
|
||||
*
|
||||
* <p>This will ONLY affect VpnManager profiles. As such, the NETWORK_SETTINGS provider MUST NOT
|
||||
* allow configuration of these options if the application has not provided a VPN profile.
|
||||
*
|
||||
* @param userId the identifier of the user to set app exclusion list
|
||||
* @param vpnPackage The package name for an installed VPN app on the device
|
||||
* @param excludedApps the app exclusion list
|
||||
* @throws IllegalStateException exception if vpn for the @code userId} is not ready yet.
|
||||
*
|
||||
* @return whether setting the list is successful or not
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(anyOf = {
|
||||
android.Manifest.permission.NETWORK_SETTINGS,
|
||||
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
android.Manifest.permission.NETWORK_STACK})
|
||||
public boolean setAppExclusionList(int userId, @NonNull String vpnPackage,
|
||||
@NonNull List<String> excludedApps) {
|
||||
try {
|
||||
return mService.setAppExclusionList(userId, vpnPackage, excludedApps);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application exclusion list for the specified VPN profile. If the caller is not
|
||||
* {@code userId}, {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission
|
||||
* is required.
|
||||
*
|
||||
* @param userId the identifier of the user to set app exclusion list
|
||||
* @param vpnPackage The package name for an installed VPN app on the device
|
||||
* @return the list of packages for the specified VPN profile or null if no corresponding VPN
|
||||
* profile configured.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(anyOf = {
|
||||
android.Manifest.permission.NETWORK_SETTINGS,
|
||||
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
android.Manifest.permission.NETWORK_STACK})
|
||||
@Nullable
|
||||
public List<String> getAppExclusionList(int userId, @NonNull String vpnPackage) {
|
||||
try {
|
||||
return mService.getAppExclusionList(userId, vpnPackage);
|
||||
} 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.
|
||||
|
||||
@@ -880,6 +880,38 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setAppExclusionList(int userId, String vpnPackage, List<String> excludedApps) {
|
||||
enforceSettingsPermission();
|
||||
enforceCrossUserPermission(userId);
|
||||
|
||||
synchronized (mVpns) {
|
||||
final Vpn vpn = mVpns.get(userId);
|
||||
if (vpn != null) {
|
||||
return vpn.setAppExclusionList(vpnPackage, excludedApps);
|
||||
} else {
|
||||
logw("User " + userId + " has no Vpn configuration");
|
||||
throw new IllegalStateException(
|
||||
"VPN for user " + userId + " not ready yet. Skipping setting the list");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAppExclusionList(int userId, String vpnPackage) {
|
||||
enforceSettingsPermission();
|
||||
enforceCrossUserPermission(userId);
|
||||
|
||||
synchronized (mVpns) {
|
||||
final Vpn vpn = mVpns.get(userId);
|
||||
if (vpn != null) {
|
||||
return vpn.getAppExclusionList(vpnPackage);
|
||||
} else {
|
||||
logw("User " + userId + " has no Vpn configuration");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void factoryReset() {
|
||||
|
||||
@@ -27,6 +27,8 @@ import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
|
||||
import static android.os.PowerWhitelistManager.REASON_VPN;
|
||||
import static android.os.UserHandle.PER_USER_RANGE;
|
||||
|
||||
import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.Manifest;
|
||||
@@ -97,6 +99,7 @@ import android.os.INetworkManagementService;
|
||||
import android.os.Looper;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.PersistableBundle;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
@@ -127,6 +130,7 @@ import com.android.net.module.util.NetworkStackConstants;
|
||||
import com.android.server.DeviceIdleInternal;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.net.BaseNetworkObserver;
|
||||
import com.android.server.vcn.util.PersistableBundleUtils;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
@@ -174,6 +178,8 @@ public class Vpn {
|
||||
private static final String VPN_PROVIDER_NAME_BASE = "VpnNetworkProvider:";
|
||||
private static final boolean LOGD = true;
|
||||
private static final String ANDROID_KEYSTORE_PROVIDER = "AndroidKeyStore";
|
||||
/** Key containing prefix of vpn app excluded list */
|
||||
@VisibleForTesting static final String VPN_APP_EXCLUDED = "VPN_APP_EXCLUDED_";
|
||||
|
||||
// Length of time (in milliseconds) that an app hosting an always-on VPN is placed on
|
||||
// the device idle allowlist during service launch and VPN bootstrap.
|
||||
@@ -2623,6 +2629,8 @@ public class Vpn {
|
||||
|
||||
mConfig.underlyingNetworks = new Network[] {network};
|
||||
|
||||
mConfig.disallowedApplications = getAppExclusionList(mPackage);
|
||||
|
||||
networkAgent = mNetworkAgent;
|
||||
|
||||
// The below must be done atomically with the mConfig update, otherwise
|
||||
@@ -3473,6 +3481,88 @@ public class Vpn {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean storeAppExclusionList(@NonNull String packageName,
|
||||
@NonNull List<String> excludedApps) {
|
||||
byte[] data;
|
||||
try {
|
||||
final PersistableBundle bundle = PersistableBundleUtils.fromList(
|
||||
excludedApps, PersistableBundleUtils.STRING_SERIALIZER);
|
||||
data = PersistableBundleUtils.toDiskStableBytes(bundle);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "problem writing into stream", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
final long oldId = Binder.clearCallingIdentity();
|
||||
try {
|
||||
getVpnProfileStore().put(getVpnAppExcludedForPackage(packageName), data);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(oldId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
String getVpnAppExcludedForPackage(String packageName) {
|
||||
return VPN_APP_EXCLUDED + mUserId + "_" + packageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the application exclusion list for the specified VPN profile.
|
||||
*
|
||||
* @param packageName the package name of the app provisioning this profile
|
||||
* @param excludedApps the list of excluded packages
|
||||
*
|
||||
* @return whether setting the list is successful or not
|
||||
*/
|
||||
public synchronized boolean setAppExclusionList(@NonNull String packageName,
|
||||
@NonNull List<String> excludedApps) {
|
||||
enforceNotRestrictedUser();
|
||||
if (!storeAppExclusionList(packageName, excludedApps)) return false;
|
||||
// Re-build and update NetworkCapabilities via NetworkAgent.
|
||||
if (mNetworkAgent != null) {
|
||||
// Only update the platform VPN
|
||||
if (isIkev2VpnRunner()) {
|
||||
mConfig.disallowedApplications = List.copyOf(excludedApps);
|
||||
mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
|
||||
.setUids(createUserAndRestrictedProfilesRanges(
|
||||
mUserId, null /* allowedApplications */, excludedApps))
|
||||
.build();
|
||||
mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application exclusion list for the specified VPN profile.
|
||||
*
|
||||
* @param packageName the package name of the app provisioning this profile
|
||||
* @return the list of excluded packages for the specified VPN profile or empty list if there is
|
||||
* no provisioned VPN profile.
|
||||
*/
|
||||
@NonNull
|
||||
public synchronized List<String> getAppExclusionList(@NonNull String packageName) {
|
||||
enforceNotRestrictedUser();
|
||||
|
||||
final long oldId = Binder.clearCallingIdentity();
|
||||
try {
|
||||
final byte[] bytes = getVpnProfileStore().get(getVpnAppExcludedForPackage(packageName));
|
||||
|
||||
if (bytes == null || bytes.length == 0) return new ArrayList<>();
|
||||
|
||||
final PersistableBundle bundle = PersistableBundleUtils.fromDiskStableBytes(bytes);
|
||||
return PersistableBundleUtils.toList(bundle, STRING_DESERIALIZER);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "problem reading from stream", e);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(oldId);
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private @VpnProfileState.State int getStateFromLegacyState(int legacyState) {
|
||||
switch (legacyState) {
|
||||
case LegacyVpnInfo.STATE_CONNECTING:
|
||||
|
||||
Reference in New Issue
Block a user