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

Commit 7783a526 authored by Chun-Ku Lin's avatar Chun-Ku Lin Committed by Android Build Coastguard Worker
Browse files

Sanitize a11y qs shortcut tile service name before using it

Get the valid a11y tile services outside of the lock mechanism to
prevent AccessibilityManagerService holding the lock for a long time
while processing valid tile services by calling PackageManager

Bug: 449392803
Bug: 441506794
Test: manual
Test: atest AccessibilityUserStateTest
Test: atest AccessibilityTileUtilsTest
Test: atest AccessibilityManagerServiceTest
Flag: EXEMPT CVE_FIX

Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:637f5b4aa4fbc87010f49f69555f6d14caef6a1f
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:9d9034b7f4fbc742471d4a6ef0585767ead7be70
Merged-In: Id05b4fe4ee0619d2bdc0421e8bfc5b4d62e322e0
Change-Id: Id05b4fe4ee0619d2bdc0421e8bfc5b4d62e322e0
parent e770e9f0
Loading
Loading
Loading
Loading
+57 −17
Original line number Diff line number Diff line
@@ -915,13 +915,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub

    private void onSomePackagesChangedLocked(
            @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
            @Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
            @Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos,
            @NonNull Set<ComponentName> validA11yTileServices) {
        final AccessibilityUserState userState = getCurrentUserStateLocked();
        // Reload the installed services since some services may have different attributes
        // or resolve info (does not support equals), etc. Remove them then to force reload.
        userState.mInstalledServices.clear();
        if (readConfigurationForUserStateLocked(userState,
                    parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos)) {
        if (readConfigurationForUserStateLocked(
                userState, parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos,
                validA11yTileServices)) {
            onUserStateChangedLocked(userState);
        }
    }
@@ -2128,6 +2130,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
        parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
        parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
        Set<ComponentName> validA11yTileServices = AccessibilityTileUtils.getValidA11yTileServices(
                mContext,
                LocalServices.getService(PackageManagerInternal.class),
                parsedAccessibilityServiceInfos,
                parsedAccessibilityShortcutInfos,
                userId
        );

        synchronized (mLock) {
            if (mCurrentUserId == userId && mInitialized) {
                Slog.w(LOG_TAG, String.format("userId: %d is already initialized", userId));
@@ -2150,7 +2160,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
            AccessibilityUserState userState = getCurrentUserStateLocked();

            readConfigurationForUserStateLocked(userState,
                    parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
                    parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos,
                    validA11yTileServices);
            mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices);
            // Even if reading did not yield change, we have to update
            // the state since the context in which the current user
@@ -2565,7 +2576,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
    }

    private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState,
            @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos) {
            @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
            @NonNull Set<ComponentName> validA11yTileServices) {
        for (int i = 0, count = parsedAccessibilityServiceInfos.size(); i < count; i++) {
            AccessibilityServiceInfo accessibilityServiceInfo =
                    parsedAccessibilityServiceInfos.get(i);
@@ -2574,14 +2586,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                accessibilityServiceInfo.crashed = true;
            }
        }
        boolean serviceInfosChanged = false;

        if (!parsedAccessibilityServiceInfos.equals(userState.mInstalledServices)) {
            userState.mInstalledServices.clear();
            userState.mInstalledServices.addAll(parsedAccessibilityServiceInfos);
            userState.updateTileServiceMapForAccessibilityServiceLocked();
            return true;
            serviceInfosChanged = true;
        }
        return false;
        // Sometimes when the package changes is called (especially for the initial load), the
        // package manager may not be able to resolve the TileService at that time. Always
        // rebuild the feature to tileService map could solve the problem.
        userState.updateTileServiceMapForAccessibilityServiceLocked(validA11yTileServices);
        return serviceInfosChanged;
    }

    /**
@@ -2600,7 +2616,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
    }

    private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState,
            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos,
            @NonNull Set<ComponentName> validA11yTileServices) {
        boolean shortcutInfosChanged = false;
        if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) {
            if (Flags.clearShortcutsWhenActivityUpdatesToService()) {
                List<String> componentNames = userState.mInstalledShortcuts.stream()
@@ -2617,10 +2635,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub

            userState.mInstalledShortcuts.clear();
            userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos);
            userState.updateTileServiceMapForAccessibilityActivityLocked();
            return true;
            shortcutInfosChanged = true;
        }
        return false;
        // Sometimes when the package changes is called (especially for the initial load), the
        // package manager may not be able to resolve the TileService at that time. Always
        // rebuild the feature to tileService map could solve the problem.
        userState.updateTileServiceMapForAccessibilityActivityLocked(validA11yTileServices);
        return shortcutInfosChanged;
    }

    private boolean readEnabledAccessibilityServicesLocked(AccessibilityUserState userState) {
@@ -3444,11 +3465,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
    private boolean readConfigurationForUserStateLocked(
            AccessibilityUserState userState,
            List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos,
            @NonNull Set<ComponentName> validA11yTileServices) {
        boolean somethingChanged = readInstalledAccessibilityServiceLocked(
                userState, parsedAccessibilityServiceInfos);
                userState, parsedAccessibilityServiceInfos, validA11yTileServices);
        somethingChanged |= readInstalledAccessibilityShortcutLocked(
                userState, parsedAccessibilityShortcutInfos);
                userState, parsedAccessibilityShortcutInfos, validA11yTileServices);
        somethingChanged |= readEnabledAccessibilityServicesLocked(userState);
        somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState);
        somethingChanged |= readTouchExplorationEnabledSettingLocked(userState);
@@ -6529,6 +6551,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                    .parseAccessibilityServiceInfos(userId);
            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = mManagerService
                    .parseAccessibilityShortcutInfos(userId);
            Set<ComponentName> validA11yTileServices =
                    AccessibilityTileUtils.getValidA11yTileServices(
                            mManagerService.mContext,
                            LocalServices.getService(PackageManagerInternal.class),
                            parsedAccessibilityServiceInfos,
                            parsedAccessibilityShortcutInfos,
                            userId
                    );
            synchronized (mManagerService.getLock()) {
                // Only the profile parent can install accessibility services.
                // Therefore we ignore packages from linked profiles.
@@ -6544,7 +6574,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                    return;
                }
                mManagerService.onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
                        parsedAccessibilityShortcutInfos);
                        parsedAccessibilityShortcutInfos, validA11yTileServices);
            }
        }

@@ -6566,6 +6596,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                    .parseAccessibilityServiceInfos(userId);
            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos =
                    mManagerService.parseAccessibilityShortcutInfos(userId);
            Set<ComponentName> validA11yTileServices =
                    AccessibilityTileUtils.getValidA11yTileServices(
                            mManagerService.mContext,
                            LocalServices.getService(PackageManagerInternal.class),
                            parsedAccessibilityServiceInfos,
                            parsedAccessibilityShortcutInfos,
                            userId
                    );
            synchronized (mManagerService.getLock()) {
                if (userId != mManagerService.getCurrentUserIdLocked()) {
                    return;
@@ -6582,7 +6620,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                final boolean configurationChanged;
                configurationChanged = mManagerService.readConfigurationForUserStateLocked(
                        userState, parsedAccessibilityServiceInfos,
                        parsedAccessibilityShortcutInfos);
                        parsedAccessibilityShortcutInfos,
                        validA11yTileServices
                );
                if (reboundAService || configurationChanged) {
                    mManagerService.onUserStateChangedLocked(userState);
                }
+183 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.accessibility;

import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Process;
import android.service.quicksettings.TileService;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;

import java.util.List;
import java.util.Set;

/**
 * Collection of utilities for Accessibility feature's TileServices.
 */
