summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Anthony Stange <stange@google.com> 2021-01-29 18:04:39 +0000
committer Anthony Stange <stange@google.com> 2021-02-17 16:53:37 +0000
commitab42e66b4c27253a6a45c3ce70bd798694d44631 (patch)
treed42bf4fc5c99528d1e1d2be522759ff39fe6fcb5
parent750d5df6c6b64807c6d14b6a5dfef008b8f1d750 (diff)
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
-rw-r--r--core/java/android/hardware/location/ContextHubManager.java12
-rw-r--r--core/java/android/hardware/location/IContextHubService.aidl3
-rw-r--r--services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java105
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java392
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java44
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java40
-rw-r--r--services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java21
7 files changed, 538 insertions, 79 deletions
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index ebb3021bf083..a65f36b14f13 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -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();
}
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 4961195a3017..92882c4f93bb 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -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(
diff --git a/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java
new file mode 100644
index 000000000000..85de4bb1eefc
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/AuthStateDenialTimer.java
@@ -0,0 +1,105 @@
+/*
+ * 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);
+ }
+ }
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index d3c853da9b02..3d294de256ff 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -17,9 +17,13 @@
package com.android.server.location.contexthub;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED_GRACE_PERIOD;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_GRANTED;
import android.Manifest;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -30,19 +34,21 @@ import android.hardware.location.ContextHubManager;
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubClient;
import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.IContextHubTransactionCallback;
import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
import android.os.Binder;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.server.location.ClientBrokerProto;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.Iterator;
-import java.util.Set;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
@@ -52,14 +58,53 @@ import java.util.function.Supplier;
* notification callbacks. This class implements the IContextHubClient object, and the implemented
* APIs must be thread-safe.
*
+ * Additionally, this class is responsible for enforcing permissions usage and attribution are
+ * handled appropriately for a given client. In general, this works as follows:
+ *
+ * Client sending a message to a nanoapp:
+ * 1) When initially sending a message to nanoapps, clients are by default in a grace period state
+ * which allows them to always send their first message to nanoapps. This is done to allow
+ * clients (especially callback clients) to reset their conection to the nanoapp if they are
+ * killed / restarted (e.g. following a permission revocation).
+ * 2) After the initial message is sent, a check of permissions state is performed. If the
+ * client doesn't have permissions to communicate, it is placed into the denied grace period
+ * state and notified so that it can clean up its communication before it is completely denied
+ * access.
+ * 3) For subsequent messages, the auth state is checked synchronously and messages are denied if
+ * the client is denied authorization
+ *
+ * Client receiving a message from a nanoapp:
+ * 1) If a nanoapp sends a message to the client, the authentication state is checked synchronously.
+ * If there has been no message between the two before, the auth state is assumed granted.
+ * 2) The broker then checks that the client has all permissions the nanoapp requires and attributes
+ * all permissions required to consume the message being sent. If both of those checks pass, then
+ * the message is delivered. Otherwise, it's dropped.
+ *
+ * Client losing or gaining permissions (callback client):
+ * 1) Clients are killed when they lose permissions. This will cause callback clients to completely
+ * disconnect from the service. When they are restarted, their initial message will still be
+ * be allowed through and their permissions will be rechecked at that time.
+ * 2) If they gain a permission, the broker will notify them if that permission allows them to
+ * communicate with a nanoapp again.
+ *
+ * Client losing or gaining permissions (PendingIntent client):
+ * 1) Unlike callback clients, PendingIntent clients are able to maintain their connection to the
+ * service when they are killed. In their case, they will receive notifications of the broker
+ * that they have been denied required permissions or gain required permissions.
+ *
* TODO: Consider refactoring this class via inheritance
*
* @hide
*/
public class ContextHubClientBroker extends IContextHubClient.Stub
- implements IBinder.DeathRecipient {
+ implements IBinder.DeathRecipient, AppOpsManager.OnOpChangedListener {
private static final String TAG = "ContextHubClientBroker";
+ /**
+ * Message used by noteOp when this client receives a message from a nanoapp.
+ */
+ private static final String RECEIVE_MSG_NOTE = "NanoappMessageDelivery ";
+
/*
* The context of the service.
*/
@@ -119,6 +164,26 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
*/
private final String mPackage;
+ /**
+ * The PID associated with this client.
+ */
+ private final int mPid;
+
+ /**
+ * The UID associated with this client.
+ */
+ private final int mUid;
+
+ /**
+ * Manager used for noting permissions usage of this broker.
+ */
+ private final AppOpsManager mAppOpsManager;
+
+ /**
+ * Manager used to queue transactions to the context hub.
+ */
+ private final ContextHubTransactionManager mTransactionManager;
+
/*
* True if a PendingIntent has been cancelled.
*/
@@ -130,11 +195,44 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
private final boolean mHasAccessContextHubPermission;
/*
- * The set of nanoapp IDs that represent the group of nanoapps this client has a messaging
- * channel with, i.e. has sent or received messages from this particular nanoapp.
+ * Map containing all nanoapps this client has a messaging channel with and whether it is
+ * allowed to communicate over that channel. A channel is defined to have been opened if the
+ * client has sent or received messages from the particular nanoapp.
+ */
+ private final Map<Long, Integer> mMessageChannelNanoappIdMap =
+ new ConcurrentHashMap<Long, Integer>();
+
+ /**
+ * Map containing all nanoapps that have active auth state denial timers.
+ */
+ private final Map<Long, AuthStateDenialTimer> mNappToAuthTimerMap =
+ new ConcurrentHashMap<Long, AuthStateDenialTimer>();
+
+ /**
+ * Callback used to obtain the latest set of nanoapp permissions and verify this client has
+ * each nanoapps permissions granted.
*/
- private final Set<Long> mMessageChannelNanoappIdSet =
- Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
+ private final IContextHubTransactionCallback mQueryPermsCallback =
+ new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onTransactionComplete(int result) {
+ }
+
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) {
+ Log.e(TAG, "Permissions query failed, but still received nanoapp state");
+ } else if (nanoAppStateList != null) {
+ for (NanoAppState state : nanoAppStateList) {
+ if (mMessageChannelNanoappIdMap.containsKey(state.getNanoAppId())) {
+ List<String> permissions = state.getNanoAppPermissions();
+ updateNanoAppAuthState(state.getNanoAppId(),
+ hasPermissions(permissions), false /* gracePeriodExpired */);
+ }
+ }
+ }
+ }
+ };
/*
* Helper class to manage registered PendingIntent requests from the client.
@@ -182,40 +280,57 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
}
}
- /* package */ ContextHubClientBroker(
- Context context, IContextHubWrapper contextHubProxy,
+ private ContextHubClientBroker(Context context, IContextHubWrapper contextHubProxy,
ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
- short hostEndPointId, IContextHubClientCallback callback, String attributionTag) {
+ short hostEndPointId, IContextHubClientCallback callback, String attributionTag,
+ ContextHubTransactionManager transactionManager, PendingIntent pendingIntent,
+ long nanoAppId, String packageName) {
mContext = context;
mContextHubProxy = contextHubProxy;
mClientManager = clientManager;
mAttachedContextHubInfo = contextHubInfo;
mHostEndPointId = hostEndPointId;
mCallbackInterface = callback;
- mPendingIntentRequest = new PendingIntentRequest();
- mPackage = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
+ if (pendingIntent == null) {
+ mPendingIntentRequest = new PendingIntentRequest();
+ } else {
+ mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
+ }
+ mPackage = packageName;
mAttributionTag = attributionTag;
+ mTransactionManager = transactionManager;
+ mPid = Binder.getCallingPid();
+ mUid = Binder.getCallingUid();
mHasAccessContextHubPermission = context.checkCallingPermission(
Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+
+ startMonitoringOpChanges();
+ }
+
+ /* package */ ContextHubClientBroker(
+ Context context, IContextHubWrapper contextHubProxy,
+ ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
+ short hostEndPointId, IContextHubClientCallback callback, String attributionTag,
+ ContextHubTransactionManager transactionManager, String packageName) {
+ this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId, callback,
+ attributionTag, transactionManager, null /* pendingIntent */, 0 /* nanoAppId */,
+ packageName);
}
/* package */ ContextHubClientBroker(
Context context, IContextHubWrapper contextHubProxy,
ContextHubClientManager clientManager, ContextHubInfo contextHubInfo,
short hostEndPointId, PendingIntent pendingIntent, long nanoAppId,
- String attributionTag) {
- mContext = context;
- mContextHubProxy = contextHubProxy;
- mClientManager = clientManager;
- mAttachedContextHubInfo = contextHubInfo;
- mHostEndPointId = hostEndPointId;
- mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
- mPackage = pendingIntent.getCreatorPackage();
- mAttributionTag = attributionTag;
+ String attributionTag, ContextHubTransactionManager transactionManager) {
+ this(context, contextHubProxy, clientManager, contextHubInfo, hostEndPointId,
+ null /* callback */, attributionTag, transactionManager, pendingIntent, nanoAppId,
+ pendingIntent.getCreatorPackage());
+ }
- mHasAccessContextHubPermission = context.checkCallingPermission(
- Manifest.permission.ACCESS_CONTEXT_HUB) == PERMISSION_GRANTED;
+ private void startMonitoringOpChanges() {
+ mAppOpsManager.startWatchingMode(AppOpsManager.OP_NONE, mPackage, this);
}
/**
@@ -229,18 +344,44 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
public int sendMessageToNanoApp(NanoAppMessage message) {
ContextHubServiceUtil.checkPermissions(mContext);
+ int authState;
+ synchronized (mMessageChannelNanoappIdMap) {
+ // Default to the granted auth state. The true auth state will be checked async if it's
+ // not denied.
+ authState = mMessageChannelNanoappIdMap.getOrDefault(
+ message.getNanoAppId(), AUTHORIZATION_GRANTED);
+ if (authState == AUTHORIZATION_DENIED) {
+ return ContextHubTransaction.RESULT_FAILED_PERMISSION_DENIED;
+ }
+ }
+
int result;
if (isRegistered()) {
- mMessageChannelNanoappIdSet.add(message.getNanoAppId());
+ // Even though the auth state is currently not denied, query the nanoapp permissions
+ // async and verify that the host app currently holds all the requisite permissions.
+ // This can't be done synchronously due to the async query that needs to be performed to
+ // obtain the nanoapp permissions.
+ boolean initialNanoappMessage = false;
+ synchronized (mMessageChannelNanoappIdMap) {
+ if (mMessageChannelNanoappIdMap.get(message.getNanoAppId()) == null) {
+ mMessageChannelNanoappIdMap.put(message.getNanoAppId(), AUTHORIZATION_GRANTED);
+ initialNanoappMessage = true;
+ }
+ }
+
+ if (initialNanoappMessage) {
+ // Only check permissions the first time a nanoapp is queried since nanoapp
+ // permissions don't currently change at runtime. If the host permission changes
+ // later, that'll be checked by onOpChanged.
+ checkNanoappPermsAsync();
+ }
+
ContextHubMsg messageToNanoApp =
ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message);
int contextHubId = mAttachedContextHubInfo.getId();
try {
- // TODO(166846988): Fill in host permissions before sending a message.
- result = mContextHubProxy.sendMessageToHub(
- contextHubId, messageToNanoApp,
- new ArrayList<String>() /* hostPermissions */);
+ result = mContextHubProxy.sendMessageToHub(contextHubId, messageToNanoApp);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
+ contextHubId + ")", e);
@@ -275,6 +416,15 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
onClientExit();
}
+ @Override
+ public void onOpChanged(String op, String packageName) {
+ if (packageName.equals(mPackage)) {
+ if (!mMessageChannelNanoappIdMap.isEmpty()) {
+ checkNanoappPermsAsync();
+ }
+ }
+ }
+
/**
* Used to override the attribution tag with a newer value if a PendingIntent broker is
* retrieved.
@@ -308,15 +458,39 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
* Sends a message to the client associated with this object.
*
* @param message the message that came from a nanoapp
- */
- /* package */ void sendMessageToClient(NanoAppMessage message) {
- mMessageChannelNanoappIdSet.add(message.getNanoAppId());
+ * @param nanoappPermissions permissions required to communicate with the nanoapp sending this
+ * message
+ * @param messagePermissions permissions required to consume the message being delivered. These
+ * permissions are what will be attributed to the client through noteOp.
+ */
+ /* package */ void sendMessageToClient(
+ NanoAppMessage message, List<String> nanoappPermissions,
+ List<String> messagePermissions) {
+ long nanoAppId = message.getNanoAppId();
+
+ int authState = mMessageChannelNanoappIdMap.getOrDefault(nanoAppId, AUTHORIZATION_GRANTED);
+
+ // If in the grace period, the host may not receive any messages containing permissions
+ // covered data.
+ if (authState == AUTHORIZATION_DENIED_GRACE_PERIOD && !messagePermissions.isEmpty()) {
+ Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
+ + " in grace period and napp msg has permissions");
+ return;
+ }
+
+ if (authState == AUTHORIZATION_DENIED || !hasPermissions(nanoappPermissions)
+ || !notePermissions(messagePermissions, RECEIVE_MSG_NOTE + nanoAppId)) {
+ Log.e(TAG, "Dropping message from " + Long.toHexString(nanoAppId) + ". " + mPackage
+ + " doesn't have permission");
+ return;
+ }
+
invokeCallback(callback -> callback.onMessageFromNanoApp(message));
Supplier<Intent> supplier =
- () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, message.getNanoAppId())
+ () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, nanoAppId)
.putExtra(ContextHubManager.EXTRA_MESSAGE, message);
- sendPendingIntent(supplier, message.getNanoAppId());
+ sendPendingIntent(supplier, nanoAppId);
}
/**
@@ -325,6 +499,10 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
* @param nanoAppId the ID of the nanoapp that was loaded.
*/
/* package */ void onNanoAppLoaded(long nanoAppId) {
+ // Check the latest state to see if the loaded nanoapp's permissions changed such that the
+ // host app can communicate with it again.
+ checkNanoappPermsAsync();
+
invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId));
sendPendingIntent(
() -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId), nanoAppId);
@@ -392,6 +570,43 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
}
/**
+ * Checks that this client has all of the provided permissions.
+ *
+ * @param permissions list of permissions to check
+ * @return true if the client has all of the permissions granted
+ */
+ /* package */ boolean hasPermissions(List<String> permissions) {
+ for (String permission : permissions) {
+ if (mContext.checkPermission(permission, mPid, mUid) != PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Attributes the provided permissions to the package of this client.
+ *
+ * @param permissions list of permissions covering data the client is about to receive
+ * @param noteMessage message that should be noted alongside permissions attribution to
+ * facilitate debugging
+ * @return true if client has ability to use all of the provided permissions
+ */
+ /* package */ boolean notePermissions(List<String> permissions, String noteMessage) {
+ for (String permission : permissions) {
+ int opCode = mAppOpsManager.permissionToOpCode(permission);
+ if (opCode != AppOpsManager.OP_NONE) {
+ if (mAppOpsManager.noteOp(opCode, mUid, mPackage, mAttributionTag, noteMessage)
+ != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
* @return true if the client is a PendingIntent client that has been cancelled.
*/
/* package */ boolean isPendingIntentCancelled() {
@@ -399,6 +614,92 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
}
/**
+ * Handles timer expiry for a client whose auth state with a nanoapp was previously in the grace
+ * period.
+ */
+ /* package */ void handleAuthStateTimerExpiry(long nanoAppId) {
+ AuthStateDenialTimer timer;
+ synchronized (mMessageChannelNanoappIdMap) {
+ timer = mNappToAuthTimerMap.remove(nanoAppId);
+ }
+
+ if (timer != null) {
+ updateNanoAppAuthState(
+ nanoAppId, false /* hasPermissions */, true /* gracePeriodExpired */);
+ }
+ }
+
+ /**
+ * Verifies this client has the permissions to communicate with all of the nanoapps it has
+ * communicated with in the past.
+ */
+ private void checkNanoappPermsAsync() {
+ ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+ mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
+ mTransactionManager.addTransaction(transaction);
+ }
+
+ /**
+ * Updates the latest authentication state for this client to be able to communicate with the
+ * given nanoapp.
+ */
+ private void updateNanoAppAuthState(
+ long nanoAppId, boolean hasPermissions, boolean gracePeriodExpired) {
+ int curAuthState;
+ int newAuthState;
+ synchronized (mMessageChannelNanoappIdMap) {
+ curAuthState = mMessageChannelNanoappIdMap.getOrDefault(
+ nanoAppId, AUTHORIZATION_GRANTED);
+ newAuthState = curAuthState;
+ // The below logic ensures that only the following transitions are possible:
+ // GRANTED -> DENIED_GRACE_PERIOD only if permissions have been lost
+ // DENIED_GRACE_PERIOD -> DENIED only if the grace period expires
+ // DENIED/DENIED_GRACE_PERIOD -> GRANTED only if permissions are granted again
+ if (gracePeriodExpired) {
+ if (curAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) {
+ newAuthState = AUTHORIZATION_DENIED;
+ }
+ } else {
+ if (curAuthState == AUTHORIZATION_GRANTED && !hasPermissions) {
+ newAuthState = AUTHORIZATION_DENIED_GRACE_PERIOD;
+ } else if (curAuthState != AUTHORIZATION_GRANTED && hasPermissions) {
+ newAuthState = AUTHORIZATION_GRANTED;
+ }
+ }
+
+ if (newAuthState == AUTHORIZATION_GRANTED) {
+ AuthStateDenialTimer timer = mNappToAuthTimerMap.remove(nanoAppId);
+ if (timer != null) {
+ timer.cancel();
+ }
+ } else if (curAuthState == AUTHORIZATION_GRANTED
+ && newAuthState == AUTHORIZATION_DENIED_GRACE_PERIOD) {
+ AuthStateDenialTimer timer =
+ new AuthStateDenialTimer(this, nanoAppId, Looper.getMainLooper());
+ mNappToAuthTimerMap.put(nanoAppId, timer);
+ timer.start();
+ }
+
+ if (curAuthState != newAuthState) {
+ mMessageChannelNanoappIdMap.put(nanoAppId, newAuthState);
+ }
+ }
+ if (curAuthState != newAuthState) {
+ // Don't send the callback in the synchronized block or it could end up in a deadlock.
+ sendAuthStateCallback(nanoAppId, newAuthState);
+ }
+ }
+
+ private void sendAuthStateCallback(long nanoAppId, int authState) {
+ invokeCallback(callback -> callback.onClientAuthorizationChanged(nanoAppId, authState));
+
+ Supplier<Intent> supplier =
+ () -> createIntent(ContextHubManager.EVENT_CLIENT_AUTHORIZATION, nanoAppId)
+ .putExtra(ContextHubManager.EXTRA_CLIENT_AUTHORIZATION_STATE, authState);
+ sendPendingIntent(supplier, nanoAppId);
+ }
+
+ /**
* Helper function to invoke a specified client callback, if the connection is open.
*
* @param consumer the consumer specifying the callback to invoke
@@ -507,6 +808,20 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
mClientManager.unregisterClient(mHostEndPointId);
mRegistered = false;
}
+ mAppOpsManager.stopWatchingMode(this);
+ }
+
+ private String authStateToString(@ContextHubManager.AuthorizationState int state) {
+ switch (state) {
+ case AUTHORIZATION_DENIED:
+ return "DENIED";
+ case AUTHORIZATION_DENIED_GRACE_PERIOD:
+ return "DENIED_GRACE_PERIOD";
+ case AUTHORIZATION_GRANTED:
+ return "GRANTED";
+ default:
+ return "UNKNOWN";
+ }
}
/**
@@ -545,11 +860,14 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
} else {
out += "package: " + mPackage;
}
- if (mMessageChannelNanoappIdSet.size() > 0) {
+ if (mMessageChannelNanoappIdMap.size() > 0) {
out += " messageChannelNanoappSet: (";
- Iterator<Long> it = mMessageChannelNanoappIdSet.iterator();
+ Iterator<Map.Entry<Long, Integer>> it =
+ mMessageChannelNanoappIdMap.entrySet().iterator();
while (it.hasNext()) {
- out += "0x" + Long.toHexString(it.next());
+ Map.Entry<Long, Integer> entry = it.next();
+ out += "0x" + Long.toHexString(entry.getKey()) + " auth state: "
+ + authStateToString(entry.getValue());
if (it.hasNext()) {
out += ",";
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
index 0351edb8f218..bc3f4b1db46d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
@@ -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(
@@ -224,9 +227,14 @@ import java.util.function.Consumer;
* Handles a message sent from a nanoapp.
*
* @param contextHubId the ID of the hub where the nanoapp sent the message from
- * @param message the message send by a nanoapp
+ * @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));
}
/**
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 2eafe6ad41a0..40986fcf03cb 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -64,6 +64,7 @@ import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -98,6 +99,7 @@ public class ContextHubService extends IContextHubService.Stub {
private final Context mContext;
private final Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
+ private final List<String> mSupportedContextHubPerms;
private final List<ContextHubInfo> mContextHubInfoList;
private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
new RemoteCallbackList<>();
@@ -140,7 +142,7 @@ public class ContextHubService extends IContextHubService.Stub {
public void handleClientMsg(ContextHubMsg message) {
handleClientMessageCallback(mContextHubId, message,
Collections.emptyList() /* nanoappPermissions */,
- Collections.emptyList() /* messageContentPermissions */);
+ Collections.emptyList() /* messagePermissions */);
}
@Override
@@ -167,9 +169,9 @@ public class ContextHubService extends IContextHubService.Stub {
@Override
public void handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message,
- ArrayList<String> messageContentPermissions) {
+ ArrayList<String> messagePermissions) {
handleClientMessageCallback(mContextHubId, message.msg_1_0, message.permissions,
- messageContentPermissions);
+ messagePermissions);
}
@Override
@@ -187,14 +189,11 @@ public class ContextHubService extends IContextHubService.Stub {
mClientManager = null;
mDefaultClientMap = Collections.emptyMap();
mContextHubIdToInfoMap = Collections.emptyMap();
+ mSupportedContextHubPerms = Collections.emptyList();
mContextHubInfoList = Collections.emptyList();
return;
}
- mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
- mTransactionManager = new ContextHubTransactionManager(
- mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
-
Pair<List<ContextHub>, List<String>> hubInfo;
try {
hubInfo = mContextHubWrapper.getHubs();
@@ -202,16 +201,21 @@ public class ContextHubService extends IContextHubService.Stub {
Log.e(TAG, "RemoteException while getting Context Hub info", e);
hubInfo = new Pair(Collections.emptyList(), Collections.emptyList());
}
+
mContextHubIdToInfoMap = Collections.unmodifiableMap(
ContextHubServiceUtil.createContextHubInfoMap(hubInfo.first));
+ mSupportedContextHubPerms = hubInfo.second;
mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
+ mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
+ mTransactionManager = new ContextHubTransactionManager(
+ mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
IContextHubClient client = mClientManager.registerClient(
contextHubInfo, createDefaultClientCallback(contextHubId),
- null /* attributionTag */);
+ null /* attributionTag */, mTransactionManager, mContext.getPackageName());
defaultClientMap.put(contextHubId, client);
try {
@@ -611,13 +615,15 @@ public class ContextHubService extends IContextHubService.Stub {
/**
* Handles a unicast or broadcast message from a nanoapp.
*
- * @param contextHubId the ID of the hub the message came from
- * @param message the message contents
+ * @param contextHubId the ID of the hub the message came from
+ * @param message the message contents
+ * @param reqPermissions the permissions required to consume this message
*/
private void handleClientMessageCallback(
int contextHubId, ContextHubMsg message, List<String> nanoappPermissions,
- List<String> messageContentPermissions) {
- mClientManager.onMessageFromNanoApp(contextHubId, message);
+ List<String> messagePermissions) {
+ mClientManager.onMessageFromNanoApp(
+ contextHubId, message, nanoappPermissions, messagePermissions);
}
/**
@@ -723,6 +729,7 @@ public class ContextHubService extends IContextHubService.Stub {
* @param contextHubId the ID of the hub this client is attached to
* @param clientCallback the client interface to register with the service
* @param attributionTag an optional attribution tag within the given package
+ * @param packageName the name of the package creating this client
* @return the generated client interface, null if registration was unsuccessful
* @throws IllegalArgumentException if contextHubId is not a valid ID
* @throws IllegalStateException if max number of clients have already registered
@@ -731,7 +738,7 @@ public class ContextHubService extends IContextHubService.Stub {
@Override
public IContextHubClient createClient(
int contextHubId, IContextHubClientCallback clientCallback,
- @Nullable String attributionTag) throws RemoteException {
+ @Nullable String attributionTag, String packageName) throws RemoteException {
checkPermissions();
if (!isValidContextHubId(contextHubId)) {
throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
@@ -741,7 +748,8 @@ public class ContextHubService extends IContextHubService.Stub {
}
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
- return mClientManager.registerClient(contextHubInfo, clientCallback, attributionTag);
+ return mClientManager.registerClient(
+ contextHubInfo, clientCallback, attributionTag, mTransactionManager, packageName);
}
/**
@@ -766,7 +774,7 @@ public class ContextHubService extends IContextHubService.Stub {
ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
return mClientManager.registerClient(
- contextHubInfo, pendingIntent, nanoAppId, attributionTag);
+ contextHubInfo, pendingIntent, nanoAppId, attributionTag, mTransactionManager);
}
/**
@@ -907,6 +915,8 @@ public class ContextHubService extends IContextHubService.Stub {
for (ContextHubInfo hubInfo : mContextHubIdToInfoMap.values()) {
pw.println(hubInfo);
}
+ pw.println("Supported permissions: "
+ + Arrays.toString(mSupportedContextHubPerms.toArray()));
pw.println("");
pw.println("=================== NANOAPPS ====================");
// Dump nanoAppHash
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index c1d63dd2fbc9..3a5c220eeeae 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -106,9 +106,8 @@ public abstract class IContextHubWrapper {
/**
* Calls the appropriate sendMessageToHub function depending on the HAL version.
*/
- public abstract int sendMessageToHub(
- int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
- ArrayList<String> hostPermissions) throws RemoteException;
+ public abstract int sendMessageToHub(int hubId,
+ android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException;
/**
* @return A valid instance of Contexthub HAL 1.0.
@@ -181,9 +180,8 @@ public abstract class IContextHubWrapper {
mHub.registerCallback(hubId, callback);
}
- public int sendMessageToHub(
- int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
- ArrayList<String> hostPermissions) throws RemoteException {
+ public int sendMessageToHub(int hubId,
+ android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
return mHub.sendMessageToHub(hubId, message);
}
@@ -236,9 +234,8 @@ public abstract class IContextHubWrapper {
mHub.registerCallback(hubId, callback);
}
- public int sendMessageToHub(
- int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
- ArrayList<String> hostPermissions) throws RemoteException {
+ public int sendMessageToHub(int hubId,
+ android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
return mHub.sendMessageToHub(hubId, message);
}
@@ -307,13 +304,11 @@ public abstract class IContextHubWrapper {
mHub.registerCallback_1_2(hubId, callback);
}
- public int sendMessageToHub(
- int hubId, android.hardware.contexthub.V1_0.ContextHubMsg message,
- ArrayList<String> hostPermissions) throws RemoteException {
+ public int sendMessageToHub(int hubId,
+ android.hardware.contexthub.V1_0.ContextHubMsg message) throws RemoteException {
android.hardware.contexthub.V1_2.ContextHubMsg newMessage =
new android.hardware.contexthub.V1_2.ContextHubMsg();
newMessage.msg_1_0 = message;
- newMessage.permissions = hostPermissions;
return mHub.sendMessageToHub_1_2(hubId, newMessage);
}