diff options
4 files changed, 333 insertions, 40 deletions
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java index bf7fb042ed3f..e095afea52ca 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java @@ -254,8 +254,8 @@ public class TetheringService extends Service { // If callerPkg's uid is not same as Binder.getCallingUid(), // checkAndNoteWriteSettingsOperation will return false and the operation will be // denied. - return Settings.checkAndNoteWriteSettingsOperation(mService, uid, callerPkg, - false /* throwException */); + return mService.checkAndNoteWriteSettingsOperation(mService, uid, callerPkg, + false /* throwException */); } private boolean hasTetherAccessPermission() { @@ -267,6 +267,19 @@ public class TetheringService extends Service { } /** + * Check if the package is a allowed to write settings. This also accounts that such an access + * happened. + * + * @return {@code true} iff the package is allowed to write settings. + */ + @VisibleForTesting + boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid, + @NonNull String callingPackage, boolean throwException) { + return Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPackage, + throwException); + } + + /** * An injection method for testing. */ @VisibleForTesting diff --git a/packages/Tethering/tests/unit/AndroidManifest.xml b/packages/Tethering/tests/unit/AndroidManifest.xml index 31eaabff5274..355342f64371 100644 --- a/packages/Tethering/tests/unit/AndroidManifest.xml +++ b/packages/Tethering/tests/unit/AndroidManifest.xml @@ -16,9 +16,6 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.networkstack.tethering.tests.unit"> - <uses-permission android:name="android.permission.READ_PHONE_STATE"/> - <uses-permission android:name="android.permission.TETHER_PRIVILEGED"/> - <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> <service diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java index 1c81c1247ded..f4d248914aed 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java @@ -15,13 +15,19 @@ */ package com.android.networkstack.tethering; +import static android.Manifest.permission.WRITE_SETTINGS; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import static org.mockito.Mockito.mock; +import android.content.Context; import android.content.Intent; import android.net.ITetheringConnector; import android.os.Binder; import android.os.IBinder; +import androidx.annotation.NonNull; + public class MockTetheringService extends TetheringService { private final Tethering mTethering = mock(Tethering.class); @@ -35,6 +41,14 @@ public class MockTetheringService extends TetheringService { return mTethering; } + @Override + boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid, + @NonNull String callingPackage, boolean throwException) { + // Test this does not verify the calling package / UID, as calling package could be shell + // and not match the UID. + return context.checkCallingOrSelfPermission(WRITE_SETTINGS) == PERMISSION_GRANTED; + } + public Tethering getTethering() { return mTethering; } diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java index 4a667b1bdc41..f4a566659896 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java @@ -16,20 +16,30 @@ package com.android.networkstack.tethering; +import static android.Manifest.permission.ACCESS_NETWORK_STATE; +import static android.Manifest.permission.TETHER_PRIVILEGED; +import static android.Manifest.permission.UPDATE_APP_OPS_STATS; +import static android.Manifest.permission.WRITE_SETTINGS; 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; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.app.UiAutomation; import android.content.Intent; import android.net.IIntResultListener; import android.net.ITetheringConnector; import android.net.ITetheringEventCallback; import android.net.TetheringRequestParcel; +import android.os.Bundle; +import android.os.Handler; import android.os.ResultReceiver; import androidx.test.InstrumentationRegistry; @@ -51,12 +61,13 @@ import org.mockito.MockitoAnnotations; @SmallTest public final class TetheringServiceTest { private static final String TEST_IFACE_NAME = "test_wlan0"; - private static final String TEST_CALLER_PKG = "test_pkg"; + private static final String TEST_CALLER_PKG = "com.android.shell"; @Mock private ITetheringEventCallback mITetheringEventCallback; @Rule public ServiceTestRule mServiceTestRule; private Tethering mTethering; private Intent mMockServiceIntent; private ITetheringConnector mTetheringConnector; + private UiAutomation mUiAutomation; private class TestTetheringResult extends IIntResultListener.Stub { private int mResult = -1; // Default value that does not match any result code. @@ -70,9 +81,26 @@ public final class TetheringServiceTest { } } + private class MyResultReceiver extends ResultReceiver { + MyResultReceiver(Handler handler) { + super(handler); + } + private int mResult = -1; // Default value that does not match any result code. + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + mResult = resultCode; + } + + public void assertResult(int expected) { + assertEquals(expected, mResult); + } + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mUiAutomation = + InstrumentationRegistry.getInstrumentation().getUiAutomation(); mServiceTestRule = new ServiceTestRule(); mMockServiceIntent = new Intent( InstrumentationRegistry.getTargetContext(), @@ -82,112 +110,353 @@ public final class TetheringServiceTest { mTetheringConnector = mockConnector.getTetheringConnector(); final MockTetheringService service = mockConnector.getService(); mTethering = service.getTethering(); - when(mTethering.isTetheringSupported()).thenReturn(true); } @After public void tearDown() throws Exception { mServiceTestRule.unbindService(); + mUiAutomation.dropShellPermissionIdentity(); } - @Test - public void testTether() throws Exception { + private interface TestTetheringCall { + void runTetheringCall(TestTetheringResult result) throws Exception; + } + + private void runAsNoPermission(final TestTetheringCall test) throws Exception { + runTetheringCall(test, new String[0]); + } + + private void runAsTetherPrivileged(final TestTetheringCall test) throws Exception { + runTetheringCall(test, TETHER_PRIVILEGED); + } + + private void runAsAccessNetworkState(final TestTetheringCall test) throws Exception { + runTetheringCall(test, ACCESS_NETWORK_STATE); + } + + private void runAsWriteSettings(final TestTetheringCall test) throws Exception { + runTetheringCall(test, WRITE_SETTINGS, UPDATE_APP_OPS_STATS); + } + + private void runTetheringCall(final TestTetheringCall test, String... permissions) + throws Exception { + if (permissions.length > 0) mUiAutomation.adoptShellPermissionIdentity(permissions); + try { + when(mTethering.isTetheringSupported()).thenReturn(true); + test.runTetheringCall(new TestTetheringResult()); + } finally { + mUiAutomation.dropShellPermissionIdentity(); + } + } + + private void verifyNoMoreInteractionsForTethering() { + verifyNoMoreInteractions(mTethering); + verifyNoMoreInteractions(mITetheringEventCallback); + reset(mTethering, mITetheringEventCallback); + } + + private void runTether(final TestTetheringResult result) throws Exception { when(mTethering.tether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR); - final TestTetheringResult result = new TestTetheringResult(); mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, result); verify(mTethering).isTetheringSupported(); verify(mTethering).tether(TEST_IFACE_NAME); - verifyNoMoreInteractions(mTethering); result.assertResult(TETHER_ERROR_NO_ERROR); } @Test - public void testUntether() throws Exception { + public void testTether() throws Exception { + runAsNoPermission((result) -> { + mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, result); + verify(mTethering).isTetherProvisioningRequired(); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + }); + + runAsTetherPrivileged((result) -> { + runTether(result); + verifyNoMoreInteractionsForTethering(); + }); + + runAsWriteSettings((result) -> { + runTether(result); + verify(mTethering).isTetherProvisioningRequired(); + verifyNoMoreInteractionsForTethering(); + }); + } + + private void runUnTether(final TestTetheringResult result) throws Exception { when(mTethering.untether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR); - final TestTetheringResult result = new TestTetheringResult(); mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, result); verify(mTethering).isTetheringSupported(); verify(mTethering).untether(TEST_IFACE_NAME); - verifyNoMoreInteractions(mTethering); result.assertResult(TETHER_ERROR_NO_ERROR); } @Test - public void testSetUsbTethering() throws Exception { + public void testUntether() throws Exception { + runAsNoPermission((result) -> { + mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, result); + verify(mTethering).isTetherProvisioningRequired(); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + }); + + runAsTetherPrivileged((result) -> { + runUnTether(result); + verifyNoMoreInteractionsForTethering(); + }); + + runAsWriteSettings((result) -> { + runUnTether(result); + verify(mTethering).isTetherProvisioningRequired(); + verifyNoMoreInteractionsForTethering(); + }); + } + + private void runSetUsbTethering(final TestTetheringResult result) throws Exception { when(mTethering.setUsbTethering(true /* enable */)).thenReturn(TETHER_ERROR_NO_ERROR); - final TestTetheringResult result = new TestTetheringResult(); mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, result); verify(mTethering).isTetheringSupported(); verify(mTethering).setUsbTethering(true /* enable */); - verifyNoMoreInteractions(mTethering); result.assertResult(TETHER_ERROR_NO_ERROR); } @Test - public void testStartTethering() throws Exception { - final TestTetheringResult result = new TestTetheringResult(); - final TetheringRequestParcel request = new TetheringRequestParcel(); - request.tetheringType = TETHERING_WIFI; + public void testSetUsbTethering() throws Exception { + runAsNoPermission((result) -> { + mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, result); + verify(mTethering).isTetherProvisioningRequired(); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + }); + + runAsTetherPrivileged((result) -> { + runSetUsbTethering(result); + verifyNoMoreInteractionsForTethering(); + }); + + runAsWriteSettings((result) -> { + runSetUsbTethering(result); + verify(mTethering).isTetherProvisioningRequired(); + verifyNoMoreInteractionsForTethering(); + }); + + } + + private void runStartTethering(final TestTetheringResult result, + final TetheringRequestParcel request) throws Exception { mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result); verify(mTethering).isTetheringSupported(); verify(mTethering).startTethering(eq(request), eq(result)); - verifyNoMoreInteractions(mTethering); } @Test - public void testStopTethering() throws Exception { - final TestTetheringResult result = new TestTetheringResult(); + public void testStartTethering() throws Exception { + final TetheringRequestParcel request = new TetheringRequestParcel(); + request.tetheringType = TETHERING_WIFI; + + runAsNoPermission((result) -> { + mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result); + verify(mTethering).isTetherProvisioningRequired(); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + }); + + runAsTetherPrivileged((result) -> { + runStartTethering(result, request); + verifyNoMoreInteractionsForTethering(); + }); + + runAsWriteSettings((result) -> { + runStartTethering(result, request); + verify(mTethering).isTetherProvisioningRequired(); + verifyNoMoreInteractionsForTethering(); + }); + } + + @Test + public void testStartTetheringWithExemptFromEntitlementCheck() throws Exception { + final TetheringRequestParcel request = new TetheringRequestParcel(); + request.tetheringType = TETHERING_WIFI; + request.exemptFromEntitlementCheck = true; + + runAsTetherPrivileged((result) -> { + runStartTethering(result, request); + verifyNoMoreInteractionsForTethering(); + }); + + runAsWriteSettings((result) -> { + mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + }); + } + + private void runStopTethering(final TestTetheringResult result) throws Exception { mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, result); verify(mTethering).isTetheringSupported(); verify(mTethering).stopTethering(TETHERING_WIFI); - verifyNoMoreInteractions(mTethering); result.assertResult(TETHER_ERROR_NO_ERROR); } @Test - public void testRequestLatestTetheringEntitlementResult() throws Exception { - final ResultReceiver result = new ResultReceiver(null); + public void testStopTethering() throws Exception { + runAsNoPermission((result) -> { + mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, result); + verify(mTethering).isTetherProvisioningRequired(); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + }); + + runAsTetherPrivileged((result) -> { + runStopTethering(result); + verifyNoMoreInteractionsForTethering(); + }); + + runAsWriteSettings((result) -> { + runStopTethering(result); + verify(mTethering).isTetherProvisioningRequired(); + verifyNoMoreInteractionsForTethering(); + }); + } + + private void runRequestLatestTetheringEntitlementResult() throws Exception { + final MyResultReceiver result = new MyResultReceiver(null); mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result, true /* showEntitlementUi */, TEST_CALLER_PKG); verify(mTethering).isTetheringSupported(); verify(mTethering).requestLatestTetheringEntitlementResult(eq(TETHERING_WIFI), eq(result), eq(true) /* showEntitlementUi */); - verifyNoMoreInteractions(mTethering); } @Test - public void testRegisterTetheringEventCallback() throws Exception { + public void testRequestLatestTetheringEntitlementResult() throws Exception { + // Run as no permission. + final MyResultReceiver result = new MyResultReceiver(null); + mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result, + true /* showEntitlementUi */, TEST_CALLER_PKG); + verify(mTethering).isTetherProvisioningRequired(); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verifyNoMoreInteractions(mTethering); + + runAsTetherPrivileged((none) -> { + runRequestLatestTetheringEntitlementResult(); + verifyNoMoreInteractionsForTethering(); + }); + + runAsWriteSettings((none) -> { + runRequestLatestTetheringEntitlementResult(); + verify(mTethering).isTetherProvisioningRequired(); + verifyNoMoreInteractionsForTethering(); + }); + } + + private void runRegisterTetheringEventCallback() throws Exception { mTetheringConnector.registerTetheringEventCallback(mITetheringEventCallback, TEST_CALLER_PKG); verify(mTethering).registerTetheringEventCallback(eq(mITetheringEventCallback)); - verifyNoMoreInteractions(mTethering); } @Test - public void testUnregisterTetheringEventCallback() throws Exception { + public void testRegisterTetheringEventCallback() throws Exception { + runAsNoPermission((result) -> { + mTetheringConnector.registerTetheringEventCallback(mITetheringEventCallback, + TEST_CALLER_PKG); + verify(mITetheringEventCallback).onCallbackStopped( + TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + }); + + runAsTetherPrivileged((none) -> { + runRegisterTetheringEventCallback(); + verifyNoMoreInteractionsForTethering(); + }); + + runAsAccessNetworkState((none) -> { + runRegisterTetheringEventCallback(); + verifyNoMoreInteractionsForTethering(); + }); + } + + private void runUnregisterTetheringEventCallback() throws Exception { mTetheringConnector.unregisterTetheringEventCallback(mITetheringEventCallback, TEST_CALLER_PKG); - verify(mTethering).unregisterTetheringEventCallback( - eq(mITetheringEventCallback)); - verifyNoMoreInteractions(mTethering); + verify(mTethering).unregisterTetheringEventCallback(eq(mITetheringEventCallback)); } @Test - public void testStopAllTethering() throws Exception { - final TestTetheringResult result = new TestTetheringResult(); + public void testUnregisterTetheringEventCallback() throws Exception { + runAsNoPermission((result) -> { + mTetheringConnector.unregisterTetheringEventCallback(mITetheringEventCallback, + TEST_CALLER_PKG); + verify(mITetheringEventCallback).onCallbackStopped( + TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + }); + + runAsTetherPrivileged((none) -> { + runUnregisterTetheringEventCallback(); + verifyNoMoreInteractionsForTethering(); + }); + + runAsAccessNetworkState((none) -> { + runUnregisterTetheringEventCallback(); + verifyNoMoreInteractionsForTethering(); + }); + } + + private void runStopAllTethering(final TestTetheringResult result) throws Exception { mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, result); verify(mTethering).isTetheringSupported(); verify(mTethering).untetherAll(); - verifyNoMoreInteractions(mTethering); result.assertResult(TETHER_ERROR_NO_ERROR); } @Test - public void testIsTetheringSupported() throws Exception { - final TestTetheringResult result = new TestTetheringResult(); + public void testStopAllTethering() throws Exception { + runAsNoPermission((result) -> { + mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, result); + verify(mTethering).isTetherProvisioningRequired(); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + }); + + runAsTetherPrivileged((result) -> { + runStopAllTethering(result); + verifyNoMoreInteractionsForTethering(); + }); + + runAsWriteSettings((result) -> { + runStopAllTethering(result); + verify(mTethering).isTetherProvisioningRequired(); + verifyNoMoreInteractionsForTethering(); + }); + } + + private void runIsTetheringSupported(final TestTetheringResult result) throws Exception { mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, result); verify(mTethering).isTetheringSupported(); - verifyNoMoreInteractions(mTethering); result.assertResult(TETHER_ERROR_NO_ERROR); } + + @Test + public void testIsTetheringSupported() throws Exception { + runAsNoPermission((result) -> { + mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, result); + verify(mTethering).isTetherProvisioningRequired(); + result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); + verifyNoMoreInteractionsForTethering(); + }); + + runAsTetherPrivileged((result) -> { + runIsTetheringSupported(result); + verifyNoMoreInteractionsForTethering(); + }); + + runAsWriteSettings((result) -> { + runIsTetheringSupported(result); + verify(mTethering).isTetherProvisioningRequired(); + verifyNoMoreInteractionsForTethering(); + }); + } } |