Merge "Restart Bypassable VPNs when changing lockdown mode state" am: 15e5cec7bc

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2221794

Change-Id: I524f1fc9fac900a281b66058d38ccff1ac4caac9
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Jean Chalard
2023-04-17 06:09:16 +00:00
committed by Automerger Merge Worker

View File

@@ -707,6 +707,14 @@ public class Vpn {
boolean isIpv4) { boolean isIpv4) {
return MtuUtils.getMtu(childProposals, maxMtu, underlyingMtu, isIpv4); return MtuUtils.getMtu(childProposals, maxMtu, underlyingMtu, isIpv4);
} }
/** Verify the binder calling UID is the one passed in arguments */
public void verifyCallingUidAndPackage(Context context, String packageName, int userId) {
final int callingUid = Binder.getCallingUid();
if (getAppUid(context, packageName, userId) != callingUid) {
throw new SecurityException(packageName + " does not belong to uid " + callingUid);
}
}
} }
@VisibleForTesting @VisibleForTesting
@@ -754,7 +762,7 @@ public class Vpn {
mUserManager = mContext.getSystemService(UserManager.class); mUserManager = mContext.getSystemService(UserManager.class);
mPackage = VpnConfig.LEGACY_VPN; mPackage = VpnConfig.LEGACY_VPN;
mOwnerUID = getAppUid(mPackage, mUserId); mOwnerUID = getAppUid(mContext, mPackage, mUserId);
mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(mPackage); mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(mPackage);
try { try {
@@ -851,7 +859,7 @@ public class Vpn {
} }
/** /**
* Chooses whether to force all connections to go though VPN. * Chooses whether to force all connections to go through VPN.
* *
* Used to enable/disable legacy VPN lockdown. * Used to enable/disable legacy VPN lockdown.
* *
@@ -859,7 +867,7 @@ public class Vpn {
* {@link #setAlwaysOnPackage(String, boolean, List<String>)}; previous settings from calling * {@link #setAlwaysOnPackage(String, boolean, List<String>)}; previous settings from calling
* that function will be replaced and saved with the always-on state. * that function will be replaced and saved with the always-on state.
* *
* @param lockdown whether to prevent all traffic outside of a VPN. * @param lockdown whether to prevent all traffic outside of the VPN.
*/ */
public synchronized void setLockdown(boolean lockdown) { public synchronized void setLockdown(boolean lockdown) {
enforceControlPermissionOrInternalCaller(); enforceControlPermissionOrInternalCaller();
@@ -1136,6 +1144,7 @@ public class Vpn {
mAlwaysOn = false; mAlwaysOn = false;
} }
final boolean oldLockdownState = mLockdown;
mLockdown = (mAlwaysOn && lockdown); mLockdown = (mAlwaysOn && lockdown);
mLockdownAllowlist = (mLockdown && lockdownAllowlist != null) mLockdownAllowlist = (mLockdown && lockdownAllowlist != null)
? Collections.unmodifiableList(new ArrayList<>(lockdownAllowlist)) ? Collections.unmodifiableList(new ArrayList<>(lockdownAllowlist))
@@ -1146,6 +1155,13 @@ public class Vpn {
if (isCurrentPreparedPackage(packageName)) { if (isCurrentPreparedPackage(packageName)) {
updateAlwaysOnNotification(mNetworkInfo.getDetailedState()); updateAlwaysOnNotification(mNetworkInfo.getDetailedState());
setVpnForcedLocked(mLockdown); setVpnForcedLocked(mLockdown);
// Lockdown forces the VPN to be non-bypassable (see #agentConnect) because it makes
// no sense for a VPN to be bypassable when connected but not when not connected.
// As such, changes in lockdown need to restart the agent.
if (mNetworkAgent != null && oldLockdownState != mLockdown) {
startNewNetworkAgent(mNetworkAgent, "Lockdown mode changed");
}
} else { } else {
// Prepare this app. The notification will update as a side-effect of updateState(). // Prepare this app. The notification will update as a side-effect of updateState().
// It also calls setVpnForcedLocked(). // It also calls setVpnForcedLocked().
@@ -1383,7 +1399,8 @@ public class Vpn {
// We can't just check that packageName matches mPackage, because if the app was uninstalled // We can't just check that packageName matches mPackage, because if the app was uninstalled
// and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the // and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the
// calling package may not be the same as the prepared package. Check both UID and package. // calling package may not be the same as the prepared package. Check both UID and package.
return getAppUid(packageName, mUserId) == mOwnerUID && mPackage.equals(packageName); return getAppUid(mContext, packageName, mUserId) == mOwnerUID
&& mPackage.equals(packageName);
} }
/** Prepare the VPN for the given package. Does not perform permission checks. */ /** Prepare the VPN for the given package. Does not perform permission checks. */
@@ -1424,7 +1441,7 @@ public class Vpn {
Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
mPackage = newPackage; mPackage = newPackage;
mOwnerUID = getAppUid(newPackage, mUserId); mOwnerUID = getAppUid(mContext, newPackage, mUserId);
mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(newPackage); mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(newPackage);
try { try {
mNms.allowProtect(mOwnerUID); mNms.allowProtect(mOwnerUID);
@@ -1445,7 +1462,7 @@ public class Vpn {
// Check if the caller is authorized. // Check if the caller is authorized.
enforceControlPermissionOrInternalCaller(); enforceControlPermissionOrInternalCaller();
final int uid = getAppUid(packageName, mUserId); final int uid = getAppUid(mContext, packageName, mUserId);
if (uid == -1 || VpnConfig.LEGACY_VPN.equals(packageName)) { if (uid == -1 || VpnConfig.LEGACY_VPN.equals(packageName)) {
// Authorization for nonexistent packages (or fake ones) can't be updated. // Authorization for nonexistent packages (or fake ones) can't be updated.
return false; return false;
@@ -1525,11 +1542,11 @@ public class Vpn {
|| isVpnServicePreConsented(context, packageName); || isVpnServicePreConsented(context, packageName);
} }
private int getAppUid(final String app, final int userId) { private static int getAppUid(final Context context, final String app, final int userId) {
if (VpnConfig.LEGACY_VPN.equals(app)) { if (VpnConfig.LEGACY_VPN.equals(app)) {
return Process.myUid(); return Process.myUid();
} }
PackageManager pm = mContext.getPackageManager(); PackageManager pm = context.getPackageManager();
final long token = Binder.clearCallingIdentity(); final long token = Binder.clearCallingIdentity();
try { try {
return pm.getPackageUidAsUser(app, userId); return pm.getPackageUidAsUser(app, userId);
@@ -1658,6 +1675,10 @@ public class Vpn {
*/ */
private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) { private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
// NetworkAgentConfig cannot be updated without registering a new NetworkAgent. // NetworkAgentConfig cannot be updated without registering a new NetworkAgent.
// Strictly speaking, bypassability is affected by lockdown and therefore it's possible
// it doesn't actually change even if mConfig.allowBypass changed. It might be theoretically
// possible to do handover in this case, but this is far from obvious to VPN authors and
// it's simpler if the rule is just "can't update in place if you change allow bypass".
if (oldConfig.allowBypass != mConfig.allowBypass) { if (oldConfig.allowBypass != mConfig.allowBypass) {
Log.i(TAG, "Handover not possible due to changes to allowBypass"); Log.i(TAG, "Handover not possible due to changes to allowBypass");
return false; return false;
@@ -1699,10 +1720,11 @@ public class Vpn {
mLegacyState = LegacyVpnInfo.STATE_CONNECTING; mLegacyState = LegacyVpnInfo.STATE_CONNECTING;
updateState(DetailedState.CONNECTING, "agentConnect"); updateState(DetailedState.CONNECTING, "agentConnect");
final boolean bypassable = mConfig.allowBypass && !mLockdown;
final NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig.Builder() final NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig.Builder()
.setLegacyType(ConnectivityManager.TYPE_VPN) .setLegacyType(ConnectivityManager.TYPE_VPN)
.setLegacyTypeName("VPN") .setLegacyTypeName("VPN")
.setBypassableVpn(mConfig.allowBypass && !mLockdown) .setBypassableVpn(bypassable)
.setVpnRequiresValidation(mConfig.requiresInternetValidation) .setVpnRequiresValidation(mConfig.requiresInternetValidation)
.setLocalRoutesExcludedForVpn(mConfig.excludeLocalRoutes) .setLocalRoutesExcludedForVpn(mConfig.excludeLocalRoutes)
.build(); .build();
@@ -1716,7 +1738,7 @@ public class Vpn {
capsBuilder.setTransportInfo(new VpnTransportInfo( capsBuilder.setTransportInfo(new VpnTransportInfo(
getActiveVpnType(), getActiveVpnType(),
mConfig.session, mConfig.session,
mConfig.allowBypass, bypassable,
expensive)); expensive));
// Only apps targeting Q and above can explicitly declare themselves as metered. // Only apps targeting Q and above can explicitly declare themselves as metered.
@@ -1747,6 +1769,10 @@ public class Vpn {
Binder.restoreCallingIdentity(token); Binder.restoreCallingIdentity(token);
} }
updateState(DetailedState.CONNECTED, "agentConnect"); updateState(DetailedState.CONNECTED, "agentConnect");
if (isIkev2VpnRunner()) {
final IkeSessionWrapper session = ((IkeV2VpnRunner) mVpnRunner).mSession;
if (null != session) session.setUnderpinnedNetwork(mNetworkAgent.getNetwork());
}
} }
private static boolean areLongLivedTcpConnectionsExpensive(@NonNull VpnRunner runner) { private static boolean areLongLivedTcpConnectionsExpensive(@NonNull VpnRunner runner) {
@@ -1941,7 +1967,7 @@ public class Vpn {
private SortedSet<Integer> getAppsUids(List<String> packageNames, int userId) { private SortedSet<Integer> getAppsUids(List<String> packageNames, int userId) {
SortedSet<Integer> uids = new TreeSet<>(); SortedSet<Integer> uids = new TreeSet<>();
for (String app : packageNames) { for (String app : packageNames) {
int uid = getAppUid(app, userId); int uid = getAppUid(mContext, app, userId);
if (uid != -1) uids.add(uid); if (uid != -1) uids.add(uid);
// TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
// ConnectivityServiceTest. // ConnectivityServiceTest.
@@ -3260,7 +3286,6 @@ public class Vpn {
prepareStatusIntent(); prepareStatusIntent();
} }
agentConnect(this::onValidationStatus); agentConnect(this::onValidationStatus);
mSession.setUnderpinnedNetwork(mNetworkAgent.getNetwork());
return; // Link properties are already sent. return; // Link properties are already sent.
} else { } else {
// Underlying networks also set in agentConnect() // Underlying networks also set in agentConnect()
@@ -3377,7 +3402,6 @@ public class Vpn {
if (!removedAddrs.isEmpty()) { if (!removedAddrs.isEmpty()) {
startNewNetworkAgent( startNewNetworkAgent(
mNetworkAgent, "MTU too low for IPv6; restarting network agent"); mNetworkAgent, "MTU too low for IPv6; restarting network agent");
mSession.setUnderpinnedNetwork(mNetworkAgent.getNetwork());
for (LinkAddress removed : removedAddrs) { for (LinkAddress removed : removedAddrs) {
mTunnelIface.removeAddress( mTunnelIface.removeAddress(
@@ -3656,7 +3680,7 @@ public class Vpn {
final VpnTransportInfo info = new VpnTransportInfo( final VpnTransportInfo info = new VpnTransportInfo(
getActiveVpnType(), getActiveVpnType(),
mConfig.session, mConfig.session,
mConfig.allowBypass, mConfig.allowBypass && !mLockdown,
areLongLivedTcpConnectionsExpensive(keepaliveDelaySec)); areLongLivedTcpConnectionsExpensive(keepaliveDelaySec));
final boolean ncUpdateRequired = !info.equals(mNetworkCapabilities.getTransportInfo()); final boolean ncUpdateRequired = !info.equals(mNetworkCapabilities.getTransportInfo());
if (ncUpdateRequired) { if (ncUpdateRequired) {
@@ -4513,10 +4537,7 @@ public class Vpn {
} }
private void verifyCallingUidAndPackage(String packageName) { private void verifyCallingUidAndPackage(String packageName) {
final int callingUid = Binder.getCallingUid(); mDeps.verifyCallingUidAndPackage(mContext, packageName, mUserId);
if (getAppUid(packageName, mUserId) != callingUid) {
throw new SecurityException(packageName + " does not belong to uid " + callingUid);
}
} }
@VisibleForTesting @VisibleForTesting