Merge changes from topic "uwb_aosp_service_permission" into sc-dev

* changes:
  UwbService: Enforce UWB_RANGING runtime permission
  UwbService: Enforce UWB_PRIVILEGED permission
This commit is contained in:
Roshan Pius
2021-04-21 00:34:54 +00:00
committed by Android (Google) Code Review
6 changed files with 166 additions and 29 deletions

View File

@@ -16,6 +16,7 @@
package android.uwb;
import android.content.AttributionSource;
import android.os.PersistableBundle;
import android.uwb.IUwbAdapterStateCallbacks;
import android.uwb.IUwbRangingCallbacks;
@@ -77,11 +78,13 @@ interface IUwbAdapter {
* If the provided sessionHandle is already open for the calling client, then
* #onRangingOpenFailed must be called and the new session must not be opened.
*
* @param attributionSource AttributionSource to use for permission enforcement.
* @param sessionHandle the session handle to open ranging for
* @param rangingCallbacks the callbacks used to deliver ranging information
* @param parameters the configuration to use for ranging
*/
void openRanging(in SessionHandle sessionHandle,
void openRanging(in AttributionSource attributionSource,
in SessionHandle sessionHandle,
in IUwbRangingCallbacks rangingCallbacks,
in PersistableBundle parameters);

View File

@@ -63,8 +63,7 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub {
new RangingSession(executor, callbacks, mAdapter, sessionHandle);
mRangingSessionTable.put(sessionHandle, session);
try {
// TODO: Pass in the attributionSource to the service.
mAdapter.openRanging(sessionHandle, this, params);
mAdapter.openRanging(attributionSource, sessionHandle, this, params);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -57,7 +57,8 @@ public class RangingManagerTest {
RangingManager rangingManager = new RangingManager(adapter);
RangingSession.Callback callback = mock(RangingSession.Callback.class);
rangingManager.openSession(ATTRIBUTION_SOURCE, PARAMS, EXECUTOR, callback);
verify(adapter, times(1)).openRanging(any(), eq(rangingManager), eq(PARAMS));
verify(adapter, times(1)).openRanging(
eq(ATTRIBUTION_SOURCE), any(), any(), any());
}
@Test
@@ -80,11 +81,13 @@ public class RangingManagerTest {
RangingManager rangingManager = new RangingManager(adapter);
rangingManager.openSession(ATTRIBUTION_SOURCE, PARAMS, EXECUTOR, callback1);
verify(adapter, times(1)).openRanging(sessionHandleCaptor.capture(), any(), any());
verify(adapter, times(1)).openRanging(
eq(ATTRIBUTION_SOURCE), sessionHandleCaptor.capture(), any(), any());
SessionHandle sessionHandle1 = sessionHandleCaptor.getValue();
rangingManager.openSession(ATTRIBUTION_SOURCE, PARAMS, EXECUTOR, callback2);
verify(adapter, times(2)).openRanging(sessionHandleCaptor.capture(), any(), any());
verify(adapter, times(2)).openRanging(
eq(ATTRIBUTION_SOURCE), sessionHandleCaptor.capture(), any(), any());
SessionHandle sessionHandle2 = sessionHandleCaptor.getValue();
rangingManager.onRangingOpened(sessionHandle1);
@@ -106,7 +109,8 @@ public class RangingManagerTest {
ArgumentCaptor.forClass(SessionHandle.class);
rangingManager.openSession(ATTRIBUTION_SOURCE, PARAMS, EXECUTOR, callback);
verify(adapter, times(1)).openRanging(sessionHandleCaptor.capture(), any(), any());
verify(adapter, times(1)).openRanging(
eq(ATTRIBUTION_SOURCE), sessionHandleCaptor.capture(), any(), any());
SessionHandle handle = sessionHandleCaptor.getValue();
rangingManager.onRangingOpened(handle);
@@ -151,11 +155,13 @@ public class RangingManagerTest {
ArgumentCaptor.forClass(SessionHandle.class);
rangingManager.openSession(ATTRIBUTION_SOURCE, PARAMS, EXECUTOR, callback1);
verify(adapter, times(1)).openRanging(sessionHandleCaptor.capture(), any(), any());
verify(adapter, times(1)).openRanging(
eq(ATTRIBUTION_SOURCE), sessionHandleCaptor.capture(), any(), any());
SessionHandle sessionHandle1 = sessionHandleCaptor.getValue();
rangingManager.openSession(ATTRIBUTION_SOURCE, PARAMS, EXECUTOR, callback2);
verify(adapter, times(2)).openRanging(sessionHandleCaptor.capture(), any(), any());
verify(adapter, times(2)).openRanging(
eq(ATTRIBUTION_SOURCE), sessionHandleCaptor.capture(), any(), any());
SessionHandle sessionHandle2 = sessionHandleCaptor.getValue();
rangingManager.onRangingClosed(sessionHandle1, REASON, PARAMS);
@@ -178,12 +184,14 @@ public class RangingManagerTest {
RangingManager rangingManager = new RangingManager(adapter);
rangingManager.openSession(ATTRIBUTION_SOURCE, PARAMS, EXECUTOR, callback1);
verify(adapter, times(1)).openRanging(sessionHandleCaptor.capture(), any(), any());
verify(adapter, times(1)).openRanging(
eq(ATTRIBUTION_SOURCE), sessionHandleCaptor.capture(), any(), any());
SessionHandle sessionHandle1 = sessionHandleCaptor.getValue();
rangingManager.onRangingStarted(sessionHandle1, PARAMS);
rangingManager.openSession(ATTRIBUTION_SOURCE, PARAMS, EXECUTOR, callback2);
verify(adapter, times(2)).openRanging(sessionHandleCaptor.capture(), any(), any());
verify(adapter, times(2)).openRanging(
eq(ATTRIBUTION_SOURCE), sessionHandleCaptor.capture(), any(), any());
SessionHandle sessionHandle2 = sessionHandleCaptor.getValue();
rangingManager.onRangingStarted(sessionHandle2, PARAMS);
@@ -230,7 +238,8 @@ public class RangingManagerTest {
ArgumentCaptor.forClass(SessionHandle.class);
rangingManager.openSession(ATTRIBUTION_SOURCE, PARAMS, EXECUTOR, callback);
verify(adapter, times(1)).openRanging(sessionHandleCaptor.capture(), any(), any());
verify(adapter, times(1)).openRanging(
eq(ATTRIBUTION_SOURCE), sessionHandleCaptor.capture(), any(), any());
SessionHandle handle = sessionHandleCaptor.getValue();
rangingManager.onRangingOpenFailed(handle, reasonIn, PARAMS);
@@ -238,7 +247,8 @@ public class RangingManagerTest {
// Open a new session
rangingManager.openSession(ATTRIBUTION_SOURCE, PARAMS, EXECUTOR, callback);
verify(adapter, times(2)).openRanging(sessionHandleCaptor.capture(), any(), any());
verify(adapter, times(2)).openRanging(
eq(ATTRIBUTION_SOURCE), sessionHandleCaptor.capture(), any(), any());
handle = sessionHandleCaptor.getValue();
rangingManager.onRangingOpened(handle);

View File

@@ -16,6 +16,8 @@
package com.android.server.uwb;
import static android.Manifest.permission.UWB_PRIVILEGED;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
@@ -24,11 +26,13 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.AttributionSource;
import android.content.Context;
import android.os.IBinder;
import android.os.PersistableBundle;
@@ -58,6 +62,11 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@Presubmit
public class UwbServiceImplTest {
private static final int UID = 343453;
private static final String PACKAGE_NAME = "com.uwb.test";
private static final AttributionSource ATTRIBUTION_SOURCE =
new AttributionSource.Builder(UID).setPackageName(PACKAGE_NAME).build();
@Mock private IUwbAdapter mVendorService;
@Mock private IBinder mVendorServiceBinder;
@Mock private Context mContext;
@@ -72,6 +81,7 @@ public class UwbServiceImplTest {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mUwbInjector.getVendorService()).thenReturn(mVendorService);
when(mUwbInjector.checkUwbRangingPermissionForDataDelivery(any(), any())).thenReturn(true);
when(mVendorService.asBinder()).thenReturn(mVendorServiceBinder);
mUwbServiceImpl = new UwbServiceImpl(mContext, mUwbInjector);
}
@@ -129,10 +139,11 @@ public class UwbServiceImplTest {
final IBinder cbBinder = mock(IBinder.class);
when(cb.asBinder()).thenReturn(cbBinder);
mUwbServiceImpl.openRanging(sessionHandle, cb, parameters);
mUwbServiceImpl.openRanging(ATTRIBUTION_SOURCE, sessionHandle, cb, parameters);
verify(mVendorService).openRanging(
eq(sessionHandle), mRangingCbCaptor.capture(), eq(parameters));
eq(ATTRIBUTION_SOURCE), eq(sessionHandle), mRangingCbCaptor.capture(),
eq(parameters));
assertThat(mRangingCbCaptor.getValue()).isNotNull();
}
@@ -182,10 +193,11 @@ public class UwbServiceImplTest {
final IBinder cbBinder = mock(IBinder.class);
when(cb.asBinder()).thenReturn(cbBinder);
mUwbServiceImpl.openRanging(sessionHandle, cb, parameters);
mUwbServiceImpl.openRanging(ATTRIBUTION_SOURCE, sessionHandle, cb, parameters);
verify(mVendorService).openRanging(
eq(sessionHandle), mRangingCbCaptor.capture(), eq(parameters));
eq(ATTRIBUTION_SOURCE), eq(sessionHandle), mRangingCbCaptor.capture(),
eq(parameters));
assertThat(mRangingCbCaptor.getValue()).isNotNull();
// Invoke vendor service callbacks and ensure that the corresponding app callback is
@@ -242,10 +254,11 @@ public class UwbServiceImplTest {
final IBinder cbBinder = mock(IBinder.class);
when(cb.asBinder()).thenReturn(cbBinder);
mUwbServiceImpl.openRanging(sessionHandle, cb, parameters);
mUwbServiceImpl.openRanging(ATTRIBUTION_SOURCE, sessionHandle, cb, parameters);
verify(mVendorService).openRanging(
eq(sessionHandle), mRangingCbCaptor.capture(), eq(parameters));
eq(ATTRIBUTION_SOURCE), eq(sessionHandle), mRangingCbCaptor.capture(),
eq(parameters));
assertThat(mRangingCbCaptor.getValue()).isNotNull();
verify(cbBinder).linkToDeath(mClientDeathCaptor.capture(), anyInt());
@@ -275,13 +288,14 @@ public class UwbServiceImplTest {
final IBinder cbBinder = mock(IBinder.class);
when(cb.asBinder()).thenReturn(cbBinder);
mUwbServiceImpl.openRanging(sessionHandle, cb, parameters);
mUwbServiceImpl.openRanging(ATTRIBUTION_SOURCE, sessionHandle, cb, parameters);
verify(mVendorServiceBinder).linkToDeath(mVendorServiceDeathCaptor.capture(), anyInt());
assertThat(mVendorServiceDeathCaptor.getValue()).isNotNull();
verify(mVendorService).openRanging(
eq(sessionHandle), mRangingCbCaptor.capture(), eq(parameters));
eq(ATTRIBUTION_SOURCE), eq(sessionHandle), mRangingCbCaptor.capture(),
eq(parameters));
assertThat(mRangingCbCaptor.getValue()).isNotNull();
clearInvocations(cb);
@@ -296,4 +310,56 @@ public class UwbServiceImplTest {
eq(sessionHandle), eq(RangingSession.Callback.REASON_UNKNOWN),
argThat((p) -> p.isEmpty()));
}
@Test
public void testThrowSecurityExceptionWhenCalledWithoutUwbPrivilegedPermission()
throws Exception {
doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
eq(UWB_PRIVILEGED), any());
final IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class);
try {
mUwbServiceImpl.registerAdapterStateCallbacks(cb);
fail();
} catch (SecurityException e) { /* pass */ }
}
@Test
public void testThrowSecurityExceptionWhenOpenRangingCalledWithoutUwbRangingPermission()
throws Exception {
doThrow(new SecurityException()).when(mUwbInjector).enforceUwbRangingPermissionForPreflight(
any());
final SessionHandle sessionHandle = new SessionHandle(5);
final IUwbRangingCallbacks cb = mock(IUwbRangingCallbacks.class);
final PersistableBundle parameters = new PersistableBundle();
final IBinder cbBinder = mock(IBinder.class);
when(cb.asBinder()).thenReturn(cbBinder);
try {
mUwbServiceImpl.openRanging(ATTRIBUTION_SOURCE, sessionHandle, cb, parameters);
fail();
} catch (SecurityException e) { /* pass */ }
}
@Test
public void testOnRangingResultCallbackNotSentWithoutUwbRangingPermission() throws Exception {
final SessionHandle sessionHandle = new SessionHandle(5);
final IUwbRangingCallbacks cb = mock(IUwbRangingCallbacks.class);
final PersistableBundle parameters = new PersistableBundle();
final IBinder cbBinder = mock(IBinder.class);
when(cb.asBinder()).thenReturn(cbBinder);
mUwbServiceImpl.openRanging(ATTRIBUTION_SOURCE, sessionHandle, cb, parameters);
verify(mVendorService).openRanging(
eq(ATTRIBUTION_SOURCE), eq(sessionHandle), mRangingCbCaptor.capture(),
eq(parameters));
assertThat(mRangingCbCaptor.getValue()).isNotNull();
when(mUwbInjector.checkUwbRangingPermissionForDataDelivery(any(), any())).thenReturn(false);
// Ensure the ranging cb is not delivered to the client.
final RangingReport rangingReport = new RangingReport.Builder().build();
mRangingCbCaptor.getValue().onRangingResult(sessionHandle, rangingReport);
verify(cb, never()).onRangingResult(sessionHandle, rangingReport);
}
}

View File

@@ -16,8 +16,13 @@
package com.android.server.uwb;
import static android.Manifest.permission.UWB_RANGING;
import static android.content.PermissionChecker.PERMISSION_GRANTED;
import android.annotation.NonNull;
import android.content.AttributionSource;
import android.content.Context;
import android.content.PermissionChecker;
import android.os.IBinder;
import android.os.ServiceManager;
import android.uwb.IUwbAdapter;
@@ -45,4 +50,34 @@ public class UwbInjector {
if (b == null) return null;
return IUwbAdapter.Stub.asInterface(b);
}
/**
* Throws security exception if the UWB_RANGING permission is not granted for the calling app.
*
* <p>Should be used in situations where the app op should not be noted.
*/
public void enforceUwbRangingPermissionForPreflight(
@NonNull AttributionSource attributionSource) {
if (!attributionSource.checkCallingUid()) {
throw new SecurityException("Invalid attribution source " + attributionSource);
}
int permissionCheckResult = PermissionChecker.checkPermissionForPreflight(
mContext, UWB_RANGING, attributionSource);
if (permissionCheckResult != PERMISSION_GRANTED) {
throw new SecurityException("Caller does not hold UWB_RANGING permission");
}
}
/**
* Returns true if the UWB_RANGING permission is granted for the calling app.
*
* <p>Should be used in situations where data will be delivered and hence the app op should
* be noted.
*/
public boolean checkUwbRangingPermissionForDataDelivery(
@NonNull AttributionSource attributionSource, @NonNull String message) {
int permissionCheckResult = PermissionChecker.checkPermissionForDataDelivery(
mContext, UWB_RANGING, -1, attributionSource, message);
return permissionCheckResult == PERMISSION_GRANTED;
}
}

View File

@@ -17,6 +17,7 @@
package com.android.server.uwb;
import android.annotation.NonNull;
import android.content.AttributionSource;
import android.content.Context;
import android.os.IBinder;
import android.os.PersistableBundle;
@@ -60,13 +61,16 @@ public class UwbServiceImpl extends IUwbAdapter.Stub implements IBinder.DeathRec
* Access to these callbacks are synchronized.
*/
private class UwbRangingCallbacksWrapper extends IUwbRangingCallbacks.Stub
implements IBinder.DeathRecipient{
implements IBinder.DeathRecipient {
private final AttributionSource mAttributionSource;
private final SessionHandle mSessionHandle;
private final IUwbRangingCallbacks mExternalCb;
private boolean mIsValid;
UwbRangingCallbacksWrapper(@NonNull SessionHandle sessionHandle,
UwbRangingCallbacksWrapper(@NonNull AttributionSource attributionSource,
@NonNull SessionHandle sessionHandle,
@NonNull IUwbRangingCallbacks externalCb) {
mAttributionSource = attributionSource;
mSessionHandle = sessionHandle;
mExternalCb = externalCb;
mIsValid = true;
@@ -167,7 +171,12 @@ public class UwbServiceImpl extends IUwbAdapter.Stub implements IBinder.DeathRec
RangingReport rangingReport)
throws RemoteException {
if (!mIsValid) return;
// TODO: Perform permission checks and noteOp.
if (!mUwbInjector.checkUwbRangingPermissionForDataDelivery(
mAttributionSource, "uwb ranging result")) {
Log.e(TAG, "Not delivering ranging result because of permission denial"
+ mSessionHandle);
return;
}
mExternalCb.onRangingResult(sessionHandle, rangingReport);
}
@@ -229,60 +238,75 @@ public class UwbServiceImpl extends IUwbAdapter.Stub implements IBinder.DeathRec
mUwbInjector = uwbInjector;
}
private void enforceUwbPrivilegedPermission() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.UWB_PRIVILEGED,
"UwbService");
}
@Override
public void registerAdapterStateCallbacks(IUwbAdapterStateCallbacks adapterStateCallbacks)
throws RemoteException {
enforceUwbPrivilegedPermission();
getVendorUwbAdapter().registerAdapterStateCallbacks(adapterStateCallbacks);
}
@Override
public void unregisterAdapterStateCallbacks(IUwbAdapterStateCallbacks adapterStateCallbacks)
throws RemoteException {
enforceUwbPrivilegedPermission();
getVendorUwbAdapter().unregisterAdapterStateCallbacks(adapterStateCallbacks);
}
@Override
public long getTimestampResolutionNanos() throws RemoteException {
enforceUwbPrivilegedPermission();
return getVendorUwbAdapter().getTimestampResolutionNanos();
}
@Override
public PersistableBundle getSpecificationInfo() throws RemoteException {
enforceUwbPrivilegedPermission();
return getVendorUwbAdapter().getSpecificationInfo();
}
@Override
public void openRanging(SessionHandle sessionHandle, IUwbRangingCallbacks rangingCallbacks,
public void openRanging(AttributionSource attributionSource,
SessionHandle sessionHandle, IUwbRangingCallbacks rangingCallbacks,
PersistableBundle parameters) throws RemoteException {
enforceUwbPrivilegedPermission();
mUwbInjector.enforceUwbRangingPermissionForPreflight(attributionSource);
UwbRangingCallbacksWrapper wrapperCb =
new UwbRangingCallbacksWrapper(sessionHandle, rangingCallbacks);
new UwbRangingCallbacksWrapper(attributionSource, sessionHandle, rangingCallbacks);
synchronized (mCallbacksMap) {
mCallbacksMap.put(sessionHandle, wrapperCb);
}
getVendorUwbAdapter().openRanging(sessionHandle, wrapperCb, parameters);
getVendorUwbAdapter().openRanging(attributionSource, sessionHandle, wrapperCb, parameters);
}
@Override
public void startRanging(SessionHandle sessionHandle, PersistableBundle parameters)
throws RemoteException {
// TODO: Perform permission checks.
enforceUwbPrivilegedPermission();
getVendorUwbAdapter().startRanging(sessionHandle, parameters);
}
@Override
public void reconfigureRanging(SessionHandle sessionHandle, PersistableBundle parameters)
throws RemoteException {
enforceUwbPrivilegedPermission();
getVendorUwbAdapter().reconfigureRanging(sessionHandle, parameters);
}
@Override
public void stopRanging(SessionHandle sessionHandle) throws RemoteException {
enforceUwbPrivilegedPermission();
getVendorUwbAdapter().stopRanging(sessionHandle);
}
@Override
public void closeRanging(SessionHandle sessionHandle) throws RemoteException {
enforceUwbPrivilegedPermission();
getVendorUwbAdapter().closeRanging(sessionHandle);
}
}