diff options
42 files changed, 2183 insertions, 45 deletions
diff --git a/hostsidetests/edi/src/android/edi/cts/ClasspathDeviceInfo.java b/hostsidetests/edi/src/android/edi/cts/ClasspathDeviceInfo.java index 717e2e2bc0f..463934f1550 100644 --- a/hostsidetests/edi/src/android/edi/cts/ClasspathDeviceInfo.java +++ b/hostsidetests/edi/src/android/edi/cts/ClasspathDeviceInfo.java @@ -18,6 +18,8 @@ package android.edi.cts; import static android.compat.testing.Classpaths.ClasspathType.BOOTCLASSPATH; import static android.compat.testing.Classpaths.ClasspathType.SYSTEMSERVERCLASSPATH; +import static org.junit.Assume.assumeTrue; + import android.compat.testing.Classpaths; import android.compat.testing.Classpaths.ClasspathType; import android.compat.testing.SharedLibraryInfo; @@ -33,6 +35,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.jf.dexlib2.iface.ClassDef; +import org.junit.Assume; +import org.junit.Before; import java.io.File; import java.io.IOException; @@ -44,15 +48,17 @@ import java.util.stream.IntStream; */ public class ClasspathDeviceInfo extends DeviceInfo { - private ITestDevice mDevice; - private DeviceSdkLevel mDeviceSdkLevel; + private final ITestDevice mDevice = getDevice(); + private final DeviceSdkLevel mDeviceSdkLevel = new DeviceSdkLevel(mDevice); private final Object mStoreLock = new Object(); + @Before + public void before() throws DeviceNotAvailableException { + assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR()); + } + @Override protected void collectDeviceInfo(HostInfoStore store) throws Exception { - mDevice = getDevice(); - mDeviceSdkLevel = new DeviceSdkLevel(mDevice); - store.startArray("jars"); collectClasspathsJars(store); collectSharedLibraries(store); diff --git a/hostsidetests/jdwptunnel/src/android/jdwptunnel/cts/JdwpTunnelTest.java b/hostsidetests/jdwptunnel/src/android/jdwptunnel/cts/JdwpTunnelTest.java index 5fbc913b073..879160207f2 100644 --- a/hostsidetests/jdwptunnel/src/android/jdwptunnel/cts/JdwpTunnelTest.java +++ b/hostsidetests/jdwptunnel/src/android/jdwptunnel/cts/JdwpTunnelTest.java @@ -26,6 +26,7 @@ import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.android.tradefed.util.AbiUtils; import com.sun.jdi.Bootstrap; import com.sun.jdi.ReferenceType; import com.sun.jdi.VirtualMachine; @@ -41,6 +42,7 @@ import java.io.*; import java.net.Socket; import java.time.Instant; import java.util.Map; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -269,6 +271,11 @@ public class JdwpTunnelTest extends BaseHostJUnit4Test { } } + private String getDeviceBaseArch() throws Exception { + String abi = mDevice.executeShellCommand("getprop ro.product.cpu.abi").replace("\n", ""); + return AbiUtils.getBaseArchForAbi(abi); + } + /** * Tests that we don't get any DDMS messages before the handshake. * @@ -277,6 +284,11 @@ public class JdwpTunnelTest extends BaseHostJUnit4Test { */ @Test public void testDdmsWaitsForHandshake() throws DeviceNotAvailableException, Exception { + // Skip this test if not running on the device's native abi. + String testingArch = AbiUtils.getBaseArchForAbi(getAbi().getName()); + String deviceArch = getDeviceBaseArch(); + Assume.assumeTrue(testingArch.equals(deviceArch)); + String port = startupForwarding(DDMS_TEST_APP_PACKAGE_NAME, DDMS_TEST_APP_ACTIVITY_CLASS_NAME, false); Socket sock = new Socket("localhost", Integer.decode(port).intValue()); 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 605f85cacac..3c860132c50 100644 --- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java +++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java @@ -85,8 +85,10 @@ import static android.scopedstorage.cts.lib.TestUtils.readExifMetadataFromTestAp import static android.scopedstorage.cts.lib.TestUtils.revokePermission; import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid; import static android.scopedstorage.cts.lib.TestUtils.setAttrAs; +import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert; import static android.scopedstorage.cts.lib.TestUtils.uninstallApp; import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow; +import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert; import static android.scopedstorage.cts.lib.TestUtils.updateDisplayNameWithMediaProvider; import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed; import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied; @@ -2057,7 +2059,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { // file. assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true); - trashFile(imageFileUri); + trashFileAndAssert(imageFileUri); // Check that only owner package, file manager and system gallery can list trashed image // file. assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true); @@ -2066,7 +2068,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { // Check that only owner package, file manager can list pending non media file. assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false); - trashFile(pdfFileUri); + trashFileAndAssert(pdfFileUri); // Check that only owner package, file manager can list trashed non media file. assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false); } finally { @@ -2198,6 +2200,65 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { } @Test + public void testSystemGalleryCanTrashOtherAndroidMediaFiles() throws Exception { + final File otherVideoFile = new File(getAndroidMediaDir(), + String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME)); + try { + allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + + assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue(); + + final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile); + assertNotNull(otherVideoUri); + + trashFileAndAssert(otherVideoUri); + untrashFileAndAssert(otherVideoUri); + } finally { + otherVideoFile.delete(); + denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + } + } + + @Test + public void testSystemGalleryCanUpdateOtherAndroidMediaFiles() throws Exception { + final File otherImageFile = new File(getAndroidMediaDir(), + String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME)); + final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME); + try { + allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + + assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue(); + + final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile); + assertNotNull(otherImageUri); + + final ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM); + // Test that we can move the file to "DCIM/" + assertWithMessage("Result of ContentResolver#update for " + otherImageUri + + " with values " + values) + .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY)) + .isEqualTo(1); + assertThat(updatedImageFileInDcim.exists()).isTrue(); + assertThat(otherImageFile.exists()).isFalse(); + + values.clear(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, + "Android/media/" + APP_B_NO_PERMS.getPackageName()); + // Test that we can move the file back to other app's owned path + assertWithMessage("Result of ContentResolver#update for " + otherImageUri + + " with values " + values) + .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY)) + .isEqualTo(1); + assertThat(otherImageFile.exists()).isTrue(); + } finally { + otherImageFile.delete(); + updatedImageFileInDcim.delete(); + denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + } + } + + @Test public void testQueryOtherAppsFiles() throws Exception { final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME); final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME); @@ -2999,16 +3060,10 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { final Uri trashedFileUri = MediaStore.scanFile(cr, trashedFile); assertNotNull(trashedFileUri); - trashFile(trashedFileUri); + trashFileAndAssert(trashedFileUri); return trashedFileUri; } - private void trashFile(Uri uri) throws Exception { - final ContentValues values = new ContentValues(); - values.put(MediaStore.MediaColumns.IS_TRASHED, 1); - assertEquals(1, getContentResolver().update(uri, values, Bundle.EMPTY)); - } - /** * Gets file path corresponding to the db row pointed by {@code uri}. If {@code uri} points to * multiple db rows, file path is extracted from the first db row of the database query result. diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java index 3b8164625f7..e4d3541b044 100644 --- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java +++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java @@ -147,6 +147,11 @@ public class LegacyStorageHostTest extends BaseHostTestCase { } @Test + public void testCanTrashOtherAndroidMediaFiles_hasRW() throws Exception { + runDeviceTest("testCanTrashOtherAndroidMediaFiles_hasRW"); + } + + @Test public void testCantRename_hasR() throws Exception { revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE"); runDeviceTest("testCantRename_hasR"); diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java index f1236b6e3eb..d31bc33dfa9 100644 --- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java +++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java @@ -179,6 +179,26 @@ public class ScopedStorageHostTest extends BaseHostTestCase { } @Test + public void testFileManagerCanTrashOtherAndroidMediaFiles() throws Exception { + allowAppOps("android:manage_external_storage"); + try { + runDeviceTest("testFileManagerCanTrashOtherAndroidMediaFiles"); + } finally { + denyAppOps("android:manage_external_storage"); + } + } + + @Test + public void testFileManagerCanUpdateOtherAndroidMediaFiles() throws Exception { + allowAppOps("android:manage_external_storage"); + try { + runDeviceTest("testFileManagerCanUpdateOtherAndroidMediaFiles"); + } finally { + denyAppOps("android:manage_external_storage"); + } + } + + @Test public void testOpenOtherPendingFilesFromFuse() throws Exception { grantPermissions("android.permission.READ_EXTERNAL_STORAGE"); try { 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 4754d74ec5d..fd83a2ef74f 100644 --- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java +++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java @@ -49,6 +49,8 @@ import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageStat import static android.scopedstorage.cts.lib.TestUtils.pollForPermission; import static android.scopedstorage.cts.lib.TestUtils.resetDefaultExternalStorageVolume; import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories; +import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert; +import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert; import static android.scopedstorage.cts.lib.TestUtils.updateFile; import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed; import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed; @@ -431,6 +433,23 @@ public class LegacyStorageTest { } } + @Test + public void testCanTrashOtherAndroidMediaFiles_hasRW() throws Exception { + final File otherVideoFile = new File(getAndroidMediaDir(), + String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME)); + try { + assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue(); + + final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile); + assertNotNull(otherVideoUri); + + trashFileAndAssert(otherVideoUri); + untrashFileAndAssert(otherVideoUri); + } finally { + otherVideoFile.delete(); + } + } + /** * Test that legacy app with only READ_EXTERNAL_STORAGE can only rename files in app external * directories. 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 64e500a0788..30683328b35 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 @@ -1690,4 +1690,21 @@ public class TestUtils { ActivityManager.class).getRunningAppProcesses().stream().filter( p -> packageName.equals(p.processName)).findFirst(); } + + public static void trashFileAndAssert(Uri uri) { + final ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.IS_TRASHED, 1); + assertWithMessage("Result of ContentResolver#update for " + uri + " with values to trash " + + "file " + values) + .that(getContentResolver().update(uri, values, Bundle.EMPTY)).isEqualTo(1); + } + + public static void untrashFileAndAssert(Uri uri) { + final ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.IS_TRASHED, 0); + assertWithMessage("Result of ContentResolver#update for " + uri + " with values to untrash " + + "file " + values) + .that(getContentResolver().update(uri, values, Bundle.EMPTY)).isEqualTo(1); + } + } diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java index c915235c607..814f1a24654 100644 --- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java +++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java @@ -52,6 +52,8 @@ import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageStat import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed; import static android.scopedstorage.cts.lib.TestUtils.pollForPermission; import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories; +import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert; +import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert; import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed; import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed; import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaData_denied; @@ -66,6 +68,7 @@ import static android.system.OsConstants.W_OK; import static androidx.test.InstrumentationRegistry.getContext; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -76,8 +79,10 @@ import static org.junit.Assume.assumeTrue; import android.Manifest; import android.app.WallpaperManager; +import android.content.ContentValues; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.os.storage.StorageManager; @@ -122,6 +127,7 @@ public class ScopedStorageTest { static final String AUDIO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp3"; static final String IMAGE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".jpg"; + static final String VIDEO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp4"; static final String NONMEDIA_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".pdf"; // The following apps are installed before the tests are run via a target_preparer. @@ -534,6 +540,62 @@ public class ScopedStorageTest { } @Test + public void testFileManagerCanTrashOtherAndroidMediaFiles() throws Exception { + pollForManageExternalStorageAllowed(); + + final File otherVideoFile = new File(getAndroidMediaDir(), + String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME)); + try { + assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue(); + + final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile); + assertNotNull(otherVideoUri); + + trashFileAndAssert(otherVideoUri); + untrashFileAndAssert(otherVideoUri); + } finally { + otherVideoFile.delete(); + } + } + + @Test + public void testFileManagerCanUpdateOtherAndroidMediaFiles() throws Exception { + pollForManageExternalStorageAllowed(); + + final File otherImageFile = new File(getAndroidMediaDir(), + String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME)); + final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME); + try { + assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue(); + + final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile); + assertNotNull(otherImageUri); + + final ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM); + // Test that we can move the file to "DCIM/" + assertWithMessage("Result of ContentResolver#update for " + otherImageUri + + " with values " + values) + .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY)) + .isEqualTo(1); + assertThat(updatedImageFileInDcim.exists()).isTrue(); + assertThat(otherImageFile.exists()).isFalse(); + + values.clear(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, + "Android/media/" + APP_B_NO_PERMS.getPackageName()); + // Test that we can move the file back to other app's owned path + assertWithMessage("Result of ContentResolver#update for " + otherImageUri + + " with values " + values) + .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY)) + .isEqualTo(1); + } finally { + otherImageFile.delete(); + updatedImageFileInDcim.delete(); + } + } + + @Test public void testAndroidMedia() throws Exception { // Check that the app does not have legacy external storage access if (isAtLeastS()) { diff --git a/hostsidetests/securitybulletin/res/cve_2021_0925 b/hostsidetests/securitybulletin/res/cve_2021_0925 Binary files differnew file mode 100644 index 00000000000..be9690ca0d0 --- /dev/null +++ b/hostsidetests/securitybulletin/res/cve_2021_0925 diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/Android.bp new file mode 100644 index 00000000000..5b9e6ddba7c --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/Android.bp @@ -0,0 +1,37 @@ +/* + * 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-2018-9549", + defaults : ["cts_hostsidetests_securitybulletin_defaults"], + srcs : [ + "poc.cpp", + ], + shared_libs : [ + "libbluetooth", + ], + include_dirs : [ + "external/aac/libSBRdec/src", + "external/aac/libSBRdec/include", + "external/aac/libFDK/include", + "external/aac/libSYS/include", + ], +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/poc.cpp new file mode 100644 index 00000000000..11165d29065 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9549/poc.cpp @@ -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. + */ + +#include <stdlib.h> +#include "lpp_tran.h" + +constexpr uint8_t kDegreeAliasMaxSize = 64; + +int main() { + FIXP_DBL qmfBufferRealVal = {}; + FIXP_DBL *qmfBufferReal = &qmfBufferRealVal; + FIXP_DBL qmfBufferImagVal = {}; + FIXP_DBL *qmfBufferImag = &qmfBufferImagVal; + QMF_SCALE_FACTOR sbrScaleFactor = {}; + FIXP_DBL degreeAlias[kDegreeAliasMaxSize] = {}; + + SBR_LPP_TRANS hLppTransVal = {}; + HANDLE_SBR_LPP_TRANS hLppTrans = &hLppTransVal; + TRANSPOSER_SETTINGS settings = {}; + for (int32_t i = 0; i < MAX_NUM_NOISE_VALUES; ++i) { + settings.bwBorders[i] = 64; + } + settings.nCols = 1; + hLppTrans->pSettings = &settings; + + lppTransposer(hLppTrans, &sbrScaleFactor, &qmfBufferReal, degreeAlias, &qmfBufferImag, 1, 0, 0, + 0, 0, 0, 0, nullptr, nullptr); + + return EXIT_SUCCESS; +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/Android.bp index 61166aa2d39..de0a9aa6aed 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/Android.bp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/Android.bp @@ -38,5 +38,6 @@ cc_test { ], cflags: [ "-DCHECK_OVERFLOW", + "-DENABLE_SELECTIVE_OVERLOADING", ], } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/poc.cpp index c8734bb370b..5205d0588b8 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2021/poc.cpp @@ -17,6 +17,29 @@ #include <stdlib.h> #include <nfc_int.h> #include <rw_int.h> +#include <unistd.h> +#include "../includes/common.h" +#include "../includes/memutils.h" + +char enable_selective_overload = ENABLE_NONE; +char *vulnPtr = nullptr; + +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) { + size_t pageSize = getpagesize(); + if (pageSize) { + char *vulnPtrGuardPage = (char *) ((size_t) vulnPtr & PAGE_MASK) + pageSize; + char *faultPage = (char *) ((size_t) info->si_addr & PAGE_MASK); + if (faultPage == vulnPtrGuardPage) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + } + } + _exit(EXIT_FAILURE); +} extern tRW_CB rw_cb; extern tNFC_CB nfc_cb; @@ -26,7 +49,7 @@ 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); + void *ptr = memalign(16, size); memset(ptr, 0x0, size); return ptr; } @@ -67,6 +90,10 @@ void GKI_stop_timer(uint8_t) { } int main() { + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigsegv_handler; + sigaction(SIGSEGV, &new_action, &old_action); tRW_T3T_CB* p_t3t = &rw_cb.tcb.t3t; GKI_init(); @@ -75,20 +102,20 @@ int main() { uint8_t peer_nfcid2[NCI_RF_F_UID_LEN]; uint8_t mrti_check = 1, mrti_update = 1; - if (rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) != NFC_STATUS_OK) { - return EXIT_FAILURE; - } + + enable_selective_overload = ENABLE_MEMALIGN_CHECK; + FAIL_CHECK((rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) == NFC_STATUS_OK)); p_data = (tNFC_CONN *) allocate_memory(sizeof(tNFC_CONN)); - if (!p_data) { - return EXIT_FAILURE; - } + FAIL_CHECK(p_data); - p_data->data.p_data = (NFC_HDR*)allocate_memory(3 * sizeof(NFC_HDR)); - if(!p_data->data.p_data) { + p_data->data.p_data = (NFC_HDR *) allocate_memory(sizeof(NFC_HDR) * 3); + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; + if (!(p_data->data.p_data)) { free(p_data); - return EXIT_FAILURE; + FAIL_CHECK(p_data->data.p_data); } + vulnPtr = (char *)p_data->data.p_data; p_data->status = NFC_STATUS_OK; p_t3t->rw_state = RW_T3T_STATE_COMMAND_PENDING; @@ -105,6 +132,8 @@ int main() { 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-2019-2022/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/Android.bp index 2187f920734..2c21381b503 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/Android.bp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/Android.bp @@ -38,5 +38,6 @@ cc_test { ], cflags: [ "-DCHECK_OVERFLOW", + "-DENABLE_SELECTIVE_OVERLOADING", ], } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/poc.cpp index f2150857d61..b9252c5fd54 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2022/poc.cpp @@ -20,6 +20,29 @@ #include <nfc_api.h> #include <tags_defs.h> #include <rw_int.h> +#include <unistd.h> +#include "../includes/common.h" +#include "../includes/memutils.h" + +char enable_selective_overload = ENABLE_NONE; +char *vulnPtr = nullptr; + +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) { + size_t pageSize = getpagesize(); + if (pageSize) { + char *vulnPtrGuardPage = (char *) ((size_t) vulnPtr & PAGE_MASK) + pageSize; + char *faultPage = (char *) ((size_t) info->si_addr & PAGE_MASK); + if (faultPage == vulnPtrGuardPage) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + } + } + _exit(EXIT_FAILURE); +} #define T3T_MSG_FELICALITE_MC_OFFSET 0x01 @@ -31,7 +54,7 @@ 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); + void *ptr = memalign(16, size); memset(ptr, 0x0, size); return ptr; } @@ -104,19 +127,19 @@ int trigger_OOB_via_rw_t3t_act_handle_fmt_rsp(){ uint8_t peer_nfcid2[NCI_RF_F_UID_LEN]; uint8_t mrti_check = 1, mrti_update = 1; - if (rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) != NFC_STATUS_OK) { - return EXIT_FAILURE; - } + enable_selective_overload = ENABLE_MEMALIGN_CHECK; + FAIL_CHECK(rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) == NFC_STATUS_OK); p_data = (tNFC_CONN *) allocate_memory(sizeof(tNFC_CONN)); - if (!p_data) { - return EXIT_FAILURE; - } + FAIL_CHECK(p_data); + p_data->data.p_data = (NFC_HDR *) allocate_memory(sizeof(NFC_HDR) * 4); + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; if (!(p_data->data.p_data)) { free(p_data); - return EXIT_FAILURE; + FAIL_CHECK(p_data->data.p_data); } + vulnPtr = (char *)p_data->data.p_data; p_data->status = NFC_STATUS_OK; p_t3t->cur_cmd = RW_T3T_CMD_FORMAT; @@ -137,7 +160,9 @@ int trigger_OOB_via_rw_t3t_act_handle_fmt_rsp(){ 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; free(p_data->data.p_data); free(p_data); return EXIT_SUCCESS; @@ -152,19 +177,19 @@ int trigger_OOB_via_rw_t3t_act_handle_sro_rsp(){ uint8_t peer_nfcid2[NCI_RF_F_UID_LEN]; uint8_t mrti_check = 1, mrti_update = 1; - if (rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) != NFC_STATUS_OK) { - return EXIT_FAILURE; - } + enable_selective_overload = ENABLE_MEMALIGN_CHECK; + FAIL_CHECK(rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) == NFC_STATUS_OK); tNFC_CONN *p_data = (tNFC_CONN *) allocate_memory(sizeof(tNFC_CONN)); - if (!p_data) { - return EXIT_FAILURE; - } + FAIL_CHECK(p_data); + p_data->data.p_data = (NFC_HDR *) allocate_memory(sizeof(NFC_HDR) * 4); + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; if (!(p_data->data.p_data)) { free(p_data); - return EXIT_FAILURE; + FAIL_CHECK(p_data->data.p_data); } + vulnPtr = (char *)p_data->data.p_data; p_data->status = NFC_STATUS_OK; p_t3t->cur_cmd = RW_T3T_CMD_SET_READ_ONLY_HARD; @@ -184,13 +209,19 @@ int trigger_OOB_via_rw_t3t_act_handle_sro_rsp(){ tNFC_CONN_CB* p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID]; tNFC_CONN_EVT event = NFC_DATA_CEVT; + testInProgress = true; p_cb->p_cback(0, event, p_data); + testInProgress = false; free(p_data->data.p_data); free(p_data); return EXIT_SUCCESS; } int main() { + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigsegv_handler; + sigaction(SIGSEGV, &new_action, &old_action); int ret = trigger_OOB_via_rw_t3t_act_handle_fmt_rsp(); ret |= trigger_OOB_via_rw_t3t_act_handle_sro_rsp(); return ret; diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/Android.bp new file mode 100644 index 00000000000..a080e0870c9 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/Android.bp @@ -0,0 +1,31 @@ +/* + * 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-2019-2027", + defaults: ["cts_hostsidetests_securitybulletin_defaults"], + srcs: [ + "poc.cpp", + ], + shared_libs: [ + "libvorbisidec", + ], +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/poc.cpp new file mode 100644 index 00000000000..b1426ee10a6 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2027/poc.cpp @@ -0,0 +1,95 @@ +/** + * 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 <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "../includes/common.h" + +#define REF_COUNT 1 + +extern "C" { +#include <Tremolo/codebook.h> +} + +bool testInProgress = false; +struct sigaction new_action, old_action; +void sigabrt_handler(int signum, siginfo_t *info, void* context) { + if (testInProgress && info->si_signo == SIGABRT) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + _exit(EXIT_FAILURE); +} + +unsigned char data[] = {/* 24 bits to make sure the alignment is correct */ + 0x42, 0x43, 0x56, + /* 16 bits for codebook.dim */ + 0x40, 0x00, + /* 24 bits for codebook.entries */ + 0x10, 0x00, 0x00, + /* 1 bit for ordering which is unset for unordered */ + /* 1 bit set for specifying unused entries */ + /* 1 bit for valid length */ + /* 5 bits for length of entry */ + 0x06, + /* 1 bit for valid length */ + /* 5 bits for length of entry */ + /* 2 bits for specifying invalid length for next 2 entries */ + 0x01, + /* 8 bits for specifying invalid length for next 8 entries */ + 0x00, + /* 4 bits for specifying invalid length for next 4 entries */ + /* 4 bits for specifying the map type 1 */ + 0x10, + /* 32 bits for codebook.q_min */ + 0x00, 0x00, 0x00, 0x00, + /* 32 bits for codebook.q_del */ + 0x00, 0x00, 0x00, 0x00, + /* 4 bits for codebook.q_bits */ + /* 1 bit for codebook.q_seq */ + /* 4 bits for quantized values of codebook.q_val for quantvals = 2 */ + /* 7 bits remaining unused */ + 0x01, 0x00}; + +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); + + ogg_buffer buf; + ogg_reference ref; + oggpack_buffer bits; + codebook book = {}; + + memset(&buf, 0, sizeof(ogg_buffer)); + memset(&ref, 0, sizeof(ogg_reference)); + memset(&bits, 0, sizeof(oggpack_buffer)); + + buf.data = (uint8_t *)data; + buf.size = sizeof(data); + buf.refcount = REF_COUNT; + + ref.buffer = &buf; + ref.length = sizeof(data); + oggpack_readinit(&bits, &ref); + + testInProgress = true; + FAIL_CHECK(vorbis_book_unpack(&bits, &book) == 0); + testInProgress = false; + return EXIT_SUCCESS; +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/Android.bp index 470f2561c51..cdcf942d497 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/Android.bp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/Android.bp @@ -15,6 +15,10 @@ * */ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_test { name: "CVE-2021-0596", defaults: ["cts_hostsidetests_securitybulletin_defaults"], @@ -38,5 +42,6 @@ cc_test { ], cflags: [ "-DCHECK_UNDERFLOW", + "-DENABLE_SELECTIVE_OVERLOADING", ], } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/poc.cpp index 9b250044e38..3b1a58014a3 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0596/poc.cpp @@ -15,21 +15,50 @@ * */ +#include <unistd.h> #include "phNxpExtns_MifareStd.h" +#include "../includes/common.h" +#include "../includes/memutils.h" +char enable_selective_overload = ENABLE_NONE; +char *vulnPtr = nullptr; +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) { + size_t pageSize = getpagesize(); + if (pageSize) { + char *vulnPtrGuardPage = (char *) ((size_t) vulnPtr & PAGE_MASK) - pageSize; + char *faultPage = (char *) ((size_t) info->si_addr & PAGE_MASK); + if (faultPage == vulnPtrGuardPage) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + } + } + _exit(EXIT_FAILURE); +} uint8_t NFC_GetNCIVersion() { return NCI_VERSION_2_0; } int main() { - uint8_t *buffer = (uint8_t*) malloc(16 * sizeof(uint8_t)); - if (buffer == nullptr) { - return EXIT_FAILURE; - } + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigsegv_handler; + sigaction(SIGSEGV, &new_action, &old_action); + enable_selective_overload = ENABLE_MEMALIGN_CHECK; + uint8_t *buffer = (uint8_t*) memalign(16, 16 * sizeof(uint8_t)); + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; + FAIL_CHECK(buffer); + + vulnPtr = (char *) buffer; uint8_t bufferSize = 1; buffer[0] = 0x10; phNxpExtns_MfcModuleInit(); + testInProgress = true; Mfc_RecvPacket(buffer, bufferSize); + testInProgress = false; free(buffer); return EXIT_SUCCESS; } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/Android.bp new file mode 100644 index 00000000000..eea87e76748 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/Android.bp @@ -0,0 +1,48 @@ +/* + * 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-0925", + defaults: ["cts_hostsidetests_securitybulletin_defaults"], + srcs: [ + "poc.cpp", + "stubs.cpp", + "t4t.cpp", + ":cts_hostsidetests_securitybulletin_memutils", + ], + compile_multilib: "64", + include_dirs: [ + "system/nfc/src/fuzzers/rw/", + "system/nfc/src/include", + "system/nfc/src/gki/ulinux", + "system/nfc/src/gki/common", + "system/nfc/src/nfc/include", + "system/nfc/src/fuzzers/inc", + ], + shared_libs: [ + "libnfc-nci", + "libchrome", + ], + cflags: [ + "-DCHECK_OVERFLOW", + "-DENABLE_SELECTIVE_OVERLOADING", + ], +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/poc.cpp new file mode 100644 index 00000000000..085f6e83d4a --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/poc.cpp @@ -0,0 +1,102 @@ +/* + * 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. + */ + +/* Following files are taken as reference to come up with this PoC */ +/* 1. 'system/nfc/src/fuzzers/fuzz_utils.cc' */ +/* 2. 'system/nfc/src/fuzzers/rw/main.cc' */ + +#include <vector> +#include "../includes/common.h" +#include "fuzz.h" +#include "fuzz_cmn.h" +extern void Type4_Fuzz(uint8_t SubType, const std::vector<bytes_t> &Packets); + +FILE *file = nullptr; +char *vulnPtr = nullptr; +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) { + size_t pageSize = getpagesize(); + if (pageSize) { + char *vulnPtrGuardPage = (char *)((size_t)vulnPtr & PAGE_MASK) + pageSize; + char *faultPage = (char *)((size_t)info->si_addr & PAGE_MASK); + if (faultPage == vulnPtrGuardPage) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + } + } + _exit(EXIT_FAILURE); +} + +void exit_Handler(void) { + if (file) { + fclose(file); + } +} + +static std::vector<bytes_t> UnpackPackets(const uint8_t *Data, size_t Size) { + std::vector<bytes_t> result; + while (Size > 0) { + auto s = *Data++; + Size--; + + if (s > Size) { + s = Size; + } + + if (s > 0) { + result.push_back(bytes_t(Data, Data + s)); + } + + Size -= s; + Data += s; + } + + return result; +} + +int main(int argc, char **argv) { + 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 > 1); + + file = fopen(argv[1], "r"); + FAIL_CHECK(file); + + fseek(file, 0, SEEK_END); + size_t size = ftell(file); + fseek(file, 0, SEEK_SET); + + std::vector<uint8_t> bufVector(size); + FAIL_CHECK(bufVector.data()); + FAIL_CHECK(fread(bufVector.data(), 1, size, file) == size); + + const uint8_t *data = (const uint8_t *)bufVector.data(); + auto Packets = UnpackPackets(data, size); + FAIL_CHECK(Packets.size() > 1); + + auto &ctrl = Packets[0]; + FAIL_CHECK(ctrl.size() > 1); + + Type4_Fuzz(ctrl[1], Packets); + + return EXIT_SUCCESS; +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/stubs.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/stubs.cpp new file mode 100644 index 00000000000..d8072329371 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/stubs.cpp @@ -0,0 +1,144 @@ +/* + * 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. + */ + +/* 'system/nfc/src/fuzzers/ce/stubs.cc' is used as reference to come up with file */ + +#include "fuzz_cmn.h" + +// These are the functions implemented elsewhere in the NFC code. Our fuzzing +// doesn't need them. To avoid pulling into more source code we simply stub +// them out. + +tNFA_PROPRIETARY_CFG nfa_proprietary_cfg = { + 0x80, /* NCI_PROTOCOL_18092_ACTIVE */ + 0x81, /* NCI_PROTOCOL_B_PRIME */ + 0x82, /* NCI_PROTOCOL_DUAL */ + 0x83, /* NCI_PROTOCOL_15693 */ + 0x8A, /* NCI_PROTOCOL_KOVIO */ + 0xFF, /* NCI_PROTOCOL_MIFARE */ + 0x77, /* NCI_DISCOVERY_TYPE_POLL_KOVIO */ + 0x74, /* NCI_DISCOVERY_TYPE_POLL_B_PRIME */ + 0xF4, /* NCI_DISCOVERY_TYPE_LISTEN_B_PRIME */ +}; + +tNFA_PROPRIETARY_CFG* p_nfa_proprietary_cfg = (tNFA_PROPRIETARY_CFG*)&nfa_proprietary_cfg; + +void nfc_start_quick_timer(TIMER_LIST_ENT*, uint16_t, uint32_t) {} +void nfc_stop_timer(TIMER_LIST_ENT*) {} +void nfc_stop_quick_timer(TIMER_LIST_ENT*) {} +uint8_t NFC_GetNCIVersion() { + return NCI_VERSION_2_0; +} + +tNFC_STATUS NFC_SendData(uint8_t conn_id, NFC_HDR* p_data) { + uint8_t* p = (uint8_t*)(p_data + 1) + p_data->offset; + uint8_t len = (uint8_t)p_data->len; + + FUZZLOG("conn_id=%d, data=%s", conn_id, BytesToHex(p, len).c_str()); + GKI_freebuf(p_data); + return NFC_STATUS_OK; +} + +uint8_t nci_snd_t3t_polling(uint16_t system_code, uint8_t rc, uint8_t tsn) { + FUZZLOG("sc=%04X, rc=%02X, tsn=%02X", system_code, rc, tsn); + return NFC_STATUS_OK; +} + +tNFC_CONN_CBACK* rf_cback = nullptr; +void NFC_SetStaticRfCback(tNFC_CONN_CBACK* p_cback) { + rf_cback = p_cback; +} + +tNFC_STATUS NFC_ISODEPNakPresCheck() { + return NFC_STATUS_OK; +} + +std::string NFC_GetStatusName(tNFC_STATUS status) { + switch (status) { + case NFC_STATUS_OK: + return "OK"; + case NFC_STATUS_REJECTED: + return "REJECTED"; + case NFC_STATUS_MSG_CORRUPTED: + return "CORRUPTED"; + case NFC_STATUS_BUFFER_FULL: + return "BUFFER_FULL"; + case NFC_STATUS_FAILED: + return "FAILED"; + case NFC_STATUS_NOT_INITIALIZED: + return "NOT_INITIALIZED"; + case NFC_STATUS_SYNTAX_ERROR: + return "SYNTAX_ERROR"; + case NFC_STATUS_SEMANTIC_ERROR: + return "SEMANTIC_ERROR"; + case NFC_STATUS_UNKNOWN_GID: + return "UNKNOWN_GID"; + case NFC_STATUS_UNKNOWN_OID: + return "UNKNOWN_OID"; + case NFC_STATUS_INVALID_PARAM: + return "INVALID_PARAM"; + case NFC_STATUS_MSG_SIZE_TOO_BIG: + return "MSG_SIZE_TOO_BIG"; + case NFC_STATUS_ALREADY_STARTED: + return "ALREADY_STARTED"; + case NFC_STATUS_ACTIVATION_FAILED: + return "ACTIVATION_FAILED"; + case NFC_STATUS_TEAR_DOWN: + return "TEAR_DOWN"; + case NFC_STATUS_RF_TRANSMISSION_ERR: + return "RF_TRANSMISSION_ERR"; + case NFC_STATUS_RF_PROTOCOL_ERR: + return "RF_PROTOCOL_ERR"; + case NFC_STATUS_TIMEOUT: + return "TIMEOUT"; + case NFC_STATUS_EE_INTF_ACTIVE_FAIL: + return "EE_INTF_ACTIVE_FAIL"; + case NFC_STATUS_EE_TRANSMISSION_ERR: + return "EE_TRANSMISSION_ERR"; + case NFC_STATUS_EE_PROTOCOL_ERR: + return "EE_PROTOCOL_ERR"; + case NFC_STATUS_EE_TIMEOUT: + return "EE_TIMEOUT"; + case NFC_STATUS_CMD_STARTED: + return "CMD_STARTED"; + case NFC_STATUS_HW_TIMEOUT: + return "HW_TIMEOUT"; + case NFC_STATUS_CONTINUE: + return "CONTINUE"; + case NFC_STATUS_REFUSED: + return "REFUSED"; + case NFC_STATUS_BAD_RESP: + return "BAD_RESP"; + case NFC_STATUS_CMD_NOT_CMPLTD: + return "CMD_CMPLTD"; + case NFC_STATUS_NO_BUFFERS: + return "NO_BUFFERS"; + case NFC_STATUS_WRONG_PROTOCOL: + return "WRONG_PROTOCOL"; + case NFC_STATUS_BUSY: + return "BUSY"; + case NFC_STATUS_LINK_LOSS: + return "LINK_LOSS"; + case NFC_STATUS_BAD_LENGTH: + return "BAD_LENGTH"; + case NFC_STATUS_BAD_HANDLE: + return "BAD_HANDLE"; + case NFC_STATUS_CONGESTED: + return "CONGESTED"; + default: + return "UNKNOWN"; + } +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/t4t.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/t4t.cpp new file mode 100644 index 00000000000..5746a9dea8f --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0925/t4t.cpp @@ -0,0 +1,207 @@ +/* + * 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. + */ + +/* 'system/nfc/src/fuzzers/rw/t4t.cc' is used as reference to come up with file */ + +#include "fuzz.h" + +#include "../includes/memutils.h" +char enable_selective_overload = ENABLE_NONE; +extern bool testInProgress; +extern char* vulnPtr; + +#define MODULE_NAME "Type4 Read/Write" + +enum { + SUB_TYPE_DETECT_NDEF, + SUB_TYPE_READ_NDEF, + SUB_TYPE_UPDATE_NDEF, + SUB_TYPE_PRESENCE_CHECK, + SUB_TYPE_SET_READ_ONLY, + SUB_TYPE_FORMAT_NDEF, + + SUB_TYPE_MAX +}; + +static void rw_cback(tRW_EVENT event, tRW_DATA* p_rw_data) { + FUZZLOG(MODULE_NAME ": rw_cback: event=0x%02x, p_rw_data=%p", event, p_rw_data); + + if (event == RW_T4T_RAW_FRAME_EVT) { + if (p_rw_data->raw_frame.p_data) { + GKI_freebuf(p_rw_data->raw_frame.p_data); + p_rw_data->raw_frame.p_data = nullptr; + } + } else if (event == RW_T4T_NDEF_READ_EVT || event == RW_T4T_NDEF_READ_CPLT_EVT) { + if (p_rw_data->data.p_data) { + GKI_freebuf(p_rw_data->data.p_data); + p_rw_data->data.p_data = nullptr; + } + } +} + +#define TEST_NFCID_VALUE \ + { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 } + +static bool Init(Fuzz_Context& /*ctx*/) { + tNFC_ACTIVATE_DEVT activate_params = {.protocol = NFC_PROTOCOL_ISO_DEP, + .rf_tech_param = { + .mode = NFC_DISCOVERY_TYPE_POLL_A, + }}; + + rw_init(); + if (NFC_STATUS_OK != RW_SetActivatedTagType(&activate_params, rw_cback)) { + FUZZLOG(MODULE_NAME ": RW_SetActivatedTagType failed"); + return false; + } + + return true; +} + +static bool Init_PresenceCheck(Fuzz_Context& /*ctx*/) { + return NFC_STATUS_OK == RW_T4tPresenceCheck(1); +} + +static bool Init_DetectNDef(Fuzz_Context& /*ctx*/) { + return NFC_STATUS_OK == RW_T4tDetectNDef(); +} + +static bool Init_ReadNDef(Fuzz_Context& /*ctx*/) { + return NFC_STATUS_OK == RW_T4tReadNDef(); +} + +static bool Init_UpdateNDef(Fuzz_Context& ctx) { + const uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, + 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04}; + + auto scratch = ctx.GetBuffer(sizeof(data), data); + return NFC_STATUS_OK == RW_T4tUpdateNDef(sizeof(data), scratch); +} + +static bool Init_FormatNDef(Fuzz_Context& /*ctx*/) { + return NFC_STATUS_OK == RW_T4tFormatNDef(); +} + +static bool Init_SetNDefReadOnly(Fuzz_Context& /*ctx*/) { + return NFC_STATUS_OK == RW_T4tSetNDefReadOnly(); +} + +static bool Fuzz_Init(Fuzz_Context& ctx) { + if (!Init(ctx)) { + FUZZLOG(MODULE_NAME ": initialization failed"); + return false; + } + + bool result = false; + switch (ctx.SubType) { + case SUB_TYPE_DETECT_NDEF: + result = Init_DetectNDef(ctx); + break; + case SUB_TYPE_UPDATE_NDEF: + result = Init_UpdateNDef(ctx); + break; + case SUB_TYPE_PRESENCE_CHECK: + result = Init_PresenceCheck(ctx); + break; + case SUB_TYPE_READ_NDEF: + result = Init_ReadNDef(ctx); + break; + case SUB_TYPE_FORMAT_NDEF: + result = Init_FormatNDef(ctx); + break; + case SUB_TYPE_SET_READ_ONLY: + result = Init_SetNDefReadOnly(ctx); + break; + default: + FUZZLOG(MODULE_NAME ": Unknown command %d", ctx.SubType); + result = false; + break; + } + + if (!result) { + FUZZLOG(MODULE_NAME ": Initializing command %02X failed", ctx.SubType); + } + + return result; +} + +static void Fuzz_Deinit(Fuzz_Context& /*ctx*/) { + if (rf_cback) { + tNFC_CONN conn = {.deactivate = {.status = NFC_STATUS_OK, + .type = NFC_DEACTIVATE_TYPE_IDLE, + .is_ntf = true, + .reason = NFC_DEACTIVATE_REASON_DH_REQ_FAILED}}; + + rf_cback(NFC_RF_CONN_ID, NFC_DEACTIVATE_CEVT, &conn); + } +} + +static void Fuzz_Run(Fuzz_Context& ctx) { + for (auto it = ctx.Data.cbegin() + 1; it != ctx.Data.cend(); ++it) { + NFC_HDR* p_msg; + + /* CVE-2021-0925 starts */ + enable_selective_overload = ENABLE_ALL; + /* CVE-2021-0925 ends */ + + p_msg = (NFC_HDR*)GKI_getbuf(sizeof(NFC_HDR) + it->size()); + + /* CVE-2021-0925 starts */ + vulnPtr = (char*)p_msg; + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; + /* CVE-2021-0925 ends */ + + if (p_msg == nullptr) { + FUZZLOG(MODULE_NAME ": GKI_getbuf returns null, size=%zu", it->size()); + return; + } + + /* Initialize NFC_HDR */ + p_msg->len = it->size(); + p_msg->offset = 0; + + uint8_t* p = (uint8_t*)(p_msg + 1) + p_msg->offset; + memcpy(p, it->data(), it->size()); + + tNFC_CONN conn = {.data = { + .status = NFC_STATUS_OK, + .p_data = p_msg, + }}; + + FUZZLOG(MODULE_NAME ": SubType=%02X, Response[%zd/%zd]=%s", ctx.SubType, + it - ctx.Data.cbegin(), ctx.Data.size() - 1, BytesToHex(*it).c_str()); + + /* CVE-2021-0925 starts */ + testInProgress = true; + /* CVE-2021-0925 ends */ + + rf_cback(NFC_RF_CONN_ID, NFC_DATA_CEVT, &conn); + + /* CVE-2021-0925 starts */ + testInProgress = false; + /* CVE-2021-0925 ends */ + } +} + +void Type4_FixPackets(uint8_t /*SubType*/, std::vector<bytes_t>& /*Data*/) {} + +void Type4_Fuzz(uint8_t SubType, const std::vector<bytes_t>& Data) { + Fuzz_Context ctx(SubType % SUB_TYPE_MAX, Data); + if (Fuzz_Init(ctx)) { + Fuzz_Run(ctx); + } + + Fuzz_Deinit(ctx); +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java index 2d39dd1d9f6..c8e8cbfe049 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java @@ -492,7 +492,7 @@ public class AdbUtils { */ public static void runPocAssertExitStatusNotVulnerable(String pocName, String arguments, ITestDevice device, int timeout) throws Exception { - runPocGetExitStatus(pocName, arguments, null, device, timeout); + runPocAssertExitStatusNotVulnerable(pocName, arguments, null, device, timeout); } /** diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9549.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9549.java new file mode 100644 index 00000000000..bf2b0d1d3f9 --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9549.java @@ -0,0 +1,44 @@ +/** + * 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.tradefed.testtype.DeviceJUnit4ClassRunner; +import org.junit.runner.RunWith; +import org.junit.Test; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2018_9549 extends SecurityTestCase { + + /** + * b/112160868 + * Vulnerability Behaviour: SIGABRT in self + */ + @AsbSecurityTest(cveBugId = 112160868) + @Test + public void testPocCVE_2018_9549() throws Exception { + String binaryName = "CVE-2018-9549"; + String signals[] = {CrashUtils.SIGABRT}; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName); + testConfig.config.setSignals(signals); + testConfig.config + .setAbortMessageIncludes(AdbUtils.escapeRegexSpecialChars("ubsan: mul-overflow")); + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2014.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2014.java index afc7a2bb65c..e6863ac86a7 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2014.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2014.java @@ -34,7 +34,7 @@ public class CVE_2019_2014 extends SecurityTestCase { public void testPocCVE_2019_2014() throws Exception { pocPusher.only64(); String binaryName = "CVE-2019-2014"; - 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_2027.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2027.java new file mode 100644 index 00000000000..df6c6f4b6b2 --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2027.java @@ -0,0 +1,44 @@ +/** + * 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.tradefed.testtype.DeviceJUnit4ClassRunner; +import org.junit.runner.RunWith; +import org.junit.Test; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2019_2027 extends SecurityTestCase { + + /** + * b/119120561 + * Vulnerability Behaviour: SIGABRT in self + */ + @AsbSecurityTest(cveBugId = 119120561) + @Test + public void testPocCVE_2019_2027() throws Exception { + String binaryName = "CVE-2019-2027"; + String signals[] = {CrashUtils.SIGABRT}; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName); + testConfig.config.setSignals(signals); + testConfig.config + .setAbortMessageIncludes(AdbUtils.escapeRegexSpecialChars("ubsan: mul-overflow")); + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0925.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0925.java new file mode 100644 index 00000000000..617658973bb --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0925.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.compatibility.common.util.CrashUtils; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2021_0925 extends SecurityTestCase { + + /** + * Vulnerability Behaviour: SIGSEGV in self + */ + @Test + @AsbSecurityTest(cveBugId = 191444150) + public void testPocCVE_2021_0925() throws Exception { + pocPusher.only64(); + String binaryName = "CVE-2021-0925"; + String inputFiles[] = {"cve_2021_0925"}; + String signals[] = {CrashUtils.SIGSEGV}; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName); + testConfig.config.setSignals(signals); + testConfig.arguments = AdbUtils.TMP_PATH + inputFiles[0]; + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0921/src/android/security/cts/CVE_2021_0921/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0921/src/android/security/cts/CVE_2021_0921/DeviceTest.java index bb6631ad9d0..233fdb44728 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0921/src/android/security/cts/CVE_2021_0921/DeviceTest.java +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0921/src/android/security/cts/CVE_2021_0921/DeviceTest.java @@ -19,6 +19,7 @@ package android.security.cts.CVE_2021_0921; import org.junit.Test; import org.junit.runner.RunWith; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -31,6 +32,7 @@ import androidx.test.uiautomator.UiDevice; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertFalse; +import static org.junit.Assume.assumeNoException; @RunWith(AndroidJUnit4.class) public class DeviceTest { @@ -54,6 +56,12 @@ public class DeviceTest { intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); + try{ + context.startActivity(intent); + } catch(ActivityNotFoundException e){ + assumeNoException(e); + return; + } //wait for poc app to complete (it takes about 6 seconds) SystemClock.sleep(20000); diff --git a/tests/PhotoPicker/Android.bp b/tests/PhotoPicker/Android.bp new file mode 100644 index 00000000000..dac10dca7c8 --- /dev/null +++ b/tests/PhotoPicker/Android.bp @@ -0,0 +1,35 @@ +// 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 { + name: "CtsPhotoPickerTest", + manifest: "AndroidManifest.xml", + test_config: "AndroidTest.xml", + srcs: ["src/**/*.java", "helper/**/*.java", ":CtsProviderTestUtils",], + compile_multilib: "both", + test_suites: ["device-tests", "mts-mediaprovider", "cts"], + sdk_version: "test_current", + min_sdk_version: "30", + libs: ["android.test.base", "android.test.runner",], + static_libs: [ + "androidx.test.rules", + "cts-install-lib", + "androidx.test.uiautomator_uiautomator", + "Harrier", + ], +} diff --git a/tests/PhotoPicker/AndroidManifest.xml b/tests/PhotoPicker/AndroidManifest.xml new file mode 100644 index 00000000000..ea26ad1424e --- /dev/null +++ b/tests/PhotoPicker/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?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.photopicker.cts"> +<application android:label="Photo Picker Device Tests"> + <uses-library android:name="android.test.runner" /> + <activity android:name="android.photopicker.cts.GetResultActivity" /> +</application> + +<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.photopicker.cts" + android:label="Device-only photo picker tests" /> + +</manifest> diff --git a/tests/PhotoPicker/AndroidTest.xml b/tests/PhotoPicker/AndroidTest.xml new file mode 100644 index 00000000000..e7f7163f688 --- /dev/null +++ b/tests/PhotoPicker/AndroidTest.xml @@ -0,0 +1,41 @@ +<?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. + --> +<configuration description="Runs device-only tests for photo picker"> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="CtsPhotoPickerTest.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="cts" /> + <option name="test-tag" value="PhotoPickerTests" /> + <!-- Instant apps cannot access external storage --> + <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> + <option name="config-descriptor:metadata" key="parameter" value="secondary_user" /> + <option name="config-descriptor:metadata" key="component" value="framework" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.photopicker.cts" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" /> + <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" /> + </test> + <option name="config-descriptor:metadata" key="parameter" value="multiuser" /> + + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <option name="mainline-module-package-name" value="com.google.android.mediaprovider" /> + </object> +</configuration> diff --git a/tests/PhotoPicker/OWNERS b/tests/PhotoPicker/OWNERS new file mode 100644 index 00000000000..79add9eb349 --- /dev/null +++ b/tests/PhotoPicker/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 95221 + +include platform/frameworks/base:/core/java/android/os/storage/OWNERS diff --git a/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg b/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg Binary files differnew file mode 100644 index 00000000000..d26419604ef --- /dev/null +++ b/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg diff --git a/tests/PhotoPicker/res/raw/testvideo_meta.mp4 b/tests/PhotoPicker/res/raw/testvideo_meta.mp4 Binary files differnew file mode 100644 index 00000000000..e83c61db02f --- /dev/null +++ b/tests/PhotoPicker/res/raw/testvideo_meta.mp4 diff --git a/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java b/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java new file mode 100644 index 00000000000..35e7830f083 --- /dev/null +++ b/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java @@ -0,0 +1,84 @@ +/* + * 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.photopicker.cts; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.WindowManager; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +public class GetResultActivity extends Activity { + private static LinkedBlockingQueue<Result> sResult; + + public static class Result { + public final int requestCode; + public final int resultCode; + public final Intent data; + + public Result(int requestCode, int resultCode, Intent data) { + this.requestCode = requestCode; + this.resultCode = resultCode; + this.data = data; + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + try { + sResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public void clearResult() { + sResult = new LinkedBlockingQueue<>(); + } + + public Result getResult() { + final Result result; + try { + result = sResult.poll(40, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + if (result == null) { + throw new IllegalStateException("Activity didn't receive a Result in 40 seconds"); + } + return result; + } + + public Result getResult(long timeout, TimeUnit unit) { + try { + return sResult.poll(timeout, unit); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java new file mode 100644 index 00000000000..e51ff082ce3 --- /dev/null +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.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.photopicker.cts; + +import android.app.Instrumentation; +import android.content.Context; +import android.content.Intent; + +import androidx.test.InstrumentationRegistry; +import androidx.test.uiautomator.UiDevice; + +import org.junit.Before; + +/** + * Photo Picker Base class for Photo Picker tests. This includes common setup methods + * required for all Photo Picker tests. + */ +public class PhotoPickerBaseTest { + public static int REQUEST_CODE = 42; + + protected GetResultActivity mActivity; + protected Context mContext; + protected UiDevice mDevice; + + @Before + public void setUp() throws Exception { + final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); + mContext = inst.getContext(); + final Intent intent = new Intent(mContext, GetResultActivity.class); + 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"); + + mActivity = (GetResultActivity) inst.startActivitySync(intent); + // Wait for the UI Thread to become idle. + inst.waitForIdleSync(); + mActivity.clearResult(); + mDevice.waitForIdle(); + } +} diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java new file mode 100644 index 00000000000..b574648e6f6 --- /dev/null +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java @@ -0,0 +1,147 @@ +/* + * 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.photopicker.cts; + +import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat; +import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess; +import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages; +import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia; +import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton; +import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList; +import static android.photopicker.cts.util.PhotoPickerUiUtils.findProfileButton; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.ClipData; +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; +import com.android.bedstead.harrier.DeviceState; +import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile; +import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile; + +import org.junit.After; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +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(); + + private List<Uri> mUriList = new ArrayList<>(); + + @After + public void tearDown() throws Exception { + for (Uri uri : mUriList) { + deleteMedia(uri, mContext.getUserId()); + } + mActivity.finish(); + } + + @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); + + Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, imageCount); + mActivity.startActivityForResult(intent, REQUEST_CODE); + + // Click the profile button to change to personal profile + final UiObject profileButton = findProfileButton(); + profileButton.click(); + mDevice.waitForIdle(); + + final List<UiObject> itemList = findItemList(imageCount); + 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(); + } + + final UiObject addButton = findAddButton(); + addButton.click(); + mDevice.waitForIdle(); + + final ClipData clipData = mActivity.getResult().data.getClipData(); + final int count = clipData.getItemCount(); + assertThat(count).isEqualTo(imageCount); + for (int i = 0; i < count; i++) { + Uri uri = clipData.getItemAt(i).getUri(); + assertPickerUriFormat(uri, sDeviceState.primaryUser().id()); + assertRedactedReadOnlyAccess(uri); + } + } + + @Test + @EnsureHasWorkProfile + public void testPersonalApp_canAccessWorkProfileContents() throws Exception { + final int imageCount = 2; + createImages(imageCount, sDeviceState.workProfile().id(), mUriList); + + Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, imageCount); + mActivity.startActivityForResult(intent, REQUEST_CODE); + + // Click the profile button to change to work profile + final UiObject profileButton = findProfileButton(); + profileButton.click(); + mDevice.waitForIdle(); + + final List<UiObject> itemList = findItemList(imageCount); + 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(); + } + + final UiObject addButton = findAddButton(); + addButton.click(); + mDevice.waitForIdle(); + + final ClipData clipData = mActivity.getResult().data.getClipData(); + final int count = clipData.getItemCount(); + assertThat(count).isEqualTo(imageCount); + for (int i = 0; i < count; i++) { + Uri uri = clipData.getItemAt(i).getUri(); + assertPickerUriFormat(uri, sDeviceState.workProfile().id()); + assertRedactedReadOnlyAccess(uri); + } + } +} diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java new file mode 100644 index 00000000000..ed76f8d2fa9 --- /dev/null +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java @@ -0,0 +1,237 @@ +/* + * 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.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.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.findAddButton; +import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList; +import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddButton; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import android.app.Activity; +import android.content.ClipData; +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; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +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 static final String TAG = "PhotoPickerTest"; + private List<Uri> mUriList = new ArrayList<>(); + + @After + public void tearDown() throws Exception { + for (Uri uri : mUriList) { + deleteMedia(uri, mContext.getUserId()); + } + mActivity.finish(); + } + + @Test + public void testSingleSelect() throws Exception { + final int itemCount = 1; + createImages(itemCount, mContext.getUserId(), mUriList); + + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + mActivity.startActivityForResult(intent, REQUEST_CODE); + + final UiObject item = findItemList(itemCount).get(0); + item.click(); + mDevice.waitForIdle(); + + final UiObject addButton = findPreviewAddButton(); + addButton.click(); + mDevice.waitForIdle(); + + final Uri uri = mActivity.getResult().data.getData(); + assertPickerUriFormat(uri, mContext.getUserId()); + assertRedactedReadOnlyAccess(uri); + } + + @Test + public void testMultiSelect_invalidParam() throws Exception { + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit() + 1); + mActivity.startActivityForResult(intent, REQUEST_CODE); + final GetResultActivity.Result res = mActivity.getResult(); + assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED); + } + + @Test + public void testMultiSelect_invalidNegativeParam() throws Exception { + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, -1); + mActivity.startActivityForResult(intent, REQUEST_CODE); + final GetResultActivity.Result res = mActivity.getResult(); + assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED); + } + + @Test + public void testMultiSelect_returnsNotMoreThanMax() throws Exception { + final int maxCount = 2; + final int imageCount = maxCount + 1; + createImages(imageCount, mContext.getUserId(), mUriList); + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxCount); + mActivity.startActivityForResult(intent, REQUEST_CODE); + + final List<UiObject> itemList = findItemList(imageCount); + final int itemCount = itemList.size(); + assertThat(itemCount).isEqualTo(imageCount); + // Select maxCount + 1 items + for (int i = 0; i < itemCount; i++) { + final UiObject item = itemList.get(i); + item.click(); + mDevice.waitForIdle(); + } + + UiObject snackbarTextView = mDevice.findObject(new UiSelector().text( + "Select up to 2 items")); + assertWithMessage("Timed out while waiting for snackbar to appear").that( + snackbarTextView.waitForExists(SHORT_TIMEOUT)).isTrue(); + + assertWithMessage("Timed out waiting for snackbar to disappear").that( + snackbarTextView.waitUntilGone(SHORT_TIMEOUT)).isTrue(); + + final UiObject addButton = findAddButton(); + addButton.click(); + mDevice.waitForIdle(); + + final ClipData clipData = mActivity.getResult().data.getClipData(); + final int count = clipData.getItemCount(); + assertThat(count).isEqualTo(maxCount); + } + + @Test + public void testMultiSelect_doesNotRespectExtraAllowMultiple() throws Exception { + final int imageCount = 2; + createImages(imageCount, mContext.getUserId(), mUriList); + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + mActivity.startActivityForResult(intent, REQUEST_CODE); + + final List<UiObject> itemList = findItemList(imageCount); + final int itemCount = itemList.size(); + assertThat(itemCount).isEqualTo(imageCount); + // Select 1 items + final UiObject item = itemList.get(0); + item.click(); + mDevice.waitForIdle(); + + // Shows preview Add button; single select flow + final UiObject addButton = findPreviewAddButton(); + addButton.click(); + mDevice.waitForIdle(); + + final Uri uri = mActivity.getResult().data.getData(); + assertPickerUriFormat(uri, mContext.getUserId()); + assertRedactedReadOnlyAccess(uri); + } + + @Test + public void testMultiSelect() throws Exception { + final int imageCount = 4; + createImages(imageCount, mContext.getUserId(), mUriList); + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit()); + mActivity.startActivityForResult(intent, REQUEST_CODE); + + final List<UiObject> itemList = findItemList(imageCount); + 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(); + } + + final UiObject addButton = findAddButton(); + addButton.click(); + mDevice.waitForIdle(); + + final ClipData clipData = mActivity.getResult().data.getClipData(); + final int count = clipData.getItemCount(); + assertThat(count).isEqualTo(itemCount); + for (int i = 0; i < count; i++) { + final Uri uri = clipData.getItemAt(i).getUri(); + assertPickerUriFormat(uri, mContext.getUserId()); + assertRedactedReadOnlyAccess(uri); + } + } + + @Test + public void testMimeTypeFilter() throws Exception { + final int videoCount = 2; + createVideos(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); + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit()); + intent.setType(mimeType); + mActivity.startActivityForResult(intent, REQUEST_CODE); + + // find all items + final List<UiObject> itemList = findItemList(-1); + 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(); + } + + final UiObject addButton = findAddButton(); + addButton.click(); + mDevice.waitForIdle(); + + final ClipData clipData = mActivity.getResult().data.getClipData(); + final int count = clipData.getItemCount(); + assertThat(count).isEqualTo(itemCount); + for (int i = 0; i < count; i++) { + final Uri uri = clipData.getItemAt(i).getUri(); + assertPickerUriFormat(uri, mContext.getUserId()); + assertRedactedReadOnlyAccess(uri); + assertMimeType(uri, mimeType); + } + } +} diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java new file mode 100644 index 00000000000..28380f9a094 --- /dev/null +++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java @@ -0,0 +1,161 @@ +/* + * 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.photopicker.cts.util; + +import static android.photopicker.cts.util.PhotoPickerFilesUtils.DISPLAY_NAME_PREFIX; +import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE; +import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.fail; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.media.ExifInterface; +import android.net.Uri; +import android.os.FileUtils; +import android.os.ParcelFileDescriptor; +import android.provider.MediaStore; +import android.util.Log; + +import androidx.test.InstrumentationRegistry; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Arrays; + +/** + * Photo Picker Utility methods for test assertions. + */ +public class PhotoPickerAssertionsUtils { + private static final String TAG = "PhotoPickerTestAssertions"; + + public static void assertPickerUriFormat(Uri uri, int expectedUserId) { + // content://media/picker/<user-id>/<media-id> + final int userId = Integer.parseInt(uri.getPathSegments().get(1)); + assertThat(userId).isEqualTo(expectedUserId); + + final String auth = uri.getPathSegments().get(0); + assertThat(auth).isEqualTo("picker"); + } + + public static void assertMimeType(Uri uri, String expectedMimeType) throws Exception { + final Context context = InstrumentationRegistry.getTargetContext(); + final String resultMimeType = context.getContentResolver().getType(uri); + assertThat(resultMimeType).isEqualTo(expectedMimeType); + } + + 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}; + 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(); + + boolean canCheckRedacted = false; + // If the file is inserted by this test case, we can check the redaction. + // To avoid checking the redaction on the other media file. + if (c.getString(0).startsWith(DISPLAY_NAME_PREFIX)) { + canCheckRedacted = true; + } else { + Log.d(TAG, uri + " is not the test file we expected, don't check the redaction"); + } + + final int mediaType = c.getInt(1); + switch (mediaType) { + case MEDIA_TYPE_IMAGE: + assertImageRedactedReadOnlyAccess(uri, canCheckRedacted, resolver); + break; + case MEDIA_TYPE_VIDEO: + assertVideoRedactedReadOnlyAccess(uri, canCheckRedacted, resolver); + break; + default: + fail("The media type is not as expected: " + mediaType); + } + } + + private static void assertVideoRedactedReadOnlyAccess(Uri uri, boolean shouldCheckRedacted, + ContentResolver resolver) throws Exception { + if (shouldCheckRedacted) { + // The location is redacted + try (InputStream in = resolver.openInputStream(uri); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + FileUtils.copy(in, out); + byte[] bytes = out.toByteArray(); + byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197); + String xmp = new String(xmpBytes); + assertWithMessage("Failed to redact XMP longitude") + .that(xmp.contains("10,41.751000E")).isFalse(); + assertWithMessage("Failed to redact XMP latitude") + .that(xmp.contains("53,50.070500N")).isFalse(); + assertWithMessage("Redacted non-location XMP") + .that(xmp.contains("13166/7763")).isTrue(); + } + } + + try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r")) { + // this should pass + } + + // assert no write access + try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) { + fail("Does not grant write access to uri " + uri.toString()); + } catch (SecurityException | FileNotFoundException expected) { + } + } + + private static void assertImageRedactedReadOnlyAccess(Uri uri, boolean shouldCheckRedacted, + ContentResolver resolver) throws Exception { + if (shouldCheckRedacted) { + // The location is redacted + try (InputStream is = resolver.openInputStream(uri)) { + final ExifInterface exif = new ExifInterface(is); + final float[] latLong = new float[2]; + exif.getLatLong(latLong); + assertWithMessage("Failed to redact latitude") + .that(latLong[0]).isWithin(0.001f).of(0); + assertWithMessage("Failed to redact longitude") + .that(latLong[1]).isWithin(0.001f).of(0); + + String xmp = exif.getAttribute(ExifInterface.TAG_XMP); + assertWithMessage("Failed to redact XMP longitude") + .that(xmp.contains("10,41.751000E")).isFalse(); + assertWithMessage("Failed to redact XMP latitude") + .that(xmp.contains("53,50.070500N")).isFalse(); + assertWithMessage("Redacted non-location XMP") + .that(xmp.contains("LensDefaults")).isTrue(); + } + } + + try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r")) { + // this should pass + } + + // assert no write access + try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) { + fail("Does not grant write access to uri " + uri.toString()); + } catch (SecurityException | FileNotFoundException expected) { + } + } +} diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java new file mode 100644 index 00000000000..37a44f99915 --- /dev/null +++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java @@ -0,0 +1,116 @@ +/* + * 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.photopicker.cts.util; + +import android.app.UiAutomation; +import android.content.Context; +import android.net.Uri; +import android.os.FileUtils; +import android.os.UserHandle; +import android.photopicker.cts.R; +import android.provider.MediaStore; +import android.provider.cts.media.MediaStoreUtils; + +import androidx.test.InstrumentationRegistry; + +import com.android.compatibility.common.util.ShellUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +/** + * Photo Picker Utility methods for media files creation and deletion. + */ +public class PhotoPickerFilesUtils { + public static final String DISPLAY_NAME_PREFIX = "ctsPhotoPicker"; + + public static void createImages(int count, int userId, List<Uri> uriList) + throws Exception { + for (int i = 0; i < count; i++) { + final Uri uri = createImage(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++) { + final Uri uri = createVideo(userId); + uriList.add(uri); + clearMediaOwner(uri, userId); + } + } + + public static void deleteMedia(Uri uri, int userId) throws Exception { + final String cmd = String.format("content delete --uri %s --user %d", uri, userId); + ShellUtils.runShellCommand(cmd); + } + + private static void clearMediaOwner(Uri uri, int userId) throws Exception { + final String cmd = String.format( + "content update --uri %s --user %d --bind owner_package_name:n:", uri, userId); + ShellUtils.runShellCommand(cmd); + } + + private static Uri createVideo(int userId) throws Exception { + final Uri uri = stageMedia(R.raw.testvideo_meta, + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId); + return uri; + } + + private static Uri createImage(int userId) throws Exception { + final Uri uri = stageMedia(R.raw.lg_g4_iso_800_jpg, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/jpeg", userId); + return uri; + } + + private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, int userId) throws + Exception { + UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); + uiAutomation.adoptShellPermissionIdentity( + android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + try { + final Context context = InstrumentationRegistry.getTargetContext(); + final Context userContext = userId == context.getUserId() ? context : + context.createPackageContextAsUser("android", /* flags= */ 0, + UserHandle.of(userId)); + return stageMedia(resId, collectionUri, mimeType, userContext); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, Context context) + throws IOException { + final String displayName = DISPLAY_NAME_PREFIX + System.nanoTime(); + final MediaStoreUtils.PendingParams params = new MediaStoreUtils.PendingParams( + collectionUri, displayName, mimeType); + final Uri pendingUri = MediaStoreUtils.createPending(context, params); + try (MediaStoreUtils.PendingSession session = MediaStoreUtils.openPending(context, + pendingUri)) { + try (InputStream source = context.getResources().openRawResource(resId); + OutputStream target = session.openOutputStream()) { + FileUtils.copy(source, target); + } + return session.publish(); + } + } +} diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java new file mode 100644 index 00000000000..e5912434a98 --- /dev/null +++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java @@ -0,0 +1,90 @@ +/* + * 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.photopicker.cts.util; + +import static com.google.common.truth.Truth.assertWithMessage; + +import android.text.format.DateUtils; + +import androidx.test.uiautomator.UiObject; +import androidx.test.uiautomator.UiObjectNotFoundException; +import androidx.test.uiautomator.UiScrollable; +import androidx.test.uiautomator.UiSelector; + +import java.util.ArrayList; +import java.util.List; + +/** + * Photo Picker Utility methods for finding UI elements. + */ +public class PhotoPickerUiUtils { + public static final long SHORT_TIMEOUT = 5 * DateUtils.SECOND_IN_MILLIS; + + private static final long TIMEOUT = 30 * DateUtils.SECOND_IN_MILLIS; + private static final String REGEX_PACKAGE_NAME = + "com(.google)?.android.providers.media(.module)?"; + + /** + * Get the list of items from the photo grid list. + * @param itemCount if the itemCount is -1, return all matching items. Otherwise, return the + * item list that its size is not greater than the itemCount. + * @throws Exception + */ + public static List<UiObject> findItemList(int itemCount) throws Exception { + final List<UiObject> itemList = new ArrayList<>(); + final UiSelector gridList = new UiSelector().className( + "androidx.recyclerview.widget.RecyclerView").resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/picker_tab_recyclerview"); + + // Wait for the first item to appear + assertWithMessage("Timed out while waiting for first item to appear") + .that(new UiObject(gridList.childSelector(new UiSelector())).waitForExists(TIMEOUT)) + .isTrue(); + + final UiSelector itemSelector = new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/icon_thumbnail"); + final UiScrollable grid = new UiScrollable(gridList); + final int childCount = grid.getChildCount(); + final int count = itemCount == -1 ? childCount : itemCount; + + for (int i = 0; i < childCount; i++) { + final UiObject item = grid.getChildByInstance(itemSelector, i); + if (item.exists()) { + itemList.add(item); + } + if (itemList.size() == count) { + break; + } + } + return itemList; + } + + public static UiObject findPreviewAddButton() throws UiObjectNotFoundException { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/preview_add_button")); + } + + public static UiObject findAddButton() throws UiObjectNotFoundException { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/button_add")); + } + + public static UiObject findProfileButton() throws UiObjectNotFoundException { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/profile_button")); + } +} |