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

Commit f1f2c847 authored by lucychang's avatar lucychang Committed by Lucy Chang
Browse files

Adds install source checks for accessibility service

Adds install source checks for accessibility services. An
accessibility category service must be installed from the
given allow list or not from the default installer if no
allow list provided.

Bug: 182959209
Test: atest AccessibilitySecurityPolicyTest

Change-Id: I023c15b9ebd87785e2f7eb1e218bec4ad006e52f
parent 8215d0e1
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -4892,4 +4892,7 @@
    <bool name="config_supportsMicToggle">false</bool>
    <!-- Whether this device is supporting the camera toggle -->
    <bool name="config_supportsCamToggle">false</bool>

    <!-- List containing the allowed install sources for accessibility service. -->
    <string-array name="config_accessibility_allowed_install_source" translatable="false"/>
</resources>
+2 −0
Original line number Diff line number Diff line
@@ -4357,6 +4357,8 @@
  <java-symbol type="drawable" name="ic_accessibility_24dp" />
  <java-symbol type="string" name="view_and_control_notification_title" />
  <java-symbol type="string" name="view_and_control_notification_content" />
  <java-symbol type="array" name="config_accessibility_allowed_install_source" />
  
  <!-- Translation -->
  <java-symbol type="string" name="ui_translation_accessibility_translated_text" />
  <java-symbol type="string" name="ui_translation_accessibility_translation_finished" />
+62 −3
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.accessibility;

import static android.content.pm.PackageManagerInternal.PACKAGE_INSTALLER;

import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
@@ -24,7 +26,9 @@ import android.app.AppOpsManager;
import android.appwidget.AppWidgetManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
@@ -33,11 +37,13 @@ import android.os.IBinder;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;

import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;

import libcore.util.EmptyArray;

@@ -666,13 +672,66 @@ public class AccessibilitySecurityPolicy {

    /**
     * Identifies whether the accessibility service is true and designed for accessibility. An
     * accessibility service is considered as accessibility category if
     * {@link AccessibilityServiceInfo#isAccessibilityTool} is true.
     * accessibility service is considered as accessibility category if meets all conditions below:
     * <ul>
     *     <li> {@link AccessibilityServiceInfo#isAccessibilityTool} is true</li>
     *     <li> is installed from the trusted install source</li>
     * </ul>
     *
     * @param serviceInfo The accessibility service's serviceInfo.
     * @return Returns true if it is a true accessibility service.
     */
    public boolean isA11yCategoryService(AccessibilityServiceInfo serviceInfo) {
        return serviceInfo.isAccessibilityTool();
        if (!serviceInfo.isAccessibilityTool()) {
            return false;
        }
        if (!serviceInfo.getResolveInfo().serviceInfo.applicationInfo.isSystemApp()) {
            return hasTrustedSystemInstallSource(
                    serviceInfo.getResolveInfo().serviceInfo.packageName);
        }
        return true;
    }

    /** Returns true if the {@code installedPackage} is installed from the trusted install source.
     */
    private boolean hasTrustedSystemInstallSource(String installedPackage) {
        try {
            InstallSourceInfo installSourceInfo = mPackageManager.getInstallSourceInfo(
                    installedPackage);
            if (installSourceInfo == null) {
                return false;
            }
            final String installSourcePackageName = installSourceInfo.getInitiatingPackageName();
            if (installSourcePackageName == null || !mPackageManager.getPackageInfo(
                    installSourcePackageName,
                    0).applicationInfo.isSystemApp()) {
                return false;
            }
            return isTrustedInstallSource(installSourcePackageName);
        } catch (PackageManager.NameNotFoundException e) {
            Slog.w(LOG_TAG, "can't find the package's install source:" + installedPackage);
        }
        return false;
    }

    /** Returns true if the {@code installerPackage} is a trusted install source. */
    private boolean isTrustedInstallSource(String installerPackage) {
        final String[] allowedInstallingSources = mContext.getResources().getStringArray(
                com.android.internal.R.array
                        .config_accessibility_allowed_install_source);

        if (allowedInstallingSources.length == 0) {
            //Filters unwanted default installers if no allowed install sources.
            String defaultInstaller = ArrayUtils.firstOrNull(LocalServices.getService(
                    PackageManagerInternal.class).getKnownPackageNames(PACKAGE_INSTALLER,
                    mCurrentUserId));
            return !TextUtils.equals(defaultInstaller, installerPackage);
        }
        for (int i = 0; i < allowedInstallingSources.length; i++) {
            if (TextUtils.equals(allowedInstallingSources[i], installerPackage)) {
                return true;
            }
        }
        return false;
    }
}
+76 −14
Original line number Diff line number Diff line
@@ -43,9 +43,12 @@ import android.appwidget.AppWidgetManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.SigningInfo;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
@@ -81,8 +84,10 @@ public class AccessibilitySecurityPolicyTest {
    private static final int APP_PID = 2000;
    private static final int SYSTEM_PID = 558;
    private static final int TEST_USER_ID = UserHandle.USER_SYSTEM;
    private static final String TEST_PACKAGE_NAME = "com.android.server.accessibility";
    private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(
            "com.android.server.accessibility", "AccessibilitySecurityPolicyTest");
            TEST_PACKAGE_NAME, "AccessibilitySecurityPolicyTest");
    private static final String ALLOWED_INSTALL_PACKAGE_NAME = "com.allowed.install.package";

    private static final int[] ALWAYS_DISPATCH_EVENTS = {
            AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
@@ -142,19 +147,40 @@ public class AccessibilitySecurityPolicyTest {
    @Mock
    private AccessibilityServiceInfo mMockA11yServiceInfo;
    @Mock
    private ResolveInfo mMockResolveInfo;
    @Mock
    private ServiceInfo mMockServiceInfo;
    @Mock
    private ApplicationInfo mMockApplicationInfo;
    @Mock
    private ApplicationInfo mMockSourceApplicationInfo;
    @Mock
    private PackageInfo mMockSourcePackageInfo;
    @Mock
    private PolicyWarningUIController mPolicyWarningUIController;

    @Before
    public void setUp() {
    public void setUp() throws PackageManager.NameNotFoundException {
        MockitoAnnotations.initMocks(this);
        mContext.setMockPackageManager(mMockPackageManager);
        mContext.addMockSystemService(Context.USER_SERVICE, mMockUserManager);
        mContext.addMockSystemService(Context.APP_OPS_SERVICE, mMockAppOpsManager);
        mContext.getOrCreateTestableResources().addOverride(
                R.dimen.accessibility_focus_highlight_stroke_width, 1);
        mContext.getOrCreateTestableResources().addOverride(R.array
                        .config_accessibility_allowed_install_source,
                new String[]{ALLOWED_INSTALL_PACKAGE_NAME});

        when(mMockA11yServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);
        when(mMockA11yServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
        when(mMockA11yServiceConnection.getServiceInfo()).thenReturn(mMockA11yServiceInfo);
        when(mMockPackageManager.getPackageInfo(ALLOWED_INSTALL_PACKAGE_NAME, 0)).thenReturn(
                mMockSourcePackageInfo);

        mMockResolveInfo.serviceInfo = mMockServiceInfo;
        mMockServiceInfo.applicationInfo = mMockApplicationInfo;
        mMockServiceInfo.packageName = TEST_PACKAGE_NAME;
        mMockSourcePackageInfo.applicationInfo = mMockSourceApplicationInfo;

        mA11ySecurityPolicy = new AccessibilitySecurityPolicy(
                mPolicyWarningUIController, mContext, mMockA11yUserManager);
@@ -595,9 +621,32 @@ public class AccessibilitySecurityPolicyTest {
    }

    @Test
    public void onBoundServicesChanged_bindA11yCategoryService_noUIControllerAction() {
    public void onBoundServicesChanged_bindNonA11yToolService_activateUIControllerAction() {
        final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>();
        boundServices.add(mMockA11yServiceConnection);
        when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(false);

        mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices);

        verify(mPolicyWarningUIController).onNonA11yCategoryServiceBound(eq(TEST_USER_ID),
                eq(TEST_COMPONENT_NAME));
    }

    @Test
    public void onBoundServicesChanged_unbindNonA11yToolService_activateUIControllerAction() {
        onBoundServicesChanged_bindNonA11yToolService_activateUIControllerAction();

        mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, new ArrayList<>());

        verify(mPolicyWarningUIController).onNonA11yCategoryServiceUnbound(eq(TEST_USER_ID),
                eq(TEST_COMPONENT_NAME));
    }

    @Test
    public void onBoundServicesChanged_bindSystemA11yToolService_noUIControllerAction() {
        final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>();
        boundServices.add(mMockA11yServiceConnection);
        when(mMockApplicationInfo.isSystemApp()).thenReturn(true);
        when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(true);

        mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices);
@@ -606,8 +655,8 @@ public class AccessibilitySecurityPolicyTest {
    }

    @Test
    public void onBoundServicesChanged_unbindA11yCategoryService_noUIControllerAction() {
        onBoundServicesChanged_bindA11yCategoryService_noUIControllerAction();
    public void onBoundServicesChanged_unbindSystemA11yToolService_noUIControllerAction() {
        onBoundServicesChanged_bindSystemA11yToolService_noUIControllerAction();

        mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, new ArrayList<>());

@@ -616,30 +665,43 @@ public class AccessibilitySecurityPolicyTest {
    }

    @Test
    public void onBoundServicesChanged_bindNonA11yCategoryService_activateUIControllerAction() {
    public void onBoundServicesChanged_bindAllowedSourceA11yToolService_noUIControllerAction()
            throws PackageManager.NameNotFoundException {
        final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>();
        boundServices.add(mMockA11yServiceConnection);
        when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(false);
        when(mMockApplicationInfo.isSystemApp()).thenReturn(false);
        final InstallSourceInfo installSourceInfo = new InstallSourceInfo(
                ALLOWED_INSTALL_PACKAGE_NAME, new SigningInfo(), null,
                ALLOWED_INSTALL_PACKAGE_NAME);
        when(mMockPackageManager.getInstallSourceInfo(TEST_PACKAGE_NAME)).thenReturn(
                installSourceInfo);
        when(mMockSourceApplicationInfo.isSystemApp()).thenReturn(true);
        when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(true);

        mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices);

        verify(mPolicyWarningUIController).onNonA11yCategoryServiceBound(eq(TEST_USER_ID),
                eq(TEST_COMPONENT_NAME));
        verify(mPolicyWarningUIController, never()).onNonA11yCategoryServiceBound(anyInt(), any());
    }

    @Test
    public void onBoundServicesChanged_unbindNonA11yCategoryService_activateUIControllerAction() {
        onBoundServicesChanged_bindNonA11yCategoryService_activateUIControllerAction();
    public void onBoundServicesChanged_bindUnknownSourceA11yToolService_activateUIControllerAction()
            throws PackageManager.NameNotFoundException {
        final ArrayList<AccessibilityServiceConnection> boundServices = new ArrayList<>();
        boundServices.add(mMockA11yServiceConnection);
        when(mMockA11yServiceInfo.isAccessibilityTool()).thenReturn(true);
        final InstallSourceInfo installSourceInfo = new InstallSourceInfo(null, null, null, null);
        when(mMockPackageManager.getInstallSourceInfo(TEST_PACKAGE_NAME)).thenReturn(
                installSourceInfo);

        mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, new ArrayList<>());
        mA11ySecurityPolicy.onBoundServicesChangedLocked(TEST_USER_ID, boundServices);

        verify(mPolicyWarningUIController).onNonA11yCategoryServiceUnbound(eq(TEST_USER_ID),
        verify(mPolicyWarningUIController).onNonA11yCategoryServiceBound(eq(TEST_USER_ID),
                eq(TEST_COMPONENT_NAME));
    }

    @Test
    public void onSwitchUser_differentUser_activateUIControllerAction() {
        onBoundServicesChanged_bindNonA11yCategoryService_activateUIControllerAction();
        onBoundServicesChanged_bindNonA11yToolService_activateUIControllerAction();

        mA11ySecurityPolicy.onSwitchUserLocked(2, new HashSet<>());