diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java index f501a50d5d..b92cf69280 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java +++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java @@ -22,6 +22,7 @@ import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.TETHER_PRIVILEGED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; +import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION; import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; @@ -58,6 +59,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.networkstack.apishim.SettingsShimImpl; import com.android.networkstack.apishim.common.SettingsShim; +import com.android.networkstack.tethering.util.TetheringPermissionsUtils; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -72,6 +74,7 @@ public class TetheringService extends Service { private TetheringConnector mConnector; private SettingsShim mSettingsShim; + private TetheringPermissionsUtils mTetheringPermissionsUtils; @Override public void onCreate() { @@ -81,6 +84,7 @@ public class TetheringService extends Service { mConnector = new TetheringConnector(makeTethering(deps), TetheringService.this); mSettingsShim = SettingsShimImpl.newInstance(); + mTetheringPermissionsUtils = new TetheringPermissionsUtils(deps.getContext()); } /** @@ -109,7 +113,11 @@ public class TetheringService extends Service { @Override public void tether(String iface, String callerPkg, String callingAttributionTag, IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; + if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, + false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */, + listener)) { + return; + } mTethering.legacyTether(iface, listener); } @@ -117,7 +125,11 @@ public class TetheringService extends Service { @Override public void untether(String iface, String callerPkg, String callingAttributionTag, IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; + if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, + false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */, + listener)) { + return; + } mTethering.legacyUntether(iface, listener); } @@ -125,32 +137,45 @@ public class TetheringService extends Service { @Override public void setUsbTethering(boolean enable, String callerPkg, String callingAttributionTag, IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; + if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, + false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */, + listener)) { + return; + } mTethering.setUsbTethering(enable, listener); } + private boolean isRequestAllowedForDeviceOwner(@NonNull TetheringRequest request) { + return request.getTetheringType() == TETHERING_WIFI + && request.getSoftApConfiguration() != null; + } + @Override - public void startTethering(TetheringRequestParcel request, String callerPkg, + public void startTethering(TetheringRequestParcel requestParcel, String callerPkg, String callingAttributionTag, IIntResultListener listener) { - boolean onlyAllowPrivileged = request.exemptFromEntitlementCheck - || request.interfaceName != null; - if (checkAndNotifyCommonError(callerPkg, - callingAttributionTag, - onlyAllowPrivileged, - listener)) { + TetheringRequest request = new TetheringRequest(requestParcel); + request.setUid(getBinderCallingUid()); + request.setPackageName(callerPkg); + boolean onlyAllowPrivileged = request.isExemptFromEntitlementCheck() + || request.getInterfaceName() != null; + boolean isDeviceOwnerAllowed = mTethering.isTetheringWithSoftApConfigEnabled() + && isRequestAllowedForDeviceOwner(request); + if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, onlyAllowPrivileged, + isDeviceOwnerAllowed, listener)) { return; } - TetheringRequest external = new TetheringRequest(request); - external.setUid(getBinderCallingUid()); - external.setPackageName(callerPkg); - mTethering.startTethering(external, callerPkg, listener); + mTethering.startTethering(request, callerPkg, listener); } @Override public void stopTethering(int type, String callerPkg, String callingAttributionTag, IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; + if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, + false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */, + listener)) { + return; + } try { mTethering.stopTethering(type); @@ -164,9 +189,16 @@ public class TetheringService extends Service { IIntResultListener listener) { if (request == null) return; if (listener == null) return; - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; request.setUid(getBinderCallingUid()); request.setPackageName(callerPkg); + boolean isDeviceOwnerAllowed = mTethering.isTetheringWithSoftApConfigEnabled() + && isRequestAllowedForDeviceOwner(request); + if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, + false /* onlyAllowPrivileged */, isDeviceOwnerAllowed, listener)) { + return; + } + // Note: Whether tethering is actually stopped or not will depend on whether the request + // matches an active one with the same UID (see RequestTracker#findFuzzyMatchedRequest). mTethering.stopTetheringRequest(request, listener); } @@ -187,7 +219,11 @@ public class TetheringService extends Service { + " internal-only listener"); } }; - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; + if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, + false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */, + listener)) { + return; + } mTethering.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi); } @@ -223,7 +259,11 @@ public class TetheringService extends Service { @Override public void stopAllTethering(String callerPkg, String callingAttributionTag, IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; + if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, + false /* onlyAllowPrivileged */, false /* isDeviceOwnerAppAllowed */, + listener)) { + return; + } try { mTethering.stopAllTethering(); @@ -234,8 +274,11 @@ public class TetheringService extends Service { @Override public void isTetheringSupported(String callerPkg, String callingAttributionTag, IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; - + boolean isDeviceOwnerAppAllowed = mTethering.isTetheringWithSoftApConfigEnabled(); + if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, + false /* onlyAllowPrivileged */, isDeviceOwnerAppAllowed, listener)) { + return; + } try { listener.onResult(TETHER_ERROR_NO_ERROR); } catch (RemoteException e) { } @@ -259,24 +302,18 @@ public class TetheringService extends Service { mTethering.dump(fd, writer, args); } - private boolean checkAndNotifyCommonError(final String callerPkg, - final String callingAttributionTag, final IIntResultListener listener) { - return checkAndNotifyCommonError(callerPkg, callingAttributionTag, - false /* onlyAllowPrivileged */, listener); - } - private boolean checkAndNotifyCommonError(final String callerPkg, final String callingAttributionTag, final boolean onlyAllowPrivileged, - final IIntResultListener listener) { + final boolean isDeviceOwnerAppAllowed, final IIntResultListener listener) { try { - if (!checkPackageNameMatchesUid(getBinderCallingUid(), callerPkg)) { - Log.e(TAG, "Package name " + callerPkg + " does not match UID " - + getBinderCallingUid()); + final int uid = getBinderCallingUid(); + if (!checkPackageNameMatchesUid(uid, callerPkg)) { + Log.e(TAG, "Package name " + callerPkg + " does not match UID " + uid); listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); return true; } - if (!hasTetherChangePermission(callerPkg, callingAttributionTag, - onlyAllowPrivileged)) { + if (!hasTetherChangePermission(uid, callerPkg, callingAttributionTag, + onlyAllowPrivileged, isDeviceOwnerAppAllowed)) { listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); return true; } @@ -308,21 +345,25 @@ public class TetheringService extends Service { return mService.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED; } - private boolean hasTetherChangePermission(final String callerPkg, - final String callingAttributionTag, final boolean onlyAllowPrivileged) { + private boolean hasTetherChangePermission(final int uid, final String callerPkg, + final String callingAttributionTag, final boolean onlyAllowPrivileged, + final boolean isDeviceOwnerAppAllowed) { if (onlyAllowPrivileged && !hasNetworkStackPermission() && !hasNetworkSettingsPermission()) return false; if (hasTetherPrivilegedPermission()) return true; + // Allow DO apps to change tethering even if they don't have TETHER_PRIVILEGED. + if (isDeviceOwnerAppAllowed && mService.isDeviceOwner(uid, callerPkg)) { + return true; + } + // After TetheringManager moves to public API, prevent third-party apps from being able // to change tethering with only WRITE_SETTINGS permission. if (mTethering.isTetheringWithSoftApConfigEnabled()) return false; if (mTethering.isTetherProvisioningRequired()) return false; - int uid = getBinderCallingUid(); - // If callerPkg's uid is not same as getBinderCallingUid(), // checkAndNoteWriteSettingsOperation will return false and the operation will be // denied. @@ -386,6 +427,14 @@ public class TetheringService extends Service { return Binder.getCallingUid(); } + /** + * Wrapper for {@link TetheringPermissionsUtils#isDeviceOwner(int, String)}, used for mocks. + */ + @VisibleForTesting + boolean isDeviceOwner(final int uid, final String callerPkg) { + return mTetheringPermissionsUtils.isDeviceOwner(uid, callerPkg); + } + /** * An injection method for testing. */ diff --git a/Tethering/src/com/android/networkstack/tethering/util/TetheringPermissionsUtils.java b/Tethering/src/com/android/networkstack/tethering/util/TetheringPermissionsUtils.java new file mode 100644 index 0000000000..944e8617c0 --- /dev/null +++ b/Tethering/src/com/android/networkstack/tethering/util/TetheringPermissionsUtils.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2025 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.networkstack.tethering.util; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.util.Log; + +import androidx.annotation.NonNull; + +/** + * Utils class for checking permissions related to Tethering APIs. + */ +public class TetheringPermissionsUtils { + private static final String TAG = "TetherPermUtils"; + + @NonNull private final Context mContext; + + public TetheringPermissionsUtils(@NonNull Context context) { + mContext = context; + } + + /** + * Checks if the package name is a Device Owner. + */ + public boolean isDeviceOwner(final int uid, @NonNull final String packageName) { + Context userContext; + try { + // There is no safe way to invoke this method since tethering package might not be + // installed for a certain user on the OEM devices, refer to b/382628161. + userContext = mContext.createContextAsUser(UserHandle.getUserHandleForUid(uid), + 0 /* flags */); + } catch (IllegalStateException e) { + // TODO: Add a terrible error metric for this case. + Log.e(TAG, "createContextAsUser failed, skipping Device Owner check", e); + return false; + } + DevicePolicyManager devicePolicyManager = + retrieveDevicePolicyManagerFromContext(userContext); + if (devicePolicyManager == null) return false; + return devicePolicyManager.isDeviceOwnerApp(packageName); + } + + private DevicePolicyManager retrieveDevicePolicyManagerFromContext( + @NonNull final Context context) { + DevicePolicyManager devicePolicyManager = + context.getSystemService(DevicePolicyManager.class); + if (devicePolicyManager == null + && context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_DEVICE_ADMIN)) { + Log.w(TAG, "Error retrieving DPM service"); + } + return devicePolicyManager; + } +} diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java index 7fcc5f1c27..a8bd2216ef 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java @@ -25,14 +25,18 @@ import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.util.ArrayMap; +import android.util.ArraySet; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.Set; + public class MockTetheringService extends TetheringService { private final Tethering mTethering = mock(Tethering.class); private final ArrayMap mMockedPermissions = new ArrayMap<>(); private final ArrayMap mMockedPackageUids = new ArrayMap<>(); + private final Set mMockedDeviceOwnerPackages = new ArraySet<>(); private int mMockCallingUid; @Override @@ -74,6 +78,11 @@ public class MockTetheringService extends TetheringService { return mMockCallingUid; } + @Override + boolean isDeviceOwner(final int uid, final String callerPkg) { + return mMockedDeviceOwnerPackages.contains(callerPkg); + } + public Tethering getTethering() { return mTethering; } @@ -118,5 +127,19 @@ public class MockTetheringService extends TetheringService { public void setCallingUid(int uid) { mMockCallingUid = uid; } + + /** + * Add a mocked carrier privileges package + */ + public void addDeviceOwnerPackage(final String packageName) { + mMockedDeviceOwnerPackages.add(packageName); + } + + /** + * Remove a mocked carrier privileges package + */ + public void removeDeviceOwnerPackage(final String packageName) { + mMockedDeviceOwnerPackages.remove(packageName); + } } } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java index b550ada808..87163ef06a 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.NETWORK_SETTINGS; import static android.Manifest.permission.TETHER_PRIVILEGED; import static android.Manifest.permission.WRITE_SETTINGS; import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_VIRTUAL; import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION; @@ -36,6 +37,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; @@ -53,6 +55,7 @@ import android.net.ITetheringEventCallback; import android.net.TetheringManager; import android.net.TetheringManager.TetheringRequest; import android.net.TetheringRequestParcel; +import android.net.wifi.SoftApConfiguration; import android.os.Bundle; import android.os.ConditionVariable; import android.os.Handler; @@ -153,43 +156,58 @@ public final class TetheringServiceTest { } private void runAsNoPermission(final TestTetheringCall test) throws Exception { - runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */, - new String[0]); + runTetheringCall(test, true /* isTetheringAllowed */, + true /* isTetheringWithSoftApConfigEnabled */, new String[0]); } private void runAsTetherPrivileged(final TestTetheringCall test) throws Exception { - runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */, - TETHER_PRIVILEGED); + runTetheringCall(test, true /* isTetheringAllowed */, + true /* isTetheringWithSoftApConfigEnabled */, TETHER_PRIVILEGED); } private void runAsAccessNetworkState(final TestTetheringCall test) throws Exception { - runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */, - ACCESS_NETWORK_STATE); + runTetheringCall(test, true /* isTetheringAllowed */, + true /* isTetheringWithSoftApConfigEnabled */, ACCESS_NETWORK_STATE); } private void runAsWriteSettings(final TestTetheringCall test) throws Exception { - runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */, - WRITE_SETTINGS); + runTetheringCall(test, true /* isTetheringAllowed */, + true /* isTetheringWithSoftApConfigEnabled */, WRITE_SETTINGS); } private void runAsWriteSettingsWhenWriteSettingsAllowed( final TestTetheringCall test) throws Exception { - runTetheringCall(test, true /* isTetheringAllowed */, true /* isWriteSettingsAllowed */, - WRITE_SETTINGS); + runTetheringCall(test, true /* isTetheringAllowed */, + false /* isTetheringWithSoftApConfigEnabled */, WRITE_SETTINGS); } private void runAsTetheringDisallowed(final TestTetheringCall test) throws Exception { - runTetheringCall(test, false /* isTetheringAllowed */, false /* isWriteSettingsAllowed */, - TETHER_PRIVILEGED); + runTetheringCall(test, false /* isTetheringAllowed */, + true /* isTetheringWithSoftApConfigEnabled */, TETHER_PRIVILEGED); } private void runAsNetworkSettings(final TestTetheringCall test) throws Exception { - runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */, - NETWORK_SETTINGS, TETHER_PRIVILEGED); + runTetheringCall(test, true /* isTetheringAllowed */, + true /* isTetheringWithSoftApConfigEnabled */, NETWORK_SETTINGS, TETHER_PRIVILEGED); + } + + private void runAsDeviceOwner(final TestTetheringCall test) throws Exception { + mMockConnector.addDeviceOwnerPackage(TEST_CALLER_PKG); + runTetheringCall(test, true /* isTetheringAllowed */, + true /* isTetheringWithSoftApConfigEnabled */, new String[0]); + mMockConnector.removeDeviceOwnerPackage(TEST_CALLER_PKG); + } + + private void runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled(final TestTetheringCall test) + throws Exception { + mMockConnector.addDeviceOwnerPackage(TEST_CALLER_PKG); + runTetheringCall(test, true /* isTetheringAllowed */, + false /* isTetheringWithSoftApConfigEnabled */, new String[0]); + mMockConnector.removeDeviceOwnerPackage(TEST_CALLER_PKG); } private void runTetheringCall(final TestTetheringCall test, boolean isTetheringAllowed, - boolean isWriteSettingsAllowed, String... permissions) throws Exception { + boolean isTetheringWithSoftApConfigEnabled, String... permissions) throws Exception { // Allow the test to run even if ACCESS_NETWORK_STATE was granted at the APK level if (!CollectionUtils.contains(permissions, ACCESS_NETWORK_STATE)) { mMockConnector.setPermission(ACCESS_NETWORK_STATE, PERMISSION_DENIED); @@ -200,7 +218,7 @@ public final class TetheringServiceTest { when(mTethering.isTetheringSupported()).thenReturn(true); when(mTethering.isTetheringAllowed()).thenReturn(isTetheringAllowed); when(mTethering.isTetheringWithSoftApConfigEnabled()) - .thenReturn(!isWriteSettingsAllowed); + .thenReturn(isTetheringWithSoftApConfigEnabled); test.runTetheringCall(new TestTetheringResult()); } finally { mUiAutomation.dropShellPermissionIdentity(); @@ -226,7 +244,7 @@ public final class TetheringServiceTest { runAsNoPermission((result) -> { mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); @@ -239,14 +257,14 @@ public final class TetheringServiceTest { runAsWriteSettings((result) -> { mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); runAsWriteSettingsWhenWriteSettingsAllowed((result) -> { runTether(result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); @@ -274,7 +292,7 @@ public final class TetheringServiceTest { runAsNoPermission((result) -> { mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); @@ -287,14 +305,14 @@ public final class TetheringServiceTest { runAsWriteSettings((result) -> { mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); runAsWriteSettingsWhenWriteSettingsAllowed((result) -> { runUnTether(result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); @@ -328,7 +346,7 @@ public final class TetheringServiceTest { runAsNoPermission((result) -> { mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); @@ -341,14 +359,14 @@ public final class TetheringServiceTest { runAsWriteSettings((result) -> { mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); runAsWriteSettingsWhenWriteSettingsAllowed((result) -> { runSetUsbTethering(result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); @@ -367,6 +385,7 @@ public final class TetheringServiceTest { final TetheringRequestParcel request) throws Exception { mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verify(mTethering).isTetheringSupported(); verify(mTethering).isTetheringAllowed(); verify(mTethering).startTethering( @@ -381,14 +400,61 @@ public final class TetheringServiceTest { runAsNoPermission((result) -> { mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); + // Not a Wifi request - Fail + runAsDeviceOwner((result) -> { + final TetheringRequestParcel notWifi = new TetheringRequestParcel(); + notWifi.tetheringType = TETHERING_USB; + mTetheringConnector.startTethering(notWifi, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, + result); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + }); + + // Request has no SoftApConfiguration - Fail + runAsDeviceOwner((result) -> { + final TetheringRequestParcel noConfig = new TetheringRequestParcel(); + noConfig.tetheringType = TETHERING_WIFI; + mTetheringConnector.startTethering(noConfig, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, + result); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + }); + + // Wifi request with SoftApConfiguration - Succeed + runAsDeviceOwner((result) -> { + final TetheringRequestParcel withConfig = new TetheringRequestParcel(); + withConfig.tetheringType = TETHERING_WIFI; + withConfig.softApConfig = new SoftApConfiguration.Builder().build(); + mTetheringConnector.startTethering(withConfig, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, + result); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); + verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); + verify(mTethering).startTethering(any(), any(), any()); + result.assertResult(-1); // No result + verifyNoMoreInteractionsForTethering(); + }); + + runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled((result) -> { + mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, + result); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verify(mTethering).isTetherProvisioningRequired(); + verifyNoMoreInteractionsForTethering(); + }); + runAsTetherPrivileged((result) -> { mTetheringConnector.startTethering(request, TEST_WRONG_PACKAGE, TEST_ATTRIBUTION_TAG, result); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verify(mTethering, never()).startTethering( eq(new TetheringRequest(request)), eq(TEST_WRONG_PACKAGE), eq(result)); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); @@ -403,14 +469,14 @@ public final class TetheringServiceTest { runAsWriteSettings((result) -> { mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); runAsWriteSettingsWhenWriteSettingsAllowed((result) -> { runStartTethering(result, request); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); @@ -418,6 +484,7 @@ public final class TetheringServiceTest { runAsTetheringDisallowed((result) -> { mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verify(mTethering).isTetheringSupported(); verify(mTethering).isTetheringAllowed(); result.assertResult(TETHER_ERROR_UNSUPPORTED); @@ -447,6 +514,7 @@ public final class TetheringServiceTest { mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verifyNoMoreInteractionsForTethering(); }); } @@ -459,6 +527,7 @@ public final class TetheringServiceTest { mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verifyNoMoreInteractionsForTethering(); } @@ -495,7 +564,7 @@ public final class TetheringServiceTest { runAsNoPermission((result) -> { mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); @@ -508,14 +577,14 @@ public final class TetheringServiceTest { runAsWriteSettings((result) -> { mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); runAsWriteSettingsWhenWriteSettingsAllowed((result) -> { runStopTethering(result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); @@ -530,6 +599,87 @@ public final class TetheringServiceTest { }); } + private void verifyHasPermissionForStopTetheringRequest(TetheringRequest request, + final TestTetheringResult result) throws Exception { + mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG, + TEST_ATTRIBUTION_TAG, result); + verify(mTethering).stopTetheringRequest(any(), any()); + verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); + reset(mTethering); + } + + private void verifyDoesNotHavePermissionForStopTetheringRequest(TetheringRequest request, + final TestTetheringResult result) throws Exception { + mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG, + TEST_ATTRIBUTION_TAG, result); + verify(mTethering, never()).stopTetheringRequest(any(), any()); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + reset(mTethering); + } + + private void verifyStopTetheringRequestWithTetheringDisallowed(TetheringRequest request, + final TestTetheringResult result) throws Exception { + mTetheringConnector.stopTetheringRequest(request, TEST_CALLER_PKG, + TEST_ATTRIBUTION_TAG, result); + verify(mTethering, never()).stopTetheringRequest(any(), any()); + result.assertResult(TETHER_ERROR_UNSUPPORTED); + reset(mTethering); + } + + @Test + public void testStopTetheringRequest() throws Exception { + TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI).build(); + + runAsNoPermission((result) -> { + verifyDoesNotHavePermissionForStopTetheringRequest(request, result); + }); + + runAsTetherPrivileged((result) -> { + verifyHasPermissionForStopTetheringRequest(request, result); + }); + + runAsWriteSettings((result) -> { + verifyDoesNotHavePermissionForStopTetheringRequest(request, result); + }); + + runAsWriteSettingsWhenWriteSettingsAllowed((result) -> { + verifyHasPermissionForStopTetheringRequest(request, result); + }); + + runAsTetheringDisallowed((result) -> { + verifyStopTetheringRequestWithTetheringDisallowed(request, result); + }); + + runAsNetworkSettings((result) -> { + verifyHasPermissionForStopTetheringRequest(request, result); + }); + + // Not wifi -> fail + runAsDeviceOwner((result) -> { + TetheringRequest notWifi = new TetheringRequest.Builder(TETHERING_USB).build(); + verifyDoesNotHavePermissionForStopTetheringRequest(notWifi, result); + }); + + // No config -> fail + runAsDeviceOwner((result) -> { + TetheringRequest noConfig = new TetheringRequest.Builder(TETHERING_WIFI).build(); + verifyDoesNotHavePermissionForStopTetheringRequest(noConfig, result); + }); + + // With config -> success + TetheringRequest withConfig = new TetheringRequest.Builder(TETHERING_WIFI) + .setSoftApConfiguration(new SoftApConfiguration.Builder().build()) + .build(); + runAsDeviceOwner((result) -> { + verifyHasPermissionForStopTetheringRequest(withConfig, result); + }); + + runAsDeviceOwnerWhenDeviceOwnerBypassNotEnabled((result) -> { + verifyDoesNotHavePermissionForStopTetheringRequest(withConfig, result); + }); + } + private void runRequestLatestTetheringEntitlementResult() throws Exception { final MyResultReceiver result = new MyResultReceiver(null); mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result, @@ -547,7 +697,7 @@ public final class TetheringServiceTest { runAsNoPermission((none) -> { mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result, true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); @@ -567,14 +717,14 @@ public final class TetheringServiceTest { runAsWriteSettings((none) -> { mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result, true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); runAsWriteSettingsWhenWriteSettingsAllowed((none) -> { runRequestLatestTetheringEntitlementResult(); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); @@ -668,7 +818,7 @@ public final class TetheringServiceTest { public void testStopAllTethering() throws Exception { runAsNoPermission((result) -> { mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); @@ -680,14 +830,14 @@ public final class TetheringServiceTest { runAsWriteSettings((result) -> { mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); runAsWriteSettingsWhenWriteSettingsAllowed((result) -> { runStopAllTethering(result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); @@ -703,6 +853,7 @@ public final class TetheringServiceTest { private void runIsTetheringSupported(final TestTetheringResult result) throws Exception { mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verify(mTethering).isTetheringSupported(); verify(mTethering).isTetheringAllowed(); result.assertResult(TETHER_ERROR_NO_ERROR); @@ -713,7 +864,7 @@ public final class TetheringServiceTest { runAsNoPermission((result) -> { mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); @@ -726,14 +877,13 @@ public final class TetheringServiceTest { runAsWriteSettings((result) -> { mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); verifyNoMoreInteractionsForTethering(); }); runAsWriteSettingsWhenWriteSettingsAllowed((result) -> { runIsTetheringSupported(result); - verify(mTethering).isTetheringWithSoftApConfigEnabled(); verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); @@ -741,6 +891,7 @@ public final class TetheringServiceTest { runAsTetheringDisallowed((result) -> { mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); + verify(mTethering, atLeastOnce()).isTetheringWithSoftApConfigEnabled(); verify(mTethering).isTetheringSupported(); verify(mTethering).isTetheringAllowed(); result.assertResult(TETHER_ERROR_UNSUPPORTED); diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringPermissionsUtilsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringPermissionsUtilsTest.java new file mode 100644 index 0000000000..57c3ecad9f --- /dev/null +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/TetheringPermissionsUtilsTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 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.networkstack.tethering.util; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.os.UserHandle; +import android.telephony.TelephonyManager; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class TetheringPermissionsUtilsTest { + private static final int TEST_UID = 12345; + private static final String TEST_PACKAGE = "test.package"; + + @Mock Context mContext; + @Mock Context mUserContext; + @Mock DevicePolicyManager mDevicePolicyManager; + @Mock TelephonyManager mTelephonyManager; + + TetheringPermissionsUtils mTetheringPermissionsUtils; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mContext.createContextAsUser(UserHandle.getUserHandleForUid(TEST_UID), 0)) + .thenReturn(mUserContext); + when(mUserContext.getSystemService(DevicePolicyManager.class)) + .thenReturn(mDevicePolicyManager); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + mTetheringPermissionsUtils = new TetheringPermissionsUtils(mContext); + } + + @Test + public void testIsDeviceOwner() { + when(mDevicePolicyManager.isDeviceOwnerApp(TEST_PACKAGE)).thenReturn(false); + assertThat(mTetheringPermissionsUtils.isDeviceOwner(TEST_UID, TEST_PACKAGE)).isFalse(); + + when(mDevicePolicyManager.isDeviceOwnerApp(TEST_PACKAGE)).thenReturn(true); + assertThat(mTetheringPermissionsUtils.isDeviceOwner(TEST_UID, TEST_PACKAGE)).isTrue(); + } +}