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

Commit ab42e66b authored by Anthony Stange's avatar Anthony Stange
Browse files

Enforce nanoapp permissions for ContextHub APIs

Adds the following behavior:
1) Ensures nanoapp messages aren't delivered to host apps unless they
hold the required permissions
2) Ensures that host apps continue to hold the required permissions set

Also pipes the package name for callback clients since their package
name can't be determined on the other side of the binder.

Bug: 166846988
Test: Disable permissions on host client and verify that nanoapp and
host lose communication after ~1 minute
Test: Re-enable permissions on host client and verify host receives
appropriate callback

Change-Id: I14af504f0b230f6ce15852f16fc8b174fdd52252
parent 750d5df6
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.ActivityThread;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -849,9 +850,18 @@ public final class ContextHubManager {
            attributionTag = context.getAttributionTag();
        }

        // Workaround for old APIs not providing a context
        String packageName;
        if (context != null) {
            packageName = context.getPackageName();
        } else {
            packageName = ActivityThread.currentPackageName();
        }

        IContextHubClient clientProxy;
        try {
            clientProxy = mService.createClient(hubInfo.getId(), clientInterface, attributionTag);
            clientProxy = mService.createClient(
                    hubInfo.getId(), clientInterface, attributionTag, packageName);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
+2 −1
Original line number Diff line number Diff line
@@ -60,7 +60,8 @@ interface IContextHubService {

    // Creates a client to send and receive messages
    IContextHubClient createClient(
            int contextHubId, in IContextHubClientCallback client, in String attributionTag);
            int contextHubId, in IContextHubClientCallback client, in String attributionTag,
            in String packageName);

    // Creates a PendingIntent-based client to send and receive messages
    IContextHubClient createPendingIntentClient(
+105 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.location.contexthub;

import static java.util.concurrent.TimeUnit.SECONDS;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;

/**
 * A class that manages a timer used to keep track of how much time is left before a
 * {@link ContextHubClientBroker} has its authorization state changed with a nanoapp from
 * DENIED_GRACE_PERIOD to DENIED. Much of this implementation is copied from
 * {@link android.os.CountDownTimer} while adding the ability to specify the provided looper.
 *
 * @hide
 */
public class AuthStateDenialTimer {
    private static final long TIMEOUT_MS = SECONDS.toMillis(60);

    private final ContextHubClientBroker mClient;
    private final long mNanoAppId;
    private final Handler mHandler;

    /**
     * Indicates when the timer should stop in the future.
     */
    private long mStopTimeInFuture;

    /**
     * boolean representing if the timer was cancelled
     */
    private boolean mCancelled = false;

    public AuthStateDenialTimer(ContextHubClientBroker client, long nanoAppId, Looper looper) {
        mClient = client;
        mNanoAppId = nanoAppId;
        mHandler = new CountDownHandler(looper);
    }

    /**
     * Cancel the countdown.
     */
    public synchronized void cancel() {
        mCancelled = true;
        mHandler.removeMessages(MSG);
    }

    /**
     * Start the countdown.
     */
    public synchronized void start() {
        mCancelled = false;
        mStopTimeInFuture = SystemClock.elapsedRealtime() + TIMEOUT_MS;
        mHandler.sendMessage(mHandler.obtainMessage(MSG));
    }

    /**
     * Called when the timer has expired.
     */
    public void onFinish() {
        mClient.handleAuthStateTimerExpiry(mNanoAppId);
    }

    // Message type used to trigger the timer.
    private static final int MSG = 1;

    private class CountDownHandler extends Handler {

        CountDownHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            synchronized (AuthStateDenialTimer.this) {
                if (mCancelled) {
                    return;
                }
                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
                if (millisLeft <= 0) {
                    onFinish();
                } else {
                    sendMessageDelayed(obtainMessage(MSG), millisLeft);
                }
            }
        }
    };
}
+355 −37

File changed.

Preview size limit exceeded, changes collapsed.

+32 −12
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

@@ -136,8 +137,7 @@ import java.util.function.Consumer;
        }
    }

    /* package */ ContextHubClientManager(
            Context context, IContextHubWrapper contextHubProxy) {
    /* package */ ContextHubClientManager(Context context, IContextHubWrapper contextHubProxy) {
        mContext = context;
        mContextHubProxy = contextHubProxy;
    }
@@ -155,13 +155,15 @@ import java.util.function.Consumer;
     */
    /* package */ IContextHubClient registerClient(
            ContextHubInfo contextHubInfo, IContextHubClientCallback clientCallback,
            String attributionTag) {
            String attributionTag, ContextHubTransactionManager transactionManager,
            String packageName) {
        ContextHubClientBroker broker;
        synchronized (this) {
            short hostEndPointId = getHostEndPointId();
            broker = new ContextHubClientBroker(
                    mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
                    hostEndPointId, clientCallback, attributionTag);
                    hostEndPointId, clientCallback, attributionTag, transactionManager,
                    packageName);
            mHostEndPointIdToClientMap.put(hostEndPointId, broker);
            mRegistrationRecordDeque.add(
                    new RegistrationRecord(broker.toString(), ACTION_REGISTERED));
@@ -194,7 +196,7 @@ import java.util.function.Consumer;
     */
    /* package */ IContextHubClient registerClient(
            ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId,
            String attributionTag) {
            String attributionTag, ContextHubTransactionManager transactionManager) {
        ContextHubClientBroker broker;
        String registerString = "Regenerated";
        synchronized (this) {
@@ -204,7 +206,8 @@ import java.util.function.Consumer;
                short hostEndPointId = getHostEndPointId();
                broker = new ContextHubClientBroker(
                        mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
                        hostEndPointId, pendingIntent, nanoAppId, attributionTag);
                        hostEndPointId, pendingIntent, nanoAppId, attributionTag,
                        transactionManager);
                mHostEndPointIdToClientMap.put(hostEndPointId, broker);
                registerString = "Registered";
                mRegistrationRecordDeque.add(
@@ -225,8 +228,13 @@ import java.util.function.Consumer;
     *
     * @param contextHubId the ID of the hub where the nanoapp sent the message from
     * @param message the message send by a nanoapp
     * @param nanoappPermissions the set of permissions the nanoapp holds
     * @param messagePermissions the set of permissions that should be used for attributing
     * permissions when this message is consumed by a client
     */
    /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) {
    /* package */ void onMessageFromNanoApp(
            int contextHubId, ContextHubMsg message, List<String> nanoappPermissions,
            List<String> messagePermissions) {
        NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message);

        if (DEBUG_LOG_ENABLED) {
@@ -234,11 +242,19 @@ import java.util.function.Consumer;
        }

        if (clientMessage.isBroadcastMessage()) {
            broadcastMessage(contextHubId, clientMessage);
            // Broadcast messages shouldn't be sent with any permissions tagged per CHRE API
            // requirements.
            if (!messagePermissions.isEmpty()) {
                Log.wtf(TAG, "Received broadcast message with permissions from " + message.appName);
            }

            broadcastMessage(
                    contextHubId, clientMessage, nanoappPermissions, messagePermissions);
        } else {
            ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint);
            if (proxy != null) {
                proxy.sendMessageToClient(clientMessage);
                proxy.sendMessageToClient(
                        clientMessage, nanoappPermissions, messagePermissions);
            } else {
                Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = "
                        + message.hostEndPoint + ")");
@@ -333,8 +349,12 @@ import java.util.function.Consumer;
     * @param contextHubId the ID of the hub where the nanoapp sent the message from
     * @param message      the message send by a nanoapp
     */
    private void broadcastMessage(int contextHubId, NanoAppMessage message) {
        forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message));
    private void broadcastMessage(
            int contextHubId, NanoAppMessage message, List<String> nanoappPermissions,
            List<String> messagePermissions) {
        forEachClientOfHub(contextHubId,
                client -> client.sendMessageToClient(
                        message, nanoappPermissions, messagePermissions));
    }

    /**
Loading