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

Commit 6448f441 authored by vaibsinghal's avatar vaibsinghal
Browse files

Persist AppFunctions agent allowlist to disk.

This change introduces a file-based storage for the AppFunctions agent allowlist. The allowlist fetched from `DeviceConfig` is now persisted to `/data/system/appfunctions/agent_allowlist.txt`.
If parsing the allowlist from `DeviceConfig` fails, the system now falls back to the last known good version from this file, making the feature more resilient to invalid configuration.

Flag: android.permission.flags.app_function_access_service_enabled
Test: atest FrameworksAppFunctionsTests
Bug: 416661798
Change-Id: I64969dbda90501a132dab5aa11186a1305a7ebe6
parent 73d957e3
Loading
Loading
Loading
Loading
+28 −7
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ import android.content.pm.SignedPackage;
import android.content.pm.SigningInfo;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -92,6 +93,7 @@ import com.android.server.SystemService;
import com.android.server.SystemService.TargetUser;
import com.android.server.uri.UriGrantsManagerInternal;

import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -111,6 +113,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
    private static final String ALLOWLISTED_APP_FUNCTIONS_AGENTS =
            "allowlisted_app_functions_agents";
    private static final String NAMESPACE_MACHINE_LEARNING = "machine_learning";
    private static final String AGENT_ALLOWLIST_FILE_NAME = "agent_allowlist.txt";
    private static final String APP_FUNCTIONS_DIR = "appfunctions";

    private final RemoteServiceCaller<IAppFunctionService> mRemoteServiceCaller;
    private final CallerValidator mCallerValidator;
@@ -139,6 +143,10 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
    @GuardedBy("mAgentAllowlistLock")
    private List<SignedPackage> mUpdatableAgentAllowlist = new ArrayList<>();

    private final Executor mWorkerExecutor;

    private final AppFunctionAgentAllowlistStorage mAgentAllowlistStorage;

    public AppFunctionManagerServiceImpl(
            @NonNull Context context,
            @NonNull PackageManagerInternal packageManagerInternal,
@@ -157,7 +165,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
                appFunctionAccessServiceInterface,
                uriGrantsManager,
                uriGrantsManagerInternal,
                new DeviceSettingHelperImpl(context));
                new DeviceSettingHelperImpl(context),
                new AppFunctionAgentAllowlistStorage(
                        new File(
                                new File(Environment.getDataSystemDirectory(), APP_FUNCTIONS_DIR),
                                AGENT_ALLOWLIST_FILE_NAME)),
                THREAD_POOL_EXECUTOR);
    }

    @VisibleForTesting
@@ -172,7 +185,9 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
            AppFunctionAccessServiceInterface appFunctionAccessServiceInterface,
            IUriGrantsManager uriGrantsManager,
            UriGrantsManagerInternal uriGrantsManagerInternal,
            DeviceSettingHelper deviceSettingHelper) {
            DeviceSettingHelper deviceSettingHelper,
            AppFunctionAgentAllowlistStorage agentAllowlistStorage,
            Executor workerExecutor) {
        mContext = Objects.requireNonNull(context);
        mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller);
        mCallerValidator = Objects.requireNonNull(callerValidator);
@@ -185,6 +200,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
        mUriGrantsManagerInternal = Objects.requireNonNull(uriGrantsManagerInternal);
        mPermissionOwner = mUriGrantsManagerInternal.newUriPermissionOwner("appfunctions");
        mDeviceSettingHelper = deviceSettingHelper;
        mAgentAllowlistStorage = Objects.requireNonNull(agentAllowlistStorage);
        mWorkerExecutor = Objects.requireNonNull(workerExecutor);
    }

    /** Called when the user is unlocked. */
