Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f74b5d16 authored by Pavel Grafov's avatar Pavel Grafov Committed by Automerger Merge Worker
Browse files

Merge "Don't reset user VPN without reason" into sc-dev am: c0115519

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

Change-Id: Ib9708489a707efb6c46d1aabcfdada69b4850e9f
parents 5596a97d c0115519
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -6845,6 +6845,10 @@ public class DevicePolicyManager {
     * <p> Enabling lockdown via {@code lockdownEnabled} argument carries the risk that any failure
     * of the VPN provider could break networking for all apps. This method clears any lockdown
     * allowlist set by {@link #setAlwaysOnVpnPackage(ComponentName, String, boolean, Set)}.
     * <p> Starting from {@link android.os.Build.VERSION_CODES#S API 31} calling this method with
     * {@code vpnPackage} set to {@code null} only removes the existing configuration if it was
     * previously created by this admin. To remove VPN configuration created by the user use
     * {@link UserManager#DISALLOW_CONFIG_VPN}.
     *
     * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} to
     *        remove an existing always-on VPN configuration.
+0 −1
Original line number Diff line number Diff line
@@ -265,5 +265,4 @@ public abstract class DevicePolicyManagerInternal {
     */
    public abstract void notifyUnsafeOperationStateChanged(DevicePolicySafetyChecker checker,
            @OperationSafetyReason int reason, boolean isSafe);

}
+2 −0
Original line number Diff line number Diff line
@@ -575,6 +575,8 @@ public class UserManager {
     * <p>This restriction also prevents VPNs from starting. However, in Android 7.0
     * ({@linkplain android.os.Build.VERSION_CODES#N API level 24}) or higher, the system does
     * start always-on VPNs created by the device or profile owner.
     * <p>From Android 12 ({@linkplain android.os.Build.VERSION_CODES#S API level 31}) enforcing
     * this restriction clears currently active VPN if it was configured by the user.
     *
     * <p>Key for user restrictions.
     * <p>Type: Boolean
+99 −31
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
@@ -990,13 +991,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        @Override
        public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
                Bundle prevRestrictions) {
            final boolean newlyDisallowed =
                    newRestrictions.getBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
            final boolean previouslyDisallowed =
                    prevRestrictions.getBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
            final boolean restrictionChanged = (newlyDisallowed != previouslyDisallowed);
            resetCrossProfileIntentFiltersIfNeeded(userId, newRestrictions, prevRestrictions);
            resetUserVpnIfNeeded(userId, newRestrictions, prevRestrictions);
        }
        private void resetUserVpnIfNeeded(
                int userId, Bundle newRestrictions, Bundle prevRestrictions) {
            final boolean newlyEnforced =
                    !prevRestrictions.getBoolean(UserManager.DISALLOW_CONFIG_VPN)
                    && newRestrictions.getBoolean(UserManager.DISALLOW_CONFIG_VPN);
            if (newlyEnforced) {
                mDpms.clearUserConfiguredVpns(userId);
            }
        }
            if (restrictionChanged) {
        private void resetCrossProfileIntentFiltersIfNeeded(
                int userId, Bundle newRestrictions, Bundle prevRestrictions) {
            if (UserRestrictionsUtils.restrictionsChanged(prevRestrictions, newRestrictions,
                    UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE)) {
                final int parentId = mUserManagerInternal.getProfileParentId(userId);
                if (parentId == userId) {
                    return;
@@ -1007,13 +1019,55 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                Slogf.i(LOG_TAG, "Resetting cross-profile intent filters on restriction "
                        + "change");
                mDpms.resetDefaultCrossProfileIntentFilters(parentId);
                mContext.sendBroadcastAsUser(new Intent(
                                DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_APPLIED),
                mContext.sendBroadcastAsUser(
                        new Intent(DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_APPLIED),
                        UserHandle.of(userId));
            }
        }
    }
    private void clearUserConfiguredVpns(int userId) {
        final String adminConfiguredVpnPkg;
        synchronized (getLockObject()) {
            final ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
            if (owner == null) {
                Slogf.wtf(LOG_TAG, "Admin not found");
                return;
            }
            adminConfiguredVpnPkg = owner.mAlwaysOnVpnPackage;
        }
        // Clear always-on configuration if it wasn't set by the admin.
        if (adminConfiguredVpnPkg == null) {
            mInjector.getVpnManager().setAlwaysOnVpnPackageForUser(userId, null, false, null);
        }
        // Clear app authorizations to establish VPNs. When DISALLOW_CONFIG_VPN is enforced apps
        // won't be able to get those authorizations unless it is configured by an admin.
        final List<AppOpsManager.PackageOps> allVpnOps = mInjector.getAppOpsManager()
                .getPackagesForOps(new int[] {AppOpsManager.OP_ACTIVATE_VPN});
        if (allVpnOps == null) {
            return;
        }
        for (AppOpsManager.PackageOps pkgOps : allVpnOps) {
            if (UserHandle.getUserId(pkgOps.getUid()) != userId
                    || pkgOps.getPackageName().equals(adminConfiguredVpnPkg)) {
                continue;
            }
            if (pkgOps.getOps().size() != 1) {
                Slogf.wtf(LOG_TAG, "Unexpected number of ops returned");
                continue;
            }
            final @Mode int mode = pkgOps.getOps().get(0).getMode();
            if (mode == MODE_ALLOWED) {
                Slogf.i(LOG_TAG, String.format("Revoking VPN authorization for package %s uid %d",
                        pkgOps.getPackageName(), pkgOps.getUid()));
                mInjector.getAppOpsManager().setMode(AppOpsManager.OP_ACTIVATE_VPN, pkgOps.getUid(),
                        pkgOps.getPackageName(), MODE_DEFAULT);
            }
        }
    }
    private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener {
        @Override
@@ -6559,6 +6613,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
        checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE);
        if (vpnPackage == null) {
            final String prevVpnPackage;
            synchronized (getLockObject()) {
                prevVpnPackage = getProfileOwnerOrDeviceOwnerLocked(caller).mAlwaysOnVpnPackage;
                // If the admin is clearing VPN package but hasn't configure any VPN previously,
                // ignore it so that it doesn't interfere with user-configured VPNs.
                if (TextUtils.isEmpty(prevVpnPackage)) {
                    return true;
                }
            }
            revokeVpnAuthorizationForPackage(prevVpnPackage, caller.getUserId());
        }
        final int userId = caller.getUserId();
        mInjector.binderWithCleanCallingIdentity(() -> {
            if (vpnPackage != null && !isPackageInstalledForUser(vpnPackage, userId)) {
@@ -6581,6 +6648,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                    userId, vpnPackage, lockdown, lockdownAllowlist)) {
                throw new UnsupportedOperationException();
            }
        });
        DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.SET_ALWAYS_ON_VPN_PACKAGE)
                .setAdmin(caller.getComponentName())
@@ -6588,7 +6656,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                .setBoolean(lockdown)
                .setInt(lockdownAllowlist != null ? lockdownAllowlist.size() : 0)
                .write();
        });
        synchronized (getLockObject()) {
            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
            if (!TextUtils.equals(vpnPackage, admin.mAlwaysOnVpnPackage)
@@ -6601,6 +6668,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        return true;
    }
    private void revokeVpnAuthorizationForPackage(String vpnPackage, int userId) {
        mInjector.binderWithCleanCallingIdentity(() -> {
            try {
                final ApplicationInfo ai = mIPackageManager.getApplicationInfo(
                        vpnPackage, /* flags= */ 0, userId);
                if (ai == null) {
                    Slogf.w(LOG_TAG, "Non-existent VPN package: " + vpnPackage);
                } else {
                    mInjector.getAppOpsManager().setMode(AppOpsManager.OP_ACTIVATE_VPN,
                            ai.uid, vpnPackage, MODE_DEFAULT);
                }
            } catch (RemoteException e) {
                Slogf.e(LOG_TAG, "Can't talk to package managed", e);
            }
        });
    }
    @Override
    public String getAlwaysOnVpnPackage(ComponentName admin) throws SecurityException {
        Objects.requireNonNull(admin, "ComponentName is null");
@@ -8390,12 +8474,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        return who != null && who.equals(profileOwner);
    }
    private boolean isProfileOwnerUncheckedLocked(ComponentName who, int userId) {
        ensureLocked();
        final ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
        return who != null && who.equals(profileOwner);
    }
    /**
     * Returns {@code true} if the provided caller identity is of a profile owner.
     * @param caller identity of caller.
@@ -13667,16 +13745,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
                + " is not device owner");
    }
    private ComponentName getOwnerComponent(String packageName, int userId) {
        if (isDeviceOwnerPackage(packageName, userId)) {
            return mOwners.getDeviceOwnerComponent();
        }
        if (isProfileOwnerPackage(packageName, userId)) {
            return mOwners.getProfileOwnerComponent(userId);
        }
        return null;
    }
    /**
     * Return device owner or profile owner set on a given user.
     */
+98 −0
Original line number Diff line number Diff line
@@ -15,6 +15,9 @@
 */
package com.android.server.devicepolicy;

import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
import static android.app.Notification.EXTRA_TEXT;
import static android.app.Notification.EXTRA_TITLE;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
@@ -7436,6 +7439,101 @@ public class DevicePolicyManagerTest extends DpmTestBase {
        assertThrows(SecurityException.class, () -> dpm.setRecommendedGlobalProxy(admin1, null));
    }

    @Test
    public void testSetAlwaysOnVpnPackage_clearsAdminVpn() throws Exception {
        setDeviceOwner();

        when(getServices().vpnManager
                .setAlwaysOnVpnPackageForUser(anyInt(), any(), anyBoolean(), any()))
                .thenReturn(true);

        // Set VPN package to admin package.
        dpm.setAlwaysOnVpnPackage(admin1, admin1.getPackageName(), false, null);

        verify(getServices().vpnManager).setAlwaysOnVpnPackageForUser(
                UserHandle.USER_SYSTEM, admin1.getPackageName(), false, null);

        // Clear VPN package.
        dpm.setAlwaysOnVpnPackage(admin1, null, false, null);

        // Change should be propagated to VpnManager
        verify(getServices().vpnManager).setAlwaysOnVpnPackageForUser(
                UserHandle.USER_SYSTEM, null, false, null);
        // The package should lose authorization to start VPN.
        verify(getServices().appOpsManager).setMode(OP_ACTIVATE_VPN,
                DpmMockContext.CALLER_SYSTEM_USER_UID, admin1.getPackageName(), MODE_DEFAULT);
    }

    @Test
    public void testSetAlwaysOnVpnPackage_doesntKillUserVpn() throws Exception {
        setDeviceOwner();

        when(getServices().vpnManager
                .setAlwaysOnVpnPackageForUser(anyInt(), any(), anyBoolean(), any()))
                .thenReturn(true);

        // this time it shouldn't go into VpnManager anymore.
        dpm.setAlwaysOnVpnPackage(admin1, null, false, null);

        verifyNoMoreInteractions(getServices().vpnManager);
        verifyNoMoreInteractions(getServices().appOpsManager);
    }

    @Test
    public void testDisallowConfigVpn_clearsUserVpn() throws Exception {
        final String userVpnPackage = "org.some.vpn.servcie";
        final int userVpnUid = 20374;

        setDeviceOwner();

        setupVpnAuthorization(userVpnPackage, userVpnUid);

        simulateRestrictionAdded(UserManager.DISALLOW_CONFIG_VPN);

        verify(getServices().vpnManager).setAlwaysOnVpnPackageForUser(
                UserHandle.USER_SYSTEM, null, false, null);
        verify(getServices().appOpsManager).setMode(OP_ACTIVATE_VPN,
                userVpnUid, userVpnPackage, MODE_DEFAULT);
    }

    @Test
    public void testDisallowConfigVpn_doesntKillAdminVpn() throws Exception {
        setDeviceOwner();

        when(getServices().vpnManager
                .setAlwaysOnVpnPackageForUser(anyInt(), any(), anyBoolean(), any()))
                .thenReturn(true);

        // Set VPN package to admin package.
        dpm.setAlwaysOnVpnPackage(admin1, admin1.getPackageName(), false, null);
        setupVpnAuthorization(admin1.getPackageName(), DpmMockContext.CALLER_SYSTEM_USER_UID);
        clearInvocations(getServices().vpnManager);

        simulateRestrictionAdded(UserManager.DISALLOW_CONFIG_VPN);

        // Admin-set package should remain always-on and should retain its authorization.
        verifyNoMoreInteractions(getServices().vpnManager);
        verify(getServices().appOpsManager, never()).setMode(OP_ACTIVATE_VPN,
                DpmMockContext.CALLER_SYSTEM_USER_UID, admin1.getPackageName(), MODE_DEFAULT);
    }

    private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) {
        final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage,
                userVpnUid, List.of(new AppOpsManager.OpEntry(
                OP_ACTIVATE_VPN, MODE_ALLOWED, Collections.emptyMap())));
        when(getServices().appOpsManager.getPackagesForOps(any(int[].class)))
                .thenReturn(List.of(vpnOp));
    }

    private void simulateRestrictionAdded(String restriction) {
        RestrictionsListener listener = new RestrictionsListener(
                mServiceContext, getServices().userManagerInternal, dpms);

        final Bundle newRestrictions = new Bundle();
        newRestrictions.putBoolean(restriction, true);
        listener.onUserRestrictionsChanged(UserHandle.USER_SYSTEM, newRestrictions, new Bundle());
    }

    private void setUserUnlocked(int userHandle, boolean unlocked) {
        when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked);
    }
Loading