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

Commit c0115519 authored by Pavel Grafov's avatar Pavel Grafov Committed by Android (Google) Code Review
Browse files

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

parents be14ed48 d406212a
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