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

Commit 3ec33a7e authored by Roshan Pius's avatar Roshan Pius
Browse files

UwbService: Enforce UWB_RANGING runtime permission

i. Use the provided AttributionSource for runtime permission checks.
ii. Check for preflight permissions when the callbacks are registered
during openRanging.
iii. Check for data delivery permission when the callback is providing
a new ranging result.

Bug: 183904955
Test: Compiles
Change-Id: I8f3b42d865ac0d33d579ad4558b3aad6470bba94
parent 03d6450b
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -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);

+1 −2
Original line number Diff line number Diff line
@@ -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();
            }
+20 −10
Original line number Diff line number Diff line
@@ -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);

+59 −8
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ 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;
@@ -61,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;
@@ -75,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);
    }
@@ -132,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();
    }

@@ -185,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
@@ -245,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());
@@ -278,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);
@@ -311,4 +322,44 @@ public class UwbServiceImplTest {
            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);
    }
}
+35 −0
Original line number Diff line number Diff line
@@ -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;
    }
}
Loading