@@ -263,7 +280,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
    public void onBootPhase(int phase) {
        if (!Flags.appFunctionAccessServiceEnabled()) return;
        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
            updateAgentAllowlist(/* readFromDeviceConfig */ true);
            mWorkerExecutor.execute(() -> updateAgentAllowlist(/* readFromDeviceConfig */ true));
            DeviceConfig.addOnPropertiesChangedListener(
                    NAMESPACE_MACHINE_LEARNING,
                    BackgroundThread.getExecutor(),
@@ -272,6 +289,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
    }

    // TODO(b/413093397): Merge allowlist agents from other sources
    @WorkerThread
    private void updateAgentAllowlist(boolean readFromDeviceConfig) {
        synchronized (mAgentAllowlistLock) {
            Set<SignedPackage> oldAgents = new HashSet<>();
@@ -301,15 +319,18 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
    }

    @Nullable
    @WorkerThread
    private List<SignedPackage> readDeviceConfigAgentAllowlist() {
        final String signatureString =
        final String allowlistString =
                DeviceConfig.getString(
                        NAMESPACE_MACHINE_LEARNING, ALLOWLISTED_APP_FUNCTIONS_AGENTS, "");
        try {
            return SignedPackageParser.parseList(signatureString);
            List<SignedPackage> parsedAllowlist = SignedPackageParser.parseList(allowlistString);
            mAgentAllowlistStorage.writeCurrentAllowlist(allowlistString);
            return parsedAllowlist;
        } catch (Exception e) {
            Slog.e(TAG, "Cannot parse signature string: " + signatureString, e);
            return null;
            Slog.e(TAG, "Cannot parse agent allowlist from config: " + allowlistString, e);
            return mAgentAllowlistStorage.readPreviousValidAllowlist();
        }
    }

+79 −9
Original line number Diff line number Diff line
@@ -18,21 +18,26 @@ package com.android.server.appfunctions

import android.app.IUriGrantsManager
import android.app.appfunctions.AppFunctionAccessServiceInterface
import android.app.appfunctions.IAppFunctionService
import android.app.appfunctions.flags.Flags
import android.content.pm.PackageManagerInternal
import android.content.pm.SignedPackage
import android.permission.flags.Flags.FLAG_APP_FUNCTION_ACCESS_API_ENABLED
import android.permission.flags.Flags.FLAG_APP_FUNCTION_ACCESS_SERVICE_ENABLED
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.provider.DeviceConfig
import android.testing.TestableContext
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.internal.R
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.server.LocalServices
import com.android.server.SystemService
import com.android.server.uri.UriGrantsManagerInternal
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
@@ -43,6 +48,7 @@ import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -54,24 +60,41 @@ class AppFunctionManagerServiceImplTest {

    @get:Rule
    val extendedMockitoRule =
        ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java).build()
        ExtendedMockitoRule.Builder(this)
            .mockStatic(LocalServices::class.java)
            .mockStatic(DeviceConfig::class.java)
            .build()

    @get:Rule
    val context: TestableContext =
        spy(TestableContext(ApplicationProvider.getApplicationContext(), null))

    private val appFunctionAccessService = mock<AppFunctionAccessServiceInterface>()
    private val agentAllowlistStorage = mock<AppFunctionAgentAllowlistStorage>()

    private val serviceImpl =
    private lateinit var serviceImpl: AppFunctionManagerServiceImpl

    @Before
    fun setup() {
        serviceImpl =
            AppFunctionManagerServiceImpl(
                context,
                mock<RemoteServiceCaller<IAppFunctionService>>(),
                mock<CallerValidator>(),
                mock<ServiceHelper>(),
                mock<ServiceConfig>(),
                mock<AppFunctionsLoggerWrapper>(),
                mock<PackageManagerInternal>(),
                appFunctionAccessService,
                mock<IUriGrantsManager>(),
                mock<UriGrantsManagerInternal>(),
                DeviceSettingHelperImpl(context),
                agentAllowlistStorage,
                MoreExecutors.directExecutor(),
            )
        clearDeviceSettingPackages()
    }

    @Before
    @After
    fun clear() {
        clearDeviceSettingPackages()
@@ -272,6 +295,53 @@ class AppFunctionManagerServiceImplTest {
        assertThat(validTargets).containsExactly("android", nonDeviceSettingPackage)
    }

    @Test
    @RequiresFlagsEnabled(
        FLAG_APP_FUNCTION_ACCESS_SERVICE_ENABLED,
        FLAG_APP_FUNCTION_ACCESS_API_ENABLED,
    )
    fun onBootPhase_writeAllowlistToStorage_whenDeviceConfigValid() {
        val signatureString = "com.example.test1:abcdef0123456789"
        val expectedPackages = listOf(SignedPackage("com.test.package", byteArrayOf()))
        whenever(
                DeviceConfig.getString(
                    eq("machine_learning"),
                    eq("allowlisted_app_functions_agents"),
                    any(),
                )
            )
            .thenReturn(signatureString)

        serviceImpl.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY)
        verify(agentAllowlistStorage).writeCurrentAllowlist(signatureString)
    }

    @RequiresFlagsEnabled(
        FLAG_APP_FUNCTION_ACCESS_SERVICE_ENABLED,
        FLAG_APP_FUNCTION_ACCESS_API_ENABLED,
    )
    @Test
    fun onBootPhase_readPreviousAllowlist_whenDeviceConfigInvalid() {
        val validPackages = listOf(SignedPackage("com.valid.package", byteArrayOf()))

        val invalidSignatureString = "com.example.test1:invalid_certificate_string"
        whenever(
                DeviceConfig.getString(
                    eq("machine_learning"),
                    eq("allowlisted_app_functions_agents"),
                    any(),
                )
            )
            .thenReturn(invalidSignatureString)

        whenever(agentAllowlistStorage.readPreviousValidAllowlist()).thenReturn(validPackages)

        serviceImpl.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY)

        verify(agentAllowlistStorage).readPreviousValidAllowlist()
        verify(agentAllowlistStorage, never()).writeCurrentAllowlist(invalidSignatureString)
    }

    private fun setDeviceSettingPackages(deviceSettings: Array<String>) {
        context.orCreateTestableResources.addOverride(
            R.array.config_appFunctionDeviceSettingsPackages,
+2 −0
Original line number Diff line number Diff line
@@ -82,6 +82,8 @@ class AppFunctionsLoggingTest {
            mock<IUriGrantsManager>(),
            mock<UriGrantsManagerInternal>(),
            mock<DeviceSettingHelper>(),
            mock<AppFunctionAgentAllowlistStorage>(),
            MoreExecutors.directExecutor(),
        )

    @Before