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

Commit 54f2ee9a authored by Oleg Blinnikov's avatar Oleg Blinnikov
Browse files

Fake WifiDisplay address without location permission

The MAC address of WifiDisplays is now only visible to apps holding the
`android.Manifest.permission.ACCESS_FINE_LOCATION` permission when
calling `DisplayManagerService.getWifiDisplayStatus()`. Otherwise, the
device address is replaced with "00:00:00:00:00:00". Internal system
operations and broadcasts still use the full device address.

Bug: 242846520
Test: atest DisplayManagerServiceTest
Flag: EXEMPT simple bugfix
Change-Id: I01265c74d6c4200f82ad24196eb6fc19ee2e6c75
parent 2525179c
Loading
Loading
Loading
Loading
+6 −0
Original line number Original line Diff line number Diff line
@@ -32,6 +32,7 @@ import java.util.Objects;
 * @hide
 * @hide
 */
 */
public final class WifiDisplay implements Parcelable {
public final class WifiDisplay implements Parcelable {
    private static final String UNKNOWN_MAC_ADDRESS = "00:00:00:00:00:00";
    private final String mDeviceAddress;
    private final String mDeviceAddress;
    private final String mDeviceName;
    private final String mDeviceName;
    private final String mDeviceAlias;
    private final String mDeviceAlias;
@@ -193,4 +194,9 @@ public final class WifiDisplay implements Parcelable {
                + ", isRemembered " + mIsRemembered;
                + ", isRemembered " + mIsRemembered;
        return result;
        return result;
    }
    }

    public WifiDisplay copy(boolean isDeviceAddressVisible) {
        return new WifiDisplay(isDeviceAddressVisible ? mDeviceAddress : UNKNOWN_MAC_ADDRESS,
                mDeviceName, mDeviceAlias, mIsAvailable, mCanConnect, mIsRemembered);
    }
}
}
+13 −4
Original line number Original line Diff line number Diff line
@@ -1690,10 +1690,11 @@ public final class DisplayManagerService extends SystemService {
        }
        }
    }
    }


    private WifiDisplayStatus getWifiDisplayStatusInternal() {
    private WifiDisplayStatus getWifiDisplayStatusInternal(boolean hasLocationPermission) {
        synchronized (mSyncRoot) {
        synchronized (mSyncRoot) {
            if (mWifiDisplayAdapter != null) {
            if (mWifiDisplayAdapter != null) {
                return mWifiDisplayAdapter.getWifiDisplayStatusLocked();
                // Device address is visible if the caller has location permission.
                return mWifiDisplayAdapter.getWifiDisplayStatusLocked(hasLocationPermission);
            }
            }
            return new WifiDisplayStatus();
            return new WifiDisplayStatus();
        }
        }
@@ -4148,6 +4149,12 @@ public final class DisplayManagerService extends SystemService {
        }
        }
    }
    }


    @VisibleForTesting
    @Nullable
    WifiDisplayController.Listener getWifiDisplayListener() {
        return mWifiDisplayAdapter != null ? mWifiDisplayAdapter.getWifiDisplayListener() : null;
    }

    private void initializeDisplayPowerControllersLocked() {
    private void initializeDisplayPowerControllersLocked() {
        mLogicalDisplayMapper.forEachLocked(this::addDisplayPowerControllerLocked);
        mLogicalDisplayMapper.forEachLocked(this::addDisplayPowerControllerLocked);
    }
    }
