Loading core/java/android/os/INetworkManagementService.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -436,4 +436,6 @@ interface INetworkManagementService void addInterfaceToLocalNetwork(String iface, in List<RouteInfo> routes); void removeInterfaceFromLocalNetwork(String iface); void setAllowOnlyVpnForUids(boolean enable, in UidRange[] uidRanges); } core/java/android/provider/Settings.java +8 −0 Original line number Diff line number Diff line Loading @@ -4701,6 +4701,14 @@ public final class Settings { */ public static final String ALWAYS_ON_VPN_APP = "always_on_vpn_app"; /** * Whether to block networking outside of VPN connections while always-on is set. * @see #ALWAYS_ON_VPN_APP * * @hide */ public static final String ALWAYS_ON_VPN_LOCKDOWN = "always_on_vpn_lockdown"; /** * Whether applications can be installed for this user via the system's * {@link Intent#ACTION_INSTALL_PACKAGE} mechanism. Loading services/core/java/com/android/server/ConnectivityService.java +66 −22 Original line number Diff line number Diff line Loading @@ -915,6 +915,13 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean networkMetered; final int uidRules; synchronized (mVpns) { final Vpn vpn = mVpns.get(UserHandle.getUserId(uid)); if (vpn != null && vpn.isBlockingUid(uid)) { return true; } } final String iface = (lp == null ? "" : lp.getInterfaceName()); synchronized (mRulesLock) { networkMetered = mMeteredIfaces.contains(iface); Loading Loading @@ -3365,23 +3372,42 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** * Sets up or tears down the always-on VPN for user {@param user} as appropriate. * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform * some setup and then call {@code establish()} to connect. * * @return {@code false} in case of errors; {@code true} otherwise. * @return {@code true} if the service was started, the service was already connected, or there * was no always-on VPN to start. {@code false} otherwise. */ private boolean updateAlwaysOnVpn(int user) { final String lockdownPackage = getAlwaysOnVpnPackage(user); if (lockdownPackage == null) { private boolean startAlwaysOnVpn(int userId) { final String alwaysOnPackage; synchronized (mVpns) { Vpn vpn = mVpns.get(userId); if (vpn == null) { // Shouldn't happen as all codepaths that point here should have checked the Vpn // exists already. Slog.wtf(TAG, "User " + userId + " has no Vpn configuration"); return false; } alwaysOnPackage = vpn.getAlwaysOnPackage(); // Skip if there is no service to start. if (alwaysOnPackage == null) { return true; } // Skip if the service is already established. This isn't bulletproof: it's not bound // until after establish(), so if it's mid-setup onStartCommand will be sent twice, // which may restart the connection. if (vpn.getNetworkInfo().isConnected()) { return true; } } // Create an intent to start the VPN service declared in the app's manifest. // Start the VPN service declared in the app's manifest. Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE); serviceIntent.setPackage(lockdownPackage); serviceIntent.setPackage(alwaysOnPackage); try { return mContext.startServiceAsUser(serviceIntent, UserHandle.of(user)) != null; return mContext.startServiceAsUser(serviceIntent, UserHandle.of(userId)) != null; } catch (RuntimeException e) { Slog.w(TAG, "VpnService " + serviceIntent + " failed to start", e); return false; } } Loading @@ -3396,25 +3422,35 @@ public class ConnectivityService extends IConnectivityManager.Stub return false; } // If the current VPN package is the same as the new one, this is a no-op final String oldPackage = getAlwaysOnVpnPackage(userId); if (TextUtils.equals(oldPackage, packageName)) { return true; } synchronized (mVpns) { Vpn vpn = mVpns.get(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return false; } if (!vpn.setAlwaysOnPackage(packageName)) { // If the current VPN package is the same as the new one, this is a no-op if (TextUtils.equals(packageName, vpn.getAlwaysOnPackage())) { return true; } if (!vpn.setAlwaysOnPackage(packageName, lockdown)) { return false; } if (!updateAlwaysOnVpn(userId)) { vpn.setAlwaysOnPackage(null); if (!startAlwaysOnVpn(userId)) { vpn.setAlwaysOnPackage(null, false); return false; } // Save the configuration final long token = Binder.clearCallingIdentity(); try { final ContentResolver cr = mContext.getContentResolver(); Settings.Secure.putStringForUser(cr, Settings.Secure.ALWAYS_ON_VPN_APP, packageName, userId); Settings.Secure.putIntForUser(cr, Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, (lockdown ? 1 : 0), userId); } finally { Binder.restoreCallingIdentity(token); } } return true; } Loading Loading @@ -3685,11 +3721,18 @@ public class ConnectivityService extends IConnectivityManager.Stub } userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId); mVpns.put(userId, userVpn); final ContentResolver cr = mContext.getContentResolver(); String alwaysOnPackage = Settings.Secure.getStringForUser(cr, Settings.Secure.ALWAYS_ON_VPN_APP, userId); final boolean alwaysOnLockdown = Settings.Secure.getIntForUser(cr, Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, /* default */ 0, userId) != 0; if (alwaysOnPackage != null) { userVpn.setAlwaysOnPackage(alwaysOnPackage, alwaysOnLockdown); } } if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { updateLockdownVpn(); } else { updateAlwaysOnVpn(userId); } } Loading @@ -3700,6 +3743,7 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("Stopped user has no VPN"); return; } userVpn.onUserStopped(); mVpns.delete(userId); } } Loading Loading @@ -3729,7 +3773,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { updateLockdownVpn(); } else { updateAlwaysOnVpn(userId); startAlwaysOnVpn(userId); } } Loading services/core/java/com/android/server/NetworkManagementService.java +16 −0 Original line number Diff line number Diff line Loading @@ -1849,6 +1849,22 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } @Override public void setAllowOnlyVpnForUids(boolean add, UidRange[] uidRanges) throws ServiceSpecificException { try { mNetdService.networkRejectNonSecureVpn(add, uidRanges); } catch (ServiceSpecificException e) { Log.w(TAG, "setAllowOnlyVpnForUids(" + add + ", " + Arrays.toString(uidRanges) + ")" + ": netd command failed", e); throw e; } catch (RemoteException e) { Log.w(TAG, "setAllowOnlyVpnForUids(" + add + ", " + Arrays.toString(uidRanges) + ")" + ": netd command failed", e); throw e.rethrowAsRuntimeException(); } } @Override public void setUidCleartextNetworkPolicy(int uid, int policy) { if (Binder.getCallingUid() != uid) { Loading services/core/java/com/android/server/connectivity/Vpn.java +152 −42 Original line number Diff line number Diff line Loading @@ -66,7 +66,6 @@ import android.os.SystemClock; import android.os.SystemService; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.security.Credentials; import android.security.KeyStore; import android.text.TextUtils; Loading Loading @@ -127,6 +126,19 @@ public class Vpn { private final Looper mLooper; private final NetworkCapabilities mNetworkCapabilities; /** * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This * only applies to {@link VpnService} connections. */ private boolean mAlwaysOn = false; /** * Whether to disable traffic outside of this VPN even when the VPN is not connected. System * apps can still bypass by choosing explicit networks. Has no effect if {@link mAlwaysOn} is * not set. */ private boolean mLockdown = false; /** * List of UIDs that are set to use this VPN by default. Normally, every UID in the user is * added to this set but that can be changed by adding allowed or disallowed applications. It Loading @@ -140,6 +152,14 @@ public class Vpn { @GuardedBy("this") private Set<UidRange> mVpnUsers = null; /** * 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. * @see mLockdown */ @GuardedBy("this") private Set<UidRange> mBlockedUsers = new ArraySet<>(); // Handle of user initiating VPN. private final int mUserHandle; Loading Loading @@ -194,9 +214,10 @@ public class Vpn { * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, * otherwise the call will fail. * * @param newPackage the package to designate as always-on VPN supplier. * @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. */ public synchronized boolean setAlwaysOnPackage(String packageName) { public synchronized boolean setAlwaysOnPackage(String packageName, boolean lockdown) { enforceControlPermissionOrInternalCaller(); // Disconnect current VPN. Loading @@ -210,14 +231,9 @@ public class Vpn { prepareInternal(packageName); } // Save the new package name in Settings.Secure. final long token = Binder.clearCallingIdentity(); try { Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ALWAYS_ON_VPN_APP, packageName, mUserHandle); } finally { Binder.restoreCallingIdentity(token); } mAlwaysOn = (packageName != null); mLockdown = (mAlwaysOn && lockdown); setVpnForcedLocked(mLockdown); return true; } Loading @@ -229,14 +245,7 @@ public class Vpn { */ public synchronized String getAlwaysOnPackage() { enforceControlPermissionOrInternalCaller(); final long token = Binder.clearCallingIdentity(); try { return Settings.Secure.getStringForUser(mContext.getContentResolver(), Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle); } finally { Binder.restoreCallingIdentity(token); } return (mAlwaysOn ? mPackage : null); } /** Loading @@ -258,6 +267,11 @@ public class Vpn { * @return true if the operation is succeeded. */ public synchronized boolean prepare(String oldPackage, String newPackage) { // Stop an existing always-on VPN from being dethroned by other apps. if (mAlwaysOn && !TextUtils.equals(mPackage, newPackage)) { return false; } if (oldPackage != null) { if (getAppUid(oldPackage, mUserHandle) != mOwnerUID) { // The package doesn't match. We return false (to obtain user consent) unless the Loading @@ -281,11 +295,6 @@ public class Vpn { return true; } // Stop an existing always-on VPN from being dethroned by other apps. if (getAlwaysOnPackage() != null) { return false; } // Check that the caller is authorized. enforceControlPermission(); Loading Loading @@ -469,7 +478,7 @@ public class Vpn { mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null); NetworkMisc networkMisc = new NetworkMisc(); networkMisc.allowBypass = mConfig.allowBypass; networkMisc.allowBypass = mConfig.allowBypass && !mLockdown; long token = Binder.clearCallingIdentity(); try { Loading Loading @@ -685,7 +694,7 @@ public class Vpn { final long token = Binder.clearCallingIdentity(); List<UserInfo> users; try { users = UserManager.get(mContext).getUsers(); users = UserManager.get(mContext).getUsers(true); } finally { Binder.restoreCallingIdentity(token); } Loading Loading @@ -774,9 +783,9 @@ public class Vpn { public void onUserAdded(int userHandle) { // If the user is restricted tie them to the parent user's VPN UserInfo user = UserManager.get(mContext).getUserInfo(userHandle); if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle && mVpnUsers != null) { if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle) { synchronized(Vpn.this) { if (mVpnUsers != null) { try { addUserToRanges(mVpnUsers, userHandle, mConfig.allowedApplications, mConfig.disallowedApplications); Loading @@ -788,22 +797,108 @@ public class Vpn { Log.wtf(TAG, "Failed to add restricted user to owner", e); } } if (mAlwaysOn) { setVpnForcedLocked(mLockdown); } } } } public void onUserRemoved(int userHandle) { // clean up if restricted UserInfo user = UserManager.get(mContext).getUserInfo(userHandle); if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle && mVpnUsers != null) { if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle) { synchronized(Vpn.this) { if (mVpnUsers != null) { try { removeVpnUserLocked(userHandle); } catch (Exception e) { Log.wtf(TAG, "Failed to remove restricted user to owner", e); } } if (mAlwaysOn) { setVpnForcedLocked(mLockdown); } } } } /** * Called when the user associated with this VPN has just been stopped. */ public synchronized void onUserStopped() { // Switch off networking lockdown (if it was enabled) setVpnForcedLocked(false); mAlwaysOn = false; // Quit any active connections agentDisconnect(); } /** * 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. * * 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. * * Calling multiple times with {@param enforce} = {@code true} will recreate the set of UIDs to * block every time, and if anything has changed update using {@link #setAllowOnlyVpnForUids}. * * @param enforce {@code true} to require that all traffic under the jurisdiction of this * {@link Vpn} goes through a VPN connection or is blocked until one is * available, {@code false} to lift the requirement. * * @see #mBlockedUsers */ @GuardedBy("this") private void setVpnForcedLocked(boolean enforce) { final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers); if (enforce) { final Set<UidRange> addedRanges = createUserAndRestrictedProfilesRanges(mUserHandle, /* allowedApplications */ null, /* disallowedApplications */ Collections.singletonList(mPackage)); removedRanges.removeAll(addedRanges); addedRanges.removeAll(mBlockedUsers); setAllowOnlyVpnForUids(false, removedRanges); setAllowOnlyVpnForUids(true, addedRanges); } else { setAllowOnlyVpnForUids(false, removedRanges); } } /** * Either add or remove a list of {@link UidRange}s to the list of UIDs that are only allowed * to make connections through sockets that have had {@code protect()} called on them. * * @param enforce {@code true} to add to the blacklist, {@code false} to remove. * @param ranges {@link Collection} of {@link UidRange}s to add (if {@param enforce} is * {@code true}) or to remove. * @return {@code true} if all of the UIDs were added/removed. {@code false} otherwise, * including added ranges that already existed or removed ones that didn't. */ @GuardedBy("this") private boolean setAllowOnlyVpnForUids(boolean enforce, Collection<UidRange> ranges) { if (ranges.size() == 0) { return true; } final UidRange[] rangesArray = ranges.toArray(new UidRange[ranges.size()]); try { mNetd.setAllowOnlyVpnForUids(enforce, rangesArray); } catch (RemoteException | RuntimeException e) { Log.e(TAG, "Updating blocked=" + enforce + " for UIDs " + Arrays.toString(ranges.toArray()) + " failed", e); return false; } if (enforce) { mBlockedUsers.addAll(ranges); } else { mBlockedUsers.removeAll(ranges); } return true; } /** Loading Loading @@ -959,6 +1054,21 @@ public class Vpn { return false; } /** * @return {@code true} if the set of users blocked whilst waiting for VPN to connect includes * the UID {@param uid}, {@code false} otherwise. * * @see #mBlockedUsers */ public synchronized boolean isBlockingUid(int uid) { for (UidRange uidRange : mBlockedUsers) { if (uidRange.contains(uid)) { return true; } } return false; } private native int jniCreate(int mtu); private native String jniGetName(int tun); private native int jniSetAddresses(String interfaze, String addresses); Loading Loading
core/java/android/os/INetworkManagementService.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -436,4 +436,6 @@ interface INetworkManagementService void addInterfaceToLocalNetwork(String iface, in List<RouteInfo> routes); void removeInterfaceFromLocalNetwork(String iface); void setAllowOnlyVpnForUids(boolean enable, in UidRange[] uidRanges); }
core/java/android/provider/Settings.java +8 −0 Original line number Diff line number Diff line Loading @@ -4701,6 +4701,14 @@ public final class Settings { */ public static final String ALWAYS_ON_VPN_APP = "always_on_vpn_app"; /** * Whether to block networking outside of VPN connections while always-on is set. * @see #ALWAYS_ON_VPN_APP * * @hide */ public static final String ALWAYS_ON_VPN_LOCKDOWN = "always_on_vpn_lockdown"; /** * Whether applications can be installed for this user via the system's * {@link Intent#ACTION_INSTALL_PACKAGE} mechanism. Loading
services/core/java/com/android/server/ConnectivityService.java +66 −22 Original line number Diff line number Diff line Loading @@ -915,6 +915,13 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean networkMetered; final int uidRules; synchronized (mVpns) { final Vpn vpn = mVpns.get(UserHandle.getUserId(uid)); if (vpn != null && vpn.isBlockingUid(uid)) { return true; } } final String iface = (lp == null ? "" : lp.getInterfaceName()); synchronized (mRulesLock) { networkMetered = mMeteredIfaces.contains(iface); Loading Loading @@ -3365,23 +3372,42 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** * Sets up or tears down the always-on VPN for user {@param user} as appropriate. * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform * some setup and then call {@code establish()} to connect. * * @return {@code false} in case of errors; {@code true} otherwise. * @return {@code true} if the service was started, the service was already connected, or there * was no always-on VPN to start. {@code false} otherwise. */ private boolean updateAlwaysOnVpn(int user) { final String lockdownPackage = getAlwaysOnVpnPackage(user); if (lockdownPackage == null) { private boolean startAlwaysOnVpn(int userId) { final String alwaysOnPackage; synchronized (mVpns) { Vpn vpn = mVpns.get(userId); if (vpn == null) { // Shouldn't happen as all codepaths that point here should have checked the Vpn // exists already. Slog.wtf(TAG, "User " + userId + " has no Vpn configuration"); return false; } alwaysOnPackage = vpn.getAlwaysOnPackage(); // Skip if there is no service to start. if (alwaysOnPackage == null) { return true; } // Skip if the service is already established. This isn't bulletproof: it's not bound // until after establish(), so if it's mid-setup onStartCommand will be sent twice, // which may restart the connection. if (vpn.getNetworkInfo().isConnected()) { return true; } } // Create an intent to start the VPN service declared in the app's manifest. // Start the VPN service declared in the app's manifest. Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE); serviceIntent.setPackage(lockdownPackage); serviceIntent.setPackage(alwaysOnPackage); try { return mContext.startServiceAsUser(serviceIntent, UserHandle.of(user)) != null; return mContext.startServiceAsUser(serviceIntent, UserHandle.of(userId)) != null; } catch (RuntimeException e) { Slog.w(TAG, "VpnService " + serviceIntent + " failed to start", e); return false; } } Loading @@ -3396,25 +3422,35 @@ public class ConnectivityService extends IConnectivityManager.Stub return false; } // If the current VPN package is the same as the new one, this is a no-op final String oldPackage = getAlwaysOnVpnPackage(userId); if (TextUtils.equals(oldPackage, packageName)) { return true; } synchronized (mVpns) { Vpn vpn = mVpns.get(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return false; } if (!vpn.setAlwaysOnPackage(packageName)) { // If the current VPN package is the same as the new one, this is a no-op if (TextUtils.equals(packageName, vpn.getAlwaysOnPackage())) { return true; } if (!vpn.setAlwaysOnPackage(packageName, lockdown)) { return false; } if (!updateAlwaysOnVpn(userId)) { vpn.setAlwaysOnPackage(null); if (!startAlwaysOnVpn(userId)) { vpn.setAlwaysOnPackage(null, false); return false; } // Save the configuration final long token = Binder.clearCallingIdentity(); try { final ContentResolver cr = mContext.getContentResolver(); Settings.Secure.putStringForUser(cr, Settings.Secure.ALWAYS_ON_VPN_APP, packageName, userId); Settings.Secure.putIntForUser(cr, Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, (lockdown ? 1 : 0), userId); } finally { Binder.restoreCallingIdentity(token); } } return true; } Loading Loading @@ -3685,11 +3721,18 @@ public class ConnectivityService extends IConnectivityManager.Stub } userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId); mVpns.put(userId, userVpn); final ContentResolver cr = mContext.getContentResolver(); String alwaysOnPackage = Settings.Secure.getStringForUser(cr, Settings.Secure.ALWAYS_ON_VPN_APP, userId); final boolean alwaysOnLockdown = Settings.Secure.getIntForUser(cr, Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, /* default */ 0, userId) != 0; if (alwaysOnPackage != null) { userVpn.setAlwaysOnPackage(alwaysOnPackage, alwaysOnLockdown); } } if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { updateLockdownVpn(); } else { updateAlwaysOnVpn(userId); } } Loading @@ -3700,6 +3743,7 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("Stopped user has no VPN"); return; } userVpn.onUserStopped(); mVpns.delete(userId); } } Loading Loading @@ -3729,7 +3773,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { updateLockdownVpn(); } else { updateAlwaysOnVpn(userId); startAlwaysOnVpn(userId); } } Loading
services/core/java/com/android/server/NetworkManagementService.java +16 −0 Original line number Diff line number Diff line Loading @@ -1849,6 +1849,22 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } @Override public void setAllowOnlyVpnForUids(boolean add, UidRange[] uidRanges) throws ServiceSpecificException { try { mNetdService.networkRejectNonSecureVpn(add, uidRanges); } catch (ServiceSpecificException e) { Log.w(TAG, "setAllowOnlyVpnForUids(" + add + ", " + Arrays.toString(uidRanges) + ")" + ": netd command failed", e); throw e; } catch (RemoteException e) { Log.w(TAG, "setAllowOnlyVpnForUids(" + add + ", " + Arrays.toString(uidRanges) + ")" + ": netd command failed", e); throw e.rethrowAsRuntimeException(); } } @Override public void setUidCleartextNetworkPolicy(int uid, int policy) { if (Binder.getCallingUid() != uid) { Loading
services/core/java/com/android/server/connectivity/Vpn.java +152 −42 Original line number Diff line number Diff line Loading @@ -66,7 +66,6 @@ import android.os.SystemClock; import android.os.SystemService; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.security.Credentials; import android.security.KeyStore; import android.text.TextUtils; Loading Loading @@ -127,6 +126,19 @@ public class Vpn { private final Looper mLooper; private final NetworkCapabilities mNetworkCapabilities; /** * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This * only applies to {@link VpnService} connections. */ private boolean mAlwaysOn = false; /** * Whether to disable traffic outside of this VPN even when the VPN is not connected. System * apps can still bypass by choosing explicit networks. Has no effect if {@link mAlwaysOn} is * not set. */ private boolean mLockdown = false; /** * List of UIDs that are set to use this VPN by default. Normally, every UID in the user is * added to this set but that can be changed by adding allowed or disallowed applications. It Loading @@ -140,6 +152,14 @@ public class Vpn { @GuardedBy("this") private Set<UidRange> mVpnUsers = null; /** * 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. * @see mLockdown */ @GuardedBy("this") private Set<UidRange> mBlockedUsers = new ArraySet<>(); // Handle of user initiating VPN. private final int mUserHandle; Loading Loading @@ -194,9 +214,10 @@ public class Vpn { * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, * otherwise the call will fail. * * @param newPackage the package to designate as always-on VPN supplier. * @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. */ public synchronized boolean setAlwaysOnPackage(String packageName) { public synchronized boolean setAlwaysOnPackage(String packageName, boolean lockdown) { enforceControlPermissionOrInternalCaller(); // Disconnect current VPN. Loading @@ -210,14 +231,9 @@ public class Vpn { prepareInternal(packageName); } // Save the new package name in Settings.Secure. final long token = Binder.clearCallingIdentity(); try { Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.ALWAYS_ON_VPN_APP, packageName, mUserHandle); } finally { Binder.restoreCallingIdentity(token); } mAlwaysOn = (packageName != null); mLockdown = (mAlwaysOn && lockdown); setVpnForcedLocked(mLockdown); return true; } Loading @@ -229,14 +245,7 @@ public class Vpn { */ public synchronized String getAlwaysOnPackage() { enforceControlPermissionOrInternalCaller(); final long token = Binder.clearCallingIdentity(); try { return Settings.Secure.getStringForUser(mContext.getContentResolver(), Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle); } finally { Binder.restoreCallingIdentity(token); } return (mAlwaysOn ? mPackage : null); } /** Loading @@ -258,6 +267,11 @@ public class Vpn { * @return true if the operation is succeeded. */ public synchronized boolean prepare(String oldPackage, String newPackage) { // Stop an existing always-on VPN from being dethroned by other apps. if (mAlwaysOn && !TextUtils.equals(mPackage, newPackage)) { return false; } if (oldPackage != null) { if (getAppUid(oldPackage, mUserHandle) != mOwnerUID) { // The package doesn't match. We return false (to obtain user consent) unless the Loading @@ -281,11 +295,6 @@ public class Vpn { return true; } // Stop an existing always-on VPN from being dethroned by other apps. if (getAlwaysOnPackage() != null) { return false; } // Check that the caller is authorized. enforceControlPermission(); Loading Loading @@ -469,7 +478,7 @@ public class Vpn { mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null); NetworkMisc networkMisc = new NetworkMisc(); networkMisc.allowBypass = mConfig.allowBypass; networkMisc.allowBypass = mConfig.allowBypass && !mLockdown; long token = Binder.clearCallingIdentity(); try { Loading Loading @@ -685,7 +694,7 @@ public class Vpn { final long token = Binder.clearCallingIdentity(); List<UserInfo> users; try { users = UserManager.get(mContext).getUsers(); users = UserManager.get(mContext).getUsers(true); } finally { Binder.restoreCallingIdentity(token); } Loading Loading @@ -774,9 +783,9 @@ public class Vpn { public void onUserAdded(int userHandle) { // If the user is restricted tie them to the parent user's VPN UserInfo user = UserManager.get(mContext).getUserInfo(userHandle); if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle && mVpnUsers != null) { if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle) { synchronized(Vpn.this) { if (mVpnUsers != null) { try { addUserToRanges(mVpnUsers, userHandle, mConfig.allowedApplications, mConfig.disallowedApplications); Loading @@ -788,22 +797,108 @@ public class Vpn { Log.wtf(TAG, "Failed to add restricted user to owner", e); } } if (mAlwaysOn) { setVpnForcedLocked(mLockdown); } } } } public void onUserRemoved(int userHandle) { // clean up if restricted UserInfo user = UserManager.get(mContext).getUserInfo(userHandle); if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle && mVpnUsers != null) { if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle) { synchronized(Vpn.this) { if (mVpnUsers != null) { try { removeVpnUserLocked(userHandle); } catch (Exception e) { Log.wtf(TAG, "Failed to remove restricted user to owner", e); } } if (mAlwaysOn) { setVpnForcedLocked(mLockdown); } } } } /** * Called when the user associated with this VPN has just been stopped. */ public synchronized void onUserStopped() { // Switch off networking lockdown (if it was enabled) setVpnForcedLocked(false); mAlwaysOn = false; // Quit any active connections agentDisconnect(); } /** * 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. * * 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. * * Calling multiple times with {@param enforce} = {@code true} will recreate the set of UIDs to * block every time, and if anything has changed update using {@link #setAllowOnlyVpnForUids}. * * @param enforce {@code true} to require that all traffic under the jurisdiction of this * {@link Vpn} goes through a VPN connection or is blocked until one is * available, {@code false} to lift the requirement. * * @see #mBlockedUsers */ @GuardedBy("this") private void setVpnForcedLocked(boolean enforce) { final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers); if (enforce) { final Set<UidRange> addedRanges = createUserAndRestrictedProfilesRanges(mUserHandle, /* allowedApplications */ null, /* disallowedApplications */ Collections.singletonList(mPackage)); removedRanges.removeAll(addedRanges); addedRanges.removeAll(mBlockedUsers); setAllowOnlyVpnForUids(false, removedRanges); setAllowOnlyVpnForUids(true, addedRanges); } else { setAllowOnlyVpnForUids(false, removedRanges); } } /** * Either add or remove a list of {@link UidRange}s to the list of UIDs that are only allowed * to make connections through sockets that have had {@code protect()} called on them. * * @param enforce {@code true} to add to the blacklist, {@code false} to remove. * @param ranges {@link Collection} of {@link UidRange}s to add (if {@param enforce} is * {@code true}) or to remove. * @return {@code true} if all of the UIDs were added/removed. {@code false} otherwise, * including added ranges that already existed or removed ones that didn't. */ @GuardedBy("this") private boolean setAllowOnlyVpnForUids(boolean enforce, Collection<UidRange> ranges) { if (ranges.size() == 0) { return true; } final UidRange[] rangesArray = ranges.toArray(new UidRange[ranges.size()]); try { mNetd.setAllowOnlyVpnForUids(enforce, rangesArray); } catch (RemoteException | RuntimeException e) { Log.e(TAG, "Updating blocked=" + enforce + " for UIDs " + Arrays.toString(ranges.toArray()) + " failed", e); return false; } if (enforce) { mBlockedUsers.addAll(ranges); } else { mBlockedUsers.removeAll(ranges); } return true; } /** Loading Loading @@ -959,6 +1054,21 @@ public class Vpn { return false; } /** * @return {@code true} if the set of users blocked whilst waiting for VPN to connect includes * the UID {@param uid}, {@code false} otherwise. * * @see #mBlockedUsers */ public synchronized boolean isBlockingUid(int uid) { for (UidRange uidRange : mBlockedUsers) { if (uidRange.contains(uid)) { return true; } } return false; } private native int jniCreate(int mtu); private native String jniGetName(int tun); private native int jniSetAddresses(String interfaze, String addresses); Loading