public class AccessibilityTileUtils {
    private AccessibilityTileUtils() {}
    private static final String TAG = "AccessibilityTileUtils";

    /**
     * Checks if a given {@link ComponentName} corresponds to a valid and enabled TileService
     * for the current user.
     *
     * @param componentName The {@link ComponentName} of the service to validate.
     * @return {@code true} if the component is a valid and enabled TileService, {@code false}
     * otherwise.
     */
    private static boolean isComponentValidTileService(
            @NonNull Context context, @NonNull PackageManagerInternal pm,
            @NonNull ComponentName componentName, @UserIdInt int userId) {
        Intent intent = new Intent(TileService.ACTION_QS_TILE);
        intent.setComponent(componentName);

        ResolveInfo resolveInfo = pm.resolveService(intent,
                intent.resolveTypeIfNeeded(context.getContentResolver()),
                /* flags= */ 0L,
                userId,
                android.os.Process.myUid());

        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
            Slog.w(TAG, "TileService could not be resolved: " + componentName);
            return false;
        }

        ServiceInfo serviceInfo = resolveInfo.serviceInfo;
        if (!serviceInfo.exported) {
            Slog.w(TAG, "TileService is not exported: " + componentName);
            return false;
        }

        if (!Manifest.permission.BIND_QUICK_SETTINGS_TILE.equals(serviceInfo.permission)) {
            Slog.w(TAG, "TileService is not protected by BIND_QUICK_SETTINGS_TILE permission: "
                    + componentName);
            return false;
        }