@@ -5032,10 +5039,12 @@ public final class DisplayManagerService extends SystemService {
        public WifiDisplayStatus getWifiDisplayStatus() {
        public WifiDisplayStatus getWifiDisplayStatus() {
            // This request does not require special permissions.
            // This request does not require special permissions.
            // Any app can get information about available wifi displays.
            // Any app can get information about available wifi displays.

            // Except for location permission, which is required to get the wifi display address.
            final boolean hasLocationPermission = checkCallingPermission(
                    android.Manifest.permission.ACCESS_FINE_LOCATION, "getWifiDisplayStatus()");
            final long token = Binder.clearCallingIdentity();
            final long token = Binder.clearCallingIdentity();
            try {
            try {
                return getWifiDisplayStatusInternal();
                return getWifiDisplayStatusInternal(hasLocationPermission);
            } finally {
            } finally {
                Binder.restoreCallingIdentity(token);
                Binder.restoreCallingIdentity(token);
            }
            }
+21 −3
Original line number Original line Diff line number Diff line
@@ -39,6 +39,7 @@ import android.view.DisplayShape;
import android.view.Surface;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl;


import androidx.annotation.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -118,7 +119,8 @@ final class WifiDisplayAdapter extends DisplayAdapter {
    public void dumpLocked(PrintWriter pw) {
    public void dumpLocked(PrintWriter pw) {
        super.dumpLocked(pw);
        super.dumpLocked(pw);


        pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked());
        pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked(
                /* isDeviceAddressVisible= */ true));
        pw.println("mFeatureState=" + mFeatureState);
        pw.println("mFeatureState=" + mFeatureState);
        pw.println("mScanState=" + mScanState);
        pw.println("mScanState=" + mScanState);
        pw.println("mActiveDisplayState=" + mActiveDisplayState);
        pw.println("mActiveDisplayState=" + mActiveDisplayState);
@@ -293,7 +295,18 @@ final class WifiDisplayAdapter extends DisplayAdapter {
        }
        }
    }
    }


    public WifiDisplayStatus getWifiDisplayStatusLocked() {
    public WifiDisplayStatus getWifiDisplayStatusLocked(boolean isDeviceAddressVisible) {
        if (!isDeviceAddressVisible) {
            var displaysCopy = new WifiDisplay[mDisplays.length];
            for (int i = 0; i < mDisplays.length; i++) {
                displaysCopy[i] = mDisplays[i].copy(isDeviceAddressVisible);
            }
            var activeDisplayCopy =
                mActiveDisplay == null ? null : mActiveDisplay.copy(isDeviceAddressVisible);
            return new WifiDisplayStatus(mFeatureState, mScanState, mActiveDisplayState,
                    activeDisplayCopy, displaysCopy, mSessionInfo);
        }

        if (mCurrentStatus == null) {
        if (mCurrentStatus == null) {
            mCurrentStatus = new WifiDisplayStatus(
            mCurrentStatus = new WifiDisplayStatus(
                    mFeatureState, mScanState, mActiveDisplayState,
                    mFeatureState, mScanState, mActiveDisplayState,
@@ -306,6 +319,11 @@ final class WifiDisplayAdapter extends DisplayAdapter {
        return mCurrentStatus;
        return mCurrentStatus;
    }
    }


    @VisibleForTesting
    public WifiDisplayController.Listener getWifiDisplayListener() {
        return mWifiDisplayListener;
    }

    private void updateDisplaysLocked() {
    private void updateDisplaysLocked() {
        List<WifiDisplay> displays = new ArrayList<WifiDisplay>(
        List<WifiDisplay> displays = new ArrayList<WifiDisplay>(
                mAvailableDisplays.length + mRememberedDisplays.length);
                mAvailableDisplays.length + mRememberedDisplays.length);
@@ -436,7 +454,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
            intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
            intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
            intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
                    getWifiDisplayStatusLocked());
                    getWifiDisplayStatusLocked(/* isDeviceAddressVisible= */ true));


            options = BroadcastOptions.makeBasic();
            options = BroadcastOptions.makeBasic();
            options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
            options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+73 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.server.display;
package com.android.server.display;


import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
@@ -126,8 +127,10 @@ import android.hardware.display.HdrConversionMode;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.hardware.display.VirtualDisplayConfig;
import android.hardware.display.WifiDisplay;
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionManager;
import android.media.projection.IMediaProjectionManager;
import android.net.wifi.p2p.WifiP2pManager;
import android.os.Binder;
import android.os.Binder;
import android.os.Build;
import android.os.Build;
import android.os.Handler;
import android.os.Handler;
@@ -232,6 +235,7 @@ import java.util.stream.LongStream;
@RunWith(JUnitParamsRunner.class)
@RunWith(JUnitParamsRunner.class)
public class DisplayManagerServiceTest {
public class DisplayManagerServiceTest {
    private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
    private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
    private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2;
    private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10;
    private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10;


    private static final float FLOAT_TOLERANCE = 0.01f;
    private static final float FLOAT_TOLERANCE = 0.01f;
@@ -446,6 +450,7 @@ public class DisplayManagerServiceTest {
    @Mock WindowManagerPolicy mMockedWindowManagerPolicy;
    @Mock WindowManagerPolicy mMockedWindowManagerPolicy;


    @Mock IBatteryStats mMockedBatteryStats;
    @Mock IBatteryStats mMockedBatteryStats;
    @Mock WifiP2pManager mMockedWifiP2pManager;


    @Rule
    @Rule
    public final ExtendedMockitoRule mExtendedMockitoRule =
    public final ExtendedMockitoRule mExtendedMockitoRule =
@@ -4274,6 +4279,66 @@ public class DisplayManagerServiceTest {
        assertThrows(SecurityException.class, displayManagerBinderService::resumeWifiDisplay);
        assertThrows(SecurityException.class, displayManagerBinderService::resumeWifiDisplay);
    }
    }


    @Test
    public void getWifiDisplayStatus_withoutFineLocationPermission_shouldReturnFakeAddress() {
        doNothing().when(mContext).sendBroadcastAsUser(any(), any(), isNull(), any());
        doReturn(mMockedWifiP2pManager).when(mContext).getSystemService(Context.WIFI_P2P_SERVICE);
        doReturn(true).when(mResources).getBoolean(R.bool.config_enableWifiDisplay);
        DisplayManagerService displayManager =
                new DisplayManagerService(mContext, mBasicInjector);
        DisplayManagerService.BinderService displayManagerBinderService =
                displayManager.new BinderService();
        registerDefaultDisplays(displayManager);
        displayManager.systemReady(/* safeMode= */ false);
        registerAdditionalDisplays(displayManager);

        var wifiDisplayListener = displayManager.getWifiDisplayListener();

        WifiDisplay[] availableDisplays = new WifiDisplay[1];
        availableDisplays[0] = new WifiDisplay(
                /* deviceAddress = */ "11:22:33:44:55:66",
                /* deviceName= */ "deviceName",
                /* deviceAlias= */ "deviceAlias",
                /* available= */ true,
                /* canConnect= */ true,
                /* remembered= */ false);
        wifiDisplayListener.onScanResults(availableDisplays);

        assertThat(displayManagerBinderService.getWifiDisplayStatus().getDisplays()[0]
                       .getDeviceAddress()).isEqualTo("00:00:00:00:00:00");
    }

    @Test
    public void getWifiDisplayStatus_withFineLocationPermission_shouldReturnRealAddress() {
        doNothing().when(mContext).sendBroadcastAsUser(any(), any(), isNull(), any());
        doReturn(mMockedWifiP2pManager).when(mContext).getSystemService(Context.WIFI_P2P_SERVICE);
        doReturn(true).when(mResources).getBoolean(R.bool.config_enableWifiDisplay);
        when(mContext.checkCallingPermission(ACCESS_FINE_LOCATION)).thenReturn(
                PackageManager.PERMISSION_GRANTED);
        DisplayManagerService displayManager =
                new DisplayManagerService(mContext, mBasicInjector);
        DisplayManagerService.BinderService displayManagerBinderService =
                displayManager.new BinderService();
        registerDefaultDisplays(displayManager);
        displayManager.systemReady(/* safeMode= */ false);
        registerAdditionalDisplays(displayManager);

        var wifiDisplayListener = displayManager.getWifiDisplayListener();

        WifiDisplay[] availableDisplays = new WifiDisplay[1];
        availableDisplays[0] = new WifiDisplay(
                /* deviceAddress = */ "11:22:33:44:55:66",
                /* deviceName= */ "deviceName",
                /* deviceAlias= */ "deviceAlias",
                /* available= */ true,
                /* canConnect= */ true,
                /* remembered= */ false);
        wifiDisplayListener.onScanResults(availableDisplays);

        assertThat(displayManagerBinderService.getWifiDisplayStatus().getDisplays()[0]
                       .getDeviceAddress()).isEqualTo("11:22:33:44:55:66");
    }

    @Test
    @Test
    public void setUserDisabledHdrTypes_withoutPermission_shouldThrowException() {
    public void setUserDisabledHdrTypes_withoutPermission_shouldThrowException() {
        DisplayManagerService displayManager =
        DisplayManagerService displayManager =
@@ -5046,6 +5111,14 @@ public class DisplayManagerServiceTest {
        flushHandlers();
        flushHandlers();
    }
    }


    private void registerAdditionalDisplays(DisplayManagerService displayManager) {
        Handler handler = displayManager.getDisplayHandler();
        // Would prefer to call displayManager.onStart() directly here but it performs binderService
        // registration which triggers security exceptions when running from a test.
        handler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
        flushHandlers();
    }

    private void flushHandlers() {
    private void flushHandlers() {
        com.android.server.testutils.TestUtils.flushLoopers(mDisplayLooperManager,
        com.android.server.testutils.TestUtils.flushLoopers(mDisplayLooperManager,
                mPowerLooperManager, mBackgroundLooperManager);
                mPowerLooperManager, mBackgroundLooperManager);