From f5078f489df8e841dd35e3266ec1a92b4fa6110b Mon Sep 17 00:00:00 2001 From: markchien Date: Fri, 8 May 2020 18:55:26 +0800 Subject: [PATCH] Allow to exempt from entitlement check To exempt from entitlement check, caller need to hold TETHER_PRIVILEGED permission. Bug: 141256482 Test: atest TetheringTests Change-Id: I2eb37f5e92f5f5150a7fb7c25b945e28704d27a0 --- .../tethering/EntitlementManager.java | 34 ++++++- .../networkstack/tethering/Tethering.java | 8 +- .../tethering/TetheringService.java | 93 ++++++++++--------- .../tethering/EntitlementManagerTest.java | 29 ++++++ .../networkstack/tethering/TetheringTest.java | 80 ++++++++++++++-- 5 files changed, 188 insertions(+), 56 deletions(-) diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java index 23b8be1d07..3c6e8d88ed 100644 --- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java +++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java @@ -80,6 +80,7 @@ public class EntitlementManager { // {@link TetheringManager.TETHERING_USB} // {@link TetheringManager.TETHERING_BLUETOOTH} private final BitSet mCurrentDownstreams; + private final BitSet mExemptedDownstreams; private final Context mContext; private final SharedLog mLog; private final SparseIntArray mEntitlementCacheValue; @@ -100,6 +101,7 @@ public class EntitlementManager { mContext = ctx; mLog = log.forSubComponent(TAG); mCurrentDownstreams = new BitSet(); + mExemptedDownstreams = new BitSet(); mCurrentEntitlementResults = new SparseIntArray(); mEntitlementCacheValue = new SparseIntArray(); mPermissionChangeCallback = callback; @@ -150,13 +152,29 @@ public class EntitlementManager { private boolean isCellularUpstreamPermitted(final TetheringConfiguration config) { if (!isTetherProvisioningRequired(config)) return true; - // If provisioning is required and EntitlementManager doesn't know any downstreams, - // cellular upstream should not be allowed. - if (mCurrentDownstreams.isEmpty()) return false; + // If provisioning is required and EntitlementManager doesn't know any downstreams, cellular + // upstream should not be enabled. Enable cellular upstream for exempted downstreams only + // when there is no non-exempted downstream. + if (mCurrentDownstreams.isEmpty()) return !mExemptedDownstreams.isEmpty(); return mCurrentEntitlementResults.indexOfValue(TETHER_ERROR_NO_ERROR) > -1; } + /** + * Set exempted downstream type. If there is only exempted downstream type active, + * corresponding entitlement check will not be run and cellular upstream will be permitted + * by default. If a privileged app enables tethering without a provisioning check, and then + * another app enables tethering of the same type but does not disable the provisioning check, + * then the downstream immediately loses exempt status and a provisioning check is run. + * If any non-exempted downstream type is active, the cellular upstream will be gated by the + * result of entitlement check from non-exempted downstreams. If entitlement check is still + * in progress on non-exempt downstreams, ceullar upstream would default be disabled. When any + * non-exempted downstream gets positive entitlement result, ceullar upstream will be enabled. + */ + public void setExemptedDownstreamType(final int type) { + mExemptedDownstreams.set(type, true); + } + /** * This is called when tethering starts. * Launch provisioning app if upstream is cellular. @@ -170,6 +188,8 @@ public class EntitlementManager { mCurrentDownstreams.set(downstreamType, true); + mExemptedDownstreams.set(downstreamType, false); + final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); if (!isTetherProvisioningRequired(config)) return; @@ -200,6 +220,7 @@ public class EntitlementManager { // "tethering supported" may change without without tethering being notified properly. // Remove the mapping all the time no matter provisioning is required or not. removeDownstreamMapping(downstreamType); + mExemptedDownstreams.set(downstreamType, false); } /** @@ -505,6 +526,13 @@ public class EntitlementManager { if (!mWaiting.block(DUMP_TIMEOUT)) { pw.println("... dump timed out after " + DUMP_TIMEOUT + "ms"); } + pw.print("Exempted: ["); + for (int type = mExemptedDownstreams.nextSetBit(0); type >= 0; + type = mExemptedDownstreams.nextSetBit(type + 1)) { + pw.print(typeString(type)); + pw.print(", "); + } + pw.println("]"); } private static String typeString(int type) { diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index 55e9c90dba..d1440a7c7b 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -516,8 +516,12 @@ public class Tethering { } mActiveTetheringRequests.put(request.tetheringType, request); - mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType, - request.showProvisioningUi); + if (request.exemptFromEntitlementCheck) { + mEntitlementMgr.setExemptedDownstreamType(request.tetheringType); + } else { + mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType, + request.showProvisioningUi); + } enableTetheringInternal(request.tetheringType, true /* enabled */, listener); }); } diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java index c82e2be72a..af349f2b92 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java +++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java @@ -16,6 +16,8 @@ package com.android.networkstack.tethering; +import static android.Manifest.permission.ACCESS_NETWORK_STATE; +import static android.Manifest.permission.TETHER_PRIVILEGED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION; import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; @@ -151,7 +153,12 @@ public class TetheringService extends Service { @Override public void startTethering(TetheringRequestParcel request, String callerPkg, String callingAttributionTag, IIntResultListener listener) { - if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return; + if (checkAndNotifyCommonError(callerPkg, + callingAttributionTag, + request.exemptFromEntitlementCheck /* onlyAllowPrivileged */, + listener)) { + return; + } mTethering.startTethering(request, listener); } @@ -179,7 +186,7 @@ public class TetheringService extends Service { public void registerTetheringEventCallback(ITetheringEventCallback callback, String callerPkg) { try { - if (!mService.hasTetherAccessPermission()) { + if (!hasTetherAccessPermission()) { callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION); return; } @@ -191,7 +198,7 @@ public class TetheringService extends Service { public void unregisterTetheringEventCallback(ITetheringEventCallback callback, String callerPkg) { try { - if (!mService.hasTetherAccessPermission()) { + if (!hasTetherAccessPermission()) { callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION); return; } @@ -226,10 +233,18 @@ public class TetheringService extends Service { mTethering.dump(fd, writer, args); } - private boolean checkAndNotifyCommonError(String callerPkg, String callingAttributionTag, - IIntResultListener listener) { + 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) { try { - if (!mService.hasTetherChangePermission(callerPkg, callingAttributionTag)) { + if (!hasTetherChangePermission(callerPkg, callingAttributionTag, + onlyAllowPrivileged)) { listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); return true; } @@ -244,9 +259,10 @@ public class TetheringService extends Service { return false; } - private boolean checkAndNotifyCommonError(String callerPkg, String callingAttributionTag, - ResultReceiver receiver) { - if (!mService.hasTetherChangePermission(callerPkg, callingAttributionTag)) { + private boolean checkAndNotifyCommonError(final String callerPkg, + final String callingAttributionTag, final ResultReceiver receiver) { + if (!hasTetherChangePermission(callerPkg, callingAttributionTag, + false /* onlyAllowPrivileged */)) { receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null); return true; } @@ -258,6 +274,30 @@ public class TetheringService extends Service { return false; } + private boolean hasTetherPrivilegedPermission() { + return mService.checkCallingOrSelfPermission(TETHER_PRIVILEGED) == PERMISSION_GRANTED; + } + + private boolean hasTetherChangePermission(final String callerPkg, + final String callingAttributionTag, final boolean onlyAllowPrivileged) { + if (hasTetherPrivilegedPermission()) return true; + + if (onlyAllowPrivileged || mTethering.isTetherProvisioningRequired()) return false; + + int uid = Binder.getCallingUid(); + // If callerPkg's uid is not same as Binder.getCallingUid(), + // checkAndNoteWriteSettingsOperation will return false and the operation will be + // denied. + return TetheringService.checkAndNoteWriteSettingsOperation(mService, uid, callerPkg, + callingAttributionTag, false /* throwException */); + } + + private boolean hasTetherAccessPermission() { + if (hasTetherPrivilegedPermission()) return true; + + return mService.checkCallingOrSelfPermission( + ACCESS_NETWORK_STATE) == PERMISSION_GRANTED; + } } // if ro.tether.denied = true we default to no tethering @@ -274,26 +314,6 @@ public class TetheringService extends Service { return tetherEnabledInSettings && mTethering.hasTetherableConfiguration(); } - private boolean hasTetherChangePermission(String callerPkg, String callingAttributionTag) { - if (checkCallingOrSelfPermission( - android.Manifest.permission.TETHER_PRIVILEGED) == PERMISSION_GRANTED) { - return true; - } - - if (mTethering.isTetherProvisioningRequired()) return false; - - - int uid = Binder.getCallingUid(); - // If callerPkg's uid is not same as Binder.getCallingUid(), - // checkAndNoteWriteSettingsOperation will return false and the operation will be denied. - if (checkAndNoteWriteSettingsOperation(mContext, uid, callerPkg, - callingAttributionTag, false /* throwException */)) { - return true; - } - - return false; - } - /** * Check if the package is a allowed to write settings. This also accounts that such an access * happened. @@ -308,21 +328,6 @@ public class TetheringService extends Service { throwException); } - private boolean hasTetherAccessPermission() { - if (checkCallingOrSelfPermission( - android.Manifest.permission.TETHER_PRIVILEGED) == PERMISSION_GRANTED) { - return true; - } - - if (checkCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE) == PERMISSION_GRANTED) { - return true; - } - - return false; - } - - /** * An injection method for testing. */ diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java index a692935375..cdd0e243e3 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java @@ -543,4 +543,33 @@ public final class EntitlementManagerTest { assertEquals(1, mEnMgr.uiProvisionCount); verify(mEntitlementFailedListener, times(1)).onUiEntitlementFailed(TETHERING_WIFI); } + + @Test + public void testsetExemptedDownstreamType() throws Exception { + setupForRequiredProvisioning(); + // Cellular upstream is not permitted when no entitlement result. + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + + // If there is exempted downstream and no other non-exempted downstreams, cellular is + // permitted. + mEnMgr.setExemptedDownstreamType(TETHERING_WIFI); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + + // If second downstream run entitlement check fail, cellular upstream is not permitted. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + + // When second downstream is down, exempted downstream can use cellular upstream. + assertEquals(1, mEnMgr.uiProvisionCount); + verify(mEntitlementFailedListener).onUiEntitlementFailed(TETHERING_USB); + mEnMgr.stopProvisioningIfNeeded(TETHERING_USB); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + + mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + } } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index a67a4b60a3..85c2f2b984 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -57,6 +57,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; @@ -172,6 +173,8 @@ public class TetheringTest { private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0"; private static final String TEST_NCM_IFNAME = "test_ncm0"; private static final String TETHERING_NAME = "Tethering"; + private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; + private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; private static final int DHCPSERVER_START_TIMEOUT_MS = 1000; @@ -539,16 +542,16 @@ public class TetheringTest { } private TetheringRequestParcel createTetheringRequestParcel(final int type) { - return createTetheringRequestParcel(type, null, null); + return createTetheringRequestParcel(type, null, null, false); } private TetheringRequestParcel createTetheringRequestParcel(final int type, - final LinkAddress serverAddr, final LinkAddress clientAddr) { + final LinkAddress serverAddr, final LinkAddress clientAddr, final boolean exempt) { final TetheringRequestParcel request = new TetheringRequestParcel(); request.tetheringType = type; request.localIPv4Address = serverAddr; request.staticClientAddress = clientAddr; - request.exemptFromEntitlementCheck = false; + request.exemptFromEntitlementCheck = exempt; request.showProvisioningUi = false; return request; @@ -1659,7 +1662,7 @@ public class TetheringTest { // Enable USB tethering and check that Tethering starts USB. mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB, - null, null), firstResult); + null, null, false), firstResult); mLooper.dispatchAll(); firstResult.assertHasResult(); verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); @@ -1667,7 +1670,7 @@ public class TetheringTest { // Enable USB tethering again with the same request and expect no change to USB. mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB, - null, null), secondResult); + null, null, false), secondResult); mLooper.dispatchAll(); secondResult.assertHasResult(); verify(mUsbManager, never()).setCurrentFunctions(UsbManager.FUNCTION_NONE); @@ -1676,7 +1679,7 @@ public class TetheringTest { // Enable USB tethering with a different request and expect that USB is stopped and // started. mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB, - serverLinkAddr, clientLinkAddr), thirdResult); + serverLinkAddr, clientLinkAddr, false), thirdResult); mLooper.dispatchAll(); thirdResult.assertHasResult(); verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE); @@ -1700,7 +1703,7 @@ public class TetheringTest { final ArgumentCaptor dhcpParamsCaptor = ArgumentCaptor.forClass(DhcpServingParamsParcel.class); mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB, - serverLinkAddr, clientLinkAddr), null); + serverLinkAddr, clientLinkAddr, false), null); mLooper.dispatchAll(); verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true); @@ -1762,6 +1765,69 @@ public class TetheringTest { mLooper.stopAutoDispatch(); } + @Test + public void testExemptFromEntitlementCheck() throws Exception { + setupForRequiredProvisioning(); + final TetheringRequestParcel wifiNotExemptRequest = + createTetheringRequestParcel(TETHERING_WIFI, null, null, false); + mTethering.startTethering(wifiNotExemptRequest, null); + mLooper.dispatchAll(); + verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false); + verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI); + assertFalse(mEntitleMgr.isCellularUpstreamPermitted()); + mTethering.stopTethering(TETHERING_WIFI); + mLooper.dispatchAll(); + verify(mEntitleMgr).stopProvisioningIfNeeded(TETHERING_WIFI); + reset(mEntitleMgr); + + setupForRequiredProvisioning(); + final TetheringRequestParcel wifiExemptRequest = + createTetheringRequestParcel(TETHERING_WIFI, null, null, true); + mTethering.startTethering(wifiExemptRequest, null); + mLooper.dispatchAll(); + verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false); + verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI); + assertTrue(mEntitleMgr.isCellularUpstreamPermitted()); + mTethering.stopTethering(TETHERING_WIFI); + mLooper.dispatchAll(); + verify(mEntitleMgr).stopProvisioningIfNeeded(TETHERING_WIFI); + reset(mEntitleMgr); + + // If one app enables tethering without provisioning check first, then another app enables + // tethering of the same type but does not disable the provisioning check. + setupForRequiredProvisioning(); + mTethering.startTethering(wifiExemptRequest, null); + mLooper.dispatchAll(); + verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false); + verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI); + assertTrue(mEntitleMgr.isCellularUpstreamPermitted()); + reset(mEntitleMgr); + setupForRequiredProvisioning(); + mTethering.startTethering(wifiNotExemptRequest, null); + mLooper.dispatchAll(); + verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false); + verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI); + assertFalse(mEntitleMgr.isCellularUpstreamPermitted()); + mTethering.stopTethering(TETHERING_WIFI); + mLooper.dispatchAll(); + verify(mEntitleMgr).stopProvisioningIfNeeded(TETHERING_WIFI); + reset(mEntitleMgr); + } + + private void setupForRequiredProvisioning() { + // Produce some acceptable looking provision app setting if requested. + when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) + .thenReturn(PROVISIONING_APP_NAME); + when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui)) + .thenReturn(PROVISIONING_NO_UI_APP_NAME); + // Act like the CarrierConfigManager is present and ready unless told otherwise. + when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) + .thenReturn(mCarrierConfigManager); + when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig); + mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true); + mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true); + sendConfigurationChanged(); + } // TODO: Test that a request for hotspot mode doesn't interfere with an // already operating tethering mode interface. }