        int enabledSetting = pm.getComponentEnabledSetting(componentName, Process.myUid(), userId);
        if (!resolveEnabledComponent(enabledSetting, serviceInfo.enabled)) {
            Slog.w(TAG, "TileService is not enabled: " + componentName.flattenToShortString());
            return false;
        }

        return true;
    }

    /**
     * Resolves the effective enabled state of a component by considering both its dynamic setting
     * and its static manifest declaration.
     *
     * @param pmResult     The component's dynamic enabled state, as returned by
     *                     {@link PackageManager#getComponentEnabledSetting(ComponentName)}.
     * @param defaultValue The component's static enabled state from its manifest (e.g.,
     *                     {@link android.content.pm.ServiceInfo#enabled}). This value is used
     *                     when {@code pmResult} is
     *                     {@link PackageManager#COMPONENT_ENABLED_STATE_DEFAULT}.
     * @return {@code true} if the component is considered enabled, {@code false} otherwise.
     */
    private static boolean resolveEnabledComponent(int pmResult, boolean defaultValue) {
        if (pmResult == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
            return true;
        }
        if (pmResult == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
            return defaultValue;
        }
        return false;
    }

    /**
     * Returns a set of {@link ComponentName}s for valid Accessibility TileServices.
     * A TileService is considered valid if it is properly declared, exported, protected by the
     * {@link Manifest.permission#BIND_QUICK_SETTINGS_TILE} permission, and enabled.
     *
     * @param context The current context.
     * @param pm The {@link PackageManagerInternal} instance.
     * @param accessibilityServiceInfos A list of installed {@link AccessibilityServiceInfo}s.
     * @param accessibilityShortcutInfos A list of installed {@link AccessibilityShortcutInfo}s.
     * @param userId The user ID for which to retrieve the TileServices.
     * @return A {@link Set} of valid {@link ComponentName}s for Accessibility TileServices.
     */
    @NonNull
    public static Set<ComponentName> getValidA11yTileServices(
            @NonNull Context context,
            @Nullable PackageManagerInternal pm,
            @Nullable List<AccessibilityServiceInfo> accessibilityServiceInfos,
            @Nullable List<AccessibilityShortcutInfo> accessibilityShortcutInfos,
            @UserIdInt int userId
    ) {
        Set<ComponentName> validA11yTileServices = new ArraySet<>();
        if (pm == null) {
            return validA11yTileServices;
        }

        if (accessibilityServiceInfos != null) {
            accessibilityServiceInfos.forEach(
                    a11yServiceInfo -> {
                        String tileServiceName = a11yServiceInfo.getTileServiceName();
                        if (!TextUtils.isEmpty(tileServiceName)) {
                            ResolveInfo resolveInfo = a11yServiceInfo.getResolveInfo();
                            ComponentName a11yFeature = new ComponentName(
                                    resolveInfo.serviceInfo.packageName,
                                    resolveInfo.serviceInfo.name
                            );
                            ComponentName tileService = new ComponentName(
                                    a11yFeature.getPackageName(),
                                    tileServiceName
                            );
                            if (isComponentValidTileService(context, pm, tileService, userId)) {
                                validA11yTileServices.add(tileService);
                            }
                        }
                    }
            );
        }

        if (accessibilityShortcutInfos != null) {
            accessibilityShortcutInfos.forEach(
                    a11yShortcutInfo -> {
                        String tileServiceName = a11yShortcutInfo.getTileServiceName();
                        if (!TextUtils.isEmpty(tileServiceName)) {
                            ComponentName a11yFeature = a11yShortcutInfo.getComponentName();
                            ComponentName tileService = new ComponentName(
                                    a11yFeature.getPackageName(),
                                    tileServiceName);
                            if (isComponentValidTileService(context, pm, tileService, userId)) {
                                validA11yTileServices.add(tileService);
                            }
                        }
                    }
            );
        }

        return validA11yTileServices;
    }

}
+29 −4
Original line number Diff line number Diff line
@@ -1120,8 +1120,19 @@ class AccessibilityUserState {
        return false;
    }

    public void updateTileServiceMapForAccessibilityServiceLocked() {
    /**
     * Updates the internal map of accessibility services to their corresponding tile services.
     *
     * @param validA11yTileServices A set of valid {@link ComponentName}s for accessibility tile
     *                              services.
     */
    public void updateTileServiceMapForAccessibilityServiceLocked(
            @NonNull Set<ComponentName> validA11yTileServices) {
        mA11yServiceToTileService.clear();
        if (validA11yTileServices.isEmpty()) {
            return;
        }

        mInstalledServices.forEach(
                a11yServiceInfo -> {
                    String tileServiceName = a11yServiceInfo.getTileServiceName();
@@ -1135,14 +1146,26 @@ class AccessibilityUserState {
                                a11yFeature.getPackageName(),
                                tileServiceName
                        );
                        if (validA11yTileServices.contains(tileService)) {
                            mA11yServiceToTileService.put(a11yFeature, tileService);
                        }
                    }
                }
        );
    }

    public void updateTileServiceMapForAccessibilityActivityLocked() {
    /**
     * Updates the internal map of accessibility activities to their corresponding tile services.
     *
     * @param validA11yTileServices A set of valid {@link ComponentName}s for accessibility tile
     *                              services.
     */
    public void updateTileServiceMapForAccessibilityActivityLocked(
            @NonNull Set<ComponentName> validA11yTileServices) {
        mA11yActivityToTileService.clear();
        if (validA11yTileServices.isEmpty()) {
            return;
        }
        mInstalledShortcuts.forEach(
                a11yShortcutInfo -> {
                    String tileServiceName = a11yShortcutInfo.getTileServiceName();
@@ -1151,9 +1174,11 @@ class AccessibilityUserState {
                        ComponentName tileService = new ComponentName(
                                a11yFeature.getPackageName(),
                                tileServiceName);
                        if (validA11yTileServices.contains(tileService)) {
                            mA11yActivityToTileService.put(a11yFeature, tileService);
                        }
                    }
                }
        );
    }

+12 −1
Original line number Diff line number Diff line
@@ -141,6 +141,7 @@ import com.android.server.accessibility.magnification.FullScreenMagnificationCon
import com.android.server.accessibility.magnification.MagnificationConnectionManager;
import com.android.server.accessibility.magnification.MagnificationController;
import com.android.server.accessibility.magnification.MagnificationProcessor;
import com.android.server.accessibility.utils.TileServiceUtil;
import com.android.server.pm.UserManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -2523,7 +2524,17 @@ public class AccessibilityManagerServiceTest {
                /* isAlwaysOnService= */ false);
        userState.mInstalledServices.addAll(
                List.of(alwaysOnServiceInfo, standardServiceInfo));
        userState.updateTileServiceMapForAccessibilityServiceLocked();
        ComponentName alwaysOnA11yServiceTile =
                new ComponentName(TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
                        alwaysOnServiceInfo.getTileServiceName());
        TileServiceUtil.setupPackageManagerForValidTileService(
                mMockPackageManagerInternal,
                userState.mUserId,
                alwaysOnA11yServiceTile
        );
        userState.updateTileServiceMapForAccessibilityServiceLocked(
                Set.of(alwaysOnA11yServiceTile)
        );
    }

    private void sendBroadcastToAccessibilityManagerService(Intent intent, @UserIdInt int userId) {
+369